Skip to content

Commit 58beb74

Browse files
committed
feat: go to symbol now goes to character instead of line
e.g for `public void method()` the cursor goes before method. It makes it easier to spot the selected symbol.
1 parent b32b8f4 commit 58beb74

3 files changed

Lines changed: 54 additions & 11 deletions

File tree

lana/src/display/OpenFileInPackage.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ export class OpenFileInPackage {
2222
return;
2323
}
2424

25-
const parts = symbolName.split('.');
26-
const fileName = parts[0]?.trim();
25+
const parts = symbolName.slice(0, symbolName.indexOf('('));
2726

28-
const paths = await context.findSymbol(fileName as string);
27+
const paths = await context.findSymbol(parts);
2928
if (!paths.length) {
3029
return;
3130
}
@@ -59,16 +58,17 @@ export class OpenFileInPackage {
5958

6059
const parsedRoot = parseApex(document.getText());
6160

62-
const symbolLocation = getMethodLine(parsedRoot, parts);
61+
const symbolLocation = getMethodLine(parsedRoot, symbolName);
6362

6463
if (!symbolLocation.isExactMatch) {
6564
context.display.showErrorMessage(
6665
`Symbol '${symbolLocation.missingSymbol}' could not be found in file '${basename(path)}'`,
6766
);
6867
}
6968
const zeroIndexedLineNumber = symbolLocation.line - 1;
69+
const character = symbolLocation.character ?? 0;
7070

71-
const pos = new Position(zeroIndexedLineNumber, 0);
71+
const pos = new Position(zeroIndexedLineNumber, character);
7272

7373
const options: TextDocumentShowOptions = {
7474
preserveFocus: false,

lana/src/salesforce/ApexParser/ApexSymbolLocator.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CharStreams } from 'antlr4ts';
1111
import { ApexVisitor, type ApexMethodNode, type ApexNode } from './ApexVisitor';
1212

1313
export type SymbolLocation = {
14+
character: number;
1415
line: number;
1516
isExactMatch: boolean;
1617
missingSymbol?: string;
@@ -26,7 +27,7 @@ export function parseApex(apexCode: string): ApexNode {
2627
}
2728

2829
export function getMethodLine(rootNode: ApexNode, symbols: string[]): SymbolLocation {
29-
const result: SymbolLocation = { line: 1, isExactMatch: true };
30+
const result: SymbolLocation = { character: 0, line: 1, isExactMatch: true };
3031

3132
if (symbols[0] === rootNode.name) {
3233
symbols = symbols.slice(1);
@@ -52,12 +53,14 @@ export function getMethodLine(rootNode: ApexNode, symbols: string[]): SymbolLoca
5253

5354
if (!methodNode) {
5455
result.line = currentRoot.line ?? 1;
56+
result.character = currentRoot.idCharacter ?? 0;
5557
result.isExactMatch = false;
5658
result.missingSymbol = symbol;
5759
break;
5860
}
5961

6062
result.line = methodNode.line;
63+
result.character = methodNode.idCharacter;
6164
}
6265
}
6366

lana/src/salesforce/ApexParser/ApexVisitor.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,50 @@ import type { ErrorNode, ParseTree, RuleNode, TerminalNode } from 'antlr4ts/tree
1111

1212
type ApexNature = 'Class' | 'Method';
1313

14+
/**
15+
* Represents a node in the Apex syntax tree.
16+
* Can be either a class or method declaration with optional child nodes.
17+
*/
1418
export interface ApexNode {
19+
/** The type of Apex construct (Class or Method) */
1520
nature?: ApexNature;
21+
/** The name of the class or method */
1622
name?: string;
23+
/** Child nodes (nested classes or methods) */
1724
children?: ApexNode[];
25+
/** Line number where the node is declared */
1826
line?: number;
27+
/** Character position of the identifier on the line */
28+
idCharacter?: number;
29+
}
30+
31+
/**
32+
* Represents a class declaration node in the Apex syntax tree.
33+
* All properties are required (non-optional) to ensure complete class metadata.
34+
*/
35+
export interface ApexClassNode extends ApexNode {
36+
/** Indicates this node represents a class declaration */
37+
nature: 'Class';
38+
/** Line number where the class is declared */
39+
line: number;
40+
/** Character position of the class identifier on the line */
41+
idCharacter: number;
1942
}
2043

21-
export type ApexMethodNode = ApexNode & {
44+
/**
45+
* Represents a method declaration node in the Apex syntax tree.
46+
* All properties are required (non-optional) to ensure complete method metadata.
47+
*/
48+
export interface ApexMethodNode extends ApexNode {
49+
/** Indicates this node represents a method declaration */
2250
nature: 'Method';
51+
/** Comma-separated list of parameter types for the method */
2352
params: string;
53+
/** Line number where the method is declared */
2454
line: number;
25-
};
55+
/** Character position of the method identifier on the line */
56+
idCharacter: number;
57+
}
2658

2759
type VisitableApex = ParseTree & {
2860
accept<Result>(visitor: ApexParserVisitor<Result>): Result;
@@ -49,22 +81,30 @@ export class ApexVisitor implements ApexParserVisitor<ApexNode> {
4981
return { children };
5082
}
5183

52-
visitClassDeclaration(ctx: ClassDeclarationContext): ApexNode {
84+
visitClassDeclaration(ctx: ClassDeclarationContext): ApexClassNode {
85+
const { start } = ctx;
86+
const ident = ctx.id();
87+
5388
return {
5489
nature: 'Class',
5590
name: ctx.id().Identifier()?.toString() ?? '',
5691
children: ctx.children?.length ? this.visitChildren(ctx).children : [],
57-
line: ctx.start.line,
92+
line: start.line,
93+
idCharacter: ident.start.charPositionInLine ?? 0,
5894
};
5995
}
6096

6197
visitMethodDeclaration(ctx: MethodDeclarationContext): ApexMethodNode {
98+
const { start } = ctx;
99+
const ident = ctx.id();
100+
62101
return {
63102
nature: 'Method',
64103
name: ctx.id().Identifier()?.toString() ?? '',
65104
children: ctx.children?.length ? this.visitChildren(ctx).children : [],
66105
params: this.getParameters(ctx.formalParameters()),
67-
line: ctx.start.line,
106+
line: start.line,
107+
idCharacter: ident.start.charPositionInLine,
68108
};
69109
}
70110

0 commit comments

Comments
 (0)