Skip to content

Commit e24776b

Browse files
committed
prevent files that are not actually open from being analyzed
1 parent 9c4cade commit e24776b

6 files changed

Lines changed: 253 additions & 27 deletions

File tree

language_server/editor_helper.lua

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ function META.New()
9999
TempFiles = {},
100100
LoadedFiles = {},
101101
OpenFiles = {},
102+
VisibleFiles = {},
102103
PublishedDiagnostics = {},
104+
UseVisibleFilesForOpen = false,
103105
debug = false,
104106
node_to_type = node_to_type,
105107
},
@@ -230,6 +232,38 @@ local function normalize_path(path)
230232
return path_util.Normalize(path)
231233
end
232234

235+
function META:SetVisibleFiles(paths)
236+
local next_visible = {}
237+
local newly_visible = {}
238+
239+
for _, path in ipairs(paths or {}) do
240+
path = normalize_path(path)
241+
242+
if path then
243+
next_visible[path] = true
244+
245+
if not self.VisibleFiles[path] then
246+
table.insert(newly_visible, path)
247+
end
248+
end
249+
end
250+
251+
self.VisibleFiles = next_visible
252+
self.UseVisibleFilesForOpen = true
253+
254+
return newly_visible
255+
end
256+
257+
function META:IsVisibleFile(path)
258+
path = normalize_path(path)
259+
return path ~= nil and self.VisibleFiles[path] or false
260+
end
261+
262+
function META:ShouldRecompileOnOpen(path)
263+
if not self.UseVisibleFilesForOpen then return true end
264+
return self:IsVisibleFile(path)
265+
end
266+
233267
function META:Recompile(path, lol, diagnostics, on_save_path)
234268
if path then path = normalize_path(path) end
235269

@@ -779,12 +813,13 @@ function META:Format(code, path, extra_emitter_config)
779813
return code
780814
end
781815

782-
function META:OpenFile(path, code)
816+
function META:OpenFile(path, code, should_recompile)
783817
path = path_util.Normalize(path)
784818
self.OpenFiles[path] = true
785819
self:SetFileContent(path, code)
820+
if should_recompile == nil then should_recompile = self:ShouldRecompileOnOpen(path) end
786821

787-
if not self:IsLightLspMode(path) then assert(self:Recompile(path)) end
822+
if should_recompile and not self:IsLightLspMode(path) then assert(self:Recompile(path)) end
788823
end
789824

790825
function META:CloseFile(path)

language_server/lsp.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,23 @@ lsp.methods["workspace/didChangeConfiguration"] = function(params)
300300
editor_helper.workspace_config = params.settings.nattlua
301301
end
302302
end
303+
lsp.methods["nattlua/visibleEditors"] = function(params)
304+
local paths = {}
305+
306+
if params and params.uris then
307+
for i, uri in ipairs(params.uris) do
308+
paths[i] = to_fs_path(uri)
309+
end
310+
end
311+
312+
local newly_visible = editor_helper:SetVisibleFiles(paths)
313+
314+
for _, path in ipairs(newly_visible) do
315+
if editor_helper.OpenFiles[path] and not editor_helper:IsLightLspMode(path) then
316+
editor_helper:Recompile(path)
317+
end
318+
end
319+
end
303320
lsp.methods["textDocument/didOpen"] = function(params)
304321
local path = to_fs_path(params.textDocument.uri)
305322
editor_helper:OpenFile(path, params.textDocument.text)

