Skip to content

Commit dd066d5

Browse files
authored
Fix unified definition navigation and Ctrl+Click local resolution (#32)
* Fix unified definition navigation and Ctrl+Click local resolution * Fix removed custom Go to Definition command from editor title actions to hide the top bar button
1 parent 0ca06a3 commit dd066d5

File tree

7 files changed

+193
-65
lines changed

7 files changed

+193
-65
lines changed

package.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,6 @@
538538
}
539539
],
540540
"editor/title": [
541-
{
542-
"command": "vscode-objectscript.ccs.goToDefinition",
543-
"group": "navigation@0",
544-
"when": "editorTextFocus && editorLangId =~ /^objectscript/"
545-
},
546541
{
547542
"command": "-editor.action.revealDefinition",
548543
"group": "navigation@0",

src/ccs/commands/followDefinitionLink.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/ccs/commands/goToDefinitionLocalFirst.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as vscode from "vscode";
2+
3+
import { navigateToDefinition } from "./navigateDefinition";
4+
5+
export async function followDefinitionLink(documentUri: string, line: number, character: number): Promise<void> {
6+
if (!documentUri || typeof line !== "number" || typeof character !== "number") {
7+
return;
8+
}
9+
10+
const uri = vscode.Uri.parse(documentUri);
11+
const document =
12+
vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === documentUri) ??
13+
(await vscode.workspace.openTextDocument(uri));
14+
15+
const position = new vscode.Position(line, character);
16+
const editor = vscode.window.visibleTextEditors.find((item) => item.document === document);
17+
18+
await navigateToDefinition(document, position, {
19+
preview: false,
20+
preserveFocus: false,
21+
viewColumn: editor?.viewColumn,
22+
});
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as vscode from "vscode";
2+
3+
import { navigateToDefinition } from "./navigateDefinition";
4+
5+
export async function goToDefinitionLocalFirst(): Promise<void> {
6+
const editor = vscode.window.activeTextEditor;
7+
if (!editor) {
8+
return;
9+
}
10+
11+
const { document, selection } = editor;
12+
const position = selection.active;
13+
14+
await navigateToDefinition(document, position, {
15+
preview: false,
16+
preserveFocus: false,
17+
viewColumn: editor.viewColumn,
18+
});
19+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import * as vscode from "vscode";
2+
3+
import { currentFile, type CurrentTextFile } from "../../../utils";
4+
import { extractDefinitionQuery, type QueryMatch } from "../../features/definitionLookup/extractQuery";
5+
import { lookupCcsDefinition } from "../../features/definitionLookup/lookup";
6+
7+
export interface NavigateOpts {
8+
preview?: boolean;
9+
preserveFocus?: boolean;
10+
viewColumn?: vscode.ViewColumn;
11+
}
12+
13+
export async function navigateToDefinition(
14+
document: vscode.TextDocument,
15+
position: vscode.Position,
16+
opts: NavigateOpts = {}
17+
): Promise<boolean> {
18+
const tokenSource = new vscode.CancellationTokenSource();
19+
try {
20+
const ccsLocation = await lookupCcsDefinition(document, position, tokenSource.token);
21+
if (tokenSource.token.isCancellationRequested) {
22+
return false;
23+
}
24+
if (ccsLocation) {
25+
await showLocation(ccsLocation, opts);
26+
return true;
27+
}
28+
29+
if (tokenSource.token.isCancellationRequested) {
30+
return false;
31+
}
32+
33+
const match = extractDefinitionQuery(document, position);
34+
if (match) {
35+
const current = currentFile(document);
36+
if (current && isLocalDefinition(match, current)) {
37+
return await useNativeDefinitionProvider(document, position);
38+
}
39+
}
40+
41+
return await useNativeDefinitionProvider(document, position);
42+
} finally {
43+
tokenSource.dispose();
44+
}
45+
}
46+
47+
type DefinitionResults = vscode.Location | vscode.Location[] | vscode.LocationLink[];
48+
49+
/*
50+
* Ask VS Code's definition providers first (non-UI), then run the standard reveal command.
51+
* This preserves native behaviors (peek/preview) and avoids unnecessary reopens.
52+
*/
53+
async function useNativeDefinitionProvider(document: vscode.TextDocument, position: vscode.Position): Promise<boolean> {
54+
let definitions: DefinitionResults | undefined;
55+
try {
56+
definitions = await vscode.commands.executeCommand<DefinitionResults>(
57+
"vscode.executeDefinitionProvider",
58+
document.uri,
59+
position
60+
);
61+
} catch (error) {
62+
return false;
63+
}
64+
65+
if (!hasDefinitions(definitions)) {
66+
return false;
67+
}
68+
69+
// Center the source position if it's outside the viewport, without smooth animation
70+
const editor = getEditorForDocument(document);
71+
if (editor) {
72+
const selection = new vscode.Selection(position, position);
73+
editor.selection = selection;
74+
editor.revealRange(selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport);
75+
}
76+
77+
await vscode.commands.executeCommand("editor.action.revealDefinition");
78+
return true;
79+
}
80+
81+
// Minimal “has result” check for any of the definition shapes
82+
function hasDefinitions(definitions: DefinitionResults | undefined): boolean {
83+
if (!definitions) {
84+
return false;
85+
}
86+
if (Array.isArray(definitions)) {
87+
return definitions.length > 0;
88+
}
89+
return true;
90+
}
91+
92+
// Open the target and place the caret exactly at the returned range (no extra reveal)
93+
async function showLocation(location: vscode.Location, opts: NavigateOpts): Promise<void> {
94+
const showOptions: vscode.TextDocumentShowOptions = { selection: location.range };
95+
if (opts.preview !== undefined) {
96+
showOptions.preview = opts.preview;
97+
}
98+
if (opts.preserveFocus !== undefined) {
99+
showOptions.preserveFocus = opts.preserveFocus;
100+
}
101+
if (opts.viewColumn !== undefined) {
102+
showOptions.viewColumn = opts.viewColumn;
103+
}
104+
await vscode.window.showTextDocument(location.uri, showOptions);
105+
}
106+
107+
// Try to reuse an already visible editor for the document (avoids reopen flicker)
108+
function getEditorForDocument(document: vscode.TextDocument): vscode.TextEditor | undefined {
109+
return (
110+
vscode.window.visibleTextEditors.find((editor) => editor.document === document) ??
111+
(vscode.window.activeTextEditor?.document === document ? vscode.window.activeTextEditor : undefined)
112+
);
113+
}
114+
115+
// Compare current file name (without extension) to routine name (case-insensitive)
116+
function isCurrentRoutine(current: CurrentTextFile, routineName: string): boolean {
117+
const match = current.name.match(/^(.*)\.(mac|int|inc)$/i);
118+
if (!match) {
119+
return false;
120+
}
121+
return match[1].toLowerCase() === routineName.toLowerCase();
122+
}
123+
124+
// Compare current .cls name (without .cls) to class name (case-insensitive)
125+
function isCurrentClass(current: CurrentTextFile, className: string): boolean {
126+
if (!current.name.toLowerCase().endsWith(".cls")) {
127+
return false;
128+
}
129+
const currentClassName = current.name.slice(0, -4);
130+
return currentClassName.toLowerCase() === className.toLowerCase();
131+
}
132+
133+
// Local if symbol belongs to the same routine or same class as the current file
134+
function isLocalDefinition(match: QueryMatch, current: CurrentTextFile): boolean {
135+
if (!match.symbolName) {
136+
return false;
137+
}
138+
139+
if (match.kind === "labelRoutine") {
140+
return isCurrentRoutine(current, match.symbolName);
141+
}
142+
143+
if (match.kind === "class") {
144+
return isCurrentClass(current, match.symbolName);
145+
}
146+
147+
return false;
148+
}

src/ccs/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ export {
1313
type QueryMatch,
1414
type QueryKind,
1515
} from "./features/definitionLookup/extractQuery";
16-
export { goToDefinitionLocalFirst } from "./commands/goToDefinitionLocalFirst";
17-
export { followDefinitionLink } from "./commands/followDefinitionLink";
16+
export { goToDefinitionLocalFirst } from "./commands/navigation/goToDefinitionLocalFirst";
17+
export { followDefinitionLink } from "./commands/navigation/followDefinitionLink";
18+
export { navigateToDefinition, type NavigateOpts } from "./commands/navigation/navigateDefinition";
1819
export { PrioritizedDefinitionProvider } from "./providers/PrioritizedDefinitionProvider";
1920
export {
2021
DefinitionDocumentLinkProvider,

0 commit comments

Comments
 (0)