Skip to content

Commit

Permalink
Improve quick peek with highlighting in both the jump and holes comma…
Browse files Browse the repository at this point in the history
…nds (#1705)
  • Loading branch information
PizieDust authored Jan 17, 2025
1 parent db53fab commit bfa5e0e
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 50 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
can be used to shrink the selection and increase the verbosity of the
displayed type. (#1675)

- Jump and holes commands: display message when target list is empty and allow
preview of available targets (#1705)

## 1.26.1

- Construct: display a message when construct list is empty. (#1695)
Expand Down
250 changes: 200 additions & 50 deletions src/extension_commands.ml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,69 @@ let _open_ocamllsp_output_pane, _open_ocaml_platform_ext_pane, _open_ocaml_comma
(handler Output.command_output_channel) )
;;

let select_and_reveal selection text_editor =
TextEditor.set_selection text_editor selection;
TextEditor.revealRange
text_editor
~range:(Selection.to_range selection)
~revealType:TextEditorRevealType.InCenterIfOutsideViewport
()
;;

module Decorations = struct
let highlighting_decoration =
let options =
DecorationRenderOptions.create
~backgroundColor:
(`ThemeColor (ThemeColor.make ~id:"editorHoverWidget.foreground"))
~color:(`ThemeColor (ThemeColor.make ~id:"editorHoverWidget.background"))
~border:"1px solid"
~borderColor:(`ThemeColor (ThemeColor.make ~id:"editorHoverWidget.border"))
~isWholeLine:false
()
in
Window.createTextEditorDecorationType ~options
;;

let highlight_range text_editor range =
let _decorationOptions =
let renderOptions =
let before = ThemableDecorationAttachmentRenderOptions.create () in
let options = ThemableDecorationInstanceRenderOptions.create ~before () in
Some (DecorationInstanceRenderOptions.create ~light:options ~dark:options ())
in
DecorationOptions.create ~range ~renderOptions ()
in
TextEditor.setDecorations
text_editor
~decorationType:highlighting_decoration
~rangesOrOptions:(`Ranges [ range ])
;;

let highlight_and_reveal_range text_editor range =
highlight_range text_editor range;
TextEditor.revealRange
text_editor
~range
~revealType:TextEditorRevealType.InCenterIfOutsideViewport
()
;;

let remove_all_highlights text_editor =
let rangesOrOptions = `Ranges [] in
TextEditor.setDecorations
text_editor
~decorationType:highlighting_decoration
~rangesOrOptions
;;
end

let watch_selection_change event_fired =
let onDidChangeTextEditorSelection_listener _event = event_fired := true in
let listener = onDidChangeTextEditorSelection_listener in
Window.onDidChangeTextEditorSelection () ~listener ()
;;

module Holes_commands : sig
val _jump_to_prev_hole : t
val _jump_to_next_hole : t
Expand Down Expand Up @@ -677,45 +740,139 @@ module MerlinJump = struct
send_request client Merlin_jump.request (Merlin_jump.make ~uri ~position))
;;

let display_results (results : Custom_requests.Merlin_jump.response) =
module JumpQuickPickItem = struct
type t =
{ item : QuickPickItem.t
; position : Position.t
}

let t_of_js js =
let position = Ojs.get_prop_ascii js "position" |> Position.t_of_js in
let item = QuickPickItem.t_of_js js in
{ item; position }
;;

let t_to_js t =
let item = QuickPickItem.t_to_js t.item in
Ojs.set_prop_ascii item "position" (Position.t_to_js t.position);
item
;;
end

module QuickPick = Vscode.QuickPick.Make (JumpQuickPickItem)

let display_results (results : Custom_requests.Merlin_jump.response) text_editor =
let selected_item = ref false in
let quickPickItems =
match results with
| [] ->
show_message `Info "No available targets to jump to.";
[]
| results ->
List.map results ~f:(fun (target, pos) ->
(QuickPickItem.create ~label:("Jump to nearest " ^ target)) (), (target, pos))
List.map results ~f:(fun (target, position) ->
let item = (QuickPickItem.create ~label:("Jump to nearest " ^ target)) () in
{ JumpQuickPickItem.item; position })
in
let quickPickOptions = QuickPickOptions.create ~title:"Available Jump Targets" () in
Window.showQuickPickItems ~choices:quickPickItems ~options:quickPickOptions ()
;;

let jump_to_position text_editor position =
let open Promise.Syntax in
let+ _ =
Window.showTextDocument
~document:(TextEditor.document text_editor)
~preserveFocus:true
let quickPick =
QuickPick.set
(Window.createQuickPick (module JumpQuickPickItem) ())
~title:"Available Jump Targets"
~activeItems:[]
~busy:false
~enabled:true
~placeholder:"Use arrow keys to preview / Select to jump"
~selectedItems:[]
~ignoreFocusOut:false
~items:quickPickItems
~matchOnDescription:true
~buttons:[]
()
in
TextEditor.set_selection
text_editor
(Selection.makePositions ~anchor:position ~active:position);
TextEditor.revealRange
text_editor
~range:(Range.makePositions ~start:position ~end_:position)
~revealType:TextEditorRevealType.InCenterIfOutsideViewport
()
let _disposable =
QuickPick.onDidChangeActive
quickPick
~listener:(function
| { position; _ } :: _ ->
let range =
Range.makePositions
~start:position
~end_:
(Position.make
~character:(Position.character position + 1)
~line:(Position.line position))
in
let text_document = TextEditor.document text_editor in
let range =
Option.value
(TextDocument.getWordRangeAtPosition
text_document
~regex:
(Js_of_ocaml.Regexp.regexp
"\\(?\\b(let|fun|match|module|module\\s*type|\\w+)(?=\\s*(?:->|\\s|\\)|$))")
~position:(Range.start range)
())
~default:range
in
(match
String.is_prefix ~prefix:"(" (TextDocument.getText text_document ~range ())
with
| false -> Decorations.highlight_and_reveal_range text_editor range
| true ->
let start_position = Range.start range in
let new_start_position =
Position.make
~line:(Position.line start_position)
~character:(Position.character start_position + 1)
in
let range =
Range.makePositions ~start:new_start_position ~end_:(Range.end_ range)
in
Decorations.highlight_and_reveal_range text_editor range)
| _ -> ())
()
in
let _disposable =
QuickPick.onDidAccept
quickPick
~listener:(fun () ->
match QuickPick.selectedItems quickPick with
| Some (item :: _) ->
ignore
(let open Promise.Syntax in
selected_item := true;
let+ _ =
Window.showTextDocument
~document:(TextEditor.document text_editor)
~preserveFocus:true
()
in
let selection =
Selection.makePositions ~anchor:item.position ~active:item.position
in
select_and_reveal selection text_editor)
| _ -> ())
()
in
let _disposable =
let initial_selection = TextEditor.selection text_editor in
(* We watch selection change events so that we don't jump back to the
original position if an external command or user action was performed. *)
let selection_changed = ref false in
let selection_listener_disposable = watch_selection_change selection_changed in
QuickPick.onDidHide
quickPick
~listener:(fun () ->
if not (!selection_changed || !selected_item)
then select_and_reveal initial_selection text_editor;
Decorations.remove_all_highlights text_editor;
Disposable.dispose selection_listener_disposable;
QuickPick.dispose quickPick)
()
in
QuickPick.show quickPick
;;

let process_jump position text_editor client =
let open Promise.Syntax in
let* successful_targets = request_possible_targets position text_editor client in
let* selected_target = display_results successful_targets in
match selected_target with
| Some (_res, position) -> jump_to_position text_editor position
| None -> Promise.return ()
let+ successful_targets = request_possible_targets position text_editor client in
match successful_targets with
| [] -> show_message `Info "No available targets to jump to"
| results -> display_results results text_editor
;;

let _jump =
Expand All @@ -734,7 +891,7 @@ module MerlinJump = struct
show_message
`Error
"Invalid file type. This command only works in ocaml files, ocaml interface \
files or reason files.."
files or reason files."
| Some text_editor ->
(match Extension_instance.lsp_client instance with
| None -> show_message `Warn "ocamllsp is not running"
Expand Down Expand Up @@ -971,15 +1128,6 @@ module Navigate_holes = struct
Custom_requests.send_request client Custom_requests.typedHoles uri
;;

let show_selection selection text_editor =
TextEditor.set_selection text_editor selection;
TextEditor.revealRange
text_editor
~range:(Selection.to_range selection)
~revealType:TextEditorRevealType.InCenterIfOutsideViewport
()
;;

let jump_to_range range text_editor =
let open Promise.Syntax in
let+ _ =
Expand All @@ -993,7 +1141,7 @@ module Navigate_holes = struct
let active = Range.end_ range in
Selection.makePositions ~anchor ~active
in
show_selection selection text_editor
select_and_reveal selection text_editor
;;

module QuickPickItemWithRange = struct
Expand Down Expand Up @@ -1039,7 +1187,7 @@ module Navigate_holes = struct
~activeItems:[]
~busy:false
~enabled:true
~placeholder:"Use arrow keys to preview / Select to jump to it"
~placeholder:"Use arrow keys to preview / Select to jump"
~selectedItems:[]
~ignoreFocusOut:false
~items:quickPickItems
Expand All @@ -1051,12 +1199,7 @@ module Navigate_holes = struct
QuickPick.onDidChangeActive
quickPick
~listener:(function
| { range; _ } :: _ ->
show_selection
(Selection.makePositions
~anchor:(Range.start range)
~active:(Range.end_ range))
text_editor
| { range; _ } :: _ -> Decorations.highlight_and_reveal_range text_editor range
| _ -> ())
()
in
Expand All @@ -1083,10 +1226,17 @@ module Navigate_holes = struct
in
let _disposable =
let initial_selection = TextEditor.selection text_editor in
(* We watch selection change events so that we don't jump back to the
original position if an external command or user action was performed. *)
let selection_changed = ref false in
let selection_listener_disposable = watch_selection_change selection_changed in
QuickPick.onDidHide
quickPick
~listener:(fun () ->
if !selected_item then () else show_selection initial_selection text_editor;
if not (!selection_changed || !selected_item)
then select_and_reveal initial_selection text_editor;
Decorations.remove_all_highlights text_editor;
Disposable.dispose selection_listener_disposable;
QuickPick.dispose quickPick)
()
in
Expand Down

0 comments on commit bfa5e0e

Please sign in to comment.