Skip to content

Commit b5b0b83

Browse files
committed
refactor:🛠️ import suggestions
1 parent 826be8e commit b5b0b83

File tree

3 files changed

+121
-94
lines changed

3 files changed

+121
-94
lines changed

src/completionItems.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
const vscode = require("vscode");
2+
const fs = require("fs");
3+
const path = require("path");
4+
5+
let remappings = null; // Cache remappings globally
6+
7+
async function loadRemappings(rootPath) {
8+
const remappingsPath = path.join(rootPath, 'remappings.txt');
9+
try {
10+
const remappingsContent = await vscode.workspace.fs.readFile(vscode.Uri.file(remappingsPath));
11+
return remappingsContent.toString().split('\n').reduce((acc, line) => {
12+
const [key, val] = line.split('=');
13+
if (key && val) {
14+
acc[key.trim()] = val.trim();
15+
}
16+
return acc;
17+
}, {});
18+
} catch (error) {
19+
console.error('Error reading remappings:', error);
20+
return {};
21+
}
22+
}
23+
24+
// Function to find all Solidity files in the current workspace
25+
async function findSolidityFiles() {
26+
const solidityFilePattern = '**/*.sol';
27+
28+
return vscode.workspace.findFiles(solidityFilePattern)
29+
.then(files => files.map(file => file.fsPath));
30+
}
31+
32+
// Function to extract contract and library names from a Solidity file
33+
function extractNames(filePath) {
34+
const fileContent = fs.readFileSync(filePath, 'utf8');
35+
const names = [];
36+
const regex = /\b(contract|library|type)\s+(\w+)/g; // Regex to match contract, library and type definitions
37+
let match;
38+
39+
while ((match = regex.exec(fileContent)) !== null) {
40+
names.push(match[2]);
41+
}
42+
43+
return names;
44+
}
45+
46+
// Function to prepopulate an index of contract names and their corresponding file paths
47+
async function prepopulateIndex() {
48+
const solidityFiles = await findSolidityFiles();
49+
const index = {};
50+
51+
// Extract names from each file and populate the index
52+
solidityFiles.forEach(filePath => {
53+
const names = extractNames(filePath);
54+
names.forEach(name => {
55+
if (!index[name]) {
56+
index[name] = [];
57+
}
58+
index[name].push(filePath);
59+
});
60+
});
61+
62+
return index;
63+
}
64+
65+
66+
// Function to provide import suggestions
67+
async function provideCompletionItems(document, position) {
68+
// Check if the current line starts with "import"
69+
const linePrefix = document.lineAt(position).text.substring(0, position.character);
70+
if (!linePrefix.startsWith('import')) {
71+
return undefined;
72+
}
73+
74+
// Ensure a workspace folder is open
75+
const workspaceFolders = vscode.workspace.workspaceFolders;
76+
if (!workspaceFolders) return undefined;
77+
78+
const rootPath = workspaceFolders[0].uri.fsPath;
79+
80+
// Load remappings if not already loaded
81+
if (remappings === null) {
82+
remappings = await loadRemappings(rootPath);
83+
}
84+
85+
const index = await prepopulateIndex();
86+
const currentFilePath = document.uri.fsPath;
87+
const currentDir = path.dirname(currentFilePath);
88+
89+
const completionItems = Object.entries(index).flatMap(([name, filePaths]) => {
90+
return filePaths.map(filePath => {
91+
let finalPath = filePath.replace(rootPath + path.sep, ''); // Truncate rootPath
92+
93+
// Apply remappings to the file path
94+
Object.entries(remappings).forEach(([key, value]) => {
95+
if (finalPath.startsWith(value)) {
96+
finalPath = finalPath.replace(value, key);
97+
}
98+
});
99+
100+
if (finalPath === filePath) {
101+
// If no remapping was applied, make it a relative path
102+
finalPath = `./${path.relative(currentDir, filePath).split(path.sep).join('/')}`;
103+
}
104+
105+
const completionItem = new vscode.CompletionItem(`${name} from "${finalPath}"`, vscode.CompletionItemKind.File);
106+
completionItem.insertText = `{ ${name} } from "${finalPath}";`;
107+
108+
return completionItem;
109+
});
110+
});
111+
112+
return completionItems;
113+
}
114+
115+
function resetRemappings() {
116+
remappings = null;
117+
}
118+
119+
module.exports = {resetRemappings, provideCompletionItems};

