Skip to content

Commit

Permalink
feat(frontend): go to definition
Browse files Browse the repository at this point in the history
  • Loading branch information
kris7t committed Dec 15, 2024
1 parent baa33ea commit e386ced
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 6 deletions.
32 changes: 30 additions & 2 deletions subprojects/frontend/src/editor/EditorStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ export default class EditorStore {
searchPanel: false,
lintPanel: false,
contentAssist: false,
hoverTooltip: false,
goToDefinition: false,
formatText: false,
});
}
Expand Down Expand Up @@ -307,8 +309,30 @@ export default class EditorStore {
this.hexTypeHashes = hexTypeHashes;
}

updateOccurrences(write: IOccurrence[], read: IOccurrence[]): void {
this.dispatch(setOccurrences(write, read));
updateOccurrences(
write: IOccurrence[],
read: IOccurrence[],
goToFirst = false,
fallbackPos?: number,
): void {
let goTo: number | undefined;
if (goToFirst) {
goTo = write[0]?.from ?? read[0]?.from ?? fallbackPos;
}
this.dispatch(
setOccurrences(write, read),
...(goTo === undefined
? []
: [
{
selection: { anchor: goTo },
effects: [EditorView.scrollIntoView(goTo)],
},
]),
);
if (goTo !== undefined) {
this.view?.focus();
}
}

async contentAssist(
Expand All @@ -327,6 +351,10 @@ export default class EditorStore {
return this.client.hoverTooltip(pos);
}

goToDefinition(pos: number): void {
this.client?.goToDefinition(pos);
}

/**
* @returns `true` if there is history to undo
*/
Expand Down
2 changes: 1 addition & 1 deletion subprojects/frontend/src/editor/EditorTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ export default styled('div', {
span: {
// Hack to restore writing direction.
direction: 'ltr',
}
},
},
'.cm-completionIcon, .cm-completionDetail': {
color:
Expand Down
2 changes: 2 additions & 0 deletions subprojects/frontend/src/editor/createEditorState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import bidiIsolatesExtension from './bidiIsolatesExtension';
import crosshairCursor from './crosshairCursor';
import exposeDiagnostics from './exposeDiagnostics';
import findOccurrences from './findOccurrences';
import goToDefinition from './goToDefinition';
import scrollbarsExtension from './scrollbarsExtension';
import semanticHighlighting from './semanticHighlighting';

Expand Down Expand Up @@ -81,6 +82,7 @@ export default function createEditorState(
EditorView.clickAddsSelectionRange.of((e) => e.altKey && !e.shiftKey),
exposeDiagnostics,
findOccurrences,
goToDefinition(store),
highlightActiveLine(),
highlightActiveLineGutter(),
highlightSpecialChars(),
Expand Down
164 changes: 164 additions & 0 deletions subprojects/frontend/src/editor/goToDefinition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
*
* SPDX-License-Identifier: EPL-2.0
*/

import { RangeSet, type Extension } from '@codemirror/state';
import { Decoration, EditorView, ViewPlugin } from '@codemirror/view';

import findToken from '../xtext/findToken';

import type EditorStore from './EditorStore';
import defineDecorationSetExtension from './defineDecorationSetExtension';

const showPointer = { style: 'cursor: pointer' };

const [setGoToDefinitionDecorations, goToDefinitionDecorations] =
defineDecorationSetExtension();

const decoration = Decoration.mark({
attributes: { style: 'text-decoration: underline' },
});

export default function goToDefinition(store: EditorStore): Extension {
const plugin = ViewPlugin.fromClass(
class {
state = false;

hasToken = false;

ctrlDown = false;

metaDown = false;

private x: number | undefined;

private y: number | undefined;

constructor(private readonly view: EditorView) {}

setCtrl(ctrlDown: boolean) {
this.ctrlDown = ctrlDown;
}

setMeta(metaDown: boolean) {
this.metaDown = metaDown;
}

update() {
this.updateDecoration();
}

updateDecoration(clientX?: number, clientY?: number) {
const newState = this.ctrlDown || this.metaDown;
let shouldUpdate = this.state !== newState;
if (clientX !== undefined && this.x !== clientX) {
this.x = clientX;
shouldUpdate = true;
}
if (clientY !== undefined && this.y !== clientY) {
this.y = clientY;
shouldUpdate = true;
}
if (!shouldUpdate) {
return;
}
if (!newState || this.x === undefined || this.y === undefined) {
this.state = false;
this.setEmpty();
return;
}
this.state = true;
const offset = this.view.posAtCoords({ x: this.x, y: this.y });
if (offset === null) {
this.setEmpty();
return;
}
const token = findToken(offset, this.view.state);
if (!token?.idenfitier) {
this.setEmpty();
return;
}
this.hasToken = true;
this.view.dispatch(
setGoToDefinitionDecorations(
RangeSet.of([
{
from: token.from,
to: token.to,
value: decoration,
},
]),
),
);
}

private setEmpty() {
this.hasToken = false;
this.view.dispatch(
setGoToDefinitionDecorations(RangeSet.empty as RangeSet<Decoration>),
);
}

goToDefinition(x: number, y: number) {
const offset = this.view.posAtCoords({ x, y });
if (offset !== null) {
store.goToDefinition(offset);
}
}
},
{
eventObservers: {
keydown(e) {
if (e.key === 'Control' || e.ctrlKey) {
this.ctrlDown = true;
}
if (e.key === 'Meta' || e.metaKey) {
this.metaDown = true;
}
this.updateDecoration();
},
keyup(e) {
if (e.key === 'Control' || !e.ctrlKey) {
this.ctrlDown = false;
}
if (e.key === 'Meta' || !e.metaKey) {
this.metaDown = false;
}
this.updateDecoration();
},
mousemove(e) {
this.ctrlDown = e.ctrlKey;
this.metaDown = e.metaKey;
this.updateDecoration(e.clientX, e.clientY);
},
},
eventHandlers: {
mousedown(e) {
if (!this.state) {
return false;
}
// Prevent normal selection when the go-to-definition function is active.
e.preventDefault();
return true;
},
click(e) {
if (!this.state) {
return false;
}
this.goToDefinition(e.clientX, e.clientY);
e.preventDefault();
return true;
},
},
},
);
return [
plugin,
goToDefinitionDecorations,
EditorView.contentAttributes.of((view) =>
view.plugin(plugin)?.hasToken ? showPointer : null,
),
];
}
10 changes: 7 additions & 3 deletions subprojects/frontend/src/xtext/OccurrencesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export default class OccurrencesService {
});
}

private async updateOccurrences() {
private async updateOccurrences(pos?: number, goToFirst = false) {
if (!this.needsOccurrences || !this.updateService.opened) {
this.clearOccurrences();
return;
Expand All @@ -115,7 +115,7 @@ export default class OccurrencesService {
return this.needsOccurrences
? {
cancelled: false,
data: this.store.state.selection.main.head,
data: pos ?? this.store.state.selection.main.head,
}
: { cancelled: true };
});
Expand All @@ -141,6 +141,10 @@ export default class OccurrencesService {
read.length,
'read occurrences',
);
this.store.updateOccurrences(write, read);
this.store.updateOccurrences(write, read, goToFirst, pos);
}

goToDefinition(pos: number): Promise<void> {
return this.updateOccurrences(pos, true);
}
}
6 changes: 6 additions & 0 deletions subprojects/frontend/src/xtext/XtextClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ export default class XtextClient {
return this.hoverService.hoverTooltip(pos);
}

goToDefinition(pos: number): void {
this.occurrencesService.goToDefinition(pos).catch((e) => {
log.error('Error while fetching occurrences', e);
});
}

startModelGeneration(randomSeed?: number): Promise<void> {
return this.modelGenerationService.start(randomSeed);
}
Expand Down
3 changes: 3 additions & 0 deletions subprojects/frontend/src/xtext/findToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface FoundToken {

to: number;

idenfitier: boolean;

implicitCompletion: boolean;

text: string;
Expand Down Expand Up @@ -57,6 +59,7 @@ export default function findToken(
return {
from,
to,
idenfitier: isQualifiedName,
implicitCompletion: token.type.prop(implicitCompletion) ?? false,
text,
};
Expand Down

0 comments on commit e386ced

Please sign in to comment.