Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -538,11 +538,6 @@
}
],
"editor/title": [
{
"command": "vscode-objectscript.ccs.goToDefinition",
"group": "navigation@0",
"when": "editorTextFocus && editorLangId =~ /^objectscript/"
},
{
"command": "-editor.action.revealDefinition",
"group": "navigation@0",
Expand Down
32 changes: 0 additions & 32 deletions src/ccs/commands/followDefinitionLink.ts

This file was deleted.

26 changes: 0 additions & 26 deletions src/ccs/commands/goToDefinitionLocalFirst.ts

This file was deleted.

23 changes: 23 additions & 0 deletions src/ccs/commands/navigation/followDefinitionLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as vscode from "vscode";

import { navigateToDefinition } from "./navigateDefinition";

export async function followDefinitionLink(documentUri: string, line: number, character: number): Promise<void> {
if (!documentUri || typeof line !== "number" || typeof character !== "number") {
return;
}

const uri = vscode.Uri.parse(documentUri);
const document =
vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === documentUri) ??
(await vscode.workspace.openTextDocument(uri));

const position = new vscode.Position(line, character);
const editor = vscode.window.visibleTextEditors.find((item) => item.document === document);

await navigateToDefinition(document, position, {
preview: false,
preserveFocus: false,
viewColumn: editor?.viewColumn,
});
}
19 changes: 19 additions & 0 deletions src/ccs/commands/navigation/goToDefinitionLocalFirst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as vscode from "vscode";

import { navigateToDefinition } from "./navigateDefinition";

export async function goToDefinitionLocalFirst(): Promise<void> {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}

const { document, selection } = editor;
const position = selection.active;

await navigateToDefinition(document, position, {
preview: false,
preserveFocus: false,
viewColumn: editor.viewColumn,
});
}
148 changes: 148 additions & 0 deletions src/ccs/commands/navigation/navigateDefinition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import * as vscode from "vscode";

import { currentFile, type CurrentTextFile } from "../../../utils";
import { extractDefinitionQuery, type QueryMatch } from "../../features/definitionLookup/extractQuery";
import { lookupCcsDefinition } from "../../features/definitionLookup/lookup";

export interface NavigateOpts {
preview?: boolean;
preserveFocus?: boolean;
viewColumn?: vscode.ViewColumn;
}

export async function navigateToDefinition(
document: vscode.TextDocument,
position: vscode.Position,
opts: NavigateOpts = {}
): Promise<boolean> {
const tokenSource = new vscode.CancellationTokenSource();
try {
const ccsLocation = await lookupCcsDefinition(document, position, tokenSource.token);
if (tokenSource.token.isCancellationRequested) {
return false;
}
if (ccsLocation) {
await showLocation(ccsLocation, opts);
return true;
}

if (tokenSource.token.isCancellationRequested) {
return false;
}

const match = extractDefinitionQuery(document, position);
if (match) {
const current = currentFile(document);
if (current && isLocalDefinition(match, current)) {
return await useNativeDefinitionProvider(document, position);
}
}

return await useNativeDefinitionProvider(document, position);
} finally {
tokenSource.dispose();
}
}

type DefinitionResults = vscode.Location | vscode.Location[] | vscode.LocationLink[];

/*
* Ask VS Code's definition providers first (non-UI), then run the standard reveal command.
* This preserves native behaviors (peek/preview) and avoids unnecessary reopens.
*/
async function useNativeDefinitionProvider(document: vscode.TextDocument, position: vscode.Position): Promise<boolean> {
let definitions: DefinitionResults | undefined;
try {
definitions = await vscode.commands.executeCommand<DefinitionResults>(
"vscode.executeDefinitionProvider",
document.uri,
position
);
} catch (error) {
return false;
}

if (!hasDefinitions(definitions)) {
return false;
}

// Center the source position if it's outside the viewport, without smooth animation
const editor = getEditorForDocument(document);
if (editor) {
const selection = new vscode.Selection(position, position);
editor.selection = selection;
editor.revealRange(selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport);
}

await vscode.commands.executeCommand("editor.action.revealDefinition");
return true;
}

// Minimal “has result” check for any of the definition shapes
function hasDefinitions(definitions: DefinitionResults | undefined): boolean {
if (!definitions) {
return false;
}
if (Array.isArray(definitions)) {
return definitions.length > 0;
}
return true;
}

// Open the target and place the caret exactly at the returned range (no extra reveal)
async function showLocation(location: vscode.Location, opts: NavigateOpts): Promise<void> {
const showOptions: vscode.TextDocumentShowOptions = { selection: location.range };
if (opts.preview !== undefined) {
showOptions.preview = opts.preview;
}
if (opts.preserveFocus !== undefined) {
showOptions.preserveFocus = opts.preserveFocus;
}
if (opts.viewColumn !== undefined) {
showOptions.viewColumn = opts.viewColumn;
}
await vscode.window.showTextDocument(location.uri, showOptions);
}

// Try to reuse an already visible editor for the document (avoids reopen flicker)
function getEditorForDocument(document: vscode.TextDocument): vscode.TextEditor | undefined {
return (
vscode.window.visibleTextEditors.find((editor) => editor.document === document) ??
(vscode.window.activeTextEditor?.document === document ? vscode.window.activeTextEditor : undefined)
);
}

// Compare current file name (without extension) to routine name (case-insensitive)
function isCurrentRoutine(current: CurrentTextFile, routineName: string): boolean {
const match = current.name.match(/^(.*)\.(mac|int|inc)$/i);
if (!match) {
return false;
}
return match[1].toLowerCase() === routineName.toLowerCase();
}

// Compare current .cls name (without .cls) to class name (case-insensitive)
function isCurrentClass(current: CurrentTextFile, className: string): boolean {
if (!current.name.toLowerCase().endsWith(".cls")) {
return false;
}
const currentClassName = current.name.slice(0, -4);
return currentClassName.toLowerCase() === className.toLowerCase();
}

// Local if symbol belongs to the same routine or same class as the current file
function isLocalDefinition(match: QueryMatch, current: CurrentTextFile): boolean {
if (!match.symbolName) {
return false;
}

if (match.kind === "labelRoutine") {
return isCurrentRoutine(current, match.symbolName);
}

if (match.kind === "class") {
return isCurrentClass(current, match.symbolName);
}

return false;
}
5 changes: 3 additions & 2 deletions src/ccs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ export {
type QueryMatch,
type QueryKind,
} from "./features/definitionLookup/extractQuery";
export { goToDefinitionLocalFirst } from "./commands/goToDefinitionLocalFirst";
export { followDefinitionLink } from "./commands/followDefinitionLink";
export { goToDefinitionLocalFirst } from "./commands/navigation/goToDefinitionLocalFirst";
export { followDefinitionLink } from "./commands/navigation/followDefinitionLink";
export { navigateToDefinition, type NavigateOpts } from "./commands/navigation/navigateDefinition";
export { PrioritizedDefinitionProvider } from "./providers/PrioritizedDefinitionProvider";
export {
DefinitionDocumentLinkProvider,
Expand Down