mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-27 18:51:41 +02:00
hints kitten: Allow clicking on matched text to select it in addition to typing the hint
This commit is contained in:
@@ -57,6 +57,8 @@ Detailed list of changes
|
|||||||
|
|
||||||
- :opt:`notify_on_cmd_finish`: Show the actual command that was finished (:iss:`7420`)
|
- :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
|
- 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`)
|
- :opt:`paste_actions`: Fix ``replace-newline`` not working with ``confirm`` (:iss:`7374`)
|
||||||
|
|||||||
@@ -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
|
select that hint or press :kbd:`Enter` or :kbd:`Space` to select the empty
|
||||||
hint.
|
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
|
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
|
options and modes of operation, see below. You can use these options to
|
||||||
create mappings in :file:`kitty.conf` to select various different text
|
create mappings in :file:`kitty.conf` to select various different text
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package hints
|
package hints
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -183,7 +184,8 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
|
|||||||
} else {
|
} else {
|
||||||
mark_text = mark_text[len(hint):]
|
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 {
|
render := func() string {
|
||||||
@@ -230,6 +232,30 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
|
|||||||
draw_screen()
|
draw_screen()
|
||||||
return nil
|
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 {
|
lp.OnText = func(text string, _, _ bool) error {
|
||||||
changed := false
|
changed := false
|
||||||
for _, ch := range text {
|
for _, ch := range text {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from kitty.cli_stub import HintsCLIOptions
|
|||||||
from kitty.clipboard import set_clipboard_string, set_primary_selection
|
from kitty.clipboard import set_clipboard_string, set_primary_selection
|
||||||
from kitty.constants import website_url
|
from kitty.constants import website_url
|
||||||
from kitty.fast_data_types import get_options
|
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 kitty.utils import get_editor, resolve_custom_file
|
||||||
|
|
||||||
from ..tui.handler import result_handler
|
from ..tui.handler import result_handler
|
||||||
@@ -312,7 +312,14 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
|
|||||||
}[action])(*cmd)
|
}[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:
|
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
|
||||||
cp = data['customize_processing']
|
cp = data['customize_processing']
|
||||||
if data['type'] == 'linenum':
|
if data['type'] == 'linenum':
|
||||||
|
|||||||
@@ -59,7 +59,10 @@ def import_kitten_main_module(config_dir: str, kitten: str) -> Dict[str, Any]:
|
|||||||
|
|
||||||
kitten = resolved_kitten(kitten)
|
kitten = resolved_kitten(kitten)
|
||||||
m = importlib.import_module(f'kittens.{kitten}.main')
|
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:
|
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, 'type_of_input', getattr(m['end'], 'type_of_input', None))
|
||||||
setattr(ans, 'no_ui', getattr(m['end'], 'no_ui', False))
|
setattr(ans, 'no_ui', getattr(m['end'], 'no_ui', False))
|
||||||
setattr(ans, 'has_ready_notification', getattr(m['end'], 'has_ready_notification', 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
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from kitty.typing import (
|
|||||||
MouseEvent,
|
MouseEvent,
|
||||||
ScreenSize,
|
ScreenSize,
|
||||||
TermManagerType,
|
TermManagerType,
|
||||||
|
WindowType,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .operations import MouseTracking, pending_update
|
from .operations import MouseTracking, pending_update
|
||||||
@@ -29,6 +30,9 @@ if TYPE_CHECKING:
|
|||||||
from kitty.file_transmission import FileTransmissionCommand
|
from kitty.file_transmission import FileTransmissionCommand
|
||||||
|
|
||||||
|
|
||||||
|
OpenUrlHandler = Optional[Callable[[BossType, WindowType, str, int, str], bool]]
|
||||||
|
|
||||||
|
|
||||||
class ButtonEvent(NamedTuple):
|
class ButtonEvent(NamedTuple):
|
||||||
mouse_event: MouseEvent
|
mouse_event: MouseEvent
|
||||||
timestamp: float
|
timestamp: float
|
||||||
@@ -223,23 +227,26 @@ class HandleResult:
|
|||||||
type_of_input: Optional[str] = None
|
type_of_input: Optional[str] = None
|
||||||
no_ui: bool = False
|
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.impl = impl
|
||||||
self.no_ui = no_ui
|
self.no_ui = no_ui
|
||||||
self.type_of_input = type_of_input
|
self.type_of_input = type_of_input
|
||||||
self.has_ready_notification = has_ready_notification
|
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:
|
def __call__(self, args: Sequence[str], data: Any, target_window_id: int, boss: BossType) -> Any:
|
||||||
return self.impl(args, data, target_window_id, boss)
|
return self.impl(args, data, target_window_id, boss)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def result_handler(
|
def result_handler(
|
||||||
type_of_input: Optional[str] = None,
|
type_of_input: Optional[str] = None,
|
||||||
no_ui: bool = False,
|
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]:
|
) -> Callable[[Callable[..., Any]], HandleResult]:
|
||||||
|
|
||||||
def wrapper(impl: 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
|
return wrapper
|
||||||
|
|||||||
@@ -1919,6 +1919,7 @@ class Boss:
|
|||||||
)
|
)
|
||||||
wid = w.id
|
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.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:
|
if action_on_removal is not None:
|
||||||
|
|
||||||
def callback_wrapper(*a: Any) -> None:
|
def callback_wrapper(*a: Any) -> None:
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ MatchPatternType = Union[Pattern[str], Tuple[Pattern[str], Optional[Pattern[str]
|
|||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from kittens.tui.handler import OpenUrlHandler
|
||||||
|
|
||||||
from .file_transmission import FileTransmission
|
from .file_transmission import FileTransmission
|
||||||
|
|
||||||
|
|
||||||
@@ -563,6 +565,7 @@ class Window:
|
|||||||
self.current_clipboard_read_ask: Optional[bool] = None
|
self.current_clipboard_read_ask: Optional[bool] = None
|
||||||
self.prev_osc99_cmd = NotificationCommand()
|
self.prev_osc99_cmd = NotificationCommand()
|
||||||
self.last_cmd_output_start_time = 0.
|
self.last_cmd_output_start_time = 0.
|
||||||
|
self.open_url_handler: 'OpenUrlHandler' = None
|
||||||
self.last_cmd_cmdline = ''
|
self.last_cmd_cmdline = ''
|
||||||
self.last_cmd_exit_status = 0
|
self.last_cmd_exit_status = 0
|
||||||
self.actions_on_close: List[Callable[['Window'], None]] = []
|
self.actions_on_close: List[Callable[['Window'], None]] = []
|
||||||
@@ -1043,6 +1046,13 @@ class Window:
|
|||||||
return get_boss().combine(action, window_for_dispatch=self, dispatch_type='MouseEvent')
|
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:
|
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()
|
opts = get_options()
|
||||||
if hyperlink_id:
|
if hyperlink_id:
|
||||||
if not opts.allow_hyperlinks:
|
if not opts.allow_hyperlinks:
|
||||||
@@ -1063,14 +1073,14 @@ class Window:
|
|||||||
url = urlunparse(purl._replace(netloc=''))
|
url = urlunparse(purl._replace(netloc=''))
|
||||||
if opts.allow_hyperlinks & 0b10:
|
if opts.allow_hyperlinks & 0b10:
|
||||||
from kittens.tui.operations import styled
|
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'),
|
'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),
|
partial(self.hyperlink_open_confirmed, url, cwd),
|
||||||
'o:Open', 'c:Copy to clipboard', 'n;red:Nothing', default='o',
|
'o:Open', 'c:Copy to clipboard', 'n;red:Nothing', default='o',
|
||||||
window=self, title=_('Hyperlink activated'),
|
window=self, title=_('Hyperlink activated'),
|
||||||
)
|
)
|
||||||
return
|
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:
|
def hyperlink_open_confirmed(self, url: str, cwd: Optional[str], q: str) -> None:
|
||||||
if q == 'o':
|
if q == 'o':
|
||||||
|
|||||||
Reference in New Issue
Block a user