From 3746787fa0eaa1e52a5d914f4ba63ddab4b1b030 Mon Sep 17 00:00:00 2001 From: Daniel Luque Date: Sat, 11 Feb 2023 18:51:24 +0100 Subject: [PATCH 1/5] Groups diagnostic configurations in renpy.diagnostics --- package.json | 14 +++++++------- src/diagnostics.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 1f75fa8..298cb36 100644 --- a/package.json +++ b/package.json @@ -115,32 +115,32 @@ "default": true, "description": "Show Automatic Images in the displayable auto-completion list. If not checked (false), only images defined in the script will be shown. If checked (true), both script-defined images and images detected in the images folders will be shown." }, - "renpy.warnOnObsoleteMethods": { + "renpy.diagnostics.warnOnObsoleteMethods": { "type": "boolean", "default": true, "description": "Enable obsolete method warnings. If checked (true), obsolete methods (e.g., im.Crop) will be marked with a warning in the editor." }, - "renpy.warnOnUndefinedPersistents": { + "renpy.diagnostics.warnOnUndefinedPersistents": { "type": "boolean", "default": true, "description": "Enable undefined persistent warnings. If checked (true), persistent variables will be marked with a warning in the editor if they haven't been defaulted/defined." }, - "renpy.warnOnUndefinedStoreVariables": { + "renpy.diagnostics.warnOnUndefinedStoreVariables": { "type": "boolean", "default": true, "description": "Enable undefined store variable warnings. If checked (true), store variables will be marked with a warning in the editor if they haven't been defaulted/defined." }, - "renpy.warnOnReservedVariableNames": { + "renpy.diagnostics.warnOnReservedVariableNames": { "type": "boolean", "default": true, "description": "Enable reserved variable warnings. If checked (true), variables will be marked with an error in the editor if they are in the list of names reserved by Python." }, - "renpy.warnOnInvalidVariableNames": { + "renpy.diagnostics.warnOnInvalidVariableNames": { "type": "boolean", "default": true, "description": "Enable invalid variable errors. Variables must begin with a letter or number. They may contain a '_' but may not begin with '_'. If set to true, variables will be flagged in the editor if they do not meet Ren'Py's specifications." }, - "renpy.warnOnIndentationAndSpacingIssues": { + "renpy.diagnostics.warnOnIndentationAndSpacingIssues": { "type": "string", "default": "Error", "enum": [ @@ -155,7 +155,7 @@ ], "description": "Enable indentation and inconsistent spacing checks. If set to Error or Warning, tab characters and inconsistent indentation spacing will be marked in the editor. If set to Disabled, indentation issues will be ignored." }, - "renpy.warnOnInvalidFilenameIssues": { + "renpy.diagnostics.warnOnInvalidFilenameIssues": { "type": "string", "default": "Error", "enum": [ diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 1644fa8..d284628 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -63,7 +63,7 @@ export function refreshDiagnostics(doc: TextDocument, diagnosticCollection: Diag } const diagnostics: Diagnostic[] = []; - const config = workspace.getConfiguration("renpy"); + const config = workspace.getConfiguration("renpy.diagnostics"); //Filenames must begin with a letter or number, //and may not begin with "00", as Ren'Py uses such files for its own purposes. From 056cd7ff013c46464ef85a75bd1a14c765cb79cb Mon Sep 17 00:00:00 2001 From: Daniel Luque Date: Sun, 12 Feb 2023 19:51:55 +0100 Subject: [PATCH 2/5] Moves diagnostics set up to diagnostics module --- src/diagnostics.ts | 25 ++++++++++++++++++++++--- src/extension.ts | 14 ++------------ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/diagnostics.ts b/src/diagnostics.ts index d284628..58e87d2 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -1,7 +1,7 @@ // Diagnostics (warnings and errors) "use strict"; -import { Diagnostic, DiagnosticCollection, DiagnosticSeverity, ExtensionContext, Range, TextDocument, window, workspace } from "vscode"; +import { commands, Diagnostic, DiagnosticCollection, DiagnosticSeverity, ExtensionContext, languages, Range, TextDocument, window, workspace } from "vscode"; import { NavigationData } from "./navigation-data"; import { extractFilename } from "./workspace"; @@ -52,12 +52,31 @@ const rxStoreCheck = /\s+store\.(\w+)[^a-zA-Z_]?/g; const rxTabCheck = /^(\t+)/g; const rsComparisonCheck = /\s+(if|while)\s+(\w+)\s*(=)\s*(\w+)\s*/g; +/** + * Set up diagnostics + * @param context extension context + */ +export function diagnosticsInit(context: ExtensionContext) { + const diagnostics = languages.createDiagnosticCollection("renpy"); + context.subscriptions.push(diagnostics); + + // custom command - refresh diagnostics + const refreshDiagnosticsCommand = commands.registerCommand("renpy.refreshDiagnostics", () => { + if (window.activeTextEditor) { + refreshDiagnostics(window.activeTextEditor.document, diagnostics); + } + }); + context.subscriptions.push(refreshDiagnosticsCommand); + + subscribeToDocumentChanges(context, diagnostics); +} + /** * Analyzes the text document for problems. * @param doc text document to analyze * @param diagnostics diagnostic collection */ -export function refreshDiagnostics(doc: TextDocument, diagnosticCollection: DiagnosticCollection): void { +function refreshDiagnostics(doc: TextDocument, diagnosticCollection: DiagnosticCollection): void { if (doc.languageId !== "renpy") { return; } @@ -167,7 +186,7 @@ export function refreshDiagnostics(doc: TextDocument, diagnosticCollection: Diag diagnosticCollection.set(doc.uri, diagnostics); } -export function subscribeToDocumentChanges(context: ExtensionContext, diagnostics: DiagnosticCollection): void { +function subscribeToDocumentChanges(context: ExtensionContext, diagnostics: DiagnosticCollection): void { if (window.activeTextEditor) { refreshDiagnostics(window.activeTextEditor.document, diagnostics); } diff --git a/src/extension.ts b/src/extension.ts index 9f04b2f..4a47704 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -43,7 +43,7 @@ import { import { RenpyColorProvider } from "./color"; import { getStatusBarText, NavigationData } from "./navigation-data"; import { cleanUpPath, getAudioFolder, getImagesFolder, getNavigationJsonFilepath, getWorkspaceFolder, stripWorkspaceFromFile } from "./workspace"; -import { refreshDiagnostics, subscribeToDocumentChanges } from "./diagnostics"; +import { diagnosticsInit } from "./diagnostics"; import { getSemanticTokens } from "./semantics"; import { getHover } from "./hover"; import { getCompletionList } from "./completion"; @@ -212,9 +212,7 @@ export async function activate(context: ExtensionContext): Promise { ); // diagnostics (errors and warnings) - const diagnostics = languages.createDiagnosticCollection("renpy"); - context.subscriptions.push(diagnostics); - subscribeToDocumentChanges(context, diagnostics); + diagnosticsInit(context); // custom command - refresh data const refreshCommand = commands.registerCommand("renpy.refreshNavigationData", async () => { @@ -241,14 +239,6 @@ export async function activate(context: ExtensionContext): Promise { }); context.subscriptions.push(gotoFileLocationCommand); - // custom command - refresh diagnostics - const refreshDiagnosticsCommand = commands.registerCommand("renpy.refreshDiagnostics", () => { - if (window.activeTextEditor) { - refreshDiagnostics(window.activeTextEditor.document, diagnostics); - } - }); - context.subscriptions.push(refreshDiagnosticsCommand); - // custom command - toggle token debug view let isShowingTokenDebugView = false; const toggleTokenDebugViewCommand = commands.registerCommand("renpy.toggleTokenDebugView", () => { From d73f7614bdfb452745c4caaef1a16601e936c5ea Mon Sep 17 00:00:00 2001 From: Daniel Luque Date: Tue, 14 Mar 2023 23:15:34 +0100 Subject: [PATCH 3/5] Adds the option `diagnosticMode`. This option allows to choose whether to diagnose the open files or all files in the workspace. Fixed #149 --- package.json | 13 +++++++ src/diagnostics.ts | 80 ++++++++++++++++++++++++++++++-------- src/utilities/functions.ts | 17 ++++++++ 3 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 src/utilities/functions.ts diff --git a/package.json b/package.json index 298cb36..52bd3ae 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,19 @@ "default": true, "description": "Show Automatic Images in the displayable auto-completion list. If not checked (false), only images defined in the script will be shown. If checked (true), both script-defined images and images detected in the images folders will be shown." }, + "renpy.diagnostics.diagnosticMode": { + "type": "string", + "default": "openFilesOnly", + "enum": [ + "openFilesOnly", + "workspace" + ], + "enumDescriptions": [ + "Only diagnoses open files.", + "Diagnose all Ren'Py files in the workspace. This mode applies recursive expressions that can be resource-intensive in projects with a large codebase." + ], + "description": "Select whether to analyze all Ren'Py files in the workspace or only open files (default)." + }, "renpy.diagnostics.warnOnObsoleteMethods": { "type": "boolean", "default": true, diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 58e87d2..cd3ca47 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -1,8 +1,9 @@ // Diagnostics (warnings and errors) "use strict"; -import { commands, Diagnostic, DiagnosticCollection, DiagnosticSeverity, ExtensionContext, languages, Range, TextDocument, window, workspace } from "vscode"; +import { commands, Diagnostic, DiagnosticCollection, DiagnosticSeverity, Disposable, ExtensionContext, FileType, languages, Range, TextDocument, Uri, window, workspace } from "vscode"; import { NavigationData } from "./navigation-data"; +import { getAllOpenTabInputTextUri } from "./utilities/functions"; import { extractFilename } from "./workspace"; // Renpy Store Variables (https://www.renpy.org/doc/html/store_variables.html) @@ -52,8 +53,10 @@ const rxStoreCheck = /\s+store\.(\w+)[^a-zA-Z_]?/g; const rxTabCheck = /^(\t+)/g; const rsComparisonCheck = /\s+(if|while)\s+(\w+)\s*(=)\s*(\w+)\s*/g; +const diagnosticModeEvents: Disposable[] = []; + /** - * Set up diagnostics + * Init diagnostics * @param context extension context */ export function diagnosticsInit(context: ExtensionContext) { @@ -68,7 +71,19 @@ export function diagnosticsInit(context: ExtensionContext) { }); context.subscriptions.push(refreshDiagnosticsCommand); - subscribeToDocumentChanges(context, diagnostics); + // Listen to diagnosticMode changes + context.subscriptions.push( + workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration("renpy.diagnostics.diagnosticMode")) { + updateDiagnosticMode(context, diagnostics); + } + }) + ); + + const onDidChangeTextDocument = workspace.onDidChangeTextDocument((doc) => refreshDiagnostics(doc.document, diagnostics)); + context.subscriptions.push(onDidChangeTextDocument); + + updateDiagnosticMode(context, diagnostics); } /** @@ -186,22 +201,55 @@ function refreshDiagnostics(doc: TextDocument, diagnosticCollection: DiagnosticC diagnosticCollection.set(doc.uri, diagnostics); } -function subscribeToDocumentChanges(context: ExtensionContext, diagnostics: DiagnosticCollection): void { - if (window.activeTextEditor) { - refreshDiagnostics(window.activeTextEditor.document, diagnostics); - } +function refreshOpenDocuments(diagnosticCollection: DiagnosticCollection) { + diagnosticCollection.clear(); + const tabInputTextUris = getAllOpenTabInputTextUri(); + tabInputTextUris.forEach((uri) => { + diagnoseFromUri(uri, diagnosticCollection); + }); +} - context.subscriptions.push( - window.onDidChangeActiveTextEditor((editor) => { - if (editor) { - refreshDiagnostics(editor.document, diagnostics); - } - }) - ); +function diagnoseFromUri(uri: Uri, diagnosticCollection: DiagnosticCollection) { + workspace.fs.stat(uri).then((stat) => { + if (stat.type === FileType.File) { + workspace.openTextDocument(uri).then((document) => refreshDiagnostics(document, diagnosticCollection)); + } + }); +} - context.subscriptions.push(workspace.onDidChangeTextDocument((e) => refreshDiagnostics(e.document, diagnostics))); +function onDeleteFromWorkspace(uri: Uri, diagnosticCollection: DiagnosticCollection) { + diagnosticCollection.forEach((diagnosticUri) => { + if (diagnosticUri.fsPath.startsWith(uri.fsPath)) { + diagnosticCollection.delete(diagnosticUri); + } + }); +} - context.subscriptions.push(workspace.onDidCloseTextDocument((doc) => diagnostics.delete(doc.uri))); +function updateDiagnosticMode(context: ExtensionContext, diagnosticCollection: DiagnosticCollection): void { + diagnosticModeEvents.forEach((e) => e.dispose()); + if (workspace.getConfiguration("renpy.diagnostics").get("diagnosticMode") === "openFilesOnly") { + context.subscriptions.push(window.onDidChangeVisibleTextEditors(() => refreshOpenDocuments(diagnosticCollection), undefined, diagnosticModeEvents)); + // There is no guarantee that this event fires when an editor tab is closed + context.subscriptions.push( + workspace.onDidCloseTextDocument( + (doc) => { + if (diagnosticCollection.has(doc.uri)) { + diagnosticCollection.delete(doc.uri); + } + }, + undefined, + diagnosticModeEvents + ) + ); + refreshOpenDocuments(diagnosticCollection); + } else { + const fsWatcher = workspace.createFileSystemWatcher("**/*"); + diagnosticModeEvents.push(fsWatcher); + fsWatcher.onDidChange((uri) => diagnoseFromUri(uri, diagnosticCollection)); + fsWatcher.onDidCreate((uri) => diagnoseFromUri(uri, diagnosticCollection)); + fsWatcher.onDidDelete((uri) => onDeleteFromWorkspace(uri, diagnosticCollection)); + workspace.findFiles("**/*.rpy").then((uris) => uris.forEach((uri) => diagnoseFromUri(uri, diagnosticCollection))); + } } function checkObsoleteMethods(diagnostics: Diagnostic[], line: string, lineIndex: number) { diff --git a/src/utilities/functions.ts b/src/utilities/functions.ts new file mode 100644 index 0000000..dd48639 --- /dev/null +++ b/src/utilities/functions.ts @@ -0,0 +1,17 @@ +import { TabInputText, Uri, window } from "vscode"; + +/** + * Gets the URIs of the tabs opened in the editor whose input is of type TabInputText. + * @returns An array of URIs + */ +export function getAllOpenTabInputTextUri(): Uri[] { + const uris: Uri[] = []; + const tabGroups = window.tabGroups.all; + const tabs = tabGroups.flatMap((group) => group.tabs.map((tab) => tab)); + tabs.forEach((tab) => { + if (tab.input instanceof TabInputText) { + uris.push(tab.input.uri); + } + }); + return uris; +} From a06097fa0bf18096d26752f479b3449c8be9ddae Mon Sep 17 00:00:00 2001 From: Daniel Luque Date: Sat, 13 Jul 2024 15:47:40 +0200 Subject: [PATCH 4/5] fix: build issues --- src/extension.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 87c7fd3..737058d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -49,7 +49,6 @@ export async function activate(context: ExtensionContext): Promise { // diagnostics (errors and warnings) const diagnostics = languages.createDiagnosticCollection("renpy"); context.subscriptions.push(diagnostics); - subscribeToDocumentChanges(context, diagnostics); // A TextDocument was saved context.subscriptions.push( @@ -135,14 +134,6 @@ export async function activate(context: ExtensionContext): Promise { }); context.subscriptions.push(migrateOldFilesCommand); - // custom command - refresh diagnostics - const refreshDiagnosticsCommand = commands.registerCommand("renpy.refreshDiagnostics", () => { - if (window.activeTextEditor) { - refreshDiagnostics(window.activeTextEditor.document, diagnostics); - } - }); - context.subscriptions.push(refreshDiagnosticsCommand); - // custom command - toggle token debug view let isShowingTokenDebugView = false; const toggleTokenDebugViewCommand = commands.registerCommand("renpy.toggleTokenDebugView", async () => { From a12de6c4e38eb988603d3664a39c3ede32f41b70 Mon Sep 17 00:00:00 2001 From: duckdoom4 <60387522+duckdoom4@users.noreply.github.com> Date: Sun, 14 Jul 2024 17:20:32 +0200 Subject: [PATCH 5/5] Fix formatting errors --- src/diagnostics.ts | 10 ++++------ src/extension.ts | 8 ++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/diagnostics.ts b/src/diagnostics.ts index c95a966..2d49467 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -1,6 +1,4 @@ // Diagnostics (warnings and errors) -"use strict"; - import { commands, Diagnostic, DiagnosticCollection, DiagnosticSeverity, Disposable, ExtensionContext, FileType, languages, Range, TextDocument, Uri, window, workspace } from "vscode"; import { NavigationData } from "./navigation-data"; import { getAllOpenTabInputTextUri } from "./utilities/functions"; @@ -77,7 +75,7 @@ export function diagnosticsInit(context: ExtensionContext) { if (e.affectsConfiguration("renpy.diagnostics.diagnosticMode")) { updateDiagnosticMode(context, diagnostics); } - }) + }), ); const onDidChangeTextDocument = workspace.onDidChangeTextDocument((doc) => refreshDiagnostics(doc.document, diagnostics)); @@ -165,7 +163,7 @@ function refreshDiagnostics(doc: TextDocument, diagnosticCollection: DiagnosticC const diagnostic = new Diagnostic( range, `Inconsistent spacing detected (${indention} given, expected a multiple of ${firstIndentation}). Indentation must consist only of spaces in Ren'Py scripts. Each indentation level must consist of the same number of spaces. (4 spaces is strongly recommended.)`, - severity + severity, ); diagnostics.push(diagnostic); } @@ -238,8 +236,8 @@ function updateDiagnosticMode(context: ExtensionContext, diagnosticCollection: D } }, undefined, - diagnosticModeEvents - ) + diagnosticModeEvents, + ), ); refreshOpenDocuments(diagnosticCollection); } else { diff --git a/src/extension.ts b/src/extension.ts index 737058d..ac3682e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -81,7 +81,7 @@ export async function activate(context: ExtensionContext): Promise { logMessage(LogLevel.Error, error as string); } } - }) + }), ); // diagnostics (errors and warnings) @@ -167,7 +167,7 @@ export async function activate(context: ExtensionContext): Promise { request: "launch", program: rpyPath, }, - { noDebug: true } + { noDebug: true }, ); //call renpy @@ -281,8 +281,8 @@ export async function activate(context: ExtensionContext): Promise { ]; }, }, - DebugConfigurationProviderTriggerKind.Dynamic - ) + DebugConfigurationProviderTriggerKind.Dynamic, + ), ); const taskProvider = new RenpyTaskProvider();