diff --git a/vscode-lean4/manual/manual.md b/vscode-lean4/manual/manual.md index c07b3975..07f88bb4 100644 --- a/vscode-lean4/manual/manual.md +++ b/vscode-lean4/manual/manual.md @@ -204,6 +204,7 @@ Instead, the state of all dependencies of `Module.lean` needs to be updated manu VS Code will display [error- or information-level diagnostics](#errors-warnings-and-information) whenever a dependency of a file is edited and saved. When initially opening a file and a dependency would need to be rebuilt to be up-to-date, it issues an error and refuses to [process](#file-processing) the rest of the file. When a dependency changes while the file is already open, it issues an information-level diagnostic, but will continue processing the file with the previous state of the dependency. In order to automatically trigger rebuilds of all changed dependencies when opening a file instead of issuing an error, the 'Lean 4: Automatically Build Dependency' setting can be enabled. +In order to automatically restart an already-open file when Lean reports that its dependencies are out of date, the 'Lean 4: Automatically Restart File On Dependency Change' setting can be enabled.
diff --git a/vscode-lean4/package.json b/vscode-lean4/package.json index 2f79c870..7bf6cb94 100644 --- a/vscode-lean4/package.json +++ b/vscode-lean4/package.json @@ -71,6 +71,11 @@ "default": false, "markdownDescription": "Enable automatically building dependencies when opening a file. In Lean versions pre-4.2.0, dependencies are always built automatically regardless of this setting." }, + "lean4.automaticallyRestartFileOnDependencyChange": { + "type": "boolean", + "default": false, + "markdownDescription": "Automatically restart a Lean file when the Lean server reports that its imports are out of date because a dependency changed. This can trigger expensive dependency rebuilds." + }, "lean4.envPathExtensions": { "type": "array", "default": [], diff --git a/vscode-lean4/src/config.ts b/vscode-lean4/src/config.ts index 597b9be5..bad1368e 100644 --- a/vscode-lean4/src/config.ts +++ b/vscode-lean4/src/config.ts @@ -17,6 +17,10 @@ export function automaticallyBuildDependencies(): boolean { return workspace.getConfiguration('lean4').get('automaticallyBuildDependencies', false) } +export function automaticallyRestartFileOnDependencyChange(): boolean { + return workspace.getConfiguration('lean4').get('automaticallyRestartFileOnDependencyChange', false) +} + export function envPathExtensions(): PATH { return new PATH(workspace.getConfiguration('lean4').get('envPathExtensions', [])) } diff --git a/vscode-lean4/src/leanclient.ts b/vscode-lean4/src/leanclient.ts index 7d53dd03..fc1fad9b 100644 --- a/vscode-lean4/src/leanclient.ts +++ b/vscode-lean4/src/leanclient.ts @@ -41,6 +41,7 @@ import { } from '@leanprover/infoview-api' import { allowedLoggingMethods, + automaticallyRestartFileOnDependencyChange, disallowedLoggingMethods, isLoggingEnabled, loggingDir, @@ -56,6 +57,7 @@ import { DiagnosticChangeEvent, LeanClientDiagnosticCollection } from './diagnos import { formatCommandExecutionOutput } from './utils/batch' import { c2pConverter, + LeanDiagnostic, LeanImport, LeanModule, LeanModuleHierarchyImportedByParams, @@ -107,7 +109,7 @@ interface LeanClientCapabilties { const leanClientCapabilities: LeanClientCapabilties = { incrementalDiagnosticSupport: true, silentDiagnosticSupport: true, - rpcWireFormat: 'v1' + rpcWireFormat: 'v1', } export type PrepareModuleHierarchyResult = @@ -136,6 +138,8 @@ export class LeanClient implements Disposable { private showingRestartMessage: boolean = false private isRestarting: boolean = false private staleDepNotifier: Disposable | undefined + // Prevent repeatedly restarting a file while the same stale-dependency diagnostic remains active. + private autoRestartedStaleDependencyUris: Set = new Set() private configFileContents: Map = new Map() private openServerDocuments: Set = new Set() @@ -549,7 +553,9 @@ export class LeanClient implements Disposable { const vsCodeCollection = languages.createDiagnosticCollection('lean4') this.diagnosticCollection = new LeanClientDiagnosticCollection(vsCodeCollection) this.diagnosticCollection.onDidChangeDiagnostics((e: DiagnosticChangeEvent) => { - this.diagnosticsEmitter.fire(e.accumulatedParams()) + const params = e.accumulatedParams() + this.diagnosticsEmitter.fire(params) + void this.maybeAutoRestartFileWithStaleDependencies(params) }) this.client.onNotification('textDocument/publishDiagnostics', (params: LeanPublishDiagnosticsParams) => { this.diagnosticCollection?.publishDiagnostics(params) @@ -621,7 +627,46 @@ export class LeanClient implements Disposable { insideRestart = false } + private isStaleDependencyDiagnostic(diagnostic: LeanDiagnostic): boolean { + const message = diagnostic.message.toLowerCase() + const mentionsRestartFile = message.includes('restart file') + const isImportsOutdated = message.includes('imports are out of date') + const isDependencyModified = message.includes('dependenc') && message.includes('modified') + return mentionsRestartFile && (isImportsOutdated || isDependencyModified) + } + + private async maybeAutoRestartFileWithStaleDependencies(params: LeanPublishDiagnosticsParams): Promise { + const fileUri = parseExtUri(params.uri) + if (fileUri === undefined) { + return + } + + const uri = fileUri.toString() + const hasStaleDependencyDiagnostic = params.diagnostics.some(d => this.isStaleDependencyDiagnostic(d)) + if (!hasStaleDependencyDiagnostic) { + this.autoRestartedStaleDependencyUris.delete(uri) + return + } + + if (!automaticallyRestartFileOnDependencyChange() || this.autoRestartedStaleDependencyUris.has(uri)) { + return + } + + const document = lean.getLeanDocumentByUri(fileUri) + if (document === undefined) { + return + } + + this.autoRestartedStaleDependencyUris.add(uri) + logger.log(`[LeanClient] Automatically restarting file with stale dependencies: ${uri}`) + await this.restartFile(document) + } + private checkForImportsOutdatedError(params: LeanPublishDiagnosticsParams) { + if (automaticallyRestartFileOnDependencyChange()) { + return + } + const fileUri = parseExtUri(params.uri) if (fileUri === undefined) { return @@ -730,6 +775,7 @@ export class LeanClient implements Disposable { this.diagnosticCollection = undefined this.client = undefined this.openServerDocuments = new Set() + this.autoRestartedStaleDependencyUris = new Set() this.running = false }