Skip to content

Commit 0665cc6

Browse files
committed
add jump to definition from one file to another file
1 parent 9172902 commit 0665cc6

File tree

3 files changed

+96
-1
lines changed

3 files changed

+96
-1
lines changed

client/index.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,5 @@ render(
5353
</Provider>,
5454
document.getElementById('root')
5555
);
56+
57+
export default store;

client/modules/IDE/components/jump-to-definition.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,72 @@
11
/* eslint-disable */
22
import p5CodeAstAnalyzer from './p5CodeAstAnalyzer';
3+
import * as parser from '@babel/parser';
34
import { getAST, getContext } from './rename-variable';
5+
import { selectFiles } from '../selectors/files';
6+
import { setSelectedFile } from '../actions/ide';
47
import announceToScreenReader from '../utils/ScreenReaderHelper';
8+
import store from '../../../index';
59
const traverse = require('@babel/traverse').default;
610

11+
function getScriptLoadOrder(files) {
12+
const indexHtmlFile = files.find((f) => f.name.endsWith('index.html'));
13+
if (!indexHtmlFile) return [];
14+
15+
const scriptRegex = /<script\s+[^>]*src=["']([^"']+)["']/g;
16+
const scripts = [];
17+
let match;
18+
while ((match = scriptRegex.exec(indexHtmlFile.content)) !== null) {
19+
scripts.push(match[1]);
20+
}
21+
return scripts;
22+
}
23+
24+
function buildProjectSymbolTable(files, scriptOrder) {
25+
const symbolTable = {};
26+
27+
for (const scriptName of scriptOrder) {
28+
const file = files.find((f) => f.name.endsWith(scriptName));
29+
if (!file) continue;
30+
31+
let ast;
32+
try {
33+
ast = parser.parse(file.content, {
34+
sourceType: 'script',
35+
plugins: ['jsx', 'typescript']
36+
});
37+
} catch (e) {
38+
continue;
39+
}
40+
41+
traverse(ast, {
42+
FunctionDeclaration(path) {
43+
const name = path.node.id?.name;
44+
if (name && !symbolTable[name]) {
45+
symbolTable[name] = {
46+
file: file.name,
47+
pos: path.node.start
48+
};
49+
}
50+
},
51+
VariableDeclarator(path) {
52+
const name = path.node.id?.name;
53+
if (name && !symbolTable[name]) {
54+
symbolTable[name] = {
55+
file: file.name,
56+
pos: path.node.start
57+
};
58+
}
59+
}
60+
});
61+
}
62+
63+
return symbolTable;
64+
}
65+
766
export function jumpToDefinition(pos) {
67+
const state = store.getState();
68+
const files = selectFiles(state);
69+
870
const cm = this._cm;
971
const token = cm.getTokenAt(pos);
1072
const tokenType = token.type;
@@ -154,6 +216,37 @@ export function jumpToDefinition(pos) {
154216
}
155217
});
156218
}
219+
if (!found) {
220+
const scriptOrder = getScriptLoadOrder(files);
221+
222+
const projectSymbolTable =
223+
buildProjectSymbolTable(files, scriptOrder) || {};
224+
const globalSymbol = projectSymbolTable[varName];
225+
226+
if (globalSymbol) {
227+
const targetFileObj = files.find((f) => f.name === globalSymbol.file);
228+
if (!targetFileObj) {
229+
return;
230+
}
231+
232+
store.dispatch(setSelectedFile(targetFileObj.id));
233+
234+
if (!targetFileObj.cmInstance) {
235+
return;
236+
}
237+
238+
const targetFileCM = targetFileObj.cmInstance;
239+
const defPos = targetFileCM.posFromIndex(globalSymbol.pos);
240+
targetFileCM.setCursor(defPos);
241+
targetFileCM.focus();
242+
243+
announceToScreenReader(
244+
`Jumped to definition of ${varName} in ${globalSymbol.file}`
245+
);
246+
found = true;
247+
}
248+
}
249+
157250
if (!found) {
158251
announceToScreenReader(`No definition found for ${varName}`, true);
159252
}

client/modules/IDE/selectors/files.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createSelector } from '@reduxjs/toolkit';
22

3-
const selectFiles = (state) => state.files;
3+
export const selectFiles = (state) => state.files;
44

55
export const selectRootFile = createSelector(selectFiles, (files) =>
66
files.find((file) => file.name === 'root')

0 commit comments

Comments
 (0)