Skip to content

Commit e96813b

Browse files
committed
Implement highlight on cursor action in the new virtual positioning system
Now it works in Notebook; there seems to be a regression in some events not firing in FileEditor CodeMirror editor. Needs further investigation.
1 parent 0b99dc7 commit e96813b

File tree

3 files changed

+116
-32
lines changed

3 files changed

+116
-32
lines changed

src/adapters/codemirror.ts

Lines changed: 96 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import * as lsProtocol from 'vscode-languageserver-protocol';
44
import { FreeTooltip } from '../free_tooltip';
55
import { DefaultMap, getModifierState, until_ready } from '../utils';
66
import { PositionConverter } from '../converter';
7-
import { CompletionTriggerKind, diagnosticSeverityNames } from '../lsp';
7+
import {
8+
CompletionTriggerKind,
9+
diagnosticSeverityNames,
10+
documentHighlightKindNames
11+
} from '../lsp';
812
import { VirtualEditor } from '../virtual/editor';
913
import { VirtualDocument } from '../virtual/document';
1014
import {
@@ -20,10 +24,17 @@ export type KeyModifier = 'Alt' | 'Control' | 'Shift' | 'Meta' | 'AltGraph';
2024
const hover_modifier: KeyModifier = 'Control';
2125
const default_severity = 2;
2226

27+
interface IEditorRange {
28+
start: IEditorPosition;
29+
end: IEditorPosition;
30+
editor: CodeMirror.Editor;
31+
}
32+
2333
export class CodeMirrorAdapterExtension extends CodeMirrorAdapter {
2434
public connection: LSPConnection;
2535
public editor: VirtualEditor;
2636

37+
protected highlight_markers: CodeMirror.TextMarker[] = [];
2738
private marked_diagnostics: Map<string, CodeMirror.TextMarker> = new Map();
2839
private _tooltip: FreeTooltip;
2940
private show_next_tooltip: boolean;
@@ -80,6 +91,9 @@ export class CodeMirrorAdapterExtension extends CodeMirrorAdapter {
8091
}
8192
});
8293

94+
this.editor.off('cursorActivity', listeners.cursorActivity);
95+
this.editor.on('cursorActivity', this.onCursorActivity.bind(this));
96+
8397
this.editor.off('change', listeners.changeListener);
8498
// due to an unknown reason the default listener (as defined in the base class) is not invoked on file editors
8599
// the workaround - setting it at doc instead - works, thus the original one is first disabled (above) and a new
@@ -185,7 +199,11 @@ export class CodeMirrorAdapterExtension extends CodeMirrorAdapter {
185199
return;
186200
}
187201

188-
this.highlight_range(response.range, 'cm-lp-hover-available');
202+
// @ts-ignore
203+
this.hoverMarker = this.highlight_range(
204+
this.editor_range_for_hover(response.range),
205+
'cm-lsp-hover-available'
206+
);
189207