test/tests/language_server/integration.lua

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,34 @@ do
8585
assert(#highlights > 0, "Should have highlights for 'a'")
8686
end
8787

88+
do
89+
local client = LSPClient.New()
90+
client:SetWorkingDirectory("/workspace")
91+
local root_uri = "file:///workspace"
92+
client:Initialize(lsp, root_uri)
93+
client:Notify(lsp, "nattlua/visibleEditors", {uris = {}})
94+
local file_uri = root_uri .. "/hidden.nlua"
95+
client:Notify(
96+
lsp,
97+
"textDocument/didOpen",
98+
{
99+
textDocument = {
100+
uri = file_uri,
101+
languageId = "nattlua",
102+
version = 1,
103+
text = "",
104+
},
105+
}
106+
)
107+
assert(#client:GetNotifications("textDocument/publishDiagnostics") == 0)
108+
client:Notify(lsp, "nattlua/visibleEditors", {uris = {file_uri}})
109+
assert(#client:GetNotifications("textDocument/publishDiagnostics") > 0)
110+
client:Notify(lsp, "nattlua/visibleEditors", {uris = {}})
111+
client:Notify(lsp, "textDocument/didClose", {textDocument = {uri = file_uri}})
112+
lsp.editor_helper.UseVisibleFilesForOpen = false
113+
lsp.editor_helper.VisibleFiles = {}
114+
end
115+
88116
do
89117
lsp.editor_helper:SetConfigFunction(function(path)
90118
return {

vscode_extension/src/extension.ts

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,85 @@ export async function activate(context: ExtensionContext) {
3232
let restartTimestamps: number[] = [];
3333
let restartTimer: NodeJS.Timeout | undefined;
3434
let startPromise: Promise<void> | undefined;
35+
const syncedVisibleDocuments = new Set<string>();
36+
37+
const isVisibleDocument = (document: TextDocument) => {
38+
return window.visibleTextEditors.some(editor => editor.document.uri.toString() === document.uri.toString());
39+
};
40+
41+
const isSyncCandidate = (document: TextDocument) => {
42+
return document.uri.scheme === 'file' && document.languageId === 'nattlua';
43+
};
44+
45+
const sendDidOpen = (document: TextDocument) => {
46+
if (!client || !client.isRunning()) {
47+
return;
48+
}
49+
50+
const uri = document.uri.toString();
51+
client.sendNotification('textDocument/didOpen', {
52+
textDocument: {
53+
uri,
54+
languageId: document.languageId,
55+
version: document.version,
56+
text: document.getText(),
57+
},
58+
});
59+
syncedVisibleDocuments.add(uri);
60+
};
61+
62+
const sendDidClose = (document: TextDocument) => {
63+
if (!client || !client.isRunning()) {
64+
return;
65+
}
66+
67+
const uri = document.uri.toString();
68+
client.sendNotification('textDocument/didClose', {
69+
textDocument: { uri },
70+
});
71+
syncedVisibleDocuments.delete(uri);
72+
};
73+
74+
const reconcileVisibleDocumentSync = () => {
75+
if (!client || !client.isRunning()) {
76+
return;
77+
}
78+
79+
const visibleDocuments = window.visibleTextEditors
80+
.map(editor => editor.document)
81+
.filter(isSyncCandidate);
82+
const nextVisibleUris = new Set(visibleDocuments.map(document => document.uri.toString()));
83+
84+
for (const document of visibleDocuments) {
85+
const uri = document.uri.toString();
86+
if (!syncedVisibleDocuments.has(uri)) {
87+
sendDidOpen(document);
88+
}
89+
}
90+
91+
for (const uri of Array.from(syncedVisibleDocuments)) {
92+
if (!nextVisibleUris.has(uri)) {
93+
const document = workspace.textDocuments.find(document => document.uri.toString() === uri);
94+
if (document && isSyncCandidate(document)) {
95+
sendDidClose(document);
96+
} else {
97+
syncedVisibleDocuments.delete(uri);
98+
}
99+
}
100+
}
101+
};
102+
103+
const sendVisibleEditors = () => {
104+
if (!client || !client.isRunning()) {
105+
return;
106+
}
107+
108+
const uris = window.visibleTextEditors
109+
.filter(editor => editor.document.uri.scheme === 'file' && editor.document.languageId === 'nattlua')
110+
.map(editor => editor.document.uri.toString());
111+
112+
client.sendNotification('nattlua/visibleEditors', { uris });
113+
};
35114

36115
const executable = resolveVariables(config.get<string>("executable") || "nattlua");
37116
const workingDirectory = resolveVariables(config.get<string>("workingDirectory") || "${workspaceFolder}");
@@ -88,6 +167,8 @@ export async function activate(context: ExtensionContext) {
88167
try {
89168
await client.start();
90169
clientOutput.appendLine(`NattLua server started successfully (${reason})`);
170+
reconcileVisibleDocumentSync();
171+
sendVisibleEditors();
91172
} catch (err: any) {
92173
const message = err?.message || String(err);
93174
clientOutput.appendLine(`NattLua failed to start (${reason}): ${message}`);
@@ -103,7 +184,7 @@ export async function activate(context: ExtensionContext) {
103184
const errorHandler: ErrorHandler = {
104185
error: (error, message, count) => {
105186
LSPOutput.appendLine(`LSP Error: ${message} (${count}) - ${error.message}`);
106-
if (count <= 3) return { action: ErrorAction.Continue };
187+
if ((count ?? 0) <= 3) return { action: ErrorAction.Continue };
107188
return { action: ErrorAction.Shutdown };
108189
},
109190
closed: () => {
@@ -118,6 +199,41 @@ export async function activate(context: ExtensionContext) {
118199
synchronize: {
119200
configurationSection: "nattlua",
120201
},
202+
middleware: {
203+
didOpen: (document, next) => {
204+
const visible = isVisibleDocument(document);
205+
206+
if (!visible) {
207+
return Promise.resolve();
208+
}
209+
210+
syncedVisibleDocuments.add(document.uri.toString());
211+
return next(document);
212+
},
213+
didChange: (event, next) => {
214+
if (!syncedVisibleDocuments.has(event.document.uri.toString())) {
215+
return Promise.resolve();
216+
}
217+
218+
return next(event);
219+
},
220+
didSave: (document, next) => {
221+
if (!syncedVisibleDocuments.has(document.uri.toString())) {
222+
return Promise.resolve();
223+
}
224+
225+
return next(document);
226+
},
227+
didClose: (document, next) => {
228+
const uri = document.uri.toString();
229+
if (!syncedVisibleDocuments.has(uri)) {
230+
return Promise.resolve();
231+
}
232+
233+
syncedVisibleDocuments.delete(uri);
234+
return next(document);
235+
},
236+
},
121237
errorHandler: errorHandler,
122238
};
123239

@@ -241,10 +357,20 @@ export async function activate(context: ExtensionContext) {
241357
},
242358
});
243359

360+
context.subscriptions.push(window.onDidChangeVisibleTextEditors(() => {
361+
reconcileVisibleDocumentSync();
362+
sendVisibleEditors();
363+
}));
364+
365+
context.subscriptions.push(window.onDidChangeActiveTextEditor(() => {
366+
reconcileVisibleDocumentSync();
367+
sendVisibleEditors();
368+
}));
369+
244370
await startClient("initial start");
245371
}
246372

247-
export async function deactivate(): Promise<void> | undefined {
373+
export async function deactivate(): Promise<void | undefined> {
248374
if (!client) {
249375
return undefined;
250376
}

vscode_extension/src/vscode-variables.ts

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,54 @@ import * as process from 'process';
33
import * as path from 'path';
44

55
export function resolveVariables(string: string, recursive = false) {
6-
let workspaces = vscode.workspace.workspaceFolders;
7-
let workspace = vscode.workspace.workspaceFolders.length ? vscode.workspace.workspaceFolders[0] : null;
8-
let activeFile = vscode.window.activeTextEditor?.document;
9-
let absoluteFilePath = activeFile?.uri.fsPath
10-
string = string.replace(/\${workspaceFolder}/g, workspace?.uri.fsPath);
11-
string = string.replace(/\${workspaceFolderBasename}/g, workspace?.name);
12-
string = string.replace(/\${file}/g, absoluteFilePath);
6+
const workspaces = vscode.workspace.workspaceFolders || [];
7+
const workspace = workspaces.length ? workspaces[0] : null;
8+
const activeEditor = vscode.window.activeTextEditor;
9+
const activeDocument = activeEditor?.document;
10+
const absoluteFilePath = activeDocument?.uri.fsPath || '';
11+
const workspaceFolderPath = workspace?.uri.fsPath || '';
12+
const workspaceFolderBasename = workspace?.name || '';
1313
let activeWorkspace = workspace;
1414
let relativeFilePath = absoluteFilePath;
15-
for (let workspace of workspaces) {
16-
if (absoluteFilePath.replace(workspace.uri.fsPath, '') !== absoluteFilePath) {
17-
activeWorkspace = workspace;
18-
relativeFilePath = absoluteFilePath.replace(workspace.uri.fsPath, '').substr(path.sep.length);
19-
break;
15+
16+
string = string.replace(/\${workspaceFolder}/g, workspaceFolderPath);
17+
string = string.replace(/\${workspaceFolderBasename}/g, workspaceFolderBasename);
18+
string = string.replace(/\${file}/g, absoluteFilePath);
19+
20+
if (absoluteFilePath) {
21+
for (const workspace of workspaces) {
22+
if (absoluteFilePath.startsWith(workspace.uri.fsPath)) {
23+
activeWorkspace = workspace;
24+
relativeFilePath = absoluteFilePath.slice(workspace.uri.fsPath.length).replace(new RegExp(`^\\${path.sep}+`), '');
25+
break;
26+
}
2027
}
2128
}
22-
let parsedPath = path.parse(absoluteFilePath);
23-
string = string.replace(/\${fileWorkspaceFolder}/g, activeWorkspace?.uri.fsPath);
29+
30+
const parsedPath = path.parse(absoluteFilePath || '');
31+
const relativeFileDirname = relativeFilePath.includes(path.sep)
32+
? relativeFilePath.slice(0, relativeFilePath.lastIndexOf(path.sep))
33+
: '';
34+
const fileDirname = parsedPath.dir
35+
? parsedPath.dir.slice(parsedPath.dir.lastIndexOf(path.sep) + 1)
36+
: '';
37+
const cwd = parsedPath.dir || workspaceFolderPath || process.cwd();
38+
const lineNumber = activeEditor ? (activeEditor.selection.start.line + 1).toString() : '1';
39+
const selectedText = activeEditor
40+
? activeEditor.document.getText(new vscode.Range(activeEditor.selection.start, activeEditor.selection.end))
41+
: '';
42+
43+
string = string.replace(/\${fileWorkspaceFolder}/g, activeWorkspace?.uri.fsPath || workspaceFolderPath);
2444
string = string.replace(/\${relativeFile}/g, relativeFilePath);
25-
string = string.replace(/\${relativeFileDirname}/g, relativeFilePath.substr(0, relativeFilePath.lastIndexOf(path.sep)));
26-
string = string.replace(/\${fileBasename}/g, parsedPath.base);
27-
string = string.replace(/\${fileBasenameNoExtension}/g, parsedPath.name);
28-
string = string.replace(/\${fileExtname}/g, parsedPath.ext);
29-
string = string.replace(/\${fileDirname}/g, parsedPath.dir.substr(parsedPath.dir.lastIndexOf(path.sep) + 1));
30-
string = string.replace(/\${cwd}/g, parsedPath.dir);
45+
string = string.replace(/\${relativeFileDirname}/g, relativeFileDirname);
46+
string = string.replace(/\${fileBasename}/g, parsedPath.base || '');
47+
string = string.replace(/\${fileBasenameNoExtension}/g, parsedPath.name || '');
48+
string = string.replace(/\${fileExtname}/g, parsedPath.ext || '');
49+
string = string.replace(/\${fileDirname}/g, fileDirname);
50+
string = string.replace(/\${cwd}/g, cwd);
3151
string = string.replace(/\${pathSeparator}/g, path.sep);
32-
string = string.replace(/\${lineNumber}/g, (vscode.window.activeTextEditor.selection.start.line + 1).toString());
33-
string = string.replace(/\${selectedText}/g, vscode.window.activeTextEditor.document.getText(new vscode.Range(vscode.window.activeTextEditor.selection.start, vscode.window.activeTextEditor.selection.end)));
52+
string = string.replace(/\${lineNumber}/g, lineNumber);
53+
string = string.replace(/\${selectedText}/g, selectedText);
3454
string = string.replace(/\${env:(.*?)}/g, function (variable) {
3555
return process.env[variable.match(/\${env:(.*?)}/)[1]] || '';
3656
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/extension.ts","./src/vscode-variables.ts"],"version":"5.8.2"}
1+
{"root":["./src/extension.ts","./src/vscode-variables.ts"],"version":"5.9.3"}

0 commit comments

Comments
 (0)