src/extension.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const { treeFilesCodeActionProvider, treeFilesDiagnosticCollection } = require("
4040

4141
const { scaffoldActiveFile, scaffoldContextMenu } = require("./commands/bulloak-scaffold");
4242

43-
const { provideCompletionItems, resetRemappings } = require("./helpers");
43+
const { provideCompletionItems, resetRemappings } = require("./completionItems.js");
4444

4545

4646
/** global vars */

src/helpers.js

Lines changed: 1 addition & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -24,98 +24,6 @@ function newWindowBeside(content) {
2424
);
2525
}
2626

27-
let remappings = null; // Cache remappings globally
28-
29-
async function loadRemappings(rootPath) {
30-
const remappingsPath = path.join(rootPath, 'remappings.txt');
31-
try {
32-
const remappingsContent = await vscode.workspace.fs.readFile(vscode.Uri.file(remappingsPath));
33-
return remappingsContent.toString().split('\n').reduce((acc, line) => {
34-
const [key, val] = line.split('=');
35-
if (key && val) {
36-
acc[key.trim()] = val.trim();
37-
}
38-
return acc;
39-
}, {});
40-
} catch (error) {
41-
console.error('Error reading remappings:', error);
42-
return {};
43-
}
44-
}
45-
46-
47-
async function provideCompletionItems(document, position) {
48-
const linePrefix = document.lineAt(position).text.substring(0, position.character);
49-
if (!linePrefix.startsWith('import')) {
50-
return undefined;
51-
}
52-
53-
// Step 1: Read and parse remappings
54-
const workspaceFolders = vscode.workspace.workspaceFolders;
55-
if (!workspaceFolders) return undefined; // No workspace folder open
56-
57-
const rootPath = workspaceFolders[0].uri.fsPath;
58-
59-
if (remappings === null) { // Load remappings if not already loaded
60-
remappings = await loadRemappings(rootPath);
61-
}
62-
63-
// Step 2: Apply remappings to import paths
64-
const solidityFiles = await findSolidityFiles();
65-
const currentFilePath = document.uri.fsPath;
66-
const currentDir = path.dirname(currentFilePath);
67-
68-
69-
const completionItems = solidityFiles.map(file => {
70-
let filePath = vscode.workspace.asRelativePath(file, false);
71-
let remappedPath = filePath; // Start with the original path
72-
73-
// Apply remappings
74-
Object.entries(remappings).forEach(([key, value]) => {
75-
if (filePath.startsWith(value)) {
76-
// Replace the matching part of the path with the remapping key
77-
remappedPath = filePath.replace(value, key);
78-
}
79-
});
80-
81-
82-
83-
// Determine if the path was remapped
84-
const isRemapped = remappedPath !== filePath;
85-
86-
// Use the remapped path if available, otherwise use the relative path
87-
const finalPath = isRemapped ? remappedPath : `./${path.relative(currentDir, file).split(path.sep).join('/')}`;
88-
89-
const contractName = path.basename(file, '.sol');
90-
const completionItem = new vscode.CompletionItem(`${contractName} from "${finalPath}"`, vscode.CompletionItemKind.File);
91-
92-
// Format the insert text based on whether the path was remapped
93-
completionItem.insertText = `{${contractName}} from "${finalPath}";`;
94-
95-
return completionItem;
96-
});
97-
return completionItems;
98-
}
99-
100-
function findSolidityFiles() {
101-
// Define the glob pattern for Solidity files
102-
const solidityFilePattern = '**/*.sol';
103-
// Exclude files from the node_modules and out dirs
104-
const excludePattern = '{**/node_modules/**,**/out/**,**/test}';
105-
106-
// Use findFiles to search for files matching the Solidity pattern, excluding undesired paths
107-
return vscode.workspace.findFiles(solidityFilePattern, excludePattern)
108-
.then(files => files.map(file => file.fsPath)); // Convert URIs to file system paths
109-
110-
}
111-
112-
function resetRemappings() {
113-
remappings = null;
114-
}
115-
116-
117-
118-
11927
const networkMap = {
12028
"1": "Ethereum",
12129
"8": "Ubiq",
@@ -214,4 +122,4 @@ const networkMap = {
214122

215123

216124

217-
module.exports = { newWindowBeside, getContractRootDir, LANGID, networkMap, provideCompletionItems, resetRemappings };
125+
module.exports = { newWindowBeside, getContractRootDir, LANGID, networkMap };

0 commit comments

Comments
 (0)