Skip to content

Commit

Permalink
hints kitten: Allow clicking on matched text to select it in addition…
Browse files Browse the repository at this point in the history
… to typing the hint
  • Loading branch information
kovidgoyal committed May 14, 2024
1 parent 38fed8b commit 8c1e365
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Detailed list of changes

- :opt:`notify_on_cmd_finish`: Show the actual command that was finished (:iss:`7420`)

- hints kitten: Allow clicking on matched text to select it in addition to typing the hint

- Shell integration: Make the currently executing cmdline available as a window variable in kitty

- :opt:`paste_actions`: Fix ``replace-newline`` not working with ``confirm`` (:iss:`7374`)
Expand Down
3 changes: 3 additions & 0 deletions docs/kittens/hints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ You can also :doc:`customize what actions are taken for different types of URLs
select that hint or press :kbd:`Enter` or :kbd:`Space` to select the empty
hint.

For mouse lovers, the hints kitten also allows you to click on any matched text to
select it instead of typing the hint character.

The hints kitten is very powerful to see more detailed help on its various
options and modes of operation, see below. You can use these options to
create mappings in :file:`kitty.conf` to select various different text
Expand Down
28 changes: 27 additions & 1 deletion kittens/hints/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package hints

import (
"encoding/json"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -183,7 +184,8 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
} else {
mark_text = mark_text[len(hint):]
}
return hint_style(hint) + text_style(mark_text)
ans := hint_style(hint) + text_style(mark_text)
return fmt.Sprintf("\x1b]8;;mark:%d\a%s\x1b]8;;\a", m.Index, ans)
}

render := func() string {
Expand Down Expand Up @@ -230,6 +232,30 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
draw_screen()
return nil
}
lp.OnRCResponse = func(data []byte) error {
var r struct {
Type string
Mark int
}
if err := json.Unmarshal(data, &r); err != nil {
return err
}
if r.Type == "mark_activated" {
if m, ok := index_map[r.Mark]; ok {
chosen = append(chosen, m)
if o.Multiple {
ignore_mark_indices.Add(m.Index)
reset()
} else {
lp.Quit(0)
return nil
}

}
}
return nil
}

lp.OnText = func(text string, _, _ bool) error {
changed := false
for _, ch := range text {
Expand Down
11 changes: 9 additions & 2 deletions kittens/hints/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from kitty.clipboard import set_clipboard_string, set_primary_selection
from kitty.constants import website_url
from kitty.fast_data_types import get_options
from kitty.typing import BossType
from kitty.typing import BossType, WindowType
from kitty.utils import get_editor, resolve_custom_file

from ..tui.handler import result_handler
Expand Down Expand Up @@ -312,7 +312,14 @@ def is_copy_action(s: str) -> bool:
}[action])(*cmd)


@result_handler(type_of_input='screen-ansi', has_ready_notification=True)
def on_mark_clicked(boss: BossType, window: WindowType, url: str, hyperlink_id: int, cwd: str) -> bool:
if url.startswith('mark:'):
window.send_cmd_response({'Type': 'mark_activated', 'Mark': int(url[5:])})
return True
return False


@result_handler(type_of_input='screen-ansi', has_ready_notification=True, open_url_handler=on_mark_clicked)
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
cp = data['customize_processing']
if data['type'] == 'linenum':
Expand Down
6 changes: 5 additions & 1 deletion kittens/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ def import_kitten_main_module(config_dir: str, kitten: str) -> Dict[str, Any]:

kitten = resolved_kitten(kitten)
m = importlib.import_module(f'kittens.{kitten}.main')
return {'start': getattr(m, 'main'), 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
return {
'start': getattr(m, 'main'),
'end': getattr(m, 'handle_result', lambda *a, **k: None),
}


def create_kitten_handler(kitten: str, orig_args: List[str]) -> Any:
Expand All @@ -70,6 +73,7 @@ def create_kitten_handler(kitten: str, orig_args: List[str]) -> Any:
setattr(ans, 'type_of_input', getattr(m['end'], 'type_of_input', None))
setattr(ans, 'no_ui', getattr(m['end'], 'no_ui', False))
setattr(ans, 'has_ready_notification', getattr(m['end'], 'has_ready_notification', False))
setattr(ans, 'open_url_handler', getattr(m['end'], 'open_url_handler', None))
return ans


Expand Down
13 changes: 10 additions & 3 deletions kittens/tui/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
MouseEvent,
ScreenSize,
TermManagerType,
WindowType,
)

from .operations import MouseTracking, pending_update
Expand All @@ -29,6 +30,9 @@
from kitty.file_transmission import FileTransmissionCommand


OpenUrlHandler = Optional[Callable[[BossType, WindowType, str, int, str], bool]]


class ButtonEvent(NamedTuple):
mouse_event: MouseEvent
timestamp: float
Expand Down Expand Up @@ -223,23 +227,26 @@ class HandleResult:
type_of_input: Optional[str] = None
no_ui: bool = False

def __init__(self, impl: Callable[..., Any], type_of_input: Optional[str], no_ui: bool, has_ready_notification: bool):
def __init__(self, impl: Callable[..., Any], type_of_input: Optional[str], no_ui: bool, has_ready_notification: bool, open_url_handler: OpenUrlHandler):
self.impl = impl
self.no_ui = no_ui
self.type_of_input = type_of_input
self.has_ready_notification = has_ready_notification
self.open_url_handler = open_url_handler

def __call__(self, args: Sequence[str], data: Any, target_window_id: int, boss: BossType) -> Any:
return self.impl(args, data, target_window_id, boss)



def result_handler(
type_of_input: Optional[str] = None,
no_ui: bool = False,
has_ready_notification: bool = Handler.overlay_ready_report_needed
has_ready_notification: bool = Handler.overlay_ready_report_needed,
open_url_handler: OpenUrlHandler = None,
) -> Callable[[Callable[..., Any]], HandleResult]:

def wrapper(impl: Callable[..., Any]) -> HandleResult:
return HandleResult(impl, type_of_input, no_ui, has_ready_notification)
return HandleResult(impl, type_of_input, no_ui, has_ready_notification, open_url_handler)

return wrapper
1 change: 1 addition & 0 deletions kitty/boss.py
Original file line number Diff line number Diff line change
Expand Up @@ -1919,6 +1919,7 @@ def run_kitten_with_metadata(
)
wid = w.id
overlay_window.actions_on_close.append(partial(self.on_kitten_finish, wid, custom_callback or end_kitten, default_data=default_data))
overlay_window.open_url_handler = end_kitten.open_url_handler
if action_on_removal is not None:

def callback_wrapper(*a: Any) -> None:
Expand Down
14 changes: 12 additions & 2 deletions kitty/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@


if TYPE_CHECKING:
from kittens.tui.handler import OpenUrlHandler

from .file_transmission import FileTransmission


Expand Down Expand Up @@ -563,6 +565,7 @@ def __init__(
self.current_clipboard_read_ask: Optional[bool] = None
self.prev_osc99_cmd = NotificationCommand()
self.last_cmd_output_start_time = 0.
self.open_url_handler: 'OpenUrlHandler' = None
self.last_cmd_cmdline = ''
self.last_cmd_exit_status = 0
self.actions_on_close: List[Callable[['Window'], None]] = []
Expand Down Expand Up @@ -1043,6 +1046,13 @@ def on_mouse_event(self, event: Dict[str, Any]) -> bool:
return get_boss().combine(action, window_for_dispatch=self, dispatch_type='MouseEvent')

def open_url(self, url: str, hyperlink_id: int, cwd: Optional[str] = None) -> None:
boss = get_boss()
try:
if self.open_url_handler and self.open_url_handler(boss, self, url, hyperlink_id, cwd or ''):
return
except Exception:
import traceback
traceback.print_exc()
opts = get_options()
if hyperlink_id:
if not opts.allow_hyperlinks:
Expand All @@ -1063,14 +1073,14 @@ def open_url(self, url: str, hyperlink_id: int, cwd: Optional[str] = None) -> No
url = urlunparse(purl._replace(netloc=''))
if opts.allow_hyperlinks & 0b10:
from kittens.tui.operations import styled
get_boss().choose(
boss.choose(
'What would you like to do with this URL:\n' + styled(sanitize_url_for_dispay_to_user(url), fg='yellow'),
partial(self.hyperlink_open_confirmed, url, cwd),
'o:Open', 'c:Copy to clipboard', 'n;red:Nothing', default='o',
window=self, title=_('Hyperlink activated'),
)
return
get_boss().open_url(url, cwd=cwd)
boss.open_url(url, cwd=cwd)

def hyperlink_open_confirmed(self, url: str, cwd: Optional[str], q: str) -> None:
if q == 'o':
Expand Down

0 comments on commit 8c1e365

Please sign in to comment.