190208
if (!this.show_next_tooltip) {
191209
this.last_hover_response = response;
@@ -352,55 +370,103 @@ export class CodeMirrorAdapterExtension extends CodeMirrorAdapter {
352370
this.last_change = change;
353371
}
354372

355-
protected highlight_range(range: lsProtocol.Range, class_name: string) {
356-
let hover_character = this.hover_character;
373+
public handleHighlight(items: lsProtocol.DocumentHighlight[]) {
374+
for (let marker of this.highlight_markers) {
375+
marker.clear();
376+
}
377+
this.highlight_markers = [];
357378

358-
let start: IVirtualPosition;
359-
let end: IVirtualPosition;
379+
if (!items) {
380+
return;
381+
}
360382

361-
let start_in_editor: any;
362-
let end_in_editor: any;
383+
for (let item of items) {
384+
let range = this.range_to_editor_range(item.range);
385+
let kind_class = item.kind
386+
? 'cm-lsp-highlight-' + documentHighlightKindNames[item.kind]
387+
: '';
388+
let marker = this.highlight_range(
389+
range,
390+
'cm-lsp-highlight ' + kind_class
391+
);
392+
this.highlight_markers.push(marker);
393+
}
394+
}
363395

364-
let cm_editor: any;
365-
// NOTE: foreign document ranges are checked before the request is sent,
366-
// no need to to this again here.
396+
protected onCursorActivity() {
397+
let root_position = this.editor
398+
.getDoc()
399+
.getCursor('start') as IRootPosition;
400+
let document = this.editor.document_at_root_position(root_position);
401+
if (document !== this.virtual_document) {
402+
return;
403+
}
404+
let virtual_position = this.editor.root_position_to_virtual_position(
405+
root_position
406+
);
407+
this.connection.getDocumentHighlights(virtual_position);
408+
}
367409

368-
if (range) {
369-
start = PositionConverter.lsp_to_cm(range.start) as IVirtualPosition;
370-
end = PositionConverter.lsp_to_cm(range.end) as IVirtualPosition;
410+
protected range_to_editor_range(
411+
range: lsProtocol.Range,
412+
cm_editor?: CodeMirror.Editor
413+
): IEditorRange {
414+
let start = PositionConverter.lsp_to_cm(range.start) as IVirtualPosition;
415+
let end = PositionConverter.lsp_to_cm(range.end) as IVirtualPosition;
371416

372-
start_in_editor = this.virtual_document.transform_virtual_to_editor(
417+
if (typeof cm_editor === 'undefined') {
418+
let start_in_root = this.transform_virtual_position_to_root_position(
373419
start
374420
);
375-
end_in_editor = this.virtual_document.transform_virtual_to_editor(end);
421+
cm_editor = this.editor.get_editor_at_root_position(start_in_root);
422+
}
423+
424+
return {
425+
start: this.virtual_document.transform_virtual_to_editor(start),
426+
end: this.virtual_document.transform_virtual_to_editor(end),
427+
editor: cm_editor
428+
};
429+
}
376430

377-
cm_editor = this.editor.get_editor_at_root_position(hover_character);
431+
protected editor_range_for_hover(range: lsProtocol.Range): IEditorRange {
432+
let character = this.hover_character;
433+
// NOTE: foreign document ranges are checked before the request is sent,
434+
// no need to to this again here.
435+
436+
if (range) {
437+
let cm_editor = this.editor.get_editor_at_root_position(character);
438+
return this.range_to_editor_range(range, cm_editor);
378439
} else {
379440
// construct range manually using the token information
380-
cm_editor = this.virtual_document.root.get_editor_at_source_line(
381-
hover_character
441+
let cm_editor = this.virtual_document.root.get_editor_at_source_line(
442+
character
382443
);
383-
let token = this.editor.getTokenAt(hover_character);
444+
let token = this.editor.getTokenAt(character);
384445

385446
let start_in_root = {
386-
line: hover_character.line,
447+
line: character.line,
387448
ch: token.start
388449
} as IRootPosition;
389450
let end_in_root = {
390-
line: hover_character.line,
451+
line: character.line,
391452
ch: token.end
392453
} as IRootPosition;
393454

394-
start_in_editor = this.editor.root_position_to_editor_position(
395-
start_in_root
396-
);
397-
end_in_editor = this.editor.root_position_to_editor_position(end_in_root);
455+
return {
456+
start: this.editor.root_position_to_editor_position(start_in_root),
457+
end: this.editor.root_position_to_editor_position(end_in_root),
458+
editor: cm_editor
459+
};
398460
}
461+
}
399462

400-
// @ts-ignore
401-
this.hoverMarker = cm_editor
463+
protected highlight_range(
464+
range: IEditorRange,
465+
class_name: string
466+
): CodeMirror.TextMarker {
467+
return range.editor
402468
.getDoc()
403-
.markText(start_in_editor, end_in_editor, { className: class_name });
469+
.markText(range.start, range.end, { className: class_name });
404470
}
405471

406472
protected position_from_mouse(ev: MouseEvent): IRootPosition {
@@ -418,7 +484,6 @@ export class CodeMirrorAdapterExtension extends CodeMirrorAdapter {
418484
// TODO || token.type.length === 0? (sometimes the underline is shown on meaningless tokens)
419485
}
420486

421-
// @ts-ignore
422487
public _handleMouseOver(event: MouseEvent) {
423488
// currently the events are coming from notebook panel; ideally these would be connected to individual cells,
424489
// (only cells with code) instead, but this is more complex to implement right. In any case filtering
@@ -481,7 +546,7 @@ export class CodeMirrorAdapterExtension extends CodeMirrorAdapter {
481546
protected collapse_overlapping_diagnostics(
482547
diagnostics: lsProtocol.Diagnostic[]
483548
): Map<lsProtocol.Range, lsProtocol.Diagnostic[]> {
484-
// because Range is not a primitive types, the equality of the objects having
549+
// because Range is not a primitive type, the equality of the objects having
485550
// the same parameters won't be compared (thus considered equal) in Map.
486551

487552
// instead, a intermediate step of mapping through a stringified representation of Range is needed:

src/lsp.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ export namespace CompletionItemKind {
3333
export const TypeParameter = 25;
3434
}
3535

36+
export namespace DocumentHighlightKind {
37+
export const Text = 1;
38+
export const Read = 2;
39+
export const Write = 3;
40+
}
41+
3642
export function inverse_namespace(namespace: object): Record<number, string> {
3743
const records: Record<number, string> = {};
3844
for (let key of Object.keys(namespace)) {
@@ -53,6 +59,9 @@ export function inverse_namespace(namespace: object): Record<number, string> {
5359
*/
5460
export const diagnosticSeverityNames = inverse_namespace(DiagnosticSeverity);
5561
export const completionItemKindNames = inverse_namespace(CompletionItemKind);
62+
export const documentHighlightKindNames = inverse_namespace(
63+
DocumentHighlightKind
64+
);
5665

5766
export namespace CompletionTriggerKind {
5867
export const Invoked = 1;

style/index.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,17 @@
3030
text-decoration-color: green;
3131
}
3232

33-
.cm-lp-hover-available {
33+
.cm-lsp-hover-available {
3434
text-decoration: underline;
3535
text-decoration-color: blue;
3636
}
37+
38+
.cm-lsp-highlight {
39+
background-color: #ddddee;
40+
}
41+
42+
/* fuzzy matches (not exact symbol matches) */
43+
.cm-lsp-highlight-Text {
44+
background-color: #ddeeee;
45+
box-shadow: 0 0 3px 3px #ddeeee;
46+
}

0 commit comments

Comments
 (0)