Skip to content

Commit

Permalink
Merge pull request #247 from renpy/fix-149
Browse files Browse the repository at this point in the history
Improve diagnostic configuration
  • Loading branch information
duckdoom4 authored Jul 14, 2024
2 parents 1d8900f + a12de6c commit 0aca155
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 42 deletions.
2 changes: 2 additions & 0 deletions examples/example.rpy
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ label start:
e "Ren'Py is a language and engine for writing and playing visual
novel games."

$ seen = 1

e "Our goal is to allow people to be able to write the script for
a game, and with very little effort, turn that script into
a working game."
Expand Down
4 changes: 2 additions & 2 deletions examples/game/script.rpy
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ label start:

character.e "You've created a new Ren'Py game."

# call a label
# call a label

call sidebar_label

Expand Down Expand Up @@ -96,7 +96,7 @@ screen hello_title():
# sample python code
init:
"Renpy code block"

python:
renpy.pause(delay)

Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 20 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,32 +226,45 @@
"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.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,
"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": [
Expand All @@ -266,7 +279,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": [
Expand Down
99 changes: 83 additions & 16 deletions src/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Diagnostics (warnings and errors)
import { Diagnostic, DiagnosticCollection, DiagnosticSeverity, ExtensionContext, 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)
Expand Down Expand Up @@ -50,18 +51,51 @@ 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[] = [];

/**
* Init 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);

// 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);
}

/**
* 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;
}

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.
Expand Down Expand Up @@ -165,22 +199,55 @@ export function refreshDiagnostics(doc: TextDocument, diagnosticCollection: Diag
diagnosticCollection.set(doc.uri, diagnostics);
}

export 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<string>("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) {
Expand Down
14 changes: 4 additions & 10 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ExtensionContext, languages, commands, window, TextDocument, Position,
import { colorProvider } 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 { semanticTokensProvider } from "./semantics";
import { hoverProvider } from "./hover";
import { completionProvider } from "./completion";
Expand Down Expand Up @@ -49,7 +49,6 @@ export async function activate(context: ExtensionContext): Promise<void> {
// diagnostics (errors and warnings)
const diagnostics = languages.createDiagnosticCollection("renpy");
context.subscriptions.push(diagnostics);
subscribeToDocumentChanges(context, diagnostics);

// A TextDocument was saved
context.subscriptions.push(
Expand Down Expand Up @@ -85,6 +84,9 @@ export async function activate(context: ExtensionContext): Promise<void> {
}),
);

// diagnostics (errors and warnings)
diagnosticsInit(context);

// custom command - refresh data
const refreshCommand = commands.registerCommand("renpy.refreshNavigationData", async () => {
updateStatusBar("$(sync~spin) Refreshing Ren'Py navigation data...");
Expand Down Expand Up @@ -132,14 +134,6 @@ export async function activate(context: ExtensionContext): Promise<void> {
});
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 () => {
Expand Down
17 changes: 17 additions & 0 deletions src/utilities/functions.ts
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 0aca155

Please sign in to comment.