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

This commit is contained in:
Kovid Goyal 2024-05-14 15:20:18 +05:30
parent 38fed8b391
commit 8c1e365eb5
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
8 changed files with 69 additions and 9 deletions

View file

@ -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`)

View file

@ -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

View file

@ -3,6 +3,7 @@
package hints
import (
"encoding/json"
"fmt"
"io"
"os"
@ -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 {
@ -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 {

View file

@ -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
@ -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':

View file

@ -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:
@ -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

View file

@ -21,6 +21,7 @@
MouseEvent,
ScreenSize,
TermManagerType,
WindowType,
)
from .operations import MouseTracking, pending_update
@ -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
@ -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

View file

@ -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:

View file

@ -122,6 +122,8 @@
if TYPE_CHECKING:
from kittens.tui.handler import OpenUrlHandler
from .file_transmission import FileTransmission
@ -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]] = []
@ -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:
@ -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':