From 2361b6906e49b28873e732af5f598847bbdfbf43 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Mon, 29 Jul 2024 16:05:07 +0200 Subject: [PATCH 01/30] Implement base use of feature to check proofs. Needs fixes --- package-lock.json | 39 ++++++++----- package.json | 1 + src/coqLsp/coqLspClient.ts | 11 ++-- src/core/coqProofChecker.ts | 63 +++++++++++++-------- src/core/inspectSourceFile.ts | 4 +- src/test/benchmark/benchmarkingFramework.ts | 4 +- src/test/coqLsp/coqLspGetGoals.test.ts | 21 +++++-- 7 files changed, 90 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index aafa17cb..5092bd40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "tiktoken": "^1.0.13", "tmp": "^0.2.3", "toml": "^3.0.0", + "ts-results": "^3.3.0", "vscode-languageclient": "^9.0.1", "yargs": "^17.7.2" }, @@ -1301,12 +1302,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2030,9 +2031,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -6139,6 +6140,11 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/ts-results": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ts-results/-/ts-results-3.3.0.tgz", + "integrity": "sha512-FWqxGX2NHp5oCyaMd96o2y2uMQmSu8Dey6kvyuFdRJ2AzfmWo3kWa4UsPlCGlfQ/qu03m09ZZtppMoY8EMHuiA==" + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -7364,12 +7370,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-stdout": { @@ -7893,9 +7899,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -10695,6 +10701,11 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "ts-results": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ts-results/-/ts-results-3.3.0.tgz", + "integrity": "sha512-FWqxGX2NHp5oCyaMd96o2y2uMQmSu8Dey6kvyuFdRJ2AzfmWo3kWa4UsPlCGlfQ/qu03m09ZZtppMoY8EMHuiA==" + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", diff --git a/package.json b/package.json index 2bf36567..e640e04e 100644 --- a/package.json +++ b/package.json @@ -418,6 +418,7 @@ "tiktoken": "^1.0.13", "tmp": "^0.2.3", "toml": "^3.0.0", + "ts-results": "^3.3.0", "vscode-languageclient": "^9.0.1", "yargs": "^17.7.2" } diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index 75cffd39..d8119fda 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -1,5 +1,6 @@ import { Mutex } from "async-mutex"; import { readFileSync } from "fs"; +import { Err, Ok, Result } from "ts-results"; import { BaseLanguageClient, Diagnostic, @@ -35,7 +36,7 @@ export interface CoqLspClientInterface extends Disposable { documentUri: Uri, version: number, command: string - ): Promise | Error>; + ): Promise, Error>>; openTextDocument(uri: Uri, version: number): Promise; @@ -89,7 +90,7 @@ export class CoqLspClient implements CoqLspClientInterface { documentUri: Uri, version: number, command?: string - ): Promise | Error> { + ): Promise, Error>> { return await this.mutex.runExclusive(async () => { return this.getFirstGoalAtPointUnsafe( position, @@ -172,7 +173,7 @@ export class CoqLspClient implements CoqLspClientInterface { documentUri: Uri, version: number, command?: string - ): Promise | Error> { + ): Promise, Error>> { let goalRequestParams: GoalRequest = { textDocument: VersionedTextDocumentIdentifier.create( documentUri.uri, @@ -194,10 +195,10 @@ export class CoqLspClient implements CoqLspClientInterface { ); const goal = goals?.goals?.goals?.shift() ?? undefined; if (!goal) { - return new CoqLspError("no goals at point"); + return Err(new CoqLspError("no goals at point")); } - return goal; + return Ok(goal); } private sleep(ms: number): Promise> { diff --git a/src/core/coqProofChecker.ts b/src/core/coqProofChecker.ts index 5a56cbe6..2f7795c5 100644 --- a/src/core/coqProofChecker.ts +++ b/src/core/coqProofChecker.ts @@ -1,5 +1,5 @@ import { Mutex } from "async-mutex"; -import { appendFileSync, existsSync, unlinkSync, writeFileSync } from "fs"; +import { existsSync, unlinkSync, writeFileSync } from "fs"; import * as path from "path"; import { Position } from "vscode-languageclient"; @@ -111,7 +111,7 @@ export class CoqProofChecker implements CoqProofCheckerInterface { try { // 2. Issue open text document request await this.coqLspClient.openTextDocument(auxFileUri); - let auxFileVersion = 1; + // let auxFileVersion = 1; // 3. Iterate over the proofs and сheck them for (const proof of proofs) { @@ -125,36 +125,51 @@ export class CoqProofChecker implements CoqProofCheckerInterface { continue; } - auxFileVersion += 1; - // 3.2. Append the proof the end of the aux file - appendFileSync(auxFileUri.fsPath, proof); - // 3.3. Issue update text request - const diagnosticMessage = - await this.coqLspClient.updateTextDocument( - sourceFileContentPrefix, - proof, - auxFileUri, - auxFileVersion - ); + const goalResult = await this.coqLspClient.getFirstGoalAtPoint( + prefixEndPosition, + auxFileUri, + 1, + proof + ); - // 3.4. Check diagnostics results.push({ proof: proof, - isValid: diagnosticMessage === undefined, - diagnostic: diagnosticMessage, + isValid: goalResult.ok, + diagnostic: goalResult.err + ? goalResult.val.message + : undefined, }); + // auxFileVersion += 1; + // 3.2. Append the proof the end of the aux file + // appendFileSync(auxFileUri.fsPath, proof); + // 3.3. Issue update text request + // const diagnosticMessage = + // await this.coqLspClient.updateTextDocument( + // sourceFileContentPrefix, + // proof, + // auxFileUri, + // auxFileVersion + // ); + + // 3.4. Check diagnostics + // results.push({ + // proof: proof, + // isValid: diagnosticMessage === undefined, + // diagnostic: diagnosticMessage, + // }); + // 3.5. Bring file to the previous state - writeFileSync(auxFileUri.fsPath, sourceFileContent); + // writeFileSync(auxFileUri.fsPath, sourceFileContent); // 3.6. Issue update text request - auxFileVersion += 1; - await this.coqLspClient.updateTextDocument( - sourceFileContentPrefix, - "", - auxFileUri, - auxFileVersion - ); + // auxFileVersion += 1; + // await this.coqLspClient.updateTextDocument( + // sourceFileContentPrefix, + // "", + // auxFileUri, + // auxFileVersion + // ); } } finally { // 4. Issue close text document request diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index d47d7243..8996ded7 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -62,9 +62,9 @@ async function createCompletionContexts( fileUri, fileVersion ); - if (!(goal instanceof Error)) { + if (goal.ok) { completionContexts.push({ - proofGoal: goal, + proofGoal: goal.val, prefixEndPosition: hole.range.start, admitEndPosition: hole.range.end, }); diff --git a/src/test/benchmark/benchmarkingFramework.ts b/src/test/benchmark/benchmarkingFramework.ts index 75e3baeb..d45eacff 100644 --- a/src/test/benchmark/benchmarkingFramework.ts +++ b/src/test/benchmark/benchmarkingFramework.ts @@ -516,9 +516,9 @@ async function resolveProofStepsToCompletionContexts( fileUri, fileVersion ); - if (!(goal instanceof Error)) { + if (goal.ok) { completionContexts.push({ - proofGoal: goal, + proofGoal: goal.val, prefixEndPosition: parentedProofStep.proofStep.range.start, admitEndPosition: parentedProofStep.proofStep.range.end, parentTheorem: parentedProofStep.parentTheorem, diff --git a/src/test/coqLsp/coqLspGetGoals.test.ts b/src/test/coqLsp/coqLspGetGoals.test.ts index cbf1bb3c..5ab70d38 100644 --- a/src/test/coqLsp/coqLspGetGoals.test.ts +++ b/src/test/coqLsp/coqLspGetGoals.test.ts @@ -1,4 +1,5 @@ import { expect } from "earl"; +import { Result } from "ts-results"; import { Goal, PpString } from "../../coqLsp/coqLspTypes"; @@ -10,8 +11,9 @@ suite("Retrieve goals from Coq file", () => { async function getGoalsAtPoints( points: { line: number; character: number }[], resourcePath: string[], - projectRootPath?: string[] - ): Promise<(Goal | Error)[]> { + projectRootPath?: string[], + command?: string + ): Promise, Error>[]> { const [filePath, rootDir] = resolveResourcesDir( resourcePath, projectRootPath @@ -22,7 +24,12 @@ suite("Retrieve goals from Coq file", () => { await client.openTextDocument(fileUri); const goals = await Promise.all( points.map(async (point) => { - return await client.getFirstGoalAtPoint(point, fileUri, 1); + return await client.getFirstGoalAtPoint( + point, + fileUri, + 1, + command + ); }) ); await client.closeTextDocument(fileUri); @@ -49,7 +56,9 @@ suite("Retrieve goals from Coq file", () => { }; expect(goals).toHaveLength(1); - expect(unpackGoal(goals[0] as Goal)).toEqual(expectedGoal); + expect(unpackGoal(goals[0].val as Goal)).toEqual( + expectedGoal + ); }); test("Check correct goals requests", async () => { @@ -90,7 +99,7 @@ suite("Retrieve goals from Coq file", () => { expect(goals).toHaveLength(5); for (const [i, goal] of goals.entries()) { expect(goals[i]).not.toBeA(Error); - expect(unpackGoal(goal as Goal)).toEqual( + expect(unpackGoal(goal.val as Goal)).toEqual( expectedGoals[i] ); } @@ -143,7 +152,7 @@ suite("Retrieve goals from Coq file", () => { expect(goals).toHaveLength(3); for (const [i, goal] of goals.entries()) { expect(goals[i]).not.toBeA(Error); - expect(unpackGoal(goal as Goal)).toEqual( + expect(unpackGoal(goal.val as Goal)).toEqual( expectedGoals[i] ); } From 9e9b6e120285e86d925f20283ee10c1c97639765 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Wed, 9 Oct 2024 13:53:31 +0200 Subject: [PATCH 02/30] Test and integrte command arg of getGoals to check proofs Refactored CoqLspClient and CoqProofChecker to use getGoals pretac to check proofs instead of manually modifying the file. ToDo: Get rid of temporary files at all, request completion only on the current document. At least in the plugin iteslf (not in benchmarks) Reference issue: JBRes-1857 --- .vscode/settings.json | 10 +- package-lock.json | 32 +-- src/coqLsp/coqLspClient.ts | 92 +++++-- src/coqLsp/coqLspTypes.ts | 6 + src/core/coqProofChecker.ts | 35 +-- src/core/inspectSourceFile.ts | 17 +- src/test/benchmark/benchmarkingFramework.ts | 19 +- .../coqLsp/coqLspCommandInGoalsReq.test.ts | 239 ++++++++++++++++++ src/test/coqLsp/coqLspGetGoals.test.ts | 61 +++-- src/test/resources/test_many_subgoals.v | 26 ++ 10 files changed, 418 insertions(+), 119 deletions(-) create mode 100644 src/test/coqLsp/coqLspCommandInGoalsReq.test.ts create mode 100644 src/test/resources/test_many_subgoals.v diff --git a/.vscode/settings.json b/.vscode/settings.json index 954f11e8..913729b7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,17 +1,17 @@ // Place your settings in this file to overwrite default and user settings. { "files.exclude": { + "**/*.vo": true, + "**/*.vok": true, + "**/*.vos": true, + "**/*.aux": true, + "**/*.glob": true, "**/.git": true, "**/.svn": true, "**/.hg": true, "**/CVS": true, "**/.DS_Store": true, "**/Thumbs.db": true, - "**/*.vo": true, - "**/*.vok": true, - "**/*.vos": true, - "**/*.aux": true, - "**/*.glob": true, "out": false, "**/*_cp_aux.v": true }, diff --git a/package-lock.json b/package-lock.json index 5092bd40..b657b79c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1231,11 +1231,11 @@ } }, "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -2684,12 +2684,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -7317,11 +7317,11 @@ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, "axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "requires": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" }, @@ -8383,12 +8383,12 @@ "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index d8119fda..637ecf82 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -31,12 +31,12 @@ import { FlecheDocument, FlecheDocumentParams } from "./coqLspTypes"; import { CoqLspError } from "./coqLspTypes"; export interface CoqLspClientInterface extends Disposable { - getFirstGoalAtPoint( + getGoalsAtPoint( position: Position, documentUri: Uri, version: number, command: string - ): Promise, Error>>; + ): Promise[], Error>>; openTextDocument(uri: Uri, version: number): Promise; @@ -85,14 +85,14 @@ export class CoqLspClient implements CoqLspClientInterface { }); } - async getFirstGoalAtPoint( + async getGoalsAtPoint( position: Position, documentUri: Uri, version: number, command?: string - ): Promise, Error>> { + ): Promise[], Error>> { return await this.mutex.runExclusive(async () => { - return this.getFirstGoalAtPointUnsafe( + return this.getGoalsAtPointUnsafe( position, documentUri, version, @@ -138,11 +138,35 @@ export class CoqLspClient implements CoqLspClientInterface { }); } + /** + * Dirty due to the fact that the client sends no pure + * error: https://github.com/ejgallego/coq-lsp/blob/f98b65344c961d1aad1e0c3785199258f21c3abc/controller/request.ml#L29 + */ + cleanLspError(errorMsg?: string): string | undefined { + const errorMsgPrefixRegex = /^Error in .* request: (.*)$/s; + if (!errorMsg) { + return undefined; + } + const match = errorMsg.match(errorMsgPrefixRegex); + return match ? match[1] : undefined; + } + + removeTraceFromLspError(errorMsgWithTrace: string): string | undefined { + const traceStartString = "Raised at"; + + if (!errorMsgWithTrace.includes(traceStartString)) { + return errorMsgWithTrace.split("\n").shift(); + } + + return errorMsgWithTrace + .substring(0, errorMsgWithTrace.indexOf(traceStartString)) + .trim(); + } + filterDiagnostics( diagnostics: Diagnostic[], position: Position ): string | undefined { - const traceStartString = "Raised at"; const diagnosticMessageWithTrace = diagnostics .filter((diag) => diag.range.start.line >= position.line) .filter((diag) => diag.severity === 1) // 1 is error @@ -150,12 +174,9 @@ export class CoqLspClient implements CoqLspClientInterface { if (!diagnosticMessageWithTrace) { return undefined; - } else if (!diagnosticMessageWithTrace.includes(traceStartString)) { - return diagnosticMessageWithTrace.split("\n").shift(); + } else { + return this.removeTraceFromLspError(diagnosticMessageWithTrace); } - return diagnosticMessageWithTrace - .substring(0, diagnosticMessageWithTrace.indexOf(traceStartString)) - .trim(); } private async getDocumentSymbolsUnsafe(uri: Uri): Promise { @@ -168,12 +189,12 @@ export class CoqLspClient implements CoqLspClientInterface { ); } - private async getFirstGoalAtPointUnsafe( + private async getGoalsAtPointUnsafe( position: Position, documentUri: Uri, version: number, command?: string - ): Promise, Error>> { + ): Promise[], Error>> { let goalRequestParams: GoalRequest = { textDocument: VersionedTextDocumentIdentifier.create( documentUri.uri, @@ -182,23 +203,39 @@ export class CoqLspClient implements CoqLspClientInterface { position, // eslint-disable-next-line @typescript-eslint/naming-convention pp_format: "Str", + command: command, }; - if (command) { - goalRequestParams.command = command; - // goalRequestParams.mode = "After"; - } - - const goals = await this.client.sendRequest( - goalReqType, - goalRequestParams - ); - const goal = goals?.goals?.goals?.shift() ?? undefined; - if (!goal) { - return Err(new CoqLspError("no goals at point")); + try { + const goalAnswer = await this.client.sendRequest( + goalReqType, + goalRequestParams + ); + const goals = goalAnswer?.goals?.goals; + + if (!goals) { + return Err(CoqLspError.unknownError()); + } + + return Ok(goals); + } catch (e) { + if (e instanceof Error) { + const errorMsg = this.cleanLspError( + this.removeTraceFromLspError(e.message) + ); + if (errorMsg) { + return Err(new CoqLspError(errorMsg)); + } + return Err( + new CoqLspError( + "Unable to parse CoqLSP error, please report this issue: " + + e.message + ) + ); + } + + return Err(CoqLspError.unknownError()); } - - return Ok(goal); } private sleep(ms: number): Promise> { @@ -357,5 +394,6 @@ export class CoqLspClient implements CoqLspClientInterface { dispose(): void { this.subscriptions.forEach((d) => d.dispose()); + this.client.stop(); } } diff --git a/src/coqLsp/coqLspTypes.ts b/src/coqLsp/coqLspTypes.ts index bcaed614..6b3d7bc1 100644 --- a/src/coqLsp/coqLspTypes.ts +++ b/src/coqLsp/coqLspTypes.ts @@ -155,4 +155,10 @@ export class CoqLspError extends Error { super(message); this.name = "CoqLspError"; } + + static unknownError(): CoqLspError { + return new CoqLspError( + "Unknown CoqLSP error, please report this issue" + ); + } } diff --git a/src/core/coqProofChecker.ts b/src/core/coqProofChecker.ts index 2f7795c5..be58c39b 100644 --- a/src/core/coqProofChecker.ts +++ b/src/core/coqProofChecker.ts @@ -111,7 +111,6 @@ export class CoqProofChecker implements CoqProofCheckerInterface { try { // 2. Issue open text document request await this.coqLspClient.openTextDocument(auxFileUri); - // let auxFileVersion = 1; // 3. Iterate over the proofs and сheck them for (const proof of proofs) { @@ -125,7 +124,8 @@ export class CoqProofChecker implements CoqProofCheckerInterface { continue; } - const goalResult = await this.coqLspClient.getFirstGoalAtPoint( + // 3.2 Check if proof is valid and closes the first goal + const goalResult = await this.coqLspClient.getGoalsAtPoint( prefixEndPosition, auxFileUri, 1, @@ -139,37 +139,6 @@ export class CoqProofChecker implements CoqProofCheckerInterface { ? goalResult.val.message : undefined, }); - - // auxFileVersion += 1; - // 3.2. Append the proof the end of the aux file - // appendFileSync(auxFileUri.fsPath, proof); - // 3.3. Issue update text request - // const diagnosticMessage = - // await this.coqLspClient.updateTextDocument( - // sourceFileContentPrefix, - // proof, - // auxFileUri, - // auxFileVersion - // ); - - // 3.4. Check diagnostics - // results.push({ - // proof: proof, - // isValid: diagnosticMessage === undefined, - // diagnostic: diagnosticMessage, - // }); - - // 3.5. Bring file to the previous state - // writeFileSync(auxFileUri.fsPath, sourceFileContent); - - // 3.6. Issue update text request - // auxFileVersion += 1; - // await this.coqLspClient.updateTextDocument( - // sourceFileContentPrefix, - // "", - // auxFileUri, - // auxFileVersion - // ); } } finally { // 4. Issue close text document request diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index 8996ded7..9e1c4343 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -57,17 +57,20 @@ async function createCompletionContexts( let completionContexts: CompletionContext[] = []; for (const hole of holesToComplete) { - const goal = await client.getFirstGoalAtPoint( + const goals = await client.getGoalsAtPoint( hole.range.start, fileUri, fileVersion ); - if (goal.ok) { - completionContexts.push({ - proofGoal: goal.val, - prefixEndPosition: hole.range.start, - admitEndPosition: hole.range.end, - }); + if (goals.ok) { + const firstGoal = goals.val.shift(); + if (firstGoal) { + completionContexts.push({ + proofGoal: firstGoal, + prefixEndPosition: hole.range.start, + admitEndPosition: hole.range.end, + }); + } } } diff --git a/src/test/benchmark/benchmarkingFramework.ts b/src/test/benchmark/benchmarkingFramework.ts index d45eacff..cd533589 100644 --- a/src/test/benchmark/benchmarkingFramework.ts +++ b/src/test/benchmark/benchmarkingFramework.ts @@ -511,18 +511,21 @@ async function resolveProofStepsToCompletionContexts( ): Promise { let completionContexts: BenchmarkingCompletionContext[] = []; for (const parentedProofStep of parentedProofSteps) { - const goal = await client.getFirstGoalAtPoint( + const goals = await client.getGoalsAtPoint( parentedProofStep.proofStep.range.start, fileUri, fileVersion ); - if (goal.ok) { - completionContexts.push({ - proofGoal: goal.val, - prefixEndPosition: parentedProofStep.proofStep.range.start, - admitEndPosition: parentedProofStep.proofStep.range.end, - parentTheorem: parentedProofStep.parentTheorem, - }); + if (goals.ok) { + const firstGoal = goals.val.shift(); + if (firstGoal) { + completionContexts.push({ + proofGoal: firstGoal, + prefixEndPosition: parentedProofStep.proofStep.range.start, + admitEndPosition: parentedProofStep.proofStep.range.end, + parentTheorem: parentedProofStep.parentTheorem, + }); + } } } return completionContexts; diff --git a/src/test/coqLsp/coqLspCommandInGoalsReq.test.ts b/src/test/coqLsp/coqLspCommandInGoalsReq.test.ts new file mode 100644 index 00000000..7054e02a --- /dev/null +++ b/src/test/coqLsp/coqLspCommandInGoalsReq.test.ts @@ -0,0 +1,239 @@ +import { expect } from "earl"; +import { Result } from "ts-results"; + +import { Goal, PpString } from "../../coqLsp/coqLspTypes"; + +import { Uri } from "../../utils/uri"; +import { createCoqLspClient } from "../commonTestFunctions/coqLspBuilder"; +import { resolveResourcesDir } from "../commonTestFunctions/pathsResolver"; + +suite("Request goals with `command/pretac` argument", () => { + async function getGoalAtPosition( + position: { line: number; character: number }, + resourcePath: string[], + command: string, + projectRootPath?: string[] + ): Promise[], Error>> { + const [filePath, rootDir] = resolveResourcesDir( + resourcePath, + projectRootPath + ); + const fileUri = Uri.fromPath(filePath); + + const client = createCoqLspClient(rootDir); + await client.openTextDocument(fileUri); + const goals = await client.getGoalsAtPoint( + position, + fileUri, + 1, + command + ); + await client.closeTextDocument(fileUri); + client.dispose(); + + return goals; + } + + test("One Coq sentence: fail to solve with bad goal", async () => { + const goal = await getGoalAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + "reflexivity." + ); + + expect(goal.err).toEqual(true); + expect(goal.val).toBeA(Error); + if (goal.val instanceof Error) { + expect(goal.val.message).toEqual( + 'In environment\nn : nat\nUnable to unify "n" with\n "0 + n + 0".' + ); + } + }); + + test("One Coq sentence: fail to solve with bad tactic", async () => { + const goal = await getGoalAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + "reflexivit." + ); + + expect(goal.err).toEqual(true); + expect(goal.val).toBeA(Error); + if (goal.err) { + expect(goal.val.message).toEqual( + "The reference reflexivit was not found in the current\nenvironment." + ); + } + }); + + test("One Coq sentence: successfully solve no goals left", async () => { + const goals = await getGoalAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + "auto." + ); + + expect(goals.ok).toEqual(true); + if (goals.ok) { + expect(goals.val).toBeEmpty(); + } + }); + + test("One Coq sentence: successfully solve 1 goal left", async () => { + const goals = await getGoalAtPosition( + { line: 9, character: 4 }, + ["test_many_subgoals.v"], + "auto." + ); + + expect(goals.ok).toEqual(true); + if (goals.ok) { + expect(goals.val).toHaveLength(1); + expect(goals.val[0].ty).toEqual("S n + 0 = S n"); + } + }); + + test("One Coq sentence: successfully solve 2 goals left", async () => { + const goals = await getGoalAtPosition( + { line: 22, character: 4 }, + ["test_many_subgoals.v"], + "auto." + ); + + expect(goals.ok).toEqual(true); + if (goals.ok) { + expect(goals.val).toHaveLength(2); + expect(goals.val[0].ty).toEqual( + "Second = First \\/ Second = Second \\/ Second = Third" + ); + expect(goals.val[1].ty).toEqual( + "Third = First \\/ Third = Second \\/ Third = Third" + ); + } + }); + + test("One Coq sentence wrapped into curly braces: solve successfully", async () => { + const goals = await getGoalAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + " { auto. }" + ); + + expect(goals.ok).toEqual(true); + if (goals.ok) { + expect(goals.val).toBeEmpty(); + } + }); + + test("One Coq sentence wrapped into curly braces: bad proof", async () => { + const goals = await getGoalAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + " { kek. }" + ); + + expect(goals.err).toEqual(true); + if (goals.err) { + expect(goals.val.message).toEqual( + "The reference kek was not found in the current\nenvironment." + ); + } + }); + + test("One Coq sentence wrapped into curly braces: good proof, test indent", async () => { + const goals = await getGoalAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ auto. }" + ); + + expect(goals.ok).toEqual(true); + if (goals.ok) { + expect(goals.val).toBeEmpty(); + } + }); + + test("Many Coq sentences: good proof", async () => { + const goals = await getGoalAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "simpl. induction n. reflexivity. auto." + ); + + expect(goals.ok).toEqual(true); + if (goals.ok) { + expect(goals.val).toBeEmpty(); + } + }); + + test("Many Coq sentences: bad proof", async () => { + const goals = await getGoalAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "simpl. induction n. reflexivity. reflexivity." + ); + + expect(goals.err).toEqual(true); + if (goals.err) { + expect(goals.val.message).toEqual( + 'In environment\nn : nat\nIHn : n + 0 = n\nUnable to unify "S n" with\n "S n + 0".' + ); + } + }); + + test("Many Coq sentences: good proof with multiple curly braces", async () => { + const goals = await getGoalAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ simpl. \n induction n. \n { reflexivity. }\n auto. \n }" + ); + + expect(goals.ok).toEqual(true); + if (goals.ok) { + expect(goals.val).toBeEmpty(); + } + }); + + test("Many Coq sentences: bad proof with multiple curly braces", async () => { + const goals = await getGoalAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ simpl. \n induction n. \n { reflexivity. }\n reflexivity. \n }" + ); + + expect(goals.err).toEqual(true); + if (goals.err) { + expect(goals.val.message).toEqual( + 'In environment\nn : nat\nIHn : n + 0 = n\nUnable to unify "S n" with\n "S n + 0".' + ); + } + }); + + test("Many Coq sentences: good proof with bullets", async () => { + const goals = await getGoalAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ \n simpl. \n induction n. \n - reflexivity.\n - auto. \n }" + ); + + expect(goals.ok).toEqual(true); + if (goals.ok) { + expect(goals.val).toBeEmpty(); + } + }); + + test("Many Coq sentences: bad proof with bullets", async () => { + const goals = await getGoalAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ \n simpl. \n induction n. \n - reflexivity.\n - reflexivity. \n }" + ); + + expect(goals.err).toEqual(true); + if (goals.err) { + expect(goals.val.message).toEqual( + 'In environment\nn : nat\nIHn : n + 0 = n\nUnable to unify "S n" with\n "S n + 0".' + ); + } + }); +}); diff --git a/src/test/coqLsp/coqLspGetGoals.test.ts b/src/test/coqLsp/coqLspGetGoals.test.ts index 5ab70d38..37703ed0 100644 --- a/src/test/coqLsp/coqLspGetGoals.test.ts +++ b/src/test/coqLsp/coqLspGetGoals.test.ts @@ -11,9 +11,8 @@ suite("Retrieve goals from Coq file", () => { async function getGoalsAtPoints( points: { line: number; character: number }[], resourcePath: string[], - projectRootPath?: string[], - command?: string - ): Promise, Error>[]> { + projectRootPath?: string[] + ): Promise[], Error>[]> { const [filePath, rootDir] = resolveResourcesDir( resourcePath, projectRootPath @@ -24,12 +23,7 @@ suite("Retrieve goals from Coq file", () => { await client.openTextDocument(fileUri); const goals = await Promise.all( points.map(async (point) => { - return await client.getFirstGoalAtPoint( - point, - fileUri, - 1, - command - ); + return await client.getGoalsAtPoint(point, fileUri, 1); }) ); await client.closeTextDocument(fileUri); @@ -56,9 +50,11 @@ suite("Retrieve goals from Coq file", () => { }; expect(goals).toHaveLength(1); - expect(unpackGoal(goals[0].val as Goal)).toEqual( - expectedGoal - ); + expect(goals[0].ok).toEqual(true); + if (goals[0].ok) { + expect(goals[0].val).toHaveLength(1); + expect(unpackGoal(goals[0].val[0])).toEqual(expectedGoal); + } }); test("Check correct goals requests", async () => { @@ -98,10 +94,10 @@ suite("Retrieve goals from Coq file", () => { expect(goals).toHaveLength(5); for (const [i, goal] of goals.entries()) { - expect(goals[i]).not.toBeA(Error); - expect(unpackGoal(goal.val as Goal)).toEqual( - expectedGoals[i] - ); + expect(goal).not.toBeA(Error); + if (goal.ok) { + expect(unpackGoal(goal.val[0])).toEqual(expectedGoals[i]); + } } }); @@ -110,16 +106,32 @@ suite("Retrieve goals from Coq file", () => { [ { line: 5, character: 0 }, { line: 6, character: 0 }, - { line: 9, character: 10 }, { line: 10, character: 9 }, + ], + ["small_document.v"] + ); + + expect(goals).toHaveLength(3); + for (const goal of goals) { + expect(goal.err).toEqual(true); + } + }); + + test("Retreive goals where no more goals", async () => { + const goals = await getGoalsAtPoints( + [ + { line: 9, character: 10 }, { line: 10, character: 3 }, ], ["small_document.v"] ); - expect(goals).toHaveLength(5); + expect(goals).toHaveLength(2); for (const goal of goals) { - expect(goal).toBeA(Error); + expect(goal.ok).toEqual(true); + if (goal.ok) { + expect(goal.val).toBeEmpty(); + } } }); @@ -151,10 +163,13 @@ suite("Retrieve goals from Coq file", () => { expect(goals).toHaveLength(3); for (const [i, goal] of goals.entries()) { - expect(goals[i]).not.toBeA(Error); - expect(unpackGoal(goal.val as Goal)).toEqual( - expectedGoals[i] - ); + if (goal.err) { + console.error("ERROR", i, goal.val.message); + } + expect(goal.ok).toEqual(true); + if (goal.ok) { + expect(unpackGoal(goal.val[0])).toEqual(expectedGoals[i]); + } } }); }); diff --git a/src/test/resources/test_many_subgoals.v b/src/test/resources/test_many_subgoals.v new file mode 100644 index 00000000..f388f68c --- /dev/null +++ b/src/test/resources/test_many_subgoals.v @@ -0,0 +1,26 @@ +Lemma one_subgoal : forall n:nat, 0 + n + 0 = n. +Proof. + intros. admit. +Admitted. + +Lemma two_subgoals : forall n:nat, n + 0 = n. +Proof. + intros. + induction n. + admit. + admit. +Admitted. + +Inductive MyType : Type := +| First : MyType +| Second : MyType +| Third : MyType. + +Theorem three_subgoals : forall t : MyType, + (t = First) \/ (t = Second) \/ (t = Third). +Proof. + intros t. destruct t. + admit. + admit. + admit. +Admitted. \ No newline at end of file From d5c66ace6df4460b695fa1710d72494f43f06fb9 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Thu, 17 Oct 2024 01:18:07 +0200 Subject: [PATCH 03/30] Upgrade coq-lsp in actions to v0.2.2 for arg in getGoals --- .github/workflows/build-and-test.yml | 4 ++-- README.md | 4 ++-- src/coqLsp/coqLspConfig.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 78da7a97..18fee300 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -20,7 +20,7 @@ on: env: coqlsp-path: "coq-lsp" - coqlsp-version: "0.1.9+8.19" + coqlsp-version: "0.2.2+8.19" artifact-name: ubuntu-latest-build jobs: @@ -60,7 +60,7 @@ jobs: env: OPAMYES: true run: | - opam install coq-lsp.0.1.9+8.19 + opam install coq-lsp.0.2.2+8.19 eval $(opam env) - name: Install Node.js diff --git a/README.md b/README.md index d5afe037..1ea5f4df 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ ## Requirements -* `coq-lsp` version `0.1.9+8.19` is currently required to run the extension. +* `coq-lsp` version `0.2.2+8.19` is currently required to run the extension. ## Brief technical overview @@ -68,7 +68,7 @@ As soon as at least one valid proof is found, it is substituted in the editor an To run the extension, you must install a `coq-lsp` server. Depending on the system used in your project, you should install it using `opam` or `nix`. A well-configured `nix` project should have the `coq-lsp` server installed as a dependency. To install `coq-lsp` using `opam`, you can use the following commands: ```bash -opam pin add coq-lsp 0.1.9+8.19 +opam pin add coq-lsp 0.2.2+8.19 opam install coq-lsp ``` For more information on how to install `coq-lsp` please refer to [coq-lsp](https://github.com/ejgallego/coq-lsp). diff --git a/src/coqLsp/coqLspConfig.ts b/src/coqLsp/coqLspConfig.ts index d2e3360e..075591df 100644 --- a/src/coqLsp/coqLspConfig.ts +++ b/src/coqLsp/coqLspConfig.ts @@ -21,7 +21,7 @@ export interface CoqLspClientConfig { export namespace CoqLspConfig { export function createServerConfig(): CoqLspServerConfig { return { - client_version: "0.1.9", + client_version: "0.2.2", eager_diagnostics: true, goal_after_tactic: false, show_coq_info_messages: false, From 09c957ef77a66a3803a25f2d904cc894e39a15e5 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Thu, 17 Oct 2024 01:37:54 +0200 Subject: [PATCH 04/30] Change type of CoqLspClient to Interface everywhere possible --- .../coqProjectParser/implementation/parseCoqProject.ts | 10 +++++----- src/coqLsp/coqLspBuilders.ts | 8 ++++---- src/coqLsp/coqLspClient.ts | 4 ++-- src/coqParser/parseCoqFile.ts | 6 +++--- src/core/coqProofChecker.ts | 4 ++-- src/core/inspectSourceFile.ts | 8 ++++---- src/test/commonTestFunctions/prepareEnvironment.ts | 4 ++-- src/test/legacyBenchmark/benchmarkingFramework.ts | 8 ++++---- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts index 81b8feb3..c3483fff 100644 --- a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts +++ b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts @@ -1,5 +1,5 @@ import { createTestCoqLspClient } from "../../../../../coqLsp/coqLspBuilders"; -import { CoqLspClient } from "../../../../../coqLsp/coqLspClient"; +import { CoqLspClientInterface } from "../../../../../coqLsp/coqLspClient"; import { createSourceFileEnvironment } from "../../../../../core/inspectSourceFile"; @@ -71,7 +71,7 @@ export namespace ParseCoqProjectImpl { async function openAndParseSourceFile( filePath: string, - coqLspClient: CoqLspClient, + coqLspClient: CoqLspClientInterface, logger: Logger ): Promise { const mockFileVersion = 1; @@ -108,7 +108,7 @@ export namespace ParseCoqProjectImpl { async function extractFileTargetsFromFile( fileTargets: Signature.ArgsModels.FileTarget[], serializedParsedFile: SerializedParsedCoqFile, - coqLspClient: CoqLspClient, + coqLspClient: CoqLspClientInterface, logger: Logger ): Promise { const parsedTargetsSets: Signature.ResultModels.ParsedFileTarget[][] = @@ -156,7 +156,7 @@ export namespace ParseCoqProjectImpl { theorem: SerializedTheorem, requestType: TargetRequestType, serializedParsedFile: SerializedParsedCoqFile, - coqLspClient: CoqLspClient, + coqLspClient: CoqLspClientInterface, logger: Logger ): Promise { const targetBuilder: ( @@ -203,7 +203,7 @@ export namespace ParseCoqProjectImpl { theoremName: string, knownGoal: SerializedGoal | undefined, serializedParsedFile: SerializedParsedCoqFile, - coqLspClient: CoqLspClient, + coqLspClient: CoqLspClientInterface, logger: Logger ): Promise { let serializedGoal = knownGoal; diff --git a/src/coqLsp/coqLspBuilders.ts b/src/coqLsp/coqLspBuilders.ts index 2904b65e..6a7017b3 100644 --- a/src/coqLsp/coqLspBuilders.ts +++ b/src/coqLsp/coqLspBuilders.ts @@ -1,12 +1,12 @@ import { OutputChannel, window } from "vscode"; -import { CoqLspClient } from "./coqLspClient"; +import { CoqLspClient, CoqLspClientInterface } from "./coqLspClient"; import { CoqLspClientConfig, CoqLspConfig } from "./coqLspConfig"; export async function createCoqLspClient( coqLspServerPath: string, logOutputChannel?: OutputChannel -): Promise { +): Promise { return createAbstractCoqLspClient( CoqLspConfig.createClientConfig(coqLspServerPath), logOutputChannel @@ -15,7 +15,7 @@ export async function createCoqLspClient( export async function createTestCoqLspClient( workspaceRootPath?: string -): Promise { +): Promise { return createAbstractCoqLspClient( CoqLspConfig.createClientConfig( process.env.COQ_LSP_PATH || "coq-lsp", @@ -29,7 +29,7 @@ async function createAbstractCoqLspClient( logOutputChannel: OutputChannel = window.createOutputChannel( "CoqPilot: coq-lsp events" ) -): Promise { +): Promise { const coqLspServerConfig = CoqLspConfig.createServerConfig(); return await CoqLspClient.create( coqLspServerConfig, diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index 348488d8..a1e7963b 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -36,10 +36,10 @@ export interface CoqLspClientInterface extends Disposable { position: Position, documentUri: Uri, version: number, - command: string + command?: string ): Promise[], Error>>; - openTextDocument(uri: Uri, version: number): Promise; + openTextDocument(uri: Uri, version?: number): Promise; getDocumentSymbols(uri: Uri): Promise; diff --git a/src/coqParser/parseCoqFile.ts b/src/coqParser/parseCoqFile.ts index 2aece1ec..acca3152 100644 --- a/src/coqParser/parseCoqFile.ts +++ b/src/coqParser/parseCoqFile.ts @@ -1,7 +1,7 @@ import { readFileSync } from "fs"; import { Position, Range } from "vscode-languageclient"; -import { CoqLspClient } from "../coqLsp/coqLspClient"; +import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; import { CoqParsingError, FlecheDocument, @@ -14,7 +14,7 @@ import { ProofStep, Theorem, TheoremProof, Vernacexpr } from "./parsedTypes"; export async function parseCoqFile( uri: Uri, - client: CoqLspClient + client: CoqLspClientInterface ): Promise { return client .getFlecheDocument(uri) @@ -34,7 +34,7 @@ export async function parseCoqFile( async function parseFlecheDocument( doc: FlecheDocument, textLines: string[], - client: CoqLspClient, + client: CoqLspClientInterface, uri: Uri ): Promise { if (doc === null) { diff --git a/src/core/coqProofChecker.ts b/src/core/coqProofChecker.ts index 7eac5c38..a82ce521 100644 --- a/src/core/coqProofChecker.ts +++ b/src/core/coqProofChecker.ts @@ -3,7 +3,7 @@ import { existsSync, unlinkSync, writeFileSync } from "fs"; import * as path from "path"; import { Position } from "vscode-languageclient"; -import { CoqLspClient } from "../coqLsp/coqLspClient"; +import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; import { CoqLspTimeoutError } from "../coqLsp/coqLspTypes"; import { Uri } from "../utils/uri"; @@ -30,7 +30,7 @@ export interface CoqProofCheckerInterface { export class CoqProofChecker implements CoqProofCheckerInterface { private mutex: Mutex = new Mutex(); - constructor(private coqLspClient: CoqLspClient) {} + constructor(private coqLspClient: CoqLspClientInterface) {} async checkProofs( sourceDirPath: string, diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index efd0e991..0c404354 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -1,7 +1,7 @@ import { readFileSync } from "fs"; import * as path from "path"; -import { CoqLspClient } from "../coqLsp/coqLspClient"; +import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; import { parseCoqFile } from "../coqParser/parseCoqFile"; import { ProofStep, Theorem } from "../coqParser/parsedTypes"; @@ -18,7 +18,7 @@ export async function inspectSourceFile( fileVersion: number, shouldCompleteHole: (hole: ProofStep) => boolean, fileUri: Uri, - client: CoqLspClient + client: CoqLspClientInterface ): Promise { const sourceFileEnvironment = await createSourceFileEnvironment( fileVersion, @@ -47,7 +47,7 @@ async function createCompletionContexts( shouldCompleteHole: (hole: ProofStep) => boolean, fileTheorems: Theorem[], fileUri: Uri, - client: CoqLspClient + client: CoqLspClientInterface ): Promise { const holesToComplete = fileTheorems .filter((thr) => thr.proof) @@ -80,7 +80,7 @@ async function createCompletionContexts( export async function createSourceFileEnvironment( fileVersion: number, fileUri: Uri, - client: CoqLspClient + client: CoqLspClientInterface ): Promise { const fileTheorems = await parseCoqFile(fileUri, client); const fileText = readFileSync(fileUri.fsPath); diff --git a/src/test/commonTestFunctions/prepareEnvironment.ts b/src/test/commonTestFunctions/prepareEnvironment.ts index 0698ae3d..b47a2246 100644 --- a/src/test/commonTestFunctions/prepareEnvironment.ts +++ b/src/test/commonTestFunctions/prepareEnvironment.ts @@ -1,7 +1,7 @@ import { ProofGenerationContext } from "../../llm/proofGenerationContext"; import { createTestCoqLspClient } from "../../coqLsp/coqLspBuilders"; -import { CoqLspClient } from "../../coqLsp/coqLspClient"; +import { CoqLspClientInterface } from "../../coqLsp/coqLspClient"; import { CompletionContext, @@ -16,7 +16,7 @@ import { Uri } from "../../utils/uri"; import { resolveResourcesDir } from "./pathsResolver"; export interface PreparedEnvironment { - coqLspClient: CoqLspClient; + coqLspClient: CoqLspClientInterface; coqProofChecker: CoqProofChecker; completionContexts: CompletionContext[]; sourceFileEnvironment: SourceFileEnvironment; diff --git a/src/test/legacyBenchmark/benchmarkingFramework.ts b/src/test/legacyBenchmark/benchmarkingFramework.ts index 72e38ec6..c6c28cff 100644 --- a/src/test/legacyBenchmark/benchmarkingFramework.ts +++ b/src/test/legacyBenchmark/benchmarkingFramework.ts @@ -12,7 +12,7 @@ import { PredefinedProofsService } from "../../llm/llmServices/predefinedProofs/ import { resolveParametersOrThrow } from "../../llm/llmServices/utils/resolveOrThrow"; import { createTestCoqLspClient } from "../../coqLsp/coqLspBuilders"; -import { CoqLspClient } from "../../coqLsp/coqLspClient"; +import { CoqLspClientInterface } from "../../coqLsp/coqLspClient"; import { ProofGoal } from "../../coqLsp/coqLspTypes"; import { @@ -427,7 +427,7 @@ async function extractCompletionTargets( fileVersion: number, shouldCompleteHole: (hole: ProofStep) => boolean, fileUri: Uri, - client: CoqLspClient + client: CoqLspClientInterface ): Promise<[BenchmarkingCompletionTargets, SourceFileEnvironment]> { const sourceFileEnvironment = await createSourceFileEnvironment( fileVersion, @@ -461,7 +461,7 @@ async function createCompletionTargets( shouldCompleteHole: (hole: ProofStep) => boolean, fileTheorems: Theorem[], fileUri: Uri, - client: CoqLspClient + client: CoqLspClientInterface ): Promise { const theoremsWithProofs = fileTheorems.filter((thr) => thr.proof); const admitHolesToComplete = theoremsWithProofs @@ -504,7 +504,7 @@ async function resolveProofStepsToCompletionContexts( parentedProofSteps: ParentedProofStep[], fileVersion: number, fileUri: Uri, - client: CoqLspClient + client: CoqLspClientInterface ): Promise { let completionContexts: BenchmarkingCompletionContext[] = []; for (const parentedProofStep of parentedProofSteps) { From e32b7f97c19a6ea136c1ee88583ee02b723c1ad7 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Thu, 17 Oct 2024 01:43:59 +0200 Subject: [PATCH 05/30] Clean CoqLspClientInterface --- src/coqLsp/coqLspClient.ts | 80 -------------------------------------- 1 file changed, 80 deletions(-) diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index a1e7963b..698ab39a 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -13,8 +13,6 @@ import { VersionedTextDocumentIdentifier, } from "vscode-languageclient"; import { - DidChangeTextDocumentNotification, - DidChangeTextDocumentParams, DidCloseTextDocumentNotification, DidCloseTextDocumentParams, DidOpenTextDocumentNotification, @@ -41,15 +39,6 @@ export interface CoqLspClientInterface extends Disposable { openTextDocument(uri: Uri, version?: number): Promise; - getDocumentSymbols(uri: Uri): Promise; - - updateTextDocument( - oldDocumentText: string[], - appendedSuffix: string, - uri: Uri, - version: number - ): Promise; - closeTextDocument(uri: Uri): Promise; getFlecheDocument(uri: Uri): Promise; @@ -95,12 +84,6 @@ export class CoqLspClient implements CoqLspClientInterface { return new CoqLspClient(connector); } - async getDocumentSymbols(uri: Uri): Promise { - return await this.mutex.runExclusive(async () => { - return this.getDocumentSymbolsUnsafe(uri); - }); - } - async getGoalsAtPoint( position: Position, documentUri: Uri, @@ -126,22 +109,6 @@ export class CoqLspClient implements CoqLspClientInterface { }); } - async updateTextDocument( - oldDocumentText: string[], - appendedSuffix: string, - uri: Uri, - version: number = 1 - ): Promise { - return await this.mutex.runExclusive(async () => { - return this.updateTextDocumentUnsafe( - oldDocumentText, - appendedSuffix, - uri, - version - ); - }); - } - async closeTextDocument(uri: Uri): Promise { return await this.mutex.runExclusive(async () => { return this.closeTextDocumentUnsafe(uri); @@ -195,16 +162,6 @@ export class CoqLspClient implements CoqLspClientInterface { } } - private async getDocumentSymbolsUnsafe(uri: Uri): Promise { - let textDocument = TextDocumentIdentifier.create(uri.uri); - let params: any = { textDocument }; - - return await this.client.sendRequest( - "textDocument/documentSymbol", - params - ); - } - private async getGoalsAtPointUnsafe( position: Position, documentUri: Uri, @@ -350,43 +307,6 @@ export class CoqLspClient implements CoqLspClientInterface { ); } - private getTextEndPosition(lines: string[]): Position { - return Position.create( - lines.length - 1, - lines[lines.length - 1].length - ); - } - - private async updateTextDocumentUnsafe( - oldDocumentText: string[], - appendedSuffix: string, - uri: Uri, - version: number = 1 - ): Promise { - const updatedText = oldDocumentText.join("\n") + appendedSuffix; - const oldEndPosition = this.getTextEndPosition(oldDocumentText); - - const params: DidChangeTextDocumentParams = { - textDocument: { - uri: uri.uri, - version: version, - }, - contentChanges: [ - { - text: updatedText, - }, - ], - }; - - return await this.waitUntilFileFullyChecked( - DidChangeTextDocumentNotification.type, - params, - uri, - version, - oldEndPosition - ); - } - private async closeTextDocumentUnsafe(uri: Uri): Promise { const params: DidCloseTextDocumentParams = { textDocument: { From 9125619eafc6de29eec611d93e9bc51e6b1b0d5b Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Thu, 17 Oct 2024 20:30:35 +0200 Subject: [PATCH 06/30] Refactor `CoqProofChecker` Now it doesn't have a responsibility to manage opening/closing files, the outer class calling it should be responsible for it. As we have different scenarios of communication with LSP from Plugin/Becnhmark, this is still in development. Get rid of Aux files. --- .../implementation/checkProofs.ts | 15 +- .../implementation/internalSignature.ts | 32 ++-- .../implementation/proofsCheckerUtils.ts | 10 +- .../completionGenerationTask.ts | 3 +- .../parsedCoqFile/parsedCoqFileData.ts | 2 + src/coqLsp/coqLspClient.ts | 27 ++-- src/coqLsp/coqLspConnector.ts | 4 - src/core/completionGenerationContext.ts | 11 +- src/core/completionGenerator.ts | 17 +- .../distanceContextTheoremsRanker.ts | 4 +- src/core/coqProofChecker.ts | 149 +++++++++--------- src/core/inspectSourceFile.ts | 4 +- src/extension/coqPilot.ts | 23 +-- src/test/commonTestFunctions/checkProofs.ts | 11 +- src/test/core/completionGenerator.test.ts | 12 +- src/test/core/coqProofChecker.test.ts | 33 ++-- .../legacyBenchmark/benchmarkingFramework.ts | 5 +- 17 files changed, 189 insertions(+), 173 deletions(-) diff --git a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/checkProofs.ts b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/checkProofs.ts index ac771628..613860f0 100644 --- a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/checkProofs.ts +++ b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/checkProofs.ts @@ -7,6 +7,7 @@ import { } from "../../../../../../core/coqProofChecker"; import { stringifyAnyValue } from "../../../../../../utils/printers"; +import { Uri } from "../../../../../../utils/uri"; import { BenchmarkingLogger } from "../../../../logging/benchmarkingLogger"; import { LogsIPCSender } from "../../../../utils/subprocessUtils/ipc/onParentProcessCallExecutor/logsIpcSender"; import { TimeMark } from "../../measureUtils"; @@ -37,12 +38,20 @@ export namespace CheckProofsImpl { try { const timeMark = new TimeMark(); + const fileUri = Uri.fromPath(args.fileUri); + + // TODO: [LspCoreRefactor] Before handling of open/close req's was done by `CoqProofChecker` itself + await coqLspClient.openTextDocument(fileUri); + const proofCheckResults = await coqProofChecker.checkProofs( - args.sourceFileDirPath, - args.sourceFileContentPrefix, - args.prefixEndPosition, + fileUri, + args.documentVersion, + args.checkAtPosition, args.preparedProofs ); + + await coqLspClient.closeTextDocument(fileUri); + const proofsValidationMillis = timeMark.measureElapsedMillis(); return buildSuccessResult( proofCheckResults, diff --git a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts index ea392959..1c5b7d6d 100644 --- a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts +++ b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts @@ -7,12 +7,21 @@ export namespace CheckProofsInternalSignature { export interface Args { workspaceRootPath: string | undefined; - sourceFileDirPath: string; - sourceFileContentPrefix: string[]; - prefixEndPosition: Position; + // sourceFileDirPath: string; + // sourceFileContentPrefix: string[]; + // prefixEndPosition: Position; + fileUri: string; + documentVersion: number; + checkAtPosition: Position; + preparedProofs: string[]; } + // sourceDirPath: string, + // sourceFileContentPrefix: string[], + // prefixEndPosition: Position, + // proofs: Proof[] + export interface Position { line: number; character: number; @@ -79,16 +88,13 @@ export namespace CheckProofsInternalSignature { type: "string", nullable: true, }, - sourceFileDirPath: { + fileUri: { type: "string", }, - sourceFileContentPrefix: { - type: "array", - items: { - type: "string", - }, + documentVersion: { + type: "number", }, - prefixEndPosition: positionSchema, + checkAtPosition: positionSchema, preparedProofs: { type: "array", items: { @@ -97,9 +103,9 @@ export namespace CheckProofsInternalSignature { }, }, required: [ - "sourceFileDirPath", - "sourceFileContentPrefix", - "prefixEndPosition", + "fileUri", + "documentVersion", + "documentVersion", "preparedProofs", ], additionalProperties: false, diff --git a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/proofsCheckerUtils.ts b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/proofsCheckerUtils.ts index 502a1758..0953a020 100644 --- a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/proofsCheckerUtils.ts +++ b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/proofsCheckerUtils.ts @@ -2,7 +2,6 @@ import { CompletionContext, SourceFileEnvironment, } from "../../../../../../core/completionGenerationContext"; -import { getTextBeforePosition } from "../../../../../../core/exposedCompletionGeneratorUtils"; import { WorkspaceRoot, @@ -28,12 +27,9 @@ export namespace ProofsCheckerUtils { workspaceRootPath: isStandaloneFilesRoot(workspaceRoot) ? undefined : workspaceRoot.directoryPath, - sourceFileDirPath: sourceFileEnvironment.dirPath, - sourceFileContentPrefix: getTextBeforePosition( - sourceFileEnvironment.fileLines, - completionContext.prefixEndPosition - ), - prefixEndPosition: completionContext.prefixEndPosition, + fileUri: sourceFileEnvironment.fileUri.toString(), + documentVersion: sourceFileEnvironment.fileVersion, + checkAtPosition: completionContext.admitRange.start, preparedProofs: preparedProofs, }; } diff --git a/src/benchmark/framework/structures/benchmarkingCore/completionGenerationTask.ts b/src/benchmark/framework/structures/benchmarkingCore/completionGenerationTask.ts index e558ff20..80768ffc 100644 --- a/src/benchmark/framework/structures/benchmarkingCore/completionGenerationTask.ts +++ b/src/benchmark/framework/structures/benchmarkingCore/completionGenerationTask.ts @@ -32,8 +32,7 @@ export class CompletionGenerationTask getCompletionContext(): CompletionContext { return { proofGoal: this.targetGoalToProve, - prefixEndPosition: this.targetPositionRange.start, - admitEndPosition: this.targetPositionRange.end, + admitRange: this.targetPositionRange, }; } diff --git a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts index e9c1b4e5..f378c4e5 100644 --- a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts +++ b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts @@ -3,6 +3,7 @@ import { JSONSchemaType } from "ajv"; import { SourceFileEnvironment } from "../../../../core/completionGenerationContext"; import { Theorem } from "../../../../coqParser/parsedTypes"; +import { Uri } from "../../../../utils/uri"; import { fromMappedObject, mapValues, @@ -48,6 +49,7 @@ export class ParsedCoqFileData { fileLines: this.fileLines, fileVersion: this.fileVersion, dirPath: getDirectoryPath(this.filePath), + fileUri: Uri.fromPath(this.filePath), }; } } diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index 698ab39a..b1c76dbc 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -5,29 +5,34 @@ import { OutputChannel } from "vscode"; import { BaseLanguageClient, Diagnostic, - Disposable, - Position, - ProtocolNotificationType, - RequestType, - TextDocumentIdentifier, - VersionedTextDocumentIdentifier, -} from "vscode-languageclient"; -import { DidCloseTextDocumentNotification, DidCloseTextDocumentParams, DidOpenTextDocumentNotification, DidOpenTextDocumentParams, + Disposable, LogTraceNotification, + Position, + ProtocolNotificationType, PublishDiagnosticsNotification, + RequestType, + TextDocumentIdentifier, + VersionedTextDocumentIdentifier, } from "vscode-languageclient"; import { Uri } from "../utils/uri"; import { CoqLspClientConfig, CoqLspServerConfig } from "./coqLspConfig"; import { CoqLspConnector } from "./coqLspConnector"; -import { Goal, GoalAnswer, GoalRequest, PpString } from "./coqLspTypes"; -import { FlecheDocument, FlecheDocumentParams } from "./coqLspTypes"; -import { CoqLspError, CoqLspStartupError } from "./coqLspTypes"; +import { + CoqLspError, + CoqLspStartupError, + FlecheDocument, + FlecheDocumentParams, + Goal, + GoalAnswer, + GoalRequest, + PpString, +} from "./coqLspTypes"; export interface CoqLspClientInterface extends Disposable { getGoalsAtPoint( diff --git a/src/coqLsp/coqLspConnector.ts b/src/coqLsp/coqLspConnector.ts index 4f2b9d41..8f648aa5 100644 --- a/src/coqLsp/coqLspConnector.ts +++ b/src/coqLsp/coqLspConnector.ts @@ -79,8 +79,4 @@ export class CoqLspConnector extends LanguageClient { private logStatusUpdate = (status: "started" | "stopped") => { this.eventLogger?.log("coq-lsp-status-change", status); }; - - restartLspClient() { - this.stop().then(() => this.start()); - } } diff --git a/src/core/completionGenerationContext.ts b/src/core/completionGenerationContext.ts index 32eaf2a2..fc036056 100644 --- a/src/core/completionGenerationContext.ts +++ b/src/core/completionGenerationContext.ts @@ -1,4 +1,4 @@ -import { Position } from "vscode-languageclient"; +import { Range } from "vscode-languageclient"; import { LLMServices } from "../llm/llmServices"; import { ModelsParams } from "../llm/llmServices/modelParams"; @@ -6,22 +6,27 @@ import { ModelsParams } from "../llm/llmServices/modelParams"; import { ProofGoal } from "../coqLsp/coqLspTypes"; import { Theorem } from "../coqParser/parsedTypes"; +import { Uri } from "../utils/uri"; import { ContextTheoremsRanker } from "./contextTheoremRanker/contextTheoremsRanker"; import { CoqProofChecker } from "./coqProofChecker"; export interface CompletionContext { proofGoal: ProofGoal; - prefixEndPosition: Position; - admitEndPosition: Position; + admitRange: Range; } export interface SourceFileEnvironment { // `fileTheorems` contain only ones that successfully finish with Qed. fileTheorems: Theorem[]; + // TODO: [LspCoreRefactor] Check if `fileLines` is needed fileLines: string[]; + // TODO: [LspCoreRefactor] Check if `fileVersion` is provided correctly && rename to `documentVersion` fileVersion: number; + // TODO: [LspCoreRefactor] Remove `dirPath` from here as it is probably not used anymore dirPath: string; + // TODO: [LspCoreRefactor] Rename to `documentUri` + fileUri: Uri; } export interface ProcessEnvironment { diff --git a/src/core/completionGenerator.ts b/src/core/completionGenerator.ts index 1a281fc4..f64c4acd 100644 --- a/src/core/completionGenerator.ts +++ b/src/core/completionGenerator.ts @@ -18,7 +18,6 @@ import { import { CoqProofChecker, ProofCheckResult } from "./coqProofChecker"; import { buildProofGenerationContext, - getTextBeforePosition, prepareProofToCheck, } from "./exposedCompletionGeneratorUtils"; @@ -71,10 +70,6 @@ export async function generateCompletion( processEnvironment.services, eventLogger ); - const sourceFileContentPrefix = getTextBeforePosition( - sourceFileEnvironment.fileLines, - completionContext.prefixEndPosition - ); try { /** newlyGeneratedProofs = generatedProofsBatch from iterator + @@ -90,7 +85,6 @@ export async function generateCompletion( ); const fixedProofsOrCompletion = await checkAndFixProofs( newlyGeneratedProofs, - sourceFileContentPrefix, completionContext, sourceFileEnvironment, processEnvironment, @@ -108,7 +102,6 @@ export async function generateCompletion( while (newlyGeneratedProofs.length > 0) { const fixedProofsOrCompletion = await checkAndFixProofs( newlyGeneratedProofs, - sourceFileContentPrefix, completionContext, sourceFileEnvironment, processEnvironment, @@ -153,7 +146,6 @@ export async function generateCompletion( async function checkAndFixProofs( newlyGeneratedProofs: GeneratedProof[], - sourceFileContentPrefix: string[], completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, processEnvironment: ProcessEnvironment, @@ -165,7 +157,6 @@ async function checkAndFixProofs( // check proofs and finish with success if at least one is valid const proofCheckResults = await checkGeneratedProofs( newlyGeneratedProofs, - sourceFileContentPrefix, completionContext, sourceFileEnvironment, processEnvironment, @@ -202,7 +193,6 @@ async function checkAndFixProofs( async function checkGeneratedProofs( generatedProofs: GeneratedProof[], - sourceFileContentPrefix: string[], completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, processEnvironment: ProcessEnvironment, @@ -215,6 +205,7 @@ async function checkGeneratedProofs( prepareProofToCheck(generatedProof.proof()) ); + // TODO: [LspCoreRefactor] Why is it happening every time? if (workspaceRootPath) { processEnvironment.coqProofChecker.dispose(); const client = await createCoqLspClient( @@ -226,9 +217,9 @@ async function checkGeneratedProofs( } return processEnvironment.coqProofChecker.checkProofs( - sourceFileEnvironment.dirPath, - sourceFileContentPrefix, - completionContext.prefixEndPosition, + sourceFileEnvironment.fileUri, + sourceFileEnvironment.fileVersion, + completionContext.admitRange.start, preparedProofBatch, perProofTimeoutMillis ); diff --git a/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts b/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts index 7de99f0d..c4dbe896 100644 --- a/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts +++ b/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts @@ -11,7 +11,7 @@ export class DistanceContextTheoremsRanker implements ContextTheoremsRanker { const theoremsBeforeCompletionPosition = theorems.filter( (theorem) => theorem.statement_range.start.line < - completionContext.prefixEndPosition.line + completionContext.admitRange.start.line ); // Sort theorems such that closer theorems are first theoremsBeforeCompletionPosition.sort((a, b) => { @@ -21,7 +21,7 @@ export class DistanceContextTheoremsRanker implements ContextTheoremsRanker { const theoremsAfterCompletionPosition = theorems.filter( (theorem) => theorem.statement_range.start.line > - completionContext.prefixEndPosition.line + completionContext.admitRange.start.line ); theoremsAfterCompletionPosition.sort((a, b) => { diff --git a/src/core/coqProofChecker.ts b/src/core/coqProofChecker.ts index a82ce521..e34b8532 100644 --- a/src/core/coqProofChecker.ts +++ b/src/core/coqProofChecker.ts @@ -1,6 +1,6 @@ import { Mutex } from "async-mutex"; -import { existsSync, unlinkSync, writeFileSync } from "fs"; -import * as path from "path"; +// import { existsSync, unlinkSync, writeFileSync } from "fs"; +// import * as path from "path"; import { Position } from "vscode-languageclient"; import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; @@ -18,10 +18,11 @@ type Proof = string; export interface CoqProofCheckerInterface { checkProofs( - sourceDirPath: string, - sourceFileContentPrefix: string[], - prefixEndPosition: Position, - proofs: Proof[] + fileUri: Uri, + documentVersion: number, + checkAtPosition: Position, + proofs: Proof[], + coqLspTimeoutMillis?: number ): Promise; dispose(): void; @@ -33,9 +34,9 @@ export class CoqProofChecker implements CoqProofCheckerInterface { constructor(private coqLspClient: CoqLspClientInterface) {} async checkProofs( - sourceDirPath: string, - sourceFileContentPrefix: string[], - prefixEndPosition: Position, + fileUri: Uri, + documentVersion: number, + checkAtPosition: Position, proofs: Proof[], coqLspTimeoutMillis: number = 15000 ): Promise { @@ -54,9 +55,9 @@ export class CoqProofChecker implements CoqProofCheckerInterface { return Promise.race([ this.checkProofsUnsafe( - sourceDirPath, - sourceFileContentPrefix, - prefixEndPosition, + fileUri, + documentVersion, + checkAtPosition, proofs ), timeoutPromise, @@ -64,83 +65,87 @@ export class CoqProofChecker implements CoqProofCheckerInterface { }); } - private buildAuxFileUri( - sourceDirPath: string, - holePosition: Position, - unique: boolean = true - ): Uri { - const holeIdentifier = `${holePosition.line}_${holePosition.character}`; - const defaultAuxFileName = `hole_${holeIdentifier}_cp_aux.v`; - let auxFilePath = path.join(sourceDirPath, defaultAuxFileName); - if (unique && existsSync(auxFilePath)) { - const randomSuffix = Math.floor(Math.random() * 1000000); - auxFilePath = auxFilePath.replace( - /\_cp_aux.v$/, - `_${randomSuffix}_cp_aux.v` - ); - } - - return Uri.fromPath(auxFilePath); - } + // private buildAuxFileUri( + // sourceDirPath: string, + // holePosition: Position, + // unique: boolean = true + // ): Uri { + // const holeIdentifier = `${holePosition.line}_${holePosition.character}`; + // const defaultAuxFileName = `hole_${holeIdentifier}_cp_aux.v`; + // let auxFilePath = path.join(sourceDirPath, defaultAuxFileName); + // if (unique && existsSync(auxFilePath)) { + // const randomSuffix = Math.floor(Math.random() * 1000000); + // auxFilePath = auxFilePath.replace( + // /\_cp_aux.v$/, + // `_${randomSuffix}_cp_aux.v` + // ); + // } + + // return Uri.fromPath(auxFilePath); + // } private checkIfProofContainsAdmit(proof: Proof): boolean { return forbiddenAdmitTactics.some((tactic) => proof.includes(tactic)); } private async checkProofsUnsafe( - sourceDirPath: string, - sourceFileContentPrefix: string[], - prefixEndPosition: Position, + fileUri: Uri, + documentVersion: number, + // sourceDirPath: string, + // sourceFileContentPrefix: string[], + checkAtPosition: Position, proofs: Proof[] ): Promise { // 1. Write the text to the aux file - const auxFileUri = this.buildAuxFileUri( - sourceDirPath, - prefixEndPosition - ); - const sourceFileContent = sourceFileContentPrefix.join("\n"); - writeFileSync(auxFileUri.fsPath, sourceFileContent); + // const auxFileUri = this.buildAuxFileUri( + // sourceDirPath, + // prefixEndPosition + // ); + // const sourceFileContent = sourceFileContentPrefix.join("\n"); + // writeFileSync(auxFileUri.fsPath, sourceFileContent); const results: ProofCheckResult[] = []; - try { - // 2. Issue open text document request - await this.coqLspClient.openTextDocument(auxFileUri); - - // 3. Iterate over the proofs and сheck them - for (const proof of proofs) { - // 3.1. Check if the proof contains admit - if (this.checkIfProofContainsAdmit(proof)) { - results.push({ - proof: proof, - isValid: false, - diagnostic: "Proof contains admit", - }); - continue; - } - - // 3.2 Check if proof is valid and closes the first goal - const goalResult = await this.coqLspClient.getGoalsAtPoint( - prefixEndPosition, - auxFileUri, - 1, - proof - ); - + // try { + // 2. Issue open text document request + // await this.coqLspClient.openTextDocument(fileUri); + + // 3. Iterate over the proofs and сheck them + for (const proof of proofs) { + // 3.1. Check if the proof contains admit + if (this.checkIfProofContainsAdmit(proof)) { results.push({ proof: proof, - isValid: goalResult.ok, - diagnostic: goalResult.err - ? goalResult.val.message - : undefined, + isValid: false, + diagnostic: "Proof contains admit", }); + continue; } - } finally { - // 4. Issue close text document request - await this.coqLspClient.closeTextDocument(auxFileUri); - // 5. Remove the aux file - unlinkSync(auxFileUri.fsPath); + // 3.2 Check if proof is valid and closes the first goal + const goalResult = await this.coqLspClient.getGoalsAtPoint( + checkAtPosition, + fileUri, + documentVersion, + proof + ); + + results.push({ + proof: proof, + isValid: goalResult.ok, + diagnostic: goalResult.err ? goalResult.val.message : undefined, + }); } + // } catch (error) { + // console.log("Error in checkProofsUnsafe", error); + // throw error; + // } + // finally { + // 4. Issue close text document request + // await this.coqLspClient.closeTextDocument(fileUri); + + // 5. Remove the aux file + // unlinkSync(auxFileUri.fsPath); + // } return results; } diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index 0c404354..8733390b 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -67,8 +67,7 @@ async function createCompletionContexts( if (firstGoal) { completionContexts.push({ proofGoal: firstGoal, - prefixEndPosition: hole.range.start, - admitEndPosition: hole.range.end, + admitRange: hole.range, }); } } @@ -96,6 +95,7 @@ export async function createSourceFileEnvironment( fileLines: fileText.toString().split("\n"), fileVersion: fileVersion, dirPath: dirPath, + fileUri: fileUri, }; } diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 72439571..fdc42e3e 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -215,16 +215,13 @@ export class CoqPilot { if (result instanceof SuccessGenerationResult) { const flatProof = this.prepareCompletionForInsertion(result.data); - const vscodeHoleRange = toVSCodeRange({ - start: completionContext.prefixEndPosition, - end: completionContext.admitEndPosition, - }); + const vscodeHoleRange = toVSCodeRange(completionContext.admitRange); const completionRange = toVSCodeRange({ - start: completionContext.prefixEndPosition, + start: completionContext.admitRange.start, end: { - line: completionContext.prefixEndPosition.line, + line: completionContext.admitRange.start.line, character: - completionContext.prefixEndPosition.character + + completionContext.admitRange.start.character + flatProof.length, }, }); @@ -233,7 +230,7 @@ export class CoqPilot { await insertCompletion( editor, flatProof, - toVSCodePosition(completionContext.prefixEndPosition) + toVSCodePosition(completionContext.admitRange.start) ); highlightTextInEditor(completionRange); } else if (result instanceof FailureGenerationResult) { @@ -249,7 +246,7 @@ export class CoqPilot { break; case FailureGenerationStatus.SEARCH_FAILED: const completionLine = - completionContext.prefixEndPosition.line + 1; + completionContext.admitRange.start.line + 1; showMessageToUser( EditorMessages.noProofsForAdmit(completionLine), "info" @@ -276,6 +273,14 @@ export class CoqPilot { > { const fileUri = Uri.fromPath(filePath); const coqLspServerPath = parseCoqLspServerPath(); + // TODO: [LspCoreRefactor] Now a tone of Coq-LSPs are created and destroyed for each completion. + // It is not efficient. Refactor it to create a single Coq-LSP client for the whole session. + // But allow restarting it when issues occur. + + // TODO: [LspCoreRefactor] Check hypothesis that we do not really need + // to send any events when user uses the plugin. + + // TODO: [LspCoreRefactor] Check what happens in plugin runtime when file not prepared, but goals requested. const client = await createCoqLspClient( coqLspServerPath, this.globalExtensionState.logOutputChannel diff --git a/src/test/commonTestFunctions/checkProofs.ts b/src/test/commonTestFunctions/checkProofs.ts index 6a56cc89..6291556a 100644 --- a/src/test/commonTestFunctions/checkProofs.ts +++ b/src/test/commonTestFunctions/checkProofs.ts @@ -3,7 +3,6 @@ import { GeneratedProof } from "../../llm/llmServices/generatedProof"; import { CompletionContext } from "../../core/completionGenerationContext"; import { ProofCheckResult } from "../../core/coqProofChecker"; import { prepareProofToCheck } from "../../core/exposedCompletionGeneratorUtils"; -import { getTextBeforePosition } from "../../core/exposedCompletionGeneratorUtils"; import { PreparedEnvironment } from "./prepareEnvironment"; @@ -12,14 +11,10 @@ export async function checkProofs( completionContext: CompletionContext, environment: PreparedEnvironment ): Promise { - const sourceFileContentPrefix = getTextBeforePosition( - environment.sourceFileEnvironment.fileLines, - completionContext.prefixEndPosition - ); return await environment.coqProofChecker.checkProofs( - environment.sourceFileEnvironment.dirPath, - sourceFileContentPrefix, - completionContext.prefixEndPosition, + environment.sourceFileEnvironment.fileUri, + environment.sourceFileEnvironment.fileVersion, + completionContext.admitRange.start, proofsToCheck ); } diff --git a/src/test/core/completionGenerator.test.ts b/src/test/core/completionGenerator.test.ts index 6855ee7a..3abafcf5 100644 --- a/src/test/core/completionGenerator.test.ts +++ b/src/test/core/completionGenerator.test.ts @@ -33,7 +33,11 @@ suite("Completion generation tests", () => { services: createDefaultServices(), }; try { - return await Promise.all( + await environment.coqLspClient.openTextDocument( + environment.sourceFileEnvironment.fileUri + ); + + const generationResult = await Promise.all( environment.completionContexts.map( async (completionContext) => { const result = await generateCompletion( @@ -45,6 +49,12 @@ suite("Completion generation tests", () => { } ) ); + + await environment.coqLspClient.closeTextDocument( + environment.sourceFileEnvironment.fileUri + ); + + return generationResult; } finally { disposeServices(processEnvironment.services); } diff --git a/src/test/core/coqProofChecker.test.ts b/src/test/core/coqProofChecker.test.ts index f9ecc454..ef864a39 100644 --- a/src/test/core/coqProofChecker.test.ts +++ b/src/test/core/coqProofChecker.test.ts @@ -1,12 +1,11 @@ import { expect } from "earl"; -import { readFileSync } from "fs"; -import * as path from "path"; import { createTestCoqLspClient } from "../../coqLsp/coqLspBuilders"; import { CoqProofChecker } from "../../core/coqProofChecker"; import { ProofCheckResult } from "../../core/coqProofChecker"; +import { Uri } from "../../utils/uri"; import { resolveResourcesDir } from "../commonTestFunctions/pathsResolver"; suite("`CoqProofChecker` tests", () => { @@ -20,21 +19,22 @@ suite("`CoqProofChecker` tests", () => { resourcePath, projectRootPath ); - const fileDir = path.dirname(filePath); + const fileUri = Uri.fromPath(filePath); + const documentVersion = 1; - const fileLines = readFileSync(filePath).toString().split("\n"); const client = await createTestCoqLspClient(rootDir); const coqProofChecker = new CoqProofChecker(client); + await client.openTextDocument(fileUri); + const preparedProofs = proofsToCheck.map((proofs) => proofs.map(prepareProofBeforeCheck) ); - return Promise.all( + const proofRes = await Promise.all( positions.map(async (position, i) => { - const textPrefix = getTextBeforePosition(fileLines, position); const proofCheckRes = await coqProofChecker.checkProofs( - fileDir, - textPrefix, + fileUri, + documentVersion, position, preparedProofs[i] ); @@ -46,6 +46,11 @@ suite("`CoqProofChecker` tests", () => { }); }) ); + + await client.closeTextDocument(fileUri); + client.dispose(); + + return proofRes; } function prepareProofBeforeCheck(proof: string) { @@ -60,18 +65,6 @@ suite("`CoqProofChecker` tests", () => { .trim(); } - function getTextBeforePosition( - textLines: string[], - position: { line: number; character: number } - ): string[] { - const textPrefix = textLines.slice(0, position.line + 1); - textPrefix[position.line] = textPrefix[position.line].slice( - 0, - position.character - ); - return textPrefix; - } - test("Check simple admits", async () => { const filePath = ["small_document.v"]; const proofs = [["reflexivity.", "auto."]]; diff --git a/src/test/legacyBenchmark/benchmarkingFramework.ts b/src/test/legacyBenchmark/benchmarkingFramework.ts index c6c28cff..8ad7bd07 100644 --- a/src/test/legacyBenchmark/benchmarkingFramework.ts +++ b/src/test/legacyBenchmark/benchmarkingFramework.ts @@ -242,7 +242,7 @@ async function benchmarkCompletionGeneration( workspaceRootPath?: string, perProofTimeoutMillis: number = 15000 ): Promise { - const completionPosition = completionContext.admitEndPosition; + const completionPosition = completionContext.admitRange.start; consoleLog( `Completion position: ${completionPosition.line}:${completionPosition.character}` ); @@ -518,8 +518,7 @@ async function resolveProofStepsToCompletionContexts( if (firstGoal) { completionContexts.push({ proofGoal: firstGoal, - prefixEndPosition: parentedProofStep.proofStep.range.start, - admitEndPosition: parentedProofStep.proofStep.range.end, + admitRange: parentedProofStep.proofStep.range, parentTheorem: parentedProofStep.parentTheorem, }); } From ad795e7d9d97b6eca21fdbb82a4f233d127a59ea Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Thu, 17 Oct 2024 20:34:29 +0200 Subject: [PATCH 07/30] Clean-up --- src/core/coqProofChecker.ts | 49 ------------------------------------- 1 file changed, 49 deletions(-) diff --git a/src/core/coqProofChecker.ts b/src/core/coqProofChecker.ts index e34b8532..a515cd89 100644 --- a/src/core/coqProofChecker.ts +++ b/src/core/coqProofChecker.ts @@ -1,6 +1,4 @@ import { Mutex } from "async-mutex"; -// import { existsSync, unlinkSync, writeFileSync } from "fs"; -// import * as path from "path"; import { Position } from "vscode-languageclient"; import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; @@ -65,25 +63,6 @@ export class CoqProofChecker implements CoqProofCheckerInterface { }); } - // private buildAuxFileUri( - // sourceDirPath: string, - // holePosition: Position, - // unique: boolean = true - // ): Uri { - // const holeIdentifier = `${holePosition.line}_${holePosition.character}`; - // const defaultAuxFileName = `hole_${holeIdentifier}_cp_aux.v`; - // let auxFilePath = path.join(sourceDirPath, defaultAuxFileName); - // if (unique && existsSync(auxFilePath)) { - // const randomSuffix = Math.floor(Math.random() * 1000000); - // auxFilePath = auxFilePath.replace( - // /\_cp_aux.v$/, - // `_${randomSuffix}_cp_aux.v` - // ); - // } - - // return Uri.fromPath(auxFilePath); - // } - private checkIfProofContainsAdmit(proof: Proof): boolean { return forbiddenAdmitTactics.some((tactic) => proof.includes(tactic)); } @@ -91,27 +70,11 @@ export class CoqProofChecker implements CoqProofCheckerInterface { private async checkProofsUnsafe( fileUri: Uri, documentVersion: number, - // sourceDirPath: string, - // sourceFileContentPrefix: string[], checkAtPosition: Position, proofs: Proof[] ): Promise { - // 1. Write the text to the aux file - // const auxFileUri = this.buildAuxFileUri( - // sourceDirPath, - // prefixEndPosition - // ); - // const sourceFileContent = sourceFileContentPrefix.join("\n"); - // writeFileSync(auxFileUri.fsPath, sourceFileContent); - const results: ProofCheckResult[] = []; - // try { - // 2. Issue open text document request - // await this.coqLspClient.openTextDocument(fileUri); - - // 3. Iterate over the proofs and сheck them for (const proof of proofs) { - // 3.1. Check if the proof contains admit if (this.checkIfProofContainsAdmit(proof)) { results.push({ proof: proof, @@ -121,7 +84,6 @@ export class CoqProofChecker implements CoqProofCheckerInterface { continue; } - // 3.2 Check if proof is valid and closes the first goal const goalResult = await this.coqLspClient.getGoalsAtPoint( checkAtPosition, fileUri, @@ -135,17 +97,6 @@ export class CoqProofChecker implements CoqProofCheckerInterface { diagnostic: goalResult.err ? goalResult.val.message : undefined, }); } - // } catch (error) { - // console.log("Error in checkProofsUnsafe", error); - // throw error; - // } - // finally { - // 4. Issue close text document request - // await this.coqLspClient.closeTextDocument(fileUri); - - // 5. Remove the aux file - // unlinkSync(auxFileUri.fsPath); - // } return results; } From 872987bd07a12c5df6b76d9b2b6a66e1f4303f0a Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Fri, 18 Oct 2024 13:04:57 +0200 Subject: [PATCH 08/30] Refactor creating in plugin mode: now once in --- src/extension/coqPilot.ts | 34 ++++++++++++++++++--------- src/extension/globalExtensionState.ts | 33 ++++++++++++++++++++++---- src/mainNode.ts | 2 +- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index fdc42e3e..dce144dd 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -7,7 +7,7 @@ import { workspace, } from "vscode"; -import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; +// import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; import { CoqLspStartupError } from "../coqLsp/coqLspTypes"; import { @@ -30,7 +30,6 @@ import { Uri } from "../utils/uri"; import { buildTheoremsRankerFromConfig, - parseCoqLspServerPath, readAndValidateUserModelsParams, } from "./configReaders"; import { @@ -61,12 +60,17 @@ export class CoqPilot { private readonly globalExtensionState: GlobalExtensionState; private readonly vscodeExtensionContext: ExtensionContext; - constructor(vscodeExtensionContext: ExtensionContext) { + private constructor( + vscodeExtensionContext: ExtensionContext, + globalExtensionState: GlobalExtensionState + ) { hideAuxFiles(); suggestAddingAuxFilesToGitignore(); this.vscodeExtensionContext = vscodeExtensionContext; - this.globalExtensionState = new GlobalExtensionState(); + this.globalExtensionState = globalExtensionState; + + console.log("CoqPilot extension is now active!"); this.registerEditorCommand( "perform_completion_under_cursor", @@ -84,6 +88,11 @@ export class CoqPilot { this.vscodeExtensionContext.subscriptions.push(this); } + static async create(vscodeExtensionContext: ExtensionContext) { + const globalExtensionState = await GlobalExtensionState.create(); + return new CoqPilot(vscodeExtensionContext, globalExtensionState); + } + async performCompletionUnderCursor(editor: TextEditor) { const cursorPosition = editor.selection.active; this.performSpecificCompletionsWithProgress( @@ -272,7 +281,7 @@ export class CoqPilot { [CompletionContext[], SourceFileEnvironment, ProcessEnvironment] > { const fileUri = Uri.fromPath(filePath); - const coqLspServerPath = parseCoqLspServerPath(); + // const coqLspServerPath = parseCoqLspServerPath(); // TODO: [LspCoreRefactor] Now a tone of Coq-LSPs are created and destroyed for each completion. // It is not efficient. Refactor it to create a single Coq-LSP client for the whole session. // But allow restarting it when issues occur. @@ -281,19 +290,22 @@ export class CoqPilot { // to send any events when user uses the plugin. // TODO: [LspCoreRefactor] Check what happens in plugin runtime when file not prepared, but goals requested. - const client = await createCoqLspClient( - coqLspServerPath, - this.globalExtensionState.logOutputChannel - ); + // const client = await createCoqLspClient( + // coqLspServerPath, + // this.globalExtensionState.logOutputChannel + // ); const contextTheoremsRanker = buildTheoremsRankerFromConfig(); - const coqProofChecker = new CoqProofChecker(client); + const coqProofChecker = new CoqProofChecker( + this.globalExtensionState.coqLspClient + ); + // TODO: [LspCoreRefactor] Unclear double dependency on Coq-LSP client. const [completionContexts, sourceFileEnvironment] = await inspectSourceFile( fileVersion, shouldCompleteHole, fileUri, - client + this.globalExtensionState.coqLspClient ); const processEnvironment: ProcessEnvironment = { coqProofChecker: coqProofChecker, diff --git a/src/extension/globalExtensionState.ts b/src/extension/globalExtensionState.ts index b1640a5b..f3835e65 100644 --- a/src/extension/globalExtensionState.ts +++ b/src/extension/globalExtensionState.ts @@ -1,7 +1,12 @@ import * as fs from "fs"; import * as path from "path"; import * as tmp from "tmp"; -import { WorkspaceConfiguration, window, workspace } from "vscode"; +import { + OutputChannel, + WorkspaceConfiguration, + window, + workspace, +} from "vscode"; import { LLMServices, disposeServices } from "../llm/llmServices"; import { GrazieService } from "../llm/llmServices/grazie/grazieService"; @@ -9,8 +14,12 @@ import { LMStudioService } from "../llm/llmServices/lmStudio/lmStudioService"; import { OpenAiService } from "../llm/llmServices/openai/openAiService"; import { PredefinedProofsService } from "../llm/llmServices/predefinedProofs/predefinedProofsService"; +import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; +import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; + import { EventLogger, Severity } from "../logging/eventLogger"; +import { parseCoqLspServerPath } from "./configReaders"; import { pluginId } from "./coqPilot"; import VSCodeLogWriter from "./vscodeLogWriter"; @@ -20,9 +29,24 @@ export class GlobalExtensionState { this.eventLogger, this.parseLoggingVerbosity(workspace.getConfiguration(pluginId)) ); - public readonly logOutputChannel = window.createOutputChannel( - "CoqPilot: coq-lsp events" - ); + + private constructor( + public readonly coqLspClient: CoqLspClientInterface, + public readonly logOutputChannel: OutputChannel + ) {} + + static async create(): Promise { + const coqLspServerPath = parseCoqLspServerPath(); + const logOutputChannel = window.createOutputChannel( + "CoqPilot: coq-lsp events" + ); + const coqLspClient = await createCoqLspClient( + coqLspServerPath, + logOutputChannel + ); + + return new GlobalExtensionState(coqLspClient, logOutputChannel); + } public readonly llmServicesLogsDir = path.join( tmp.dirSync().name, @@ -67,6 +91,7 @@ export class GlobalExtensionState { dispose(): void { disposeServices(this.llmServices); this.logWriter.dispose(); + this.coqLspClient.dispose(); fs.rmSync(this.llmServicesLogsDir, { recursive: true, force: true }); this.logOutputChannel.dispose(); } diff --git a/src/mainNode.ts b/src/mainNode.ts index 7516115a..4a931eb5 100644 --- a/src/mainNode.ts +++ b/src/mainNode.ts @@ -5,7 +5,7 @@ import { CoqPilot } from "./extension/coqPilot"; export let extension: CoqPilot | undefined; export async function activate(context: ExtensionContext): Promise { - extension = new CoqPilot(context); + extension = await CoqPilot.create(context); context.subscriptions.push(extension); } From e97e1ecfbfbd4b45520303b2eb8de3beb7986c84 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Sun, 20 Oct 2024 13:50:30 +0200 Subject: [PATCH 09/30] Create decorator that logs execution time of the method --- .vscode/settings.json | 9 ++-- set_gitignore.sh | 33 --------------- src/coqLsp/coqLspBuilders.ts | 14 +++++-- src/coqLsp/coqLspClient.ts | 14 +++++-- src/extension/coqPilot.ts | 7 ---- src/extension/editGitignoreCommand.ts | 52 ----------------------- src/extension/globalExtensionState.ts | 14 +++++-- src/extension/tmpFilesCleanup.ts | 39 ------------------ src/logging/timeMeasureDecorator.ts | 59 +++++++++++++++++++++++++++ 9 files changed, 96 insertions(+), 145 deletions(-) delete mode 100755 set_gitignore.sh delete mode 100644 src/extension/editGitignoreCommand.ts delete mode 100644 src/extension/tmpFilesCleanup.ts create mode 100644 src/logging/timeMeasureDecorator.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 913729b7..69726f9b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,12 +12,15 @@ "**/CVS": true, "**/.DS_Store": true, "**/Thumbs.db": true, - "out": false, - "**/*_cp_aux.v": true + "out": false }, "search.exclude": { "out": true // set this to false to include "out" folder in search results }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "typescript.tsc.autoDetect": "off", + "coq-lsp.check_only_on_request": false, + "eslint.options": { + "experimentalDecorators": true + } } \ No newline at end of file diff --git a/set_gitignore.sh b/set_gitignore.sh deleted file mode 100755 index 26d8d06d..00000000 --- a/set_gitignore.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# Git config directory -directory=~/.config/git - -# Check if the directory exists -if [ ! -d "$directory" ]; then - # If not, create directory - echo "Git directory not found. Creating..." - mkdir $directory -else - echo "Git directory exists." -fi - -# Navigate to the directory -cd $directory - -# Check if ignore file exists -if [ ! -f "ignore" ]; then - # If not, create file - echo "Ignore file not found. Creating..." - touch ignore -else - echo "Ignore file exists." -fi - -# Append the string to the file -echo "Appending string to the file..." -echo "*_cp_aux.v" >> ignore - -# Display the contents of the file -echo "Contents of the ignore file:" -cat ignore \ No newline at end of file diff --git a/src/coqLsp/coqLspBuilders.ts b/src/coqLsp/coqLspBuilders.ts index 6a7017b3..abb4dd31 100644 --- a/src/coqLsp/coqLspBuilders.ts +++ b/src/coqLsp/coqLspBuilders.ts @@ -1,15 +1,19 @@ import { OutputChannel, window } from "vscode"; +import { EventLogger } from "../logging/eventLogger"; + import { CoqLspClient, CoqLspClientInterface } from "./coqLspClient"; import { CoqLspClientConfig, CoqLspConfig } from "./coqLspConfig"; export async function createCoqLspClient( coqLspServerPath: string, - logOutputChannel?: OutputChannel + logOutputChannel?: OutputChannel, + eventLogger?: EventLogger ): Promise { return createAbstractCoqLspClient( CoqLspConfig.createClientConfig(coqLspServerPath), - logOutputChannel + logOutputChannel, + eventLogger ); } @@ -28,12 +32,14 @@ async function createAbstractCoqLspClient( coqLspClientConfig: CoqLspClientConfig, logOutputChannel: OutputChannel = window.createOutputChannel( "CoqPilot: coq-lsp events" - ) + ), + eventLogger?: EventLogger ): Promise { const coqLspServerConfig = CoqLspConfig.createServerConfig(); return await CoqLspClient.create( coqLspServerConfig, coqLspClientConfig, - logOutputChannel + logOutputChannel, + eventLogger ); } diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index b1c76dbc..894b0eb6 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -19,6 +19,7 @@ import { VersionedTextDocumentIdentifier, } from "vscode-languageclient"; +import { EventLogger } from "../logging/eventLogger"; import { Uri } from "../utils/uri"; import { CoqLspClientConfig, CoqLspServerConfig } from "./coqLspConfig"; @@ -33,6 +34,7 @@ import { GoalRequest, PpString, } from "./coqLspTypes"; +import { logExecutionTime } from "../logging/timeMeasureDecorator"; export interface CoqLspClientInterface extends Disposable { getGoalsAtPoint( @@ -66,14 +68,18 @@ export class CoqLspClient implements CoqLspClientInterface { private subscriptions: Disposable[] = []; private mutex = new Mutex(); - private constructor(coqLspConnector: CoqLspConnector) { + private constructor( + coqLspConnector: CoqLspConnector, + public readonly eventLogger?: EventLogger + ) { this.client = coqLspConnector; } static async create( serverConfig: CoqLspServerConfig, clientConfig: CoqLspClientConfig, - logOutputChannel: OutputChannel + logOutputChannel: OutputChannel, + eventLogger?: EventLogger ): Promise { const connector = new CoqLspConnector( serverConfig, @@ -86,9 +92,10 @@ export class CoqLspClient implements CoqLspClientInterface { clientConfig.coq_lsp_server_path ); }); - return new CoqLspClient(connector); + return new CoqLspClient(connector, eventLogger); } + @logExecutionTime async getGoalsAtPoint( position: Position, documentUri: Uri, @@ -120,6 +127,7 @@ export class CoqLspClient implements CoqLspClientInterface { }); } + @logExecutionTime async getFlecheDocument(uri: Uri): Promise { return await this.mutex.runExclusive(async () => { return this.getFlecheDocumentUnsafe(uri); diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index dce144dd..a760f337 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -7,7 +7,6 @@ import { workspace, } from "vscode"; -// import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; import { CoqLspStartupError } from "../coqLsp/coqLspTypes"; import { @@ -37,7 +36,6 @@ import { highlightTextInEditor, insertCompletion, } from "./documentEditor"; -import { suggestAddingAuxFilesToGitignore } from "./editGitignoreCommand"; import { EditorMessages, showMessageToUser, @@ -51,7 +49,6 @@ import { toVSCodeRange, } from "./positionRangeUtils"; import { SettingsValidationError } from "./settingsValidationError"; -import { cleanAuxFiles, hideAuxFiles } from "./tmpFilesCleanup"; export const pluginId = "coqpilot"; export const pluginName = "CoqPilot"; @@ -64,9 +61,6 @@ export class CoqPilot { vscodeExtensionContext: ExtensionContext, globalExtensionState: GlobalExtensionState ) { - hideAuxFiles(); - suggestAddingAuxFilesToGitignore(); - this.vscodeExtensionContext = vscodeExtensionContext; this.globalExtensionState = globalExtensionState; @@ -332,7 +326,6 @@ export class CoqPilot { } dispose(): void { - cleanAuxFiles(); this.globalExtensionState.dispose(); } } diff --git a/src/extension/editGitignoreCommand.ts b/src/extension/editGitignoreCommand.ts deleted file mode 100644 index cb2012c2..00000000 --- a/src/extension/editGitignoreCommand.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { appendFile, existsSync, readFileSync } from "fs"; -import * as path from "path"; -import { window, workspace } from "vscode"; - -import { showMessageToUser } from "./editorMessages"; - -export async function suggestAddingAuxFilesToGitignore() { - const workspaceFolders = workspace.workspaceFolders; - if (!workspaceFolders) { - return; - } - - for (const folder of workspaceFolders) { - const gitIgnorePath = path.join(folder.uri.fsPath, ".gitignore"); - if (!existsSync(gitIgnorePath)) { - // .gitignore not found. Exit. - return; - } - - const data = readFileSync(gitIgnorePath, "utf8"); - const auxExt = "*_cp_aux.v"; - if (data.indexOf(auxExt) === -1) { - // Not found. Ask user if we should add it. - await window - .showInformationMessage( - 'Do you want to add "*_cp_aux.v" to .gitignore?', - "Yes", - "No" - ) - .then((choice) => { - if (choice === "Yes") { - const rule = `\n# CoqPilot auxiliary files\n${auxExt}`; - appendFile(gitIgnorePath, rule, (err) => { - if (err) { - showMessageToUser( - `Unexpected error writing to .gitignore: ${err.message}`, - "error" - ); - } else { - showMessageToUser( - 'Successfully added "*_cp_aux.v" to .gitignore', - "info" - ); - } - }); - } - }); - } else { - return; - } - } -} diff --git a/src/extension/globalExtensionState.ts b/src/extension/globalExtensionState.ts index f3835e65..7fb3ca5a 100644 --- a/src/extension/globalExtensionState.ts +++ b/src/extension/globalExtensionState.ts @@ -24,7 +24,6 @@ import { pluginId } from "./coqPilot"; import VSCodeLogWriter from "./vscodeLogWriter"; export class GlobalExtensionState { - public readonly eventLogger: EventLogger = new EventLogger(); public readonly logWriter: VSCodeLogWriter = new VSCodeLogWriter( this.eventLogger, this.parseLoggingVerbosity(workspace.getConfiguration(pluginId)) @@ -32,7 +31,8 @@ export class GlobalExtensionState { private constructor( public readonly coqLspClient: CoqLspClientInterface, - public readonly logOutputChannel: OutputChannel + public readonly logOutputChannel: OutputChannel, + public readonly eventLogger: EventLogger ) {} static async create(): Promise { @@ -40,12 +40,18 @@ export class GlobalExtensionState { const logOutputChannel = window.createOutputChannel( "CoqPilot: coq-lsp events" ); + const eventLogger = new EventLogger(); const coqLspClient = await createCoqLspClient( coqLspServerPath, - logOutputChannel + logOutputChannel, + eventLogger ); - return new GlobalExtensionState(coqLspClient, logOutputChannel); + return new GlobalExtensionState( + coqLspClient, + logOutputChannel, + eventLogger + ); } public readonly llmServicesLogsDir = path.join( diff --git a/src/extension/tmpFilesCleanup.ts b/src/extension/tmpFilesCleanup.ts deleted file mode 100644 index c0b18344..00000000 --- a/src/extension/tmpFilesCleanup.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as glob from "glob"; -import * as path from "path"; -import { Uri, workspace } from "vscode"; - -export function hideAuxFiles() { - // Hide files generated to check proofs - let activationConfig = workspace.getConfiguration(); - let fexc: any = activationConfig.get("files.exclude"); - activationConfig.update("files.exclude", { - ...fexc, - // eslint-disable-next-line @typescript-eslint/naming-convention - "**/*_cp_aux.v": true, - }); -} - -export function cleanAuxFiles() { - // Glob *_cp_aux.v files and delete them - const workspaceFolders = workspace.workspaceFolders; - if (!workspaceFolders) { - return; - } - - workspaceFolders.forEach((folder) => { - glob( - "**/*_cp_aux.v", - { sync: false, cwd: folder.uri.fsPath }, - (err, files) => { - if (err) { - return; - } - - files.forEach((file) => { - const filePath = path.resolve(folder.uri.fsPath, file); - workspace.fs.delete(Uri.file(filePath)); - }); - } - ); - }); -} diff --git a/src/logging/timeMeasureDecorator.ts b/src/logging/timeMeasureDecorator.ts new file mode 100644 index 00000000..74b145ad --- /dev/null +++ b/src/logging/timeMeasureDecorator.ts @@ -0,0 +1,59 @@ +import { Severity } from "./eventLogger"; + + +/** + * A decorator that logs the execution time of a method. + * Execution time is logged in milliseconds with severity + * `DEBUG` in the event logger. If the class does not have + * an event logger, the execution time is logged to the console. + * + * (Note: typescript supports decorators only for class methods). + */ +export function logExecutionTime( + _target: any, + propertyKey: string, + descriptor: PropertyDescriptor +) { + const originalMethod = descriptor.value; + + descriptor.value = function (this: any, ...args: any[]) { + const start = performance.now(); + + const result = originalMethod.apply(this, args); + + const logTime = (duration: number) => { + if (this.eventLogger) { + this.eventLogger.log( + "function-execution-time", + `${propertyKey} took ${duration.toFixed(2)}ms to execute`, + undefined, + Severity.DEBUG + ); + } else { + console.log( + `${propertyKey} took ${duration.toFixed(2)}ms to execute` + ); + } + }; + + if (result && typeof result.then === "function") { + return result + .then((res: any) => { + const duration = performance.now() - start; + logTime(duration); + return res; + }) + .catch((err: any) => { + const duration = performance.now() - start; + logTime(duration); + throw err; + }); + } else { + const duration = performance.now() - start; + logTime(duration); + return result; + } + }; + + return descriptor; +} \ No newline at end of file From 5baf1f8610d642e672fba141007fe947a50682d8 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Sun, 20 Oct 2024 15:25:06 +0200 Subject: [PATCH 10/30] Make computation of `initialGoal` optional Now it depends on the particular chosen Ranker. It is computed iff. ranker requires unwrapped notations. --- package.json | 1 + src/coqParser/parseCoqFile.ts | 41 +++++++++++++------ .../contextTheoremsRanker.ts | 2 + .../distanceContextTheoremsRanker.ts | 2 + .../euclidContextTheoremRanker.ts | 2 + .../jaccardIndexContextTheoremsRanker.ts | 2 + .../randomContextTheoremsRanker.ts | 2 + .../weightedJaccardIndexTheoremRanker.ts | 2 + .../\321\201osineContextTheoremRanker.ts" | 2 + src/core/inspectSourceFile.ts | 11 +++-- src/extension/coqPilot.ts | 6 ++- src/logging/timeMeasureDecorator.ts | 22 ++-------- 12 files changed, 59 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 902cab41..f4f33a36 100644 --- a/package.json +++ b/package.json @@ -392,6 +392,7 @@ "pretest": "npm run compile && npm run lint", "test": "npm run test-only", "clean": "rm -rf out", + "rebuild": "npm run clean && npm run compile", "rebuild-test-resources": "cd ./src/test/resources/coqProj && make clean && make", "preclean-test": "npm run clean && npm run rebuild-test-resources && npm run compile && npm run lint", "clean-test": "npm run test-only", diff --git a/src/coqParser/parseCoqFile.ts b/src/coqParser/parseCoqFile.ts index acca3152..27bf6753 100644 --- a/src/coqParser/parseCoqFile.ts +++ b/src/coqParser/parseCoqFile.ts @@ -5,16 +5,24 @@ import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; import { CoqParsingError, FlecheDocument, + Goal, + PpString, RangedSpan, } from "../coqLsp/coqLspTypes"; import { Uri } from "../utils/uri"; import { ProofStep, Theorem, TheoremProof, Vernacexpr } from "./parsedTypes"; +import { Result } from "ts-results"; +/** + * TODO: [LspCoreRefactor] Refactor retrieveInitialGoal param + * to be something more reasonable and readable. + */ export async function parseCoqFile( uri: Uri, - client: CoqLspClientInterface + client: CoqLspClientInterface, + retrieveInitialGoal: boolean = true ): Promise { return client .getFlecheDocument(uri) @@ -22,7 +30,7 @@ export async function parseCoqFile( const documentText = readFileSync(uri.fsPath) .toString() .split("\n"); - return parseFlecheDocument(doc, documentText, client, uri); + return parseFlecheDocument(doc, documentText, client, uri, retrieveInitialGoal); }) .catch((error) => { throw new CoqParsingError( @@ -35,7 +43,8 @@ async function parseFlecheDocument( doc: FlecheDocument, textLines: string[], client: CoqLspClientInterface, - uri: Uri + uri: Uri, + retrieveInitialGoal: boolean ): Promise { if (doc === null) { throw Error("could not parse file"); @@ -91,16 +100,22 @@ async function parseFlecheDocument( ) ); } else { - const initialGoal = await client.getGoalsAtPoint( - doc.spans[i + 1].range.start, - uri, - 1 - ); - - if (initialGoal.err) { - throw new CoqParsingError( - `unable to get initial goal for theorem: ${thrName}` + // TODO: [LspCoreRefactor] Discuss invariants on initial_goal + // and allow it's absence. As calculation of initial_goal + // brings overhead of 100ms per theorem. + let initialGoal: Result[], Error> | null = null; + if (retrieveInitialGoal) { + initialGoal = await client.getGoalsAtPoint( + doc.spans[i + 1].range.start, + uri, + 1 ); + + if (initialGoal.err) { + throw new CoqParsingError( + `unable to get initial goal for theorem: ${thrName}` + ); + } } const proof = parseProof(i + 1, doc.spans, textLines); @@ -110,7 +125,7 @@ async function parseFlecheDocument( doc.spans[i].range, thrStatement, proof, - initialGoal.val[0] + initialGoal?.val[0] ) ); } diff --git a/src/core/contextTheoremRanker/contextTheoremsRanker.ts b/src/core/contextTheoremRanker/contextTheoremsRanker.ts index 0f5c7c41..821448c1 100644 --- a/src/core/contextTheoremRanker/contextTheoremsRanker.ts +++ b/src/core/contextTheoremRanker/contextTheoremsRanker.ts @@ -6,4 +6,6 @@ export interface ContextTheoremsRanker { theorems: Theorem[], completionContext: CompletionContext ): Theorem[]; + + needsUnwrappedNotations: boolean; } diff --git a/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts b/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts index c4dbe896..769377a0 100644 --- a/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts +++ b/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts @@ -4,6 +4,8 @@ import { CompletionContext } from "../completionGenerationContext"; import { ContextTheoremsRanker } from "./contextTheoremsRanker"; export class DistanceContextTheoremsRanker implements ContextTheoremsRanker { + public readonly needsUnwrappedNotations = false; + rankContextTheorems( theorems: Theorem[], completionContext: CompletionContext diff --git a/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts b/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts index aaffc345..24d81e28 100644 --- a/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts +++ b/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts @@ -11,6 +11,8 @@ import { goalAsTheoremString } from "./tokenUtils"; * */ export class EuclidContextTheoremsRanker implements ContextTheoremsRanker { + public readonly needsUnwrappedNotations = true; + rankContextTheorems( theorems: Theorem[], completionContext: CompletionContext diff --git a/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts b/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts index b5b885ac..bf5ed5c9 100644 --- a/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts +++ b/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts @@ -14,6 +14,8 @@ import { goalAsTheoremString } from "./tokenUtils"; export class JaccardIndexContextTheoremsRanker implements ContextTheoremsRanker { + public readonly needsUnwrappedNotations = true; + rankContextTheorems( theorems: Theorem[], completionContext: CompletionContext diff --git a/src/core/contextTheoremRanker/randomContextTheoremsRanker.ts b/src/core/contextTheoremRanker/randomContextTheoremsRanker.ts index c12d79e7..aa2e548f 100644 --- a/src/core/contextTheoremRanker/randomContextTheoremsRanker.ts +++ b/src/core/contextTheoremRanker/randomContextTheoremsRanker.ts @@ -4,6 +4,8 @@ import { CompletionContext } from "../completionGenerationContext"; import { ContextTheoremsRanker } from "./contextTheoremsRanker"; export class RandomContextTheoremsRanker implements ContextTheoremsRanker { + public readonly needsUnwrappedNotations = false; + private shuffleArray(array: any[]) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); diff --git a/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts b/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts index 801d6019..ac3617a3 100644 --- a/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts +++ b/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts @@ -14,6 +14,8 @@ import { goalAsTheoremString } from "./tokenUtils"; export class WeightedJaccardIndexContextTheoremsRanker implements ContextTheoremsRanker { + public readonly needsUnwrappedNotations = true; + rankContextTheorems( theorems: Theorem[], completionContext: CompletionContext diff --git "a/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" "b/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" index 308f6bf3..02a0db60 100644 --- "a/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" +++ "b/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" @@ -12,6 +12,8 @@ import { goalAsTheoremString } from "./tokenUtils"; * ```cosine(A, B) = |A ∩ B| / sqrt(|A| * |B|)``` */ export class CosineContextTheoremsRanker implements ContextTheoremsRanker { + public readonly needsUnwrappedNotations = true; + rankContextTheorems( theorems: Theorem[], completionContext: CompletionContext diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index 8733390b..e273e7a0 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -18,12 +18,14 @@ export async function inspectSourceFile( fileVersion: number, shouldCompleteHole: (hole: ProofStep) => boolean, fileUri: Uri, - client: CoqLspClientInterface + client: CoqLspClientInterface, + rankerNeedsInitialGoals: boolean = true ): Promise { const sourceFileEnvironment = await createSourceFileEnvironment( fileVersion, fileUri, - client + client, + rankerNeedsInitialGoals ); const completionContexts = await createCompletionContexts( fileVersion, @@ -79,9 +81,10 @@ async function createCompletionContexts( export async function createSourceFileEnvironment( fileVersion: number, fileUri: Uri, - client: CoqLspClientInterface + client: CoqLspClientInterface, + rankerNeedsInitialGoals: boolean = true ): Promise { - const fileTheorems = await parseCoqFile(fileUri, client); + const fileTheorems = await parseCoqFile(fileUri, client, rankerNeedsInitialGoals); const fileText = readFileSync(fileUri.fsPath); const dirPath = getSourceFolderPath(fileUri); if (!dirPath) { diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index a760f337..cb78ed6e 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -49,6 +49,7 @@ import { toVSCodeRange, } from "./positionRangeUtils"; import { SettingsValidationError } from "./settingsValidationError"; +import { logExecutionTime } from "../logging/timeMeasureDecorator"; export const pluginId = "coqpilot"; export const pluginName = "CoqPilot"; @@ -202,6 +203,7 @@ export class CoqPilot { } } + @logExecutionTime private async performSingleCompletion( completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, @@ -267,6 +269,7 @@ export class CoqPilot { .trim(); } + @logExecutionTime private async prepareForCompletions( shouldCompleteHole: (hole: ProofStep) => boolean, fileVersion: number, @@ -299,7 +302,8 @@ export class CoqPilot { fileVersion, shouldCompleteHole, fileUri, - this.globalExtensionState.coqLspClient + this.globalExtensionState.coqLspClient, + contextTheoremsRanker.needsUnwrappedNotations ); const processEnvironment: ProcessEnvironment = { coqProofChecker: coqProofChecker, diff --git a/src/logging/timeMeasureDecorator.ts b/src/logging/timeMeasureDecorator.ts index 74b145ad..ab591037 100644 --- a/src/logging/timeMeasureDecorator.ts +++ b/src/logging/timeMeasureDecorator.ts @@ -1,11 +1,6 @@ -import { Severity } from "./eventLogger"; - - /** * A decorator that logs the execution time of a method. - * Execution time is logged in milliseconds with severity - * `DEBUG` in the event logger. If the class does not have - * an event logger, the execution time is logged to the console. + * Execution time is logged into console in milliseconds. * * (Note: typescript supports decorators only for class methods). */ @@ -22,18 +17,9 @@ export function logExecutionTime( const result = originalMethod.apply(this, args); const logTime = (duration: number) => { - if (this.eventLogger) { - this.eventLogger.log( - "function-execution-time", - `${propertyKey} took ${duration.toFixed(2)}ms to execute`, - undefined, - Severity.DEBUG - ); - } else { - console.log( - `${propertyKey} took ${duration.toFixed(2)}ms to execute` - ); - } + console.log( + `${propertyKey} took ${duration.toFixed(2)}ms to execute` + ); }; if (result && typeof result.then === "function") { From 9319114f8c72a4ff7eecbff039bd3590c0d7f68a Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Sun, 20 Oct 2024 17:06:17 +0200 Subject: [PATCH 11/30] Create permanent status bar button --- .prettierrc.json | 3 +- package.json | 2 +- src/coqLsp/coqLspClient.ts | 2 +- src/coqParser/parseCoqFile.ts | 23 +++-- .../euclidContextTheoremRanker.ts | 2 +- .../jaccardIndexContextTheoremsRanker.ts | 2 +- .../weightedJaccardIndexTheoremRanker.ts | 2 +- .../\321\201osineContextTheoremRanker.ts" | 2 +- src/core/inspectSourceFile.ts | 8 +- src/extension/coqPilot.ts | 85 ++++++++++--------- src/extension/globalExtensionState.ts | 2 +- src/extension/statusBarButton.ts | 74 ++++++++++++++++ src/logging/timeMeasureDecorator.ts | 6 +- src/mainNode.ts | 28 +++++- 14 files changed, 177 insertions(+), 64 deletions(-) create mode 100644 src/extension/statusBarButton.ts diff --git a/.prettierrc.json b/.prettierrc.json index d9954e13..a2ed5e7b 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -7,5 +7,6 @@ "printWidth": 80, "importOrder": ["^[./]*llm/(.*)$", "^[./]*coqLsp/(.*)$", "^[./]*core/(.*)$", "^../", "^./"], "importOrderSeparation": true, - "importOrderSortSpecifiers": true + "importOrderSortSpecifiers": true, + "importOrderParserPlugins": ["typescript", "@trivago/prettier-plugin-sort-imports", "decorators-legacy"] } \ No newline at end of file diff --git a/package.json b/package.json index f4f33a36..f5cce704 100644 --- a/package.json +++ b/package.json @@ -392,7 +392,7 @@ "pretest": "npm run compile && npm run lint", "test": "npm run test-only", "clean": "rm -rf out", - "rebuild": "npm run clean && npm run compile", + "rebuild": "npm run clean && npm run compile && npm run format", "rebuild-test-resources": "cd ./src/test/resources/coqProj && make clean && make", "preclean-test": "npm run clean && npm run rebuild-test-resources && npm run compile && npm run lint", "clean-test": "npm run test-only", diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index 894b0eb6..00cc6fdb 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -20,6 +20,7 @@ import { } from "vscode-languageclient"; import { EventLogger } from "../logging/eventLogger"; +import { logExecutionTime } from "../logging/timeMeasureDecorator"; import { Uri } from "../utils/uri"; import { CoqLspClientConfig, CoqLspServerConfig } from "./coqLspConfig"; @@ -34,7 +35,6 @@ import { GoalRequest, PpString, } from "./coqLspTypes"; -import { logExecutionTime } from "../logging/timeMeasureDecorator"; export interface CoqLspClientInterface extends Disposable { getGoalsAtPoint( diff --git a/src/coqParser/parseCoqFile.ts b/src/coqParser/parseCoqFile.ts index 27bf6753..298fad0b 100644 --- a/src/coqParser/parseCoqFile.ts +++ b/src/coqParser/parseCoqFile.ts @@ -1,4 +1,5 @@ import { readFileSync } from "fs"; +import { Result } from "ts-results"; import { Position, Range } from "vscode-languageclient"; import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; @@ -13,11 +14,10 @@ import { import { Uri } from "../utils/uri"; import { ProofStep, Theorem, TheoremProof, Vernacexpr } from "./parsedTypes"; -import { Result } from "ts-results"; /** - * TODO: [LspCoreRefactor] Refactor retrieveInitialGoal param - * to be something more reasonable and readable. + * TODO: [LspCoreRefactor] Refactor retrieveInitialGoal param + * to be something more reasonable and readable. */ export async function parseCoqFile( uri: Uri, @@ -30,7 +30,13 @@ export async function parseCoqFile( const documentText = readFileSync(uri.fsPath) .toString() .split("\n"); - return parseFlecheDocument(doc, documentText, client, uri, retrieveInitialGoal); + return parseFlecheDocument( + doc, + documentText, + client, + uri, + retrieveInitialGoal + ); }) .catch((error) => { throw new CoqParsingError( @@ -100,10 +106,11 @@ async function parseFlecheDocument( ) ); } else { - // TODO: [LspCoreRefactor] Discuss invariants on initial_goal - // and allow it's absence. As calculation of initial_goal - // brings overhead of 100ms per theorem. - let initialGoal: Result[], Error> | null = null; + // TODO: [LspCoreRefactor] Discuss invariants on initial_goal + // and allow it's absence. As calculation of initial_goal + // brings overhead of 100ms per theorem. + let initialGoal: Result[], Error> | null = + null; if (retrieveInitialGoal) { initialGoal = await client.getGoalsAtPoint( doc.spans[i + 1].range.start, diff --git a/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts b/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts index 24d81e28..1e366c2c 100644 --- a/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts +++ b/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts @@ -12,7 +12,7 @@ import { goalAsTheoremString } from "./tokenUtils"; */ export class EuclidContextTheoremsRanker implements ContextTheoremsRanker { public readonly needsUnwrappedNotations = true; - + rankContextTheorems( theorems: Theorem[], completionContext: CompletionContext diff --git a/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts b/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts index bf5ed5c9..cbd3b4b9 100644 --- a/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts +++ b/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts @@ -15,7 +15,7 @@ export class JaccardIndexContextTheoremsRanker implements ContextTheoremsRanker { public readonly needsUnwrappedNotations = true; - + rankContextTheorems( theorems: Theorem[], completionContext: CompletionContext diff --git a/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts b/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts index ac3617a3..a4eab92b 100644 --- a/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts +++ b/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts @@ -15,7 +15,7 @@ export class WeightedJaccardIndexContextTheoremsRanker implements ContextTheoremsRanker { public readonly needsUnwrappedNotations = true; - + rankContextTheorems( theorems: Theorem[], completionContext: CompletionContext diff --git "a/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" "b/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" index 02a0db60..0ec9c825 100644 --- "a/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" +++ "b/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" @@ -13,7 +13,7 @@ import { goalAsTheoremString } from "./tokenUtils"; */ export class CosineContextTheoremsRanker implements ContextTheoremsRanker { public readonly needsUnwrappedNotations = true; - + rankContextTheorems( theorems: Theorem[], completionContext: CompletionContext diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index e273e7a0..d29101b6 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -18,7 +18,7 @@ export async function inspectSourceFile( fileVersion: number, shouldCompleteHole: (hole: ProofStep) => boolean, fileUri: Uri, - client: CoqLspClientInterface, + client: CoqLspClientInterface, rankerNeedsInitialGoals: boolean = true ): Promise { const sourceFileEnvironment = await createSourceFileEnvironment( @@ -84,7 +84,11 @@ export async function createSourceFileEnvironment( client: CoqLspClientInterface, rankerNeedsInitialGoals: boolean = true ): Promise { - const fileTheorems = await parseCoqFile(fileUri, client, rankerNeedsInitialGoals); + const fileTheorems = await parseCoqFile( + fileUri, + client, + rankerNeedsInitialGoals + ); const fileText = readFileSync(fileUri.fsPath); const dirPath = getSourceFolderPath(fileUri); if (!dirPath) { diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index cb78ed6e..cbbde6e1 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -1,9 +1,7 @@ import { - ExtensionContext, - ProgressLocation, + ExtensionContext, // ProgressLocation, TextEditor, - commands, - window, + commands, // window, workspace, } from "vscode"; @@ -24,6 +22,7 @@ import { CoqProofChecker } from "../core/coqProofChecker"; import { inspectSourceFile } from "../core/inspectSourceFile"; import { ProofStep } from "../coqParser/parsedTypes"; +import { logExecutionTime } from "../logging/timeMeasureDecorator"; import { buildErrorCompleteLog } from "../utils/errorsUtils"; import { Uri } from "../utils/uri"; @@ -49,7 +48,7 @@ import { toVSCodeRange, } from "./positionRangeUtils"; import { SettingsValidationError } from "./settingsValidationError"; -import { logExecutionTime } from "../logging/timeMeasureDecorator"; +import { StatusBarButton } from "./statusBarButton"; export const pluginId = "coqpilot"; export const pluginName = "CoqPilot"; @@ -57,16 +56,16 @@ export const pluginName = "CoqPilot"; export class CoqPilot { private readonly globalExtensionState: GlobalExtensionState; private readonly vscodeExtensionContext: ExtensionContext; + private subscriptions: { dispose(): any }[] = []; private constructor( vscodeExtensionContext: ExtensionContext, - globalExtensionState: GlobalExtensionState + globalExtensionState: GlobalExtensionState, + private readonly statusBarButton: StatusBarButton ) { this.vscodeExtensionContext = vscodeExtensionContext; this.globalExtensionState = globalExtensionState; - console.log("CoqPilot extension is now active!"); - this.registerEditorCommand( "perform_completion_under_cursor", this.performCompletionUnderCursor.bind(this) @@ -83,9 +82,16 @@ export class CoqPilot { this.vscodeExtensionContext.subscriptions.push(this); } - static async create(vscodeExtensionContext: ExtensionContext) { + static async create( + vscodeExtensionContext: ExtensionContext, + statusBarItem: StatusBarButton + ) { const globalExtensionState = await GlobalExtensionState.create(); - return new CoqPilot(vscodeExtensionContext, globalExtensionState); + return new CoqPilot( + vscodeExtensionContext, + globalExtensionState, + statusBarItem + ); } async performCompletionUnderCursor(editor: TextEditor) { @@ -112,38 +118,31 @@ export class CoqPilot { shouldCompleteHole: (hole: ProofStep) => boolean, editor: TextEditor ) { - await window.withProgress( - { - location: ProgressLocation.Window, - title: `${pluginName}: In progress`, - }, - async () => { - try { - await this.performSpecificCompletions( - shouldCompleteHole, - editor - ); - } catch (e) { - if (e instanceof SettingsValidationError) { - e.showAsMessageToUser(); - } else if (e instanceof CoqLspStartupError) { - showMessageToUserWithSettingsHint( - EditorMessages.coqLspStartupFailure(e.path), - "error", - `${pluginId}.coqLspServerPath` - ); - } else { - showMessageToUser( - e instanceof Error - ? EditorMessages.errorOccurred(e.message) - : EditorMessages.objectWasThrownAsError(e), - "error" - ); - console.error(buildErrorCompleteLog(e)); - } - } + try { + this.statusBarButton.showSpinner(); + + await this.performSpecificCompletions(shouldCompleteHole, editor); + } catch (e) { + if (e instanceof SettingsValidationError) { + e.showAsMessageToUser(); + } else if (e instanceof CoqLspStartupError) { + showMessageToUserWithSettingsHint( + EditorMessages.coqLspStartupFailure(e.path), + "error", + `${pluginId}.coqLspServerPath` + ); + } else { + showMessageToUser( + e instanceof Error + ? EditorMessages.errorOccurred(e.message) + : EditorMessages.objectWasThrownAsError(e), + "error" + ); + console.error(buildErrorCompleteLog(e)); } - ); + } finally { + this.statusBarButton.hideSpinner(); + } } private async performSpecificCompletions( @@ -327,9 +326,13 @@ export class CoqPilot { fn ); this.vscodeExtensionContext.subscriptions.push(disposable); + this.subscriptions.push(disposable); } dispose(): void { + // This is not the same as vscodeExtensionContext.subscriptions + // As it doesn't contain the statusBar item and the toggle command + this.subscriptions.forEach((d) => d.dispose()); this.globalExtensionState.dispose(); } } diff --git a/src/extension/globalExtensionState.ts b/src/extension/globalExtensionState.ts index 7fb3ca5a..cbb8c197 100644 --- a/src/extension/globalExtensionState.ts +++ b/src/extension/globalExtensionState.ts @@ -95,9 +95,9 @@ export class GlobalExtensionState { } dispose(): void { + this.coqLspClient.dispose(); disposeServices(this.llmServices); this.logWriter.dispose(); - this.coqLspClient.dispose(); fs.rmSync(this.llmServicesLogsDir, { recursive: true, force: true }); this.logOutputChannel.dispose(); } diff --git a/src/extension/statusBarButton.ts b/src/extension/statusBarButton.ts new file mode 100644 index 00000000..ae76c4ab --- /dev/null +++ b/src/extension/statusBarButton.ts @@ -0,0 +1,74 @@ +import { + ExtensionContext, + StatusBarAlignment, + StatusBarItem, + commands, + window, +} from "vscode"; + +import { pluginId, pluginName } from "./coqPilot"; + +export class StatusBarButton { + private statusBarItem: StatusBarItem; + private isActive: boolean; + private context: ExtensionContext; + private toggleCallback: ( + isActive: boolean, + context: ExtensionContext + ) => void; + + constructor( + context: ExtensionContext, + toggleCallback: (isActive: boolean, context: ExtensionContext) => void + ) { + this.context = context; + this.isActive = false; + this.toggleCallback = toggleCallback; + + this.statusBarItem = window.createStatusBarItem( + StatusBarAlignment.Left, + 0 + ); + this.updateStatusBar(); + + this.statusBarItem.show(); + this.context.subscriptions.push(this.statusBarItem); + + const command = `${pluginId}.toggleExtension`; + this.statusBarItem.command = command; + const toggleCommand = commands.registerCommand( + command, + this.toggle.bind(this) + ); + this.context.subscriptions.push(toggleCommand); + } + + toggle() { + this.isActive = !this.isActive; + this.updateStatusBar(); + this.toggleCallback(this.isActive, this.context); + } + + private updateStatusBar() { + if (this.isActive) { + this.statusBarItem.text = `$(debug-stop) ${pluginName}: Running`; + this.statusBarItem.tooltip = "Click to stop the extension"; + } else { + this.statusBarItem.text = `$(debug-stop) ${pluginName}: Stopped`; + this.statusBarItem.tooltip = "Click to start the extension"; + } + } + + public showSpinner() { + this.statusBarItem.text = `$(sync~spin) ${pluginName}: In progress`; + this.statusBarItem.tooltip = "Operation in progress..."; + } + + public hideSpinner() { + this.updateStatusBar(); + } + + public dispose() { + this.statusBarItem.dispose(); + } +} diff --git a/src/logging/timeMeasureDecorator.ts b/src/logging/timeMeasureDecorator.ts index ab591037..a1041ad6 100644 --- a/src/logging/timeMeasureDecorator.ts +++ b/src/logging/timeMeasureDecorator.ts @@ -1,7 +1,7 @@ /** - * A decorator that logs the execution time of a method. + * A decorator that logs the execution time of a method. * Execution time is logged into console in milliseconds. - * + * * (Note: typescript supports decorators only for class methods). */ export function logExecutionTime( @@ -42,4 +42,4 @@ export function logExecutionTime( }; return descriptor; -} \ No newline at end of file +} diff --git a/src/mainNode.ts b/src/mainNode.ts index 4a931eb5..4fb40639 100644 --- a/src/mainNode.ts +++ b/src/mainNode.ts @@ -1,14 +1,38 @@ import { ExtensionContext } from "vscode"; import { CoqPilot } from "./extension/coqPilot"; +import { StatusBarButton } from "./extension/statusBarButton"; export let extension: CoqPilot | undefined; +let statusBar: StatusBarButton | undefined = undefined; export async function activate(context: ExtensionContext): Promise { - extension = await CoqPilot.create(context); + statusBar = new StatusBarButton(context, toggleExtension); + statusBar.toggle(); +} + +async function startExtension(context: ExtensionContext) { + if (!statusBar) { + statusBar = new StatusBarButton(context, toggleExtension); + } + extension = await CoqPilot.create(context, statusBar); context.subscriptions.push(extension); } -export function deactivate() { +function stopExtension() { extension?.dispose(); + extension = undefined; +} + +function toggleExtension(isActive: boolean, context: ExtensionContext) { + if (isActive) { + startExtension(context); + } else { + stopExtension(); + } +} + +export function deactivate() { + stopExtension(); + statusBar?.dispose(); } From b2c18bf99963f74c854689440b119a650e1a6bca Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Sun, 20 Oct 2024 17:07:32 +0200 Subject: [PATCH 12/30] Add refactor TODO to --- src/extension/statusBarButton.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/extension/statusBarButton.ts b/src/extension/statusBarButton.ts index ae76c4ab..e2ba3eae 100644 --- a/src/extension/statusBarButton.ts +++ b/src/extension/statusBarButton.ts @@ -8,6 +8,7 @@ import { import { pluginId, pluginName } from "./coqPilot"; +// TODO: [LspCoreRefactor] Looks a bit dirty, refactor export class StatusBarButton { private statusBarItem: StatusBarItem; private isActive: boolean; From 3f44690e48c9c9fba236923b28e7c7a37ecfde52 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Fri, 25 Oct 2024 18:49:18 +0200 Subject: [PATCH 13/30] Sketch aborting the completion generation through `AbortSignal` --- .../implementation/parseCoqProject.ts | 4 +- src/coqParser/parseCoqFile.ts | 6 +++ src/core/completionGenerator.ts | 3 ++ src/core/inspectSourceFile.ts | 14 ++++-- src/extension/coqPilot.ts | 48 +++++++++++++++---- src/extension/editorMessages.ts | 3 ++ src/extension/extensionAbortUtils.ts | 14 ++++++ src/test/commonTestFunctions/coqFileParser.ts | 3 +- .../commonTestFunctions/prepareEnvironment.ts | 4 +- src/test/core/completionGenerator.test.ts | 4 +- .../legacyBenchmark/benchmarkingFramework.ts | 6 ++- 11 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 src/extension/extensionAbortUtils.ts diff --git a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts index c3483fff..14cf7bd9 100644 --- a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts +++ b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts @@ -77,10 +77,12 @@ export namespace ParseCoqProjectImpl { const mockFileVersion = 1; const sourceFileUri = Uri.fromPath(filePath); await coqLspClient.openTextDocument(sourceFileUri); + const abortController = new AbortController(); const sourceFileEnvironment = await createSourceFileEnvironment( mockFileVersion, sourceFileUri, - coqLspClient + coqLspClient, + abortController.signal ); const serializedParsedFile: SerializedParsedCoqFile = { serializedTheoremsByNames: packIntoMappedObject( diff --git a/src/coqParser/parseCoqFile.ts b/src/coqParser/parseCoqFile.ts index 298fad0b..5cd95f1f 100644 --- a/src/coqParser/parseCoqFile.ts +++ b/src/coqParser/parseCoqFile.ts @@ -14,6 +14,7 @@ import { import { Uri } from "../utils/uri"; import { ProofStep, Theorem, TheoremProof, Vernacexpr } from "./parsedTypes"; +import { throwOnAbort } from "../extension/extensionAbortUtils"; /** * TODO: [LspCoreRefactor] Refactor retrieveInitialGoal param @@ -22,6 +23,7 @@ import { ProofStep, Theorem, TheoremProof, Vernacexpr } from "./parsedTypes"; export async function parseCoqFile( uri: Uri, client: CoqLspClientInterface, + abortSignal: AbortSignal, retrieveInitialGoal: boolean = true ): Promise { return client @@ -35,6 +37,7 @@ export async function parseCoqFile( documentText, client, uri, + abortSignal, retrieveInitialGoal ); }) @@ -50,6 +53,7 @@ async function parseFlecheDocument( textLines: string[], client: CoqLspClientInterface, uri: Uri, + abortSignal: AbortSignal, retrieveInitialGoal: boolean ): Promise { if (doc === null) { @@ -58,6 +62,8 @@ async function parseFlecheDocument( const theorems: Theorem[] = []; for (let i = 0; i < doc.spans.length; i++) { + throwOnAbort(abortSignal); + const span = doc.spans[i]; try { const vernacType = getVernacexpr(getExpr(span)); diff --git a/src/core/completionGenerator.ts b/src/core/completionGenerator.ts index f64c4acd..412a321f 100644 --- a/src/core/completionGenerator.ts +++ b/src/core/completionGenerator.ts @@ -20,6 +20,7 @@ import { buildProofGenerationContext, prepareProofToCheck, } from "./exposedCompletionGeneratorUtils"; +import { throwOnAbort } from "../extension/extensionAbortUtils"; export interface GenerationResult {} @@ -47,6 +48,7 @@ export async function generateCompletion( completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, processEnvironment: ProcessEnvironment, + abortSignal: AbortSignal, logOutputChannel?: OutputChannel, eventLogger?: EventLogger, workspaceRootPath?: string, @@ -77,6 +79,7 @@ export async function generateCompletion( let newlyGeneratedProofs: GeneratedProof[] = []; for await (const generatedProofsBatch of iterator) { + throwOnAbort(abortSignal); newlyGeneratedProofs.push(...generatedProofsBatch); eventLogger?.log( "core-new-proofs-ready-for-checking", diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index d29101b6..89616a66 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -11,6 +11,7 @@ import { CompletionContext, SourceFileEnvironment, } from "./completionGenerationContext"; +import { throwOnAbort } from "../extension/extensionAbortUtils"; type AnalyzedFile = [CompletionContext[], SourceFileEnvironment]; @@ -19,20 +20,23 @@ export async function inspectSourceFile( shouldCompleteHole: (hole: ProofStep) => boolean, fileUri: Uri, client: CoqLspClientInterface, + abortSignal: AbortSignal, rankerNeedsInitialGoals: boolean = true ): Promise { const sourceFileEnvironment = await createSourceFileEnvironment( fileVersion, fileUri, client, - rankerNeedsInitialGoals + abortSignal, + rankerNeedsInitialGoals, ); const completionContexts = await createCompletionContexts( fileVersion, shouldCompleteHole, sourceFileEnvironment.fileTheorems, fileUri, - client + client, + abortSignal ); const sourceFileEnvironmentWithCompleteProofs: SourceFileEnvironment = { ...sourceFileEnvironment, @@ -49,7 +53,8 @@ async function createCompletionContexts( shouldCompleteHole: (hole: ProofStep) => boolean, fileTheorems: Theorem[], fileUri: Uri, - client: CoqLspClientInterface + client: CoqLspClientInterface, + abortSignal: AbortSignal ): Promise { const holesToComplete = fileTheorems .filter((thr) => thr.proof) @@ -59,6 +64,7 @@ async function createCompletionContexts( let completionContexts: CompletionContext[] = []; for (const hole of holesToComplete) { + throwOnAbort(abortSignal); const goals = await client.getGoalsAtPoint( hole.range.start, fileUri, @@ -82,11 +88,13 @@ export async function createSourceFileEnvironment( fileVersion: number, fileUri: Uri, client: CoqLspClientInterface, + abortSignal: AbortSignal, rankerNeedsInitialGoals: boolean = true ): Promise { const fileTheorems = await parseCoqFile( fileUri, client, + abortSignal, rankerNeedsInitialGoals ); const fileText = readFileSync(fileUri.fsPath); diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index cbbde6e1..8f98a941 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -49,6 +49,7 @@ import { } from "./positionRangeUtils"; import { SettingsValidationError } from "./settingsValidationError"; import { StatusBarButton } from "./statusBarButton"; +import { CompletionAbortError, throwOnAbort } from "./extensionAbortUtils"; export const pluginId = "coqpilot"; export const pluginName = "CoqPilot"; @@ -57,6 +58,7 @@ export class CoqPilot { private readonly globalExtensionState: GlobalExtensionState; private readonly vscodeExtensionContext: ExtensionContext; private subscriptions: { dispose(): any }[] = []; + private abortController = new AbortController(); private constructor( vscodeExtensionContext: ExtensionContext, @@ -78,6 +80,10 @@ export class CoqPilot { "perform_completion_for_all_admits", this.performCompletionForAllAdmits.bind(this) ); + this.registerEditorCommand( + "toggle_current_session", + this.performCompletionForAllAdmits.bind(this) + ); this.vscodeExtensionContext.subscriptions.push(this); } @@ -98,7 +104,8 @@ export class CoqPilot { const cursorPosition = editor.selection.active; this.performSpecificCompletionsWithProgress( (hole) => positionInRange(cursorPosition, hole.range), - editor + editor, + this.abortController.signal ); } @@ -106,22 +113,33 @@ export class CoqPilot { const selection = editor.selection; this.performSpecificCompletionsWithProgress( (hole) => selection.contains(toVSCodePosition(hole.range.start)), - editor + editor, + this.abortController.signal ); } async performCompletionForAllAdmits(editor: TextEditor) { - this.performSpecificCompletionsWithProgress((_hole) => true, editor); + this.performSpecificCompletionsWithProgress( + (_hole) => true, + editor, + this.abortController.signal + ); + } + + async toggleCurrentSession() { + console.log("Toggle current session"); + this.abortController.abort(new CompletionAbortError()); } private async performSpecificCompletionsWithProgress( shouldCompleteHole: (hole: ProofStep) => boolean, - editor: TextEditor + editor: TextEditor, + abortSignal: AbortSignal ) { try { this.statusBarButton.showSpinner(); - await this.performSpecificCompletions(shouldCompleteHole, editor); + await this.performSpecificCompletions(shouldCompleteHole, editor, abortSignal); } catch (e) { if (e instanceof SettingsValidationError) { e.showAsMessageToUser(); @@ -131,6 +149,8 @@ export class CoqPilot { "error", `${pluginId}.coqLspServerPath` ); + } else if (e instanceof CompletionAbortError) { + showMessageToUser(EditorMessages.completionAborted, "info"); } else { showMessageToUser( e instanceof Error @@ -147,7 +167,8 @@ export class CoqPilot { private async performSpecificCompletions( shouldCompleteHole: (hole: ProofStep) => boolean, - editor: TextEditor + editor: TextEditor, + abortSignal: AbortSignal ) { this.globalExtensionState.eventLogger.log( "completion-started", @@ -166,7 +187,8 @@ export class CoqPilot { await this.prepareForCompletions( shouldCompleteHole, editor.document.version, - editor.document.uri.fsPath + editor.document.uri.fsPath, + abortSignal ); this.globalExtensionState.eventLogger.log( "completion-preparation-finished", @@ -191,7 +213,8 @@ export class CoqPilot { completionContext, sourceFileEnvironment, processEnvironment, - editor + editor, + abortSignal ); } ); @@ -207,12 +230,15 @@ export class CoqPilot { completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, processEnvironment: ProcessEnvironment, - editor: TextEditor + editor: TextEditor, + abortSignal: AbortSignal ) { + throwOnAbort(abortSignal); const result = await generateCompletion( completionContext, sourceFileEnvironment, processEnvironment, + abortSignal, this.globalExtensionState.logOutputChannel, this.globalExtensionState.eventLogger ); @@ -272,7 +298,8 @@ export class CoqPilot { private async prepareForCompletions( shouldCompleteHole: (hole: ProofStep) => boolean, fileVersion: number, - filePath: string + filePath: string, + abortSignal: AbortSignal ): Promise< [CompletionContext[], SourceFileEnvironment, ProcessEnvironment] > { @@ -302,6 +329,7 @@ export class CoqPilot { shouldCompleteHole, fileUri, this.globalExtensionState.coqLspClient, + abortSignal, contextTheoremsRanker.needsUnwrappedNotations ); const processEnvironment: ProcessEnvironment = { diff --git a/src/extension/editorMessages.ts b/src/extension/editorMessages.ts index 902d69ed..4d177bf9 100644 --- a/src/extension/editorMessages.ts +++ b/src/extension/editorMessages.ts @@ -31,6 +31,9 @@ export namespace EditorMessages { export const reportUnexpectedError = (errorDescription: string) => `Coqpilot got an unexpected error: ${errorDescription}. Please report this crash by opening an issue in the Coqpilot GitHub repository.`; + export const completionAborted = + "Completion generation was forcefully aborted, Coq-LSP server stopping. Please hit the Status Bar button again to restart the server and proceed with completions."; + export const objectWasThrownAsError = (e: any) => reportUnexpectedError( `object was thrown as error, ${stringifyAnyValue(e)}` diff --git a/src/extension/extensionAbortUtils.ts b/src/extension/extensionAbortUtils.ts new file mode 100644 index 00000000..c3fef01f --- /dev/null +++ b/src/extension/extensionAbortUtils.ts @@ -0,0 +1,14 @@ +export class CompletionAbortError extends Error { + static readonly abortMessage = + "User has triggered a sesion abort: Stopping all completions"; + + constructor() { + super(CompletionAbortError.abortMessage); + } +} + +export function throwOnAbort(abortSignal: AbortSignal) { + if (abortSignal.aborted) { + throw abortSignal.reason; + } +} \ No newline at end of file diff --git a/src/test/commonTestFunctions/coqFileParser.ts b/src/test/commonTestFunctions/coqFileParser.ts index 9648f459..8dd79200 100644 --- a/src/test/commonTestFunctions/coqFileParser.ts +++ b/src/test/commonTestFunctions/coqFileParser.ts @@ -19,7 +19,8 @@ export async function parseTheoremsFromCoqFile( const client = await createTestCoqLspClient(rootDir); await client.openTextDocument(fileUri); - const document = await parseCoqFile(fileUri, client); + const abortController = new AbortController(); + const document = await parseCoqFile(fileUri, client, abortController.signal); await client.closeTextDocument(fileUri); return document; diff --git a/src/test/commonTestFunctions/prepareEnvironment.ts b/src/test/commonTestFunctions/prepareEnvironment.ts index b47a2246..ef029e83 100644 --- a/src/test/commonTestFunctions/prepareEnvironment.ts +++ b/src/test/commonTestFunctions/prepareEnvironment.ts @@ -38,11 +38,13 @@ export async function prepareEnvironment( const coqProofChecker = new CoqProofChecker(client); await client.openTextDocument(fileUri); + const abortController = new AbortController(); const [completionContexts, sourceFileEnvironment] = await inspectSourceFile( 1, (_hole) => true, fileUri, - client + client, + abortController.signal ); await client.closeTextDocument(fileUri); diff --git a/src/test/core/completionGenerator.test.ts b/src/test/core/completionGenerator.test.ts index 3abafcf5..5c963dc7 100644 --- a/src/test/core/completionGenerator.test.ts +++ b/src/test/core/completionGenerator.test.ts @@ -32,6 +32,7 @@ suite("Completion generation tests", () => { modelsParams: createPredefinedProofsModelsParams(predefinedProofs), services: createDefaultServices(), }; + const abortController = new AbortController(); try { await environment.coqLspClient.openTextDocument( environment.sourceFileEnvironment.fileUri @@ -43,7 +44,8 @@ suite("Completion generation tests", () => { const result = await generateCompletion( completionContext, environment.sourceFileEnvironment, - processEnvironment + processEnvironment, + abortController.signal ); return result; } diff --git a/src/test/legacyBenchmark/benchmarkingFramework.ts b/src/test/legacyBenchmark/benchmarkingFramework.ts index 8ad7bd07..7862e68b 100644 --- a/src/test/legacyBenchmark/benchmarkingFramework.ts +++ b/src/test/legacyBenchmark/benchmarkingFramework.ts @@ -271,10 +271,12 @@ async function benchmarkCompletionGeneration( premisesNumber: maxPremisesNumber, }; + const abortController = new AbortController(); const result = await generateCompletion( completionContext, sourceFileEnvironmentWithFilteredContext, processEnvironmentWithPremisesNumber, + abortController.signal, undefined, undefined, workspaceRootPath, @@ -429,10 +431,12 @@ async function extractCompletionTargets( fileUri: Uri, client: CoqLspClientInterface ): Promise<[BenchmarkingCompletionTargets, SourceFileEnvironment]> { + const abortController = new AbortController(); const sourceFileEnvironment = await createSourceFileEnvironment( fileVersion, fileUri, - client + client, + abortController.signal ); const completionTargets = await createCompletionTargets( fileVersion, From 9689e857a1e47b1e25a7ec044c74cd8ac2fc4739 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Fri, 25 Oct 2024 19:28:18 +0200 Subject: [PATCH 14/30] Refactor `SourceFileEnvironment`: remove dirPath and rename fileVersion --- .../implementation/proofsCheckerUtils.ts | 2 +- .../parseDataset/cacheHandlers/cacheReader.ts | 2 +- .../cacheHandlers/cacheUpdater.ts | 8 ++--- .../parseDataset/cacheHandlers/cacheWriter.ts | 2 +- .../cacheStructures/cacheHolders.ts | 12 +++---- .../cacheStructures/cacheModels.ts | 6 ++-- .../implementation/parseCoqProject.ts | 8 ++--- .../parsedCoqFile/parsedCoqFileData.ts | 16 ++++------ src/core/completionGenerationContext.ts | 5 +-- src/core/completionGenerator.ts | 2 +- src/core/inspectSourceFile.ts | 32 +++++-------------- src/extension/coqPilot.ts | 4 +-- src/test/commonTestFunctions/checkProofs.ts | 2 +- .../legacyBenchmark/benchmarkingFramework.ts | 20 ++++++------ .../cached_auto_benchmark.json | 2 +- 15 files changed, 51 insertions(+), 72 deletions(-) diff --git a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/proofsCheckerUtils.ts b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/proofsCheckerUtils.ts index 0953a020..2af4eb88 100644 --- a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/proofsCheckerUtils.ts +++ b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/proofsCheckerUtils.ts @@ -28,7 +28,7 @@ export namespace ProofsCheckerUtils { ? undefined : workspaceRoot.directoryPath, fileUri: sourceFileEnvironment.fileUri.toString(), - documentVersion: sourceFileEnvironment.fileVersion, + documentVersion: sourceFileEnvironment.documentVersion, checkAtPosition: completionContext.admitRange.start, preparedProofs: preparedProofs, }; diff --git a/src/benchmark/framework/parseDataset/cacheHandlers/cacheReader.ts b/src/benchmark/framework/parseDataset/cacheHandlers/cacheReader.ts index 8a04ecf1..348f3551 100644 --- a/src/benchmark/framework/parseDataset/cacheHandlers/cacheReader.ts +++ b/src/benchmark/framework/parseDataset/cacheHandlers/cacheReader.ts @@ -151,7 +151,7 @@ namespace BuildCacheHoldersFromModels { theorems, readCachedFile.filePathRelativeToWorkspace, readCachedFile.fileLines, - readCachedFile.fileVersion, + readCachedFile.documentVersion, workspacePath ); } diff --git a/src/benchmark/framework/parseDataset/cacheHandlers/cacheUpdater.ts b/src/benchmark/framework/parseDataset/cacheHandlers/cacheUpdater.ts index e8558af2..34c62811 100644 --- a/src/benchmark/framework/parseDataset/cacheHandlers/cacheUpdater.ts +++ b/src/benchmark/framework/parseDataset/cacheHandlers/cacheUpdater.ts @@ -77,13 +77,13 @@ namespace UpdateCacheHolders { ); } - if (cachedFile.getFileVersion() !== parsedFile.fileVersion) { + if (cachedFile.getDocumentVersion() !== parsedFile.documentVersion) { cacheUpdaterLogger.debug( - `* file version update: ${cachedFile.getFileVersion()} -> ${parsedFile.fileVersion}` + `* file version update: ${cachedFile.getDocumentVersion()} -> ${parsedFile.documentVersion}` ); } cachedFile.updateFileLines(parsedFile.fileLines); - cachedFile.updateFileVersion(parsedFile.fileVersion); + cachedFile.updateDocumentVersion(parsedFile.documentVersion); for (const fileTarget of parsedFileHolder.targets()) { let cachedTheorem = cachedFile.getCachedTheorem( @@ -162,7 +162,7 @@ namespace UpdateCacheHolders { cachedTheoremsMap, relativizeAbsolutePaths(workspacePath, parsedFile.filePath), parsedFile.fileLines, - parsedFile.fileVersion, + parsedFile.documentVersion, workspacePath ); } diff --git a/src/benchmark/framework/parseDataset/cacheHandlers/cacheWriter.ts b/src/benchmark/framework/parseDataset/cacheHandlers/cacheWriter.ts index a03380cc..672c35b7 100644 --- a/src/benchmark/framework/parseDataset/cacheHandlers/cacheWriter.ts +++ b/src/benchmark/framework/parseDataset/cacheHandlers/cacheWriter.ts @@ -120,7 +120,7 @@ namespace SerializeCacheHolders { ) ), fileLines: cachedCoqFileData.getFileLines(), - fileVersion: cachedCoqFileData.getFileVersion(), + documentVersion: cachedCoqFileData.getDocumentVersion(), filePathRelativeToWorkspace: cachedCoqFileData.filePathRelativeToWorkspace, }; diff --git a/src/benchmark/framework/parseDataset/cacheStructures/cacheHolders.ts b/src/benchmark/framework/parseDataset/cacheStructures/cacheHolders.ts index 9ec2bc3f..9cc08608 100644 --- a/src/benchmark/framework/parseDataset/cacheStructures/cacheHolders.ts +++ b/src/benchmark/framework/parseDataset/cacheStructures/cacheHolders.ts @@ -92,7 +92,7 @@ export namespace CacheHolderData { private readonly theorems: Map, readonly filePathRelativeToWorkspace: string, private fileLines: string[], - private fileVersion: number, + private documentVersion: number, readonly workspacePath: string ) {} @@ -108,8 +108,8 @@ export namespace CacheHolderData { return this.fileLines; } - getFileVersion(): number { - return this.fileVersion; + getDocumentVersion(): number { + return this.documentVersion; } addCachedTheorem(cachedTheorem: CachedTheoremData) { @@ -120,8 +120,8 @@ export namespace CacheHolderData { this.fileLines = fileLines; } - updateFileVersion(fileVersion: number) { - this.fileVersion = fileVersion; + updateDocumentVersion(documentVersion: number) { + this.documentVersion = documentVersion; } restoreParsedCoqFileData(): ParsedCoqFileData { @@ -132,7 +132,7 @@ export namespace CacheHolderData { cachedTheorem.theoremData ), this.fileLines, - this.fileVersion, + this.documentVersion, joinPaths(this.workspacePath, this.filePathRelativeToWorkspace) ); } diff --git a/src/benchmark/framework/parseDataset/cacheStructures/cacheModels.ts b/src/benchmark/framework/parseDataset/cacheStructures/cacheModels.ts index f7aecfba..d84e2bcd 100644 --- a/src/benchmark/framework/parseDataset/cacheStructures/cacheModels.ts +++ b/src/benchmark/framework/parseDataset/cacheStructures/cacheModels.ts @@ -23,7 +23,7 @@ export namespace DatasetCacheModels { allFileTheorems: CachedTheoremsByNames; fileLines: string[]; - fileVersion: number; + documentVersion: number; filePathRelativeToWorkspace: string; } @@ -101,7 +101,7 @@ export namespace DatasetCacheModels { type: "string", }, }, - fileVersion: { + documentVersion: { type: "number", }, filePathRelativeToWorkspace: { @@ -111,7 +111,7 @@ export namespace DatasetCacheModels { required: [ "allFileTheorems", "fileLines", - "fileVersion", + "documentVersion", "filePathRelativeToWorkspace", ], additionalProperties: false, diff --git a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts index c3483fff..616b4713 100644 --- a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts +++ b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts @@ -74,11 +74,11 @@ export namespace ParseCoqProjectImpl { coqLspClient: CoqLspClientInterface, logger: Logger ): Promise { - const mockFileVersion = 1; + const mockDocumentVersion = 1; const sourceFileUri = Uri.fromPath(filePath); await coqLspClient.openTextDocument(sourceFileUri); const sourceFileEnvironment = await createSourceFileEnvironment( - mockFileVersion, + mockDocumentVersion, sourceFileUri, coqLspClient ); @@ -94,7 +94,7 @@ export namespace ParseCoqProjectImpl { (serializedTheorem) => serializedTheorem ), fileLines: sourceFileEnvironment.fileLines, - fileVersion: sourceFileEnvironment.fileVersion, + documentVersion: sourceFileEnvironment.documentVersion, filePath: filePath, }; const foundTheoremsLog = `found ${Object.keys(serializedParsedFile.serializedTheoremsByNames).length} theorem(s)`; @@ -211,7 +211,7 @@ export namespace ParseCoqProjectImpl { const goals = await coqLspClient.getGoalsAtPoint( proofStep.range.start, Uri.fromPath(serializedParsedFile.filePath), - serializedParsedFile.fileVersion + serializedParsedFile.documentVersion ); const startPosition = deserializeCodeElementPosition( proofStep.range.start diff --git a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts index f378c4e5..c49853cc 100644 --- a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts +++ b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts @@ -9,7 +9,6 @@ import { mapValues, toMappedObject, } from "../../utils/collectionUtils/mapUtils"; -import { getDirectoryPath } from "../../utils/fileUtils/fs"; import { SerializedTheorem, @@ -27,7 +26,7 @@ export class ParsedCoqFileData { */ readonly theoremsByNames: Map, readonly fileLines: string[], - readonly fileVersion: number, + readonly documentVersion: number, readonly filePath: string ) {} @@ -47,8 +46,7 @@ export class ParsedCoqFileData { (theorem) => theorem.proof && !theorem.proof.is_incomplete ), fileLines: this.fileLines, - fileVersion: this.fileVersion, - dirPath: getDirectoryPath(this.filePath), + documentVersion: this.documentVersion, fileUri: Uri.fromPath(this.filePath), }; } @@ -62,7 +60,7 @@ export interface SerializedParsedCoqFile { serializedTheoremsByNames: SerializedTheoremsByNames; fileLines: string[]; - fileVersion: number; + documentVersion: number; filePath: string; } @@ -92,7 +90,7 @@ export const serializedParsedCoqFileSchema: JSONSchemaType boolean, fileUri: Uri, client: CoqLspClientInterface, rankerNeedsInitialGoals: boolean = true ): Promise { const sourceFileEnvironment = await createSourceFileEnvironment( - fileVersion, + documentVersion, fileUri, client, rankerNeedsInitialGoals ); const completionContexts = await createCompletionContexts( - fileVersion, + documentVersion, shouldCompleteHole, sourceFileEnvironment.fileTheorems, fileUri, @@ -45,7 +44,7 @@ export async function inspectSourceFile( } async function createCompletionContexts( - fileVersion: number, + documentVersion: number, shouldCompleteHole: (hole: ProofStep) => boolean, fileTheorems: Theorem[], fileUri: Uri, @@ -62,7 +61,7 @@ async function createCompletionContexts( const goals = await client.getGoalsAtPoint( hole.range.start, fileUri, - fileVersion + documentVersion ); if (goals.ok) { const firstGoal = goals.val.shift(); @@ -79,7 +78,7 @@ async function createCompletionContexts( } export async function createSourceFileEnvironment( - fileVersion: number, + documentVersion: number, fileUri: Uri, client: CoqLspClientInterface, rankerNeedsInitialGoals: boolean = true @@ -90,26 +89,11 @@ export async function createSourceFileEnvironment( rankerNeedsInitialGoals ); const fileText = readFileSync(fileUri.fsPath); - const dirPath = getSourceFolderPath(fileUri); - if (!dirPath) { - throw Error( - `unable to get source folder path from \`fileUri\`: ${fileUri}` - ); - } return { fileTheorems: fileTheorems, fileLines: fileText.toString().split("\n"), - fileVersion: fileVersion, - dirPath: dirPath, + documentVersion: documentVersion, fileUri: fileUri, }; -} - -function getSourceFolderPath(documentUri: Uri): string | undefined { - try { - return path.dirname(documentUri.fsPath); - } catch (error) { - return undefined; - } -} +} \ No newline at end of file diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index cbbde6e1..961c0c10 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -271,7 +271,7 @@ export class CoqPilot { @logExecutionTime private async prepareForCompletions( shouldCompleteHole: (hole: ProofStep) => boolean, - fileVersion: number, + documentVersion: number, filePath: string ): Promise< [CompletionContext[], SourceFileEnvironment, ProcessEnvironment] @@ -298,7 +298,7 @@ export class CoqPilot { // TODO: [LspCoreRefactor] Unclear double dependency on Coq-LSP client. const [completionContexts, sourceFileEnvironment] = await inspectSourceFile( - fileVersion, + documentVersion, shouldCompleteHole, fileUri, this.globalExtensionState.coqLspClient, diff --git a/src/test/commonTestFunctions/checkProofs.ts b/src/test/commonTestFunctions/checkProofs.ts index 6291556a..bf9cace7 100644 --- a/src/test/commonTestFunctions/checkProofs.ts +++ b/src/test/commonTestFunctions/checkProofs.ts @@ -13,7 +13,7 @@ export async function checkProofs( ): Promise { return await environment.coqProofChecker.checkProofs( environment.sourceFileEnvironment.fileUri, - environment.sourceFileEnvironment.fileVersion, + environment.sourceFileEnvironment.documentVersion, completionContext.admitRange.start, proofsToCheck ); diff --git a/src/test/legacyBenchmark/benchmarkingFramework.ts b/src/test/legacyBenchmark/benchmarkingFramework.ts index 8ad7bd07..d7bf0f6f 100644 --- a/src/test/legacyBenchmark/benchmarkingFramework.ts +++ b/src/test/legacyBenchmark/benchmarkingFramework.ts @@ -393,10 +393,10 @@ async function prepareForBenchmarkCompletions( await client.openTextDocument(fileUri); const coqProofChecker = new CoqProofChecker(client); - const mockFileVersion = 1; + const mockDocumentVersion = 1; const [completionTargets, sourceFileEnvironment] = await extractCompletionTargets( - mockFileVersion, + mockDocumentVersion, shouldCompleteHole, fileUri, client @@ -424,18 +424,18 @@ async function prepareForBenchmarkCompletions( } async function extractCompletionTargets( - fileVersion: number, + documentVersion: number, shouldCompleteHole: (hole: ProofStep) => boolean, fileUri: Uri, client: CoqLspClientInterface ): Promise<[BenchmarkingCompletionTargets, SourceFileEnvironment]> { const sourceFileEnvironment = await createSourceFileEnvironment( - fileVersion, + documentVersion, fileUri, client ); const completionTargets = await createCompletionTargets( - fileVersion, + documentVersion, shouldCompleteHole, sourceFileEnvironment.fileTheorems, fileUri, @@ -457,7 +457,7 @@ interface ParentedProofStep { } async function createCompletionTargets( - fileVersion: number, + documentVersion: number, shouldCompleteHole: (hole: ProofStep) => boolean, fileTheorems: Theorem[], fileUri: Uri, @@ -487,13 +487,13 @@ async function createCompletionTargets( return { admitTargets: await resolveProofStepsToCompletionContexts( admitHolesToComplete, - fileVersion, + documentVersion, fileUri, client ), theoremTargets: await resolveProofStepsToCompletionContexts( firstProofSteps, - fileVersion, + documentVersion, fileUri, client ), @@ -502,7 +502,7 @@ async function createCompletionTargets( async function resolveProofStepsToCompletionContexts( parentedProofSteps: ParentedProofStep[], - fileVersion: number, + documentVersion: number, fileUri: Uri, client: CoqLspClientInterface ): Promise { @@ -511,7 +511,7 @@ async function resolveProofStepsToCompletionContexts( const goals = await client.getGoalsAtPoint( parentedProofStep.proofStep.range.start, fileUri, - fileVersion + documentVersion ); if (goals.ok) { const firstGoal = goals.val.shift(); diff --git a/src/test/resources/benchmarking/singleTaskRunnerExampleInput/cached_auto_benchmark.json b/src/test/resources/benchmarking/singleTaskRunnerExampleInput/cached_auto_benchmark.json index d99b27bc..dbf52499 100644 --- a/src/test/resources/benchmarking/singleTaskRunnerExampleInput/cached_auto_benchmark.json +++ b/src/test/resources/benchmarking/singleTaskRunnerExampleInput/cached_auto_benchmark.json @@ -136,6 +136,6 @@ "Admitted. *)", "" ], - "fileVersion": 1, + "documentVersion": 1, "filePath": "auto_benchmark.v" } \ No newline at end of file From 095b99b806b6516057beb7779346e65853f11799 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Fri, 25 Oct 2024 19:36:22 +0200 Subject: [PATCH 15/30] Refactor `SourceFileEnvironment`: remove `fileLines` --- .../parseDataset/cacheHandlers/cacheReader.ts | 1 - .../cacheHandlers/cacheUpdater.ts | 2 - .../parseDataset/cacheHandlers/cacheWriter.ts | 1 - .../cacheStructures/cacheHolders.ts | 10 ---- .../cacheStructures/cacheModels.ts | 9 ---- .../implementation/parseCoqProject.ts | 4 +- .../parsedCoqFile/parsedCoqFileData.ts | 13 ----- src/core/completionGenerationContext.ts | 3 -- src/core/inspectSourceFile.ts | 4 -- .../cached_auto_benchmark.json | 48 ------------------- 10 files changed, 1 insertion(+), 94 deletions(-) diff --git a/src/benchmark/framework/parseDataset/cacheHandlers/cacheReader.ts b/src/benchmark/framework/parseDataset/cacheHandlers/cacheReader.ts index 348f3551..73182765 100644 --- a/src/benchmark/framework/parseDataset/cacheHandlers/cacheReader.ts +++ b/src/benchmark/framework/parseDataset/cacheHandlers/cacheReader.ts @@ -150,7 +150,6 @@ namespace BuildCacheHoldersFromModels { return new CacheHolderData.CachedCoqFileData( theorems, readCachedFile.filePathRelativeToWorkspace, - readCachedFile.fileLines, readCachedFile.documentVersion, workspacePath ); diff --git a/src/benchmark/framework/parseDataset/cacheHandlers/cacheUpdater.ts b/src/benchmark/framework/parseDataset/cacheHandlers/cacheUpdater.ts index 34c62811..76c9730b 100644 --- a/src/benchmark/framework/parseDataset/cacheHandlers/cacheUpdater.ts +++ b/src/benchmark/framework/parseDataset/cacheHandlers/cacheUpdater.ts @@ -82,7 +82,6 @@ namespace UpdateCacheHolders { `* file version update: ${cachedFile.getDocumentVersion()} -> ${parsedFile.documentVersion}` ); } - cachedFile.updateFileLines(parsedFile.fileLines); cachedFile.updateDocumentVersion(parsedFile.documentVersion); for (const fileTarget of parsedFileHolder.targets()) { @@ -161,7 +160,6 @@ namespace UpdateCacheHolders { return new CacheHolderData.CachedCoqFileData( cachedTheoremsMap, relativizeAbsolutePaths(workspacePath, parsedFile.filePath), - parsedFile.fileLines, parsedFile.documentVersion, workspacePath ); diff --git a/src/benchmark/framework/parseDataset/cacheHandlers/cacheWriter.ts b/src/benchmark/framework/parseDataset/cacheHandlers/cacheWriter.ts index 672c35b7..8de7c2f6 100644 --- a/src/benchmark/framework/parseDataset/cacheHandlers/cacheWriter.ts +++ b/src/benchmark/framework/parseDataset/cacheHandlers/cacheWriter.ts @@ -119,7 +119,6 @@ namespace SerializeCacheHolders { ) ) ), - fileLines: cachedCoqFileData.getFileLines(), documentVersion: cachedCoqFileData.getDocumentVersion(), filePathRelativeToWorkspace: cachedCoqFileData.filePathRelativeToWorkspace, diff --git a/src/benchmark/framework/parseDataset/cacheStructures/cacheHolders.ts b/src/benchmark/framework/parseDataset/cacheStructures/cacheHolders.ts index 9cc08608..b89eae07 100644 --- a/src/benchmark/framework/parseDataset/cacheStructures/cacheHolders.ts +++ b/src/benchmark/framework/parseDataset/cacheStructures/cacheHolders.ts @@ -91,7 +91,6 @@ export namespace CacheHolderData { constructor( private readonly theorems: Map, readonly filePathRelativeToWorkspace: string, - private fileLines: string[], private documentVersion: number, readonly workspacePath: string ) {} @@ -104,10 +103,6 @@ export namespace CacheHolderData { return this.theorems.get(theoremName); } - getFileLines(): string[] { - return this.fileLines; - } - getDocumentVersion(): number { return this.documentVersion; } @@ -116,10 +111,6 @@ export namespace CacheHolderData { this.theorems.set(cachedTheorem.theoremData.name, cachedTheorem); } - updateFileLines(fileLines: string[]) { - this.fileLines = fileLines; - } - updateDocumentVersion(documentVersion: number) { this.documentVersion = documentVersion; } @@ -131,7 +122,6 @@ export namespace CacheHolderData { (_: string, cachedTheorem: CachedTheoremData) => cachedTheorem.theoremData ), - this.fileLines, this.documentVersion, joinPaths(this.workspacePath, this.filePathRelativeToWorkspace) ); diff --git a/src/benchmark/framework/parseDataset/cacheStructures/cacheModels.ts b/src/benchmark/framework/parseDataset/cacheStructures/cacheModels.ts index d84e2bcd..2b8cb791 100644 --- a/src/benchmark/framework/parseDataset/cacheStructures/cacheModels.ts +++ b/src/benchmark/framework/parseDataset/cacheStructures/cacheModels.ts @@ -21,8 +21,6 @@ export namespace DatasetCacheModels { * Ones that don't end with `Qed.` are also included. */ allFileTheorems: CachedTheoremsByNames; - - fileLines: string[]; documentVersion: number; filePathRelativeToWorkspace: string; } @@ -95,12 +93,6 @@ export namespace DatasetCacheModels { type: "object", properties: { allFileTheorems: cachedTheoremsByNamesSchema, - fileLines: { - type: "array", - items: { - type: "string", - }, - }, documentVersion: { type: "number", }, @@ -110,7 +102,6 @@ export namespace DatasetCacheModels { }, required: [ "allFileTheorems", - "fileLines", "documentVersion", "filePathRelativeToWorkspace", ], diff --git a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts index 616b4713..9a6597c0 100644 --- a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts +++ b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts @@ -93,14 +93,12 @@ export namespace ParseCoqProjectImpl { (serializedTheorem) => serializedTheorem.name, (serializedTheorem) => serializedTheorem ), - fileLines: sourceFileEnvironment.fileLines, documentVersion: sourceFileEnvironment.documentVersion, filePath: filePath, }; const foundTheoremsLog = `found ${Object.keys(serializedParsedFile.serializedTheoremsByNames).length} theorem(s)`; - const readLinesLog = `read ${serializedParsedFile.fileLines.length} lines`; logger.debug( - `Successfully parsed "${filePath}": ${foundTheoremsLog}, ${readLinesLog}` + `Successfully parsed "${filePath}": ${foundTheoremsLog}` ); return serializedParsedFile; } diff --git a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts index c49853cc..45bcb1c9 100644 --- a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts +++ b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts @@ -25,7 +25,6 @@ export class ParsedCoqFileData { * Ones that don't end with `Qed.` are also included. */ readonly theoremsByNames: Map, - readonly fileLines: string[], readonly documentVersion: number, readonly filePath: string ) {} @@ -45,7 +44,6 @@ export class ParsedCoqFileData { fileTheorems: this.getOrderedFileTheorems().filter( (theorem) => theorem.proof && !theorem.proof.is_incomplete ), - fileLines: this.fileLines, documentVersion: this.documentVersion, fileUri: Uri.fromPath(this.filePath), }; @@ -58,8 +56,6 @@ export interface SerializedParsedCoqFile { * Ones that don't end with `Qed.` are also included. */ serializedTheoremsByNames: SerializedTheoremsByNames; - - fileLines: string[]; documentVersion: number; filePath: string; } @@ -84,12 +80,6 @@ export const serializedParsedCoqFileSchema: JSONSchemaType deserializeTheoremData(serializedTheorem) ), - serializedParsedCoqFile.fileLines, serializedParsedCoqFile.documentVersion, serializedParsedCoqFile.filePath ); @@ -132,7 +120,6 @@ export function serializeParsedCoqFile( serializeTheoremData(theoremData) ) ), - fileLines: parsedCoqFileData.fileLines, documentVersion: parsedCoqFileData.documentVersion, filePath: parsedCoqFileData.filePath, }; diff --git a/src/core/completionGenerationContext.ts b/src/core/completionGenerationContext.ts index a28a5d55..3af5601f 100644 --- a/src/core/completionGenerationContext.ts +++ b/src/core/completionGenerationContext.ts @@ -19,10 +19,7 @@ export interface CompletionContext { export interface SourceFileEnvironment { // `fileTheorems` contain only ones that successfully finish with Qed. fileTheorems: Theorem[]; - // TODO: [LspCoreRefactor] Check if `fileLines` is needed - fileLines: string[]; documentVersion: number; - // TODO: [LspCoreRefactor] Rename to `documentUri` fileUri: Uri; } diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index e18061e4..9da597f4 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -1,5 +1,3 @@ -import { readFileSync } from "fs"; - import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; import { parseCoqFile } from "../coqParser/parseCoqFile"; @@ -88,11 +86,9 @@ export async function createSourceFileEnvironment( client, rankerNeedsInitialGoals ); - const fileText = readFileSync(fileUri.fsPath); return { fileTheorems: fileTheorems, - fileLines: fileText.toString().split("\n"), documentVersion: documentVersion, fileUri: fileUri, }; diff --git a/src/test/resources/benchmarking/singleTaskRunnerExampleInput/cached_auto_benchmark.json b/src/test/resources/benchmarking/singleTaskRunnerExampleInput/cached_auto_benchmark.json index dbf52499..06b4b401 100644 --- a/src/test/resources/benchmarking/singleTaskRunnerExampleInput/cached_auto_benchmark.json +++ b/src/test/resources/benchmarking/singleTaskRunnerExampleInput/cached_auto_benchmark.json @@ -88,54 +88,6 @@ } } ], - "fileLines": [ - "Theorem test : forall (A : Type) (P : A -> Prop) (x : A), P x -> P x.", - "Proof.", - " admit.", - "Admitted.", - "", - "(* Theorem test2 : forall (A : Type) (P : A -> Prop) (x : A), P x -> P x.", - "Proof.", - " intros A P x H.", - " admit.", - "Admitted.", - "", - "Theorem test2nat1 : forall n : nat, n = 0 \\/ n <> 0.", - "Proof.", - " intros n.", - " destruct n.", - " - admit.", - " - right.", - " discriminate.", - "Admitted.", - "", - "Theorem test2nat2 : forall n : nat, n = 0 \\/ n <> 0.", - "Proof.", - " intros n.", - " destruct n.", - " {", - " admit.", - " }", - " {", - " admit.", - " }", - "Admitted.", - "", - "Theorem test_thr : forall n:nat, 0 + n = n.", - "Proof.", - " intros n. Print plus.", - " admit.", - " (* reflexivity. *)", - "Admitted.", - "", - "Lemma test_thr1 : forall n:nat, 0 + n + 0 = n.", - "Proof.", - " intros n. Print plus.", - " admit.", - " (* reflexivity. *)", - "Admitted. *)", - "" - ], "documentVersion": 1, "filePath": "auto_benchmark.v" } \ No newline at end of file From df91211af31c963faf1a6a8bc10c54fe20ba90ee Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Fri, 25 Oct 2024 19:56:25 +0200 Subject: [PATCH 16/30] Refactor `checkGeneratedProofs` --- .../implementation/checkProofs.ts | 2 +- src/coqParser/parseCoqFile.ts | 7 ++-- src/core/completionGenerator.ts | 37 ++++++------------- src/extension/coqPilot.ts | 15 -------- .../legacyBenchmark/benchmarkingFramework.ts | 7 ---- 5 files changed, 15 insertions(+), 53 deletions(-) diff --git a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/checkProofs.ts b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/checkProofs.ts index 613860f0..93606ce4 100644 --- a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/checkProofs.ts +++ b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/checkProofs.ts @@ -40,7 +40,7 @@ export namespace CheckProofsImpl { const timeMark = new TimeMark(); const fileUri = Uri.fromPath(args.fileUri); - // TODO: [LspCoreRefactor] Before handling of open/close req's was done by `CoqProofChecker` itself + // TODO: [@Gleb Solovev] Pay Atteniton that it was previously done by the CoqProofChecker await coqLspClient.openTextDocument(fileUri); const proofCheckResults = await coqProofChecker.checkProofs( diff --git a/src/coqParser/parseCoqFile.ts b/src/coqParser/parseCoqFile.ts index 298fad0b..6cb54d69 100644 --- a/src/coqParser/parseCoqFile.ts +++ b/src/coqParser/parseCoqFile.ts @@ -16,7 +16,7 @@ import { Uri } from "../utils/uri"; import { ProofStep, Theorem, TheoremProof, Vernacexpr } from "./parsedTypes"; /** - * TODO: [LspCoreRefactor] Refactor retrieveInitialGoal param + * TODO: [@Gleb Solovev] Refactor retrieveInitialGoal param * to be something more reasonable and readable. */ export async function parseCoqFile( @@ -106,9 +106,8 @@ async function parseFlecheDocument( ) ); } else { - // TODO: [LspCoreRefactor] Discuss invariants on initial_goal - // and allow it's absence. As calculation of initial_goal - // brings overhead of 100ms per theorem. + // TODO: Might be a source of bugs if somewhere absense of initialGoal + // is not handled properly or invariants are broken let initialGoal: Result[], Error> | null = null; if (retrieveInitialGoal) { diff --git a/src/core/completionGenerator.ts b/src/core/completionGenerator.ts index 512dc685..d7f52145 100644 --- a/src/core/completionGenerator.ts +++ b/src/core/completionGenerator.ts @@ -1,9 +1,6 @@ -import { OutputChannel } from "vscode"; - import { LLMSequentialIterator } from "../llm/llmIterator"; import { GeneratedProof } from "../llm/llmServices/generatedProof"; -import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; import { CoqLspTimeoutError } from "../coqLsp/coqLspTypes"; import { EventLogger } from "../logging/eventLogger"; @@ -15,7 +12,7 @@ import { ProcessEnvironment, SourceFileEnvironment, } from "./completionGenerationContext"; -import { CoqProofChecker, ProofCheckResult } from "./coqProofChecker"; +import { ProofCheckResult } from "./coqProofChecker"; import { buildProofGenerationContext, prepareProofToCheck, @@ -47,9 +44,7 @@ export async function generateCompletion( completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, processEnvironment: ProcessEnvironment, - logOutputChannel?: OutputChannel, eventLogger?: EventLogger, - workspaceRootPath?: string, perProofTimeoutMillis: number = 15000 ): Promise { const context = buildProofGenerationContext( @@ -88,9 +83,7 @@ export async function generateCompletion( completionContext, sourceFileEnvironment, processEnvironment, - logOutputChannel, eventLogger, - workspaceRootPath, perProofTimeoutMillis ); if (fixedProofsOrCompletion instanceof SuccessGenerationResult) { @@ -105,9 +98,7 @@ export async function generateCompletion( completionContext, sourceFileEnvironment, processEnvironment, - logOutputChannel, eventLogger, - workspaceRootPath, perProofTimeoutMillis ); if (fixedProofsOrCompletion instanceof SuccessGenerationResult) { @@ -149,9 +140,7 @@ async function checkAndFixProofs( completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, processEnvironment: ProcessEnvironment, - logOutputChannel?: OutputChannel, eventLogger?: EventLogger, - workspaceRootPath?: string, perProofTimeoutMillis: number = 15000 ): Promise { // check proofs and finish with success if at least one is valid @@ -160,8 +149,6 @@ async function checkAndFixProofs( completionContext, sourceFileEnvironment, processEnvironment, - logOutputChannel, - workspaceRootPath, perProofTimeoutMillis ); const completion = getFirstValidProof(proofCheckResults); @@ -196,8 +183,6 @@ async function checkGeneratedProofs( completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, processEnvironment: ProcessEnvironment, - logOutputChannel?: OutputChannel, - workspaceRootPath?: string, perProofTimeoutMillis = 15000 ): Promise { const preparedProofBatch = generatedProofs.map( @@ -205,16 +190,16 @@ async function checkGeneratedProofs( prepareProofToCheck(generatedProof.proof()) ); - // TODO: [LspCoreRefactor] Why is it happening every time? - if (workspaceRootPath) { - processEnvironment.coqProofChecker.dispose(); - const client = await createCoqLspClient( - workspaceRootPath, - logOutputChannel - ); - const coqProofChecker = new CoqProofChecker(client); - processEnvironment.coqProofChecker = coqProofChecker; - } + // TODO: Why is it happening every time? + // if (workspaceRootPath) { + // processEnvironment.coqProofChecker.dispose(); + // const client = await createCoqLspClient( + // workspaceRootPath, + // logOutputChannel + // ); + // const coqProofChecker = new CoqProofChecker(client); + // processEnvironment.coqProofChecker = coqProofChecker; + // } return processEnvironment.coqProofChecker.checkProofs( sourceFileEnvironment.fileUri, diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 961c0c10..317cb4dd 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -213,7 +213,6 @@ export class CoqPilot { completionContext, sourceFileEnvironment, processEnvironment, - this.globalExtensionState.logOutputChannel, this.globalExtensionState.eventLogger ); @@ -277,25 +276,11 @@ export class CoqPilot { [CompletionContext[], SourceFileEnvironment, ProcessEnvironment] > { const fileUri = Uri.fromPath(filePath); - // const coqLspServerPath = parseCoqLspServerPath(); - // TODO: [LspCoreRefactor] Now a tone of Coq-LSPs are created and destroyed for each completion. - // It is not efficient. Refactor it to create a single Coq-LSP client for the whole session. - // But allow restarting it when issues occur. - - // TODO: [LspCoreRefactor] Check hypothesis that we do not really need - // to send any events when user uses the plugin. - - // TODO: [LspCoreRefactor] Check what happens in plugin runtime when file not prepared, but goals requested. - // const client = await createCoqLspClient( - // coqLspServerPath, - // this.globalExtensionState.logOutputChannel - // ); const contextTheoremsRanker = buildTheoremsRankerFromConfig(); const coqProofChecker = new CoqProofChecker( this.globalExtensionState.coqLspClient ); - // TODO: [LspCoreRefactor] Unclear double dependency on Coq-LSP client. const [completionContexts, sourceFileEnvironment] = await inspectSourceFile( documentVersion, diff --git a/src/test/legacyBenchmark/benchmarkingFramework.ts b/src/test/legacyBenchmark/benchmarkingFramework.ts index d7bf0f6f..55a655fa 100644 --- a/src/test/legacyBenchmark/benchmarkingFramework.ts +++ b/src/test/legacyBenchmark/benchmarkingFramework.ts @@ -98,7 +98,6 @@ export async function runTestBenchmark( eventLogger, maxPremisesNumber, reportHolder, - workspaceRootPath, perProofTimeoutMillis ); consoleLog( @@ -123,7 +122,6 @@ export async function runTestBenchmark( eventLogger, maxPremisesNumber, reportHolder, - workspaceRootPath, perProofTimeoutMillis ); consoleLog( @@ -200,7 +198,6 @@ export async function benchmarkTargets( eventLogger: EventLogger, maxPremisesNumber?: number, reportHolder?: BenchmarkReportHolder, - workspaceRootPath?: string, perProofTimeoutMillis: number = 15000 ): Promise { const totalCompletionsNumber = targets.length; @@ -216,7 +213,6 @@ export async function benchmarkTargets( eventLogger, maxPremisesNumber, reportHolder, - workspaceRootPath, perProofTimeoutMillis ); if (success) { @@ -239,7 +235,6 @@ async function benchmarkCompletionGeneration( eventLogger: EventLogger, maxPremisesNumber?: number, reportHolder?: BenchmarkReportHolder, - workspaceRootPath?: string, perProofTimeoutMillis: number = 15000 ): Promise { const completionPosition = completionContext.admitRange.start; @@ -276,8 +271,6 @@ async function benchmarkCompletionGeneration( sourceFileEnvironmentWithFilteredContext, processEnvironmentWithPremisesNumber, undefined, - undefined, - workspaceRootPath, perProofTimeoutMillis ); let message = "unknown"; From 632c80ae64bb2aee9b9d3da8c12d4c468a85c5c5 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Sun, 27 Oct 2024 01:10:12 +0200 Subject: [PATCH 17/30] Minor changes after sketching completion abort in extension --- src/extension/coqPilot.ts | 2 +- src/extension/editorMessages.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 8f98a941..950420be 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -82,7 +82,7 @@ export class CoqPilot { ); this.registerEditorCommand( "toggle_current_session", - this.performCompletionForAllAdmits.bind(this) + this.toggleCurrentSession.bind(this) ); this.vscodeExtensionContext.subscriptions.push(this); diff --git a/src/extension/editorMessages.ts b/src/extension/editorMessages.ts index 4d177bf9..d234b73f 100644 --- a/src/extension/editorMessages.ts +++ b/src/extension/editorMessages.ts @@ -32,7 +32,7 @@ export namespace EditorMessages { `Coqpilot got an unexpected error: ${errorDescription}. Please report this crash by opening an issue in the Coqpilot GitHub repository.`; export const completionAborted = - "Completion generation was forcefully aborted, Coq-LSP server stopping. Please hit the Status Bar button again to restart the server and proceed with completions."; + "Completion generation was forcefully aborted, Coq-LSP server stopping. Please hit the CoqPilot Status Bar button again to restart the server and proceed with completions."; export const objectWasThrownAsError = (e: any) => reportUnexpectedError( From d8f591f3a8364d0509a1791786e073510aee5091 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Sun, 27 Oct 2024 01:21:55 +0200 Subject: [PATCH 18/30] Fix merge of `abort-completion` into `core-lsp-refactor` --- src/core/completionGenerator.ts | 1 - src/extension/coqPilot.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/core/completionGenerator.ts b/src/core/completionGenerator.ts index b0905d46..256c197b 100644 --- a/src/core/completionGenerator.ts +++ b/src/core/completionGenerator.ts @@ -46,7 +46,6 @@ export async function generateCompletion( sourceFileEnvironment: SourceFileEnvironment, processEnvironment: ProcessEnvironment, abortSignal: AbortSignal, - logOutputChannel?: OutputChannel, eventLogger?: EventLogger, perProofTimeoutMillis: number = 15000 ): Promise { diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 7360764c..1357683c 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -239,7 +239,6 @@ export class CoqPilot { sourceFileEnvironment, processEnvironment, abortSignal, - this.globalExtensionState.logOutputChannel, this.globalExtensionState.eventLogger ); From 0ec73167067eaf1efac42a4f6eebad851d889f87 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Sun, 27 Oct 2024 01:24:30 +0200 Subject: [PATCH 19/30] Add TODOs on `LspCoreRefactor` --- src/coqLsp/coqLspClient.ts | 1 + src/coqParser/parsedTypes.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index 00cc6fdb..83d7bde0 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -36,6 +36,7 @@ import { PpString, } from "./coqLspTypes"; +// TODO: [LspCoreRefactor] Leave comments on invariants preserved by the client export interface CoqLspClientInterface extends Disposable { getGoalsAtPoint( position: Position, diff --git a/src/coqParser/parsedTypes.ts b/src/coqParser/parsedTypes.ts index a965c24d..54314092 100644 --- a/src/coqParser/parsedTypes.ts +++ b/src/coqParser/parsedTypes.ts @@ -138,6 +138,8 @@ export class TheoremProof { } } +// TODO: [LspCoreRefactor] Refactor Theorem -> Premise +// TODO: [LspCoreRefactor] Make Theorem.proof non nullable export class Theorem { constructor( public name: string, From cdd87b372fa764198bb1e3d8b7d2ead32a80c36d Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Sun, 27 Oct 2024 02:46:14 +0200 Subject: [PATCH 20/30] Create `SessionExtensionState` --- .../implementation/parseCoqProject.ts | 4 +- .../parsedCoqFile/parsedCoqFileData.ts | 6 +- src/coqLsp/coqLspClient.ts | 30 +++--- src/coqParser/parseCoqFile.ts | 2 +- src/core/completionGenerator.ts | 2 +- src/core/inspectSourceFile.ts | 6 +- src/extension/coqPilot.ts | 99 ++++++++++++------- src/extension/editorMessages.ts | 5 +- src/extension/extensionAbortUtils.ts | 2 +- src/extension/globalExtensionState.ts | 44 ++------- src/extension/healthStatusIndicator.ts | 49 +++++++++ src/extension/sessionExtensionState.ts | 38 +++++++ src/extension/statusBarButton.ts | 75 -------------- src/mainNode.ts | 28 +----- src/test/commonTestFunctions/coqFileParser.ts | 6 +- src/utils/sleep.ts | 3 + 16 files changed, 196 insertions(+), 203 deletions(-) create mode 100644 src/extension/healthStatusIndicator.ts create mode 100644 src/extension/sessionExtensionState.ts delete mode 100644 src/extension/statusBarButton.ts create mode 100644 src/utils/sleep.ts diff --git a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts index c2125868..a0ddfeba 100644 --- a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts +++ b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts @@ -99,9 +99,7 @@ export namespace ParseCoqProjectImpl { filePath: filePath, }; const foundTheoremsLog = `found ${Object.keys(serializedParsedFile.serializedTheoremsByNames).length} theorem(s)`; - logger.debug( - `Successfully parsed "${filePath}": ${foundTheoremsLog}` - ); + logger.debug(`Successfully parsed "${filePath}": ${foundTheoremsLog}`); return serializedParsedFile; } diff --git a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts index 45bcb1c9..2394efbe 100644 --- a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts +++ b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts @@ -87,11 +87,7 @@ export const serializedParsedCoqFileSchema: JSONSchemaType[], Error>>; + /** + * Returns a FlecheDocument for the given uri. + * This method doesn't open the document implicitly, therefore + * it assumes that openTextDocument has been called before. + */ + getFlecheDocument(uri: Uri): Promise; + openTextDocument(uri: Uri, version?: number): Promise; closeTextDocument(uri: Uri): Promise; - - getFlecheDocument(uri: Uri): Promise; } const goalReqType = new RequestType, void>( @@ -96,7 +110,6 @@ export class CoqLspClient implements CoqLspClientInterface { return new CoqLspClient(connector, eventLogger); } - @logExecutionTime async getGoalsAtPoint( position: Position, documentUri: Uri, @@ -128,7 +141,6 @@ export class CoqLspClient implements CoqLspClientInterface { }); } - @logExecutionTime async getFlecheDocument(uri: Uri): Promise { return await this.mutex.runExclusive(async () => { return this.getFlecheDocumentUnsafe(uri); @@ -225,10 +237,6 @@ export class CoqLspClient implements CoqLspClientInterface { } } - private sleep(ms: number): Promise> { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - private async waitUntilFileFullyChecked( requestType: ProtocolNotificationType, params: any, @@ -277,7 +285,7 @@ export class CoqLspClient implements CoqLspClientInterface { ); while (timeout > 0 && (pendingProgress || pendingDiagnostic)) { - await this.sleep(100); + await sleep(100); timeout -= 100; } diff --git a/src/coqParser/parseCoqFile.ts b/src/coqParser/parseCoqFile.ts index 45f36abb..efff94c6 100644 --- a/src/coqParser/parseCoqFile.ts +++ b/src/coqParser/parseCoqFile.ts @@ -11,10 +11,10 @@ import { RangedSpan, } from "../coqLsp/coqLspTypes"; +import { throwOnAbort } from "../extension/extensionAbortUtils"; import { Uri } from "../utils/uri"; import { ProofStep, Theorem, TheoremProof, Vernacexpr } from "./parsedTypes"; -import { throwOnAbort } from "../extension/extensionAbortUtils"; /** * TODO: [@Gleb Solovev] Refactor retrieveInitialGoal param diff --git a/src/core/completionGenerator.ts b/src/core/completionGenerator.ts index 256c197b..09bb292c 100644 --- a/src/core/completionGenerator.ts +++ b/src/core/completionGenerator.ts @@ -3,6 +3,7 @@ import { GeneratedProof } from "../llm/llmServices/generatedProof"; import { CoqLspTimeoutError } from "../coqLsp/coqLspTypes"; +import { throwOnAbort } from "../extension/extensionAbortUtils"; import { EventLogger } from "../logging/eventLogger"; import { asErrorOrRethrow, buildErrorCompleteLog } from "../utils/errorsUtils"; import { stringifyAnyValue } from "../utils/printers"; @@ -17,7 +18,6 @@ import { buildProofGenerationContext, prepareProofToCheck, } from "./exposedCompletionGeneratorUtils"; -import { throwOnAbort } from "../extension/extensionAbortUtils"; export interface GenerationResult {} diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index df33ad0d..e7b6123a 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -2,13 +2,13 @@ import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; import { parseCoqFile } from "../coqParser/parseCoqFile"; import { ProofStep, Theorem } from "../coqParser/parsedTypes"; +import { throwOnAbort } from "../extension/extensionAbortUtils"; import { Uri } from "../utils/uri"; import { CompletionContext, SourceFileEnvironment, } from "./completionGenerationContext"; -import { throwOnAbort } from "../extension/extensionAbortUtils"; type AnalyzedFile = [CompletionContext[], SourceFileEnvironment]; @@ -25,7 +25,7 @@ export async function inspectSourceFile( fileUri, client, abortSignal, - rankerNeedsInitialGoals, + rankerNeedsInitialGoals ); const completionContexts = await createCompletionContexts( documentVersion, @@ -100,4 +100,4 @@ export async function createSourceFileEnvironment( documentVersion: documentVersion, fileUri: fileUri, }; -} \ No newline at end of file +} diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 1357683c..1c0ffd01 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -22,8 +22,8 @@ import { CoqProofChecker } from "../core/coqProofChecker"; import { inspectSourceFile } from "../core/inspectSourceFile"; import { ProofStep } from "../coqParser/parsedTypes"; -import { logExecutionTime } from "../logging/timeMeasureDecorator"; import { buildErrorCompleteLog } from "../utils/errorsUtils"; +import { sleep } from "../utils/sleep"; import { Uri } from "../utils/uri"; import { @@ -40,34 +40,29 @@ import { showMessageToUser, showMessageToUserWithSettingsHint, } from "./editorMessages"; +import { CompletionAbortError, throwOnAbort } from "./extensionAbortUtils"; import { GlobalExtensionState } from "./globalExtensionState"; +import { HealthStatusIndicator } from "./healthStatusIndicator"; import { subscribeToHandleLLMServicesEvents } from "./llmServicesEventsHandler"; import { positionInRange, toVSCodePosition, toVSCodeRange, } from "./positionRangeUtils"; +import { SessionExtensionState } from "./sessionExtensionState"; import { SettingsValidationError } from "./settingsValidationError"; -import { StatusBarButton } from "./statusBarButton"; -import { CompletionAbortError, throwOnAbort } from "./extensionAbortUtils"; export const pluginId = "coqpilot"; export const pluginName = "CoqPilot"; export class CoqPilot { - private readonly globalExtensionState: GlobalExtensionState; - private readonly vscodeExtensionContext: ExtensionContext; - private subscriptions: { dispose(): any }[] = []; - private abortController = new AbortController(); + private readonly healthStatusIndicator: HealthStatusIndicator; private constructor( - vscodeExtensionContext: ExtensionContext, - globalExtensionState: GlobalExtensionState, - private readonly statusBarButton: StatusBarButton + private readonly vscodeExtensionContext: ExtensionContext, + private readonly globalExtensionState: GlobalExtensionState, + private sessionExtensionState: SessionExtensionState ) { - this.vscodeExtensionContext = vscodeExtensionContext; - this.globalExtensionState = globalExtensionState; - this.registerEditorCommand( "perform_completion_under_cursor", this.performCompletionUnderCursor.bind(this) @@ -80,23 +75,31 @@ export class CoqPilot { "perform_completion_for_all_admits", this.performCompletionForAllAdmits.bind(this) ); + + const toggleCommand = `toggle_current_session`; this.registerEditorCommand( - "toggle_current_session", + toggleCommand, this.toggleCurrentSession.bind(this) ); + this.healthStatusIndicator = new HealthStatusIndicator( + `${pluginId}.${toggleCommand}`, + vscodeExtensionContext + ); + this.healthStatusIndicator.updateStatusBar(true); this.vscodeExtensionContext.subscriptions.push(this); } - static async create( - vscodeExtensionContext: ExtensionContext, - statusBarItem: StatusBarButton - ) { - const globalExtensionState = await GlobalExtensionState.create(); + static async create(vscodeExtensionContext: ExtensionContext) { + const globalExtensionState = new GlobalExtensionState(); + const sessionExtensionState = await SessionExtensionState.create( + globalExtensionState.logOutputChannel, + globalExtensionState.eventLogger + ); return new CoqPilot( vscodeExtensionContext, globalExtensionState, - statusBarItem + sessionExtensionState ); } @@ -105,7 +108,7 @@ export class CoqPilot { this.performSpecificCompletionsWithProgress( (hole) => positionInRange(cursorPosition, hole.range), editor, - this.abortController.signal + this.sessionExtensionState.abortController.signal ); } @@ -114,21 +117,35 @@ export class CoqPilot { this.performSpecificCompletionsWithProgress( (hole) => selection.contains(toVSCodePosition(hole.range.start)), editor, - this.abortController.signal + this.sessionExtensionState.abortController.signal ); } async performCompletionForAllAdmits(editor: TextEditor) { this.performSpecificCompletionsWithProgress( - (_hole) => true, - editor, - this.abortController.signal + (_hole) => true, + editor, + this.sessionExtensionState.abortController.signal ); } async toggleCurrentSession() { - console.log("Toggle current session"); - this.abortController.abort(new CompletionAbortError()); + if (this.globalExtensionState.hasActiveSession) { + this.sessionExtensionState.abort(); + // TODO: [LspCoreRefactor] Change this. Need to reach the throwOnAbort + // checkpoint before disposing the LSP client. + await sleep(500); + this.sessionExtensionState.dispose(); + this.globalExtensionState.hasActiveSession = false; + this.healthStatusIndicator.updateStatusBar(false); + } else { + this.sessionExtensionState = await SessionExtensionState.create( + this.globalExtensionState.logOutputChannel, + this.globalExtensionState.eventLogger + ); + this.globalExtensionState.hasActiveSession = true; + this.healthStatusIndicator.updateStatusBar(true); + } } private async performSpecificCompletionsWithProgress( @@ -136,10 +153,19 @@ export class CoqPilot { editor: TextEditor, abortSignal: AbortSignal ) { + if (!this.globalExtensionState.hasActiveSession) { + showMessageToUser(EditorMessages.extensionIsPaused, "warning"); + return; + } + let isSessionHealthy = true; try { - this.statusBarButton.showSpinner(); + this.healthStatusIndicator.showSpinner(); - await this.performSpecificCompletions(shouldCompleteHole, editor, abortSignal); + await this.performSpecificCompletions( + shouldCompleteHole, + editor, + abortSignal + ); } catch (e) { if (e instanceof SettingsValidationError) { e.showAsMessageToUser(); @@ -151,6 +177,7 @@ export class CoqPilot { ); } else if (e instanceof CompletionAbortError) { showMessageToUser(EditorMessages.completionAborted, "info"); + isSessionHealthy = false; } else { showMessageToUser( e instanceof Error @@ -161,7 +188,7 @@ export class CoqPilot { console.error(buildErrorCompleteLog(e)); } } finally { - this.statusBarButton.hideSpinner(); + this.healthStatusIndicator.hideSpinner(isSessionHealthy); } } @@ -225,7 +252,6 @@ export class CoqPilot { } } - @logExecutionTime private async performSingleCompletion( completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, @@ -293,7 +319,6 @@ export class CoqPilot { .trim(); } - @logExecutionTime private async prepareForCompletions( shouldCompleteHole: (hole: ProofStep) => boolean, documentVersion: number, @@ -306,14 +331,14 @@ export class CoqPilot { const contextTheoremsRanker = buildTheoremsRankerFromConfig(); const coqProofChecker = new CoqProofChecker( - this.globalExtensionState.coqLspClient + this.sessionExtensionState.coqLspClient ); const [completionContexts, sourceFileEnvironment] = await inspectSourceFile( documentVersion, shouldCompleteHole, fileUri, - this.globalExtensionState.coqLspClient, + this.sessionExtensionState.coqLspClient, abortSignal, contextTheoremsRanker.needsUnwrappedNotations ); @@ -339,13 +364,11 @@ export class CoqPilot { fn ); this.vscodeExtensionContext.subscriptions.push(disposable); - this.subscriptions.push(disposable); } dispose(): void { - // This is not the same as vscodeExtensionContext.subscriptions - // As it doesn't contain the statusBar item and the toggle command - this.subscriptions.forEach((d) => d.dispose()); + this.vscodeExtensionContext.subscriptions.forEach((d) => d.dispose()); this.globalExtensionState.dispose(); + this.sessionExtensionState.dispose(); } } diff --git a/src/extension/editorMessages.ts b/src/extension/editorMessages.ts index d234b73f..4641d2c0 100644 --- a/src/extension/editorMessages.ts +++ b/src/extension/editorMessages.ts @@ -31,9 +31,12 @@ export namespace EditorMessages { export const reportUnexpectedError = (errorDescription: string) => `Coqpilot got an unexpected error: ${errorDescription}. Please report this crash by opening an issue in the Coqpilot GitHub repository.`; - export const completionAborted = + export const completionAborted = "Completion generation was forcefully aborted, Coq-LSP server stopping. Please hit the CoqPilot Status Bar button again to restart the server and proceed with completions."; + export const extensionIsPaused = + "You have stopped CoqPilot. To resume, click on the CoqPilot icon in the status bar."; + export const objectWasThrownAsError = (e: any) => reportUnexpectedError( `object was thrown as error, ${stringifyAnyValue(e)}` diff --git a/src/extension/extensionAbortUtils.ts b/src/extension/extensionAbortUtils.ts index c3fef01f..81c6975b 100644 --- a/src/extension/extensionAbortUtils.ts +++ b/src/extension/extensionAbortUtils.ts @@ -11,4 +11,4 @@ export function throwOnAbort(abortSignal: AbortSignal) { if (abortSignal.aborted) { throw abortSignal.reason; } -} \ No newline at end of file +} diff --git a/src/extension/globalExtensionState.ts b/src/extension/globalExtensionState.ts index cbb8c197..4974e8ba 100644 --- a/src/extension/globalExtensionState.ts +++ b/src/extension/globalExtensionState.ts @@ -1,12 +1,7 @@ import * as fs from "fs"; import * as path from "path"; import * as tmp from "tmp"; -import { - OutputChannel, - WorkspaceConfiguration, - window, - workspace, -} from "vscode"; +import { Disposable, WorkspaceConfiguration, window, workspace } from "vscode"; import { LLMServices, disposeServices } from "../llm/llmServices"; import { GrazieService } from "../llm/llmServices/grazie/grazieService"; @@ -14,45 +9,21 @@ import { LMStudioService } from "../llm/llmServices/lmStudio/lmStudioService"; import { OpenAiService } from "../llm/llmServices/openai/openAiService"; import { PredefinedProofsService } from "../llm/llmServices/predefinedProofs/predefinedProofsService"; -import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; -import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; - import { EventLogger, Severity } from "../logging/eventLogger"; -import { parseCoqLspServerPath } from "./configReaders"; import { pluginId } from "./coqPilot"; import VSCodeLogWriter from "./vscodeLogWriter"; -export class GlobalExtensionState { +export class GlobalExtensionState implements Disposable { + public readonly eventLogger: EventLogger = new EventLogger(); + public hasActiveSession = true; public readonly logWriter: VSCodeLogWriter = new VSCodeLogWriter( this.eventLogger, this.parseLoggingVerbosity(workspace.getConfiguration(pluginId)) ); - - private constructor( - public readonly coqLspClient: CoqLspClientInterface, - public readonly logOutputChannel: OutputChannel, - public readonly eventLogger: EventLogger - ) {} - - static async create(): Promise { - const coqLspServerPath = parseCoqLspServerPath(); - const logOutputChannel = window.createOutputChannel( - "CoqPilot: coq-lsp events" - ); - const eventLogger = new EventLogger(); - const coqLspClient = await createCoqLspClient( - coqLspServerPath, - logOutputChannel, - eventLogger - ); - - return new GlobalExtensionState( - coqLspClient, - logOutputChannel, - eventLogger - ); - } + public readonly logOutputChannel = window.createOutputChannel( + "CoqPilot: coq-lsp events" + ); public readonly llmServicesLogsDir = path.join( tmp.dirSync().name, @@ -95,7 +66,6 @@ export class GlobalExtensionState { } dispose(): void { - this.coqLspClient.dispose(); disposeServices(this.llmServices); this.logWriter.dispose(); fs.rmSync(this.llmServicesLogsDir, { recursive: true, force: true }); diff --git a/src/extension/healthStatusIndicator.ts b/src/extension/healthStatusIndicator.ts new file mode 100644 index 00000000..5f582ec4 --- /dev/null +++ b/src/extension/healthStatusIndicator.ts @@ -0,0 +1,49 @@ +import { + ExtensionContext, + StatusBarAlignment, + StatusBarItem, + window, +} from "vscode"; + +import { pluginName } from "./coqPilot"; + +export class HealthStatusIndicator { + private statusBarItem: StatusBarItem; + + constructor( + invocationCommand: string, + private readonly context: ExtensionContext + ) { + this.statusBarItem = window.createStatusBarItem( + StatusBarAlignment.Left, + 0 + ); + this.statusBarItem.show(); + + this.context.subscriptions.push(this.statusBarItem); + this.statusBarItem.command = invocationCommand; + } + + updateStatusBar(isActive: boolean) { + if (isActive) { + this.statusBarItem.text = `$(debug-stop) ${pluginName}: Running`; + this.statusBarItem.tooltip = "Click to stop the extension"; + } else { + this.statusBarItem.text = `$(debug-start) ${pluginName}: Stopped`; + this.statusBarItem.tooltip = "Click to start the extension"; + } + } + + showSpinner() { + this.statusBarItem.text = `$(sync~spin) ${pluginName}: In progress`; + this.statusBarItem.tooltip = "Operation in progress..."; + } + + hideSpinner(isActive: boolean) { + this.updateStatusBar(isActive); + } + + dispose() { + this.statusBarItem.dispose(); + } +} diff --git a/src/extension/sessionExtensionState.ts b/src/extension/sessionExtensionState.ts new file mode 100644 index 00000000..7e639c09 --- /dev/null +++ b/src/extension/sessionExtensionState.ts @@ -0,0 +1,38 @@ +import { Disposable, OutputChannel } from "vscode"; + +import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; +import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; + +import { EventLogger } from "../logging/eventLogger"; + +import { parseCoqLspServerPath } from "./configReaders"; +import { CompletionAbortError } from "./extensionAbortUtils"; + +export class SessionExtensionState implements Disposable { + public abortController: AbortController = new AbortController(); + + private constructor(public readonly coqLspClient: CoqLspClientInterface) {} + + static async create( + logOutputChannel: OutputChannel, + eventLogger: EventLogger + ): Promise { + const coqLspServerPath = parseCoqLspServerPath(); + const coqLspClient = await createCoqLspClient( + coqLspServerPath, + logOutputChannel, + eventLogger + ); + + return new SessionExtensionState(coqLspClient); + } + + abort(): void { + this.abortController.abort(new CompletionAbortError()); + } + + dispose(): void { + this.abortController.abort(); + this.coqLspClient.dispose(); + } +} diff --git a/src/extension/statusBarButton.ts b/src/extension/statusBarButton.ts deleted file mode 100644 index e2ba3eae..00000000 --- a/src/extension/statusBarButton.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - ExtensionContext, - StatusBarAlignment, - StatusBarItem, - commands, - window, -} from "vscode"; - -import { pluginId, pluginName } from "./coqPilot"; - -// TODO: [LspCoreRefactor] Looks a bit dirty, refactor -export class StatusBarButton { - private statusBarItem: StatusBarItem; - private isActive: boolean; - private context: ExtensionContext; - private toggleCallback: ( - isActive: boolean, - context: ExtensionContext - ) => void; - - constructor( - context: ExtensionContext, - toggleCallback: (isActive: boolean, context: ExtensionContext) => void - ) { - this.context = context; - this.isActive = false; - this.toggleCallback = toggleCallback; - - this.statusBarItem = window.createStatusBarItem( - StatusBarAlignment.Left, - 0 - ); - this.updateStatusBar(); - - this.statusBarItem.show(); - this.context.subscriptions.push(this.statusBarItem); - - const command = `${pluginId}.toggleExtension`; - this.statusBarItem.command = command; - const toggleCommand = commands.registerCommand( - command, - this.toggle.bind(this) - ); - this.context.subscriptions.push(toggleCommand); - } - - toggle() { - this.isActive = !this.isActive; - this.updateStatusBar(); - this.toggleCallback(this.isActive, this.context); - } - - private updateStatusBar() { - if (this.isActive) { - this.statusBarItem.text = `$(debug-stop) ${pluginName}: Running`; - this.statusBarItem.tooltip = "Click to stop the extension"; - } else { - this.statusBarItem.text = `$(debug-stop) ${pluginName}: Stopped`; - this.statusBarItem.tooltip = "Click to start the extension"; - } - } - - public showSpinner() { - this.statusBarItem.text = `$(sync~spin) ${pluginName}: In progress`; - this.statusBarItem.tooltip = "Operation in progress..."; - } - - public hideSpinner() { - this.updateStatusBar(); - } - - public dispose() { - this.statusBarItem.dispose(); - } -} diff --git a/src/mainNode.ts b/src/mainNode.ts index 4fb40639..4a931eb5 100644 --- a/src/mainNode.ts +++ b/src/mainNode.ts @@ -1,38 +1,14 @@ import { ExtensionContext } from "vscode"; import { CoqPilot } from "./extension/coqPilot"; -import { StatusBarButton } from "./extension/statusBarButton"; export let extension: CoqPilot | undefined; -let statusBar: StatusBarButton | undefined = undefined; export async function activate(context: ExtensionContext): Promise { - statusBar = new StatusBarButton(context, toggleExtension); - statusBar.toggle(); -} - -async function startExtension(context: ExtensionContext) { - if (!statusBar) { - statusBar = new StatusBarButton(context, toggleExtension); - } - extension = await CoqPilot.create(context, statusBar); + extension = await CoqPilot.create(context); context.subscriptions.push(extension); } -function stopExtension() { - extension?.dispose(); - extension = undefined; -} - -function toggleExtension(isActive: boolean, context: ExtensionContext) { - if (isActive) { - startExtension(context); - } else { - stopExtension(); - } -} - export function deactivate() { - stopExtension(); - statusBar?.dispose(); + extension?.dispose(); } diff --git a/src/test/commonTestFunctions/coqFileParser.ts b/src/test/commonTestFunctions/coqFileParser.ts index 8dd79200..a73137e8 100644 --- a/src/test/commonTestFunctions/coqFileParser.ts +++ b/src/test/commonTestFunctions/coqFileParser.ts @@ -20,7 +20,11 @@ export async function parseTheoremsFromCoqFile( await client.openTextDocument(fileUri); const abortController = new AbortController(); - const document = await parseCoqFile(fileUri, client, abortController.signal); + const document = await parseCoqFile( + fileUri, + client, + abortController.signal + ); await client.closeTextDocument(fileUri); return document; diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 00000000..994de271 --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,3 @@ +export function sleep(ms: number): Promise> { + return new Promise((resolve) => setTimeout(resolve, ms)); +} From 34bdfa258a58179a448da5fdcba726a637e43be0 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Sun, 27 Oct 2024 02:49:09 +0200 Subject: [PATCH 21/30] Refactor `CoqLspClient` to `CoqLspClientImpl` --- .../coqProjectParser/implementation/parseCoqProject.ts | 10 +++++----- src/coqLsp/coqLspBuilders.ts | 10 +++++----- src/coqLsp/coqLspClient.ts | 8 ++++---- src/coqParser/parseCoqFile.ts | 6 +++--- src/core/coqProofChecker.ts | 4 ++-- src/core/inspectSourceFile.ts | 8 ++++---- src/extension/coqPilot.ts | 4 ++-- src/extension/sessionExtensionState.ts | 4 ++-- src/test/commonTestFunctions/prepareEnvironment.ts | 4 ++-- src/test/legacyBenchmark/benchmarkingFramework.ts | 8 ++++---- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts index a0ddfeba..e742aa35 100644 --- a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts +++ b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts @@ -1,5 +1,5 @@ import { createTestCoqLspClient } from "../../../../../coqLsp/coqLspBuilders"; -import { CoqLspClientInterface } from "../../../../../coqLsp/coqLspClient"; +import { CoqLspClient } from "../../../../../coqLsp/coqLspClient"; import { createSourceFileEnvironment } from "../../../../../core/inspectSourceFile"; @@ -71,7 +71,7 @@ export namespace ParseCoqProjectImpl { async function openAndParseSourceFile( filePath: string, - coqLspClient: CoqLspClientInterface, + coqLspClient: CoqLspClient, logger: Logger ): Promise { const mockDocumentVersion = 1; @@ -106,7 +106,7 @@ export namespace ParseCoqProjectImpl { async function extractFileTargetsFromFile( fileTargets: Signature.ArgsModels.FileTarget[], serializedParsedFile: SerializedParsedCoqFile, - coqLspClient: CoqLspClientInterface, + coqLspClient: CoqLspClient, logger: Logger ): Promise { const parsedTargetsSets: Signature.ResultModels.ParsedFileTarget[][] = @@ -154,7 +154,7 @@ export namespace ParseCoqProjectImpl { theorem: SerializedTheorem, requestType: TargetRequestType, serializedParsedFile: SerializedParsedCoqFile, - coqLspClient: CoqLspClientInterface, + coqLspClient: CoqLspClient, logger: Logger ): Promise { const targetBuilder: ( @@ -201,7 +201,7 @@ export namespace ParseCoqProjectImpl { theoremName: string, knownGoal: SerializedGoal | undefined, serializedParsedFile: SerializedParsedCoqFile, - coqLspClient: CoqLspClientInterface, + coqLspClient: CoqLspClient, logger: Logger ): Promise { let serializedGoal = knownGoal; diff --git a/src/coqLsp/coqLspBuilders.ts b/src/coqLsp/coqLspBuilders.ts index abb4dd31..e79b5bed 100644 --- a/src/coqLsp/coqLspBuilders.ts +++ b/src/coqLsp/coqLspBuilders.ts @@ -2,14 +2,14 @@ import { OutputChannel, window } from "vscode"; import { EventLogger } from "../logging/eventLogger"; -import { CoqLspClient, CoqLspClientInterface } from "./coqLspClient"; +import { CoqLspClient, CoqLspClientImpl } from "./coqLspClient"; import { CoqLspClientConfig, CoqLspConfig } from "./coqLspConfig"; export async function createCoqLspClient( coqLspServerPath: string, logOutputChannel?: OutputChannel, eventLogger?: EventLogger -): Promise { +): Promise { return createAbstractCoqLspClient( CoqLspConfig.createClientConfig(coqLspServerPath), logOutputChannel, @@ -19,7 +19,7 @@ export async function createCoqLspClient( export async function createTestCoqLspClient( workspaceRootPath?: string -): Promise { +): Promise { return createAbstractCoqLspClient( CoqLspConfig.createClientConfig( process.env.COQ_LSP_PATH || "coq-lsp", @@ -34,9 +34,9 @@ async function createAbstractCoqLspClient( "CoqPilot: coq-lsp events" ), eventLogger?: EventLogger -): Promise { +): Promise { const coqLspServerConfig = CoqLspConfig.createServerConfig(); - return await CoqLspClient.create( + return await CoqLspClientImpl.create( coqLspServerConfig, coqLspClientConfig, logOutputChannel, diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index fd265061..08ad600c 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -36,7 +36,7 @@ import { PpString, } from "./coqLspTypes"; -export interface CoqLspClientInterface extends Disposable { +export interface CoqLspClient extends Disposable { /** * Fetches all goals present at the given position in the document. * This method doesn't open the document implicitly, therefore @@ -78,7 +78,7 @@ const flecheDocReqType = new RequestType< export type DiagnosticMessage = string | undefined; -export class CoqLspClient implements CoqLspClientInterface { +export class CoqLspClientImpl implements CoqLspClient { private client: BaseLanguageClient; private subscriptions: Disposable[] = []; private mutex = new Mutex(); @@ -95,7 +95,7 @@ export class CoqLspClient implements CoqLspClientInterface { clientConfig: CoqLspClientConfig, logOutputChannel: OutputChannel, eventLogger?: EventLogger - ): Promise { + ): Promise { const connector = new CoqLspConnector( serverConfig, clientConfig, @@ -107,7 +107,7 @@ export class CoqLspClient implements CoqLspClientInterface { clientConfig.coq_lsp_server_path ); }); - return new CoqLspClient(connector, eventLogger); + return new CoqLspClientImpl(connector, eventLogger); } async getGoalsAtPoint( diff --git a/src/coqParser/parseCoqFile.ts b/src/coqParser/parseCoqFile.ts index efff94c6..8f9e458c 100644 --- a/src/coqParser/parseCoqFile.ts +++ b/src/coqParser/parseCoqFile.ts @@ -2,7 +2,7 @@ import { readFileSync } from "fs"; import { Result } from "ts-results"; import { Position, Range } from "vscode-languageclient"; -import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; +import { CoqLspClient } from "../coqLsp/coqLspClient"; import { CoqParsingError, FlecheDocument, @@ -22,7 +22,7 @@ import { ProofStep, Theorem, TheoremProof, Vernacexpr } from "./parsedTypes"; */ export async function parseCoqFile( uri: Uri, - client: CoqLspClientInterface, + client: CoqLspClient, abortSignal: AbortSignal, retrieveInitialGoal: boolean = true ): Promise { @@ -51,7 +51,7 @@ export async function parseCoqFile( async function parseFlecheDocument( doc: FlecheDocument, textLines: string[], - client: CoqLspClientInterface, + client: CoqLspClient, uri: Uri, abortSignal: AbortSignal, retrieveInitialGoal: boolean diff --git a/src/core/coqProofChecker.ts b/src/core/coqProofChecker.ts index a515cd89..7774b9a4 100644 --- a/src/core/coqProofChecker.ts +++ b/src/core/coqProofChecker.ts @@ -1,7 +1,7 @@ import { Mutex } from "async-mutex"; import { Position } from "vscode-languageclient"; -import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; +import { CoqLspClient } from "../coqLsp/coqLspClient"; import { CoqLspTimeoutError } from "../coqLsp/coqLspTypes"; import { Uri } from "../utils/uri"; @@ -29,7 +29,7 @@ export interface CoqProofCheckerInterface { export class CoqProofChecker implements CoqProofCheckerInterface { private mutex: Mutex = new Mutex(); - constructor(private coqLspClient: CoqLspClientInterface) {} + constructor(private coqLspClient: CoqLspClient) {} async checkProofs( fileUri: Uri, diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index e7b6123a..ef89bd23 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -1,4 +1,4 @@ -import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; +import { CoqLspClient } from "../coqLsp/coqLspClient"; import { parseCoqFile } from "../coqParser/parseCoqFile"; import { ProofStep, Theorem } from "../coqParser/parsedTypes"; @@ -16,7 +16,7 @@ export async function inspectSourceFile( documentVersion: number, shouldCompleteHole: (hole: ProofStep) => boolean, fileUri: Uri, - client: CoqLspClientInterface, + client: CoqLspClient, abortSignal: AbortSignal, rankerNeedsInitialGoals: boolean = true ): Promise { @@ -50,7 +50,7 @@ async function createCompletionContexts( shouldCompleteHole: (hole: ProofStep) => boolean, fileTheorems: Theorem[], fileUri: Uri, - client: CoqLspClientInterface, + client: CoqLspClient, abortSignal: AbortSignal ): Promise { const holesToComplete = fileTheorems @@ -84,7 +84,7 @@ async function createCompletionContexts( export async function createSourceFileEnvironment( documentVersion: number, fileUri: Uri, - client: CoqLspClientInterface, + client: CoqLspClient, abortSignal: AbortSignal, rankerNeedsInitialGoals: boolean = true ): Promise { diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 1c0ffd01..eb329afe 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -132,8 +132,8 @@ export class CoqPilot { async toggleCurrentSession() { if (this.globalExtensionState.hasActiveSession) { this.sessionExtensionState.abort(); - // TODO: [LspCoreRefactor] Change this. Need to reach the throwOnAbort - // checkpoint before disposing the LSP client. + // TODO: [LspCoreRefactor] Change this. Need to reach the throwOnAbort + // checkpoint before disposing the LSP client await sleep(500); this.sessionExtensionState.dispose(); this.globalExtensionState.hasActiveSession = false; diff --git a/src/extension/sessionExtensionState.ts b/src/extension/sessionExtensionState.ts index 7e639c09..063d5a37 100644 --- a/src/extension/sessionExtensionState.ts +++ b/src/extension/sessionExtensionState.ts @@ -1,7 +1,7 @@ import { Disposable, OutputChannel } from "vscode"; import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; -import { CoqLspClientInterface } from "../coqLsp/coqLspClient"; +import { CoqLspClient } from "../coqLsp/coqLspClient"; import { EventLogger } from "../logging/eventLogger"; @@ -11,7 +11,7 @@ import { CompletionAbortError } from "./extensionAbortUtils"; export class SessionExtensionState implements Disposable { public abortController: AbortController = new AbortController(); - private constructor(public readonly coqLspClient: CoqLspClientInterface) {} + private constructor(public readonly coqLspClient: CoqLspClient) {} static async create( logOutputChannel: OutputChannel, diff --git a/src/test/commonTestFunctions/prepareEnvironment.ts b/src/test/commonTestFunctions/prepareEnvironment.ts index ef029e83..d4e51847 100644 --- a/src/test/commonTestFunctions/prepareEnvironment.ts +++ b/src/test/commonTestFunctions/prepareEnvironment.ts @@ -1,7 +1,7 @@ import { ProofGenerationContext } from "../../llm/proofGenerationContext"; import { createTestCoqLspClient } from "../../coqLsp/coqLspBuilders"; -import { CoqLspClientInterface } from "../../coqLsp/coqLspClient"; +import { CoqLspClient } from "../../coqLsp/coqLspClient"; import { CompletionContext, @@ -16,7 +16,7 @@ import { Uri } from "../../utils/uri"; import { resolveResourcesDir } from "./pathsResolver"; export interface PreparedEnvironment { - coqLspClient: CoqLspClientInterface; + coqLspClient: CoqLspClient; coqProofChecker: CoqProofChecker; completionContexts: CompletionContext[]; sourceFileEnvironment: SourceFileEnvironment; diff --git a/src/test/legacyBenchmark/benchmarkingFramework.ts b/src/test/legacyBenchmark/benchmarkingFramework.ts index 9e051aca..3d485604 100644 --- a/src/test/legacyBenchmark/benchmarkingFramework.ts +++ b/src/test/legacyBenchmark/benchmarkingFramework.ts @@ -12,7 +12,7 @@ import { PredefinedProofsService } from "../../llm/llmServices/predefinedProofs/ import { resolveParametersOrThrow } from "../../llm/llmServices/utils/resolveOrThrow"; import { createTestCoqLspClient } from "../../coqLsp/coqLspBuilders"; -import { CoqLspClientInterface } from "../../coqLsp/coqLspClient"; +import { CoqLspClient } from "../../coqLsp/coqLspClient"; import { ProofGoal } from "../../coqLsp/coqLspTypes"; import { @@ -422,7 +422,7 @@ async function extractCompletionTargets( documentVersion: number, shouldCompleteHole: (hole: ProofStep) => boolean, fileUri: Uri, - client: CoqLspClientInterface + client: CoqLspClient ): Promise<[BenchmarkingCompletionTargets, SourceFileEnvironment]> { const abortController = new AbortController(); const sourceFileEnvironment = await createSourceFileEnvironment( @@ -458,7 +458,7 @@ async function createCompletionTargets( shouldCompleteHole: (hole: ProofStep) => boolean, fileTheorems: Theorem[], fileUri: Uri, - client: CoqLspClientInterface + client: CoqLspClient ): Promise { const theoremsWithProofs = fileTheorems.filter((thr) => thr.proof); const admitHolesToComplete = theoremsWithProofs @@ -501,7 +501,7 @@ async function resolveProofStepsToCompletionContexts( parentedProofSteps: ParentedProofStep[], documentVersion: number, fileUri: Uri, - client: CoqLspClientInterface + client: CoqLspClient ): Promise { let completionContexts: BenchmarkingCompletionContext[] = []; for (const parentedProofStep of parentedProofSteps) { From bab8a268abb4377b7b09bb7e6c0d7799dc21e7e0 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Mon, 28 Oct 2024 00:35:16 +0100 Subject: [PATCH 22/30] Finalize aborting `coq-lsp` gracefully --- src/coqLsp/coqLspBuilders.ts | 12 ++++++++---- src/coqLsp/coqLspClient.ts | 20 +++++++++++++++----- src/coqParser/parsedTypes.ts | 1 - src/extension/coqPilot.ts | 17 ++++++++--------- src/extension/extensionAbortUtils.ts | 5 +++-- src/extension/sessionExtensionState.ts | 14 +++++++++----- src/utils/sleep.ts | 3 --- 7 files changed, 43 insertions(+), 29 deletions(-) delete mode 100644 src/utils/sleep.ts diff --git a/src/coqLsp/coqLspBuilders.ts b/src/coqLsp/coqLspBuilders.ts index e79b5bed..934e18ad 100644 --- a/src/coqLsp/coqLspBuilders.ts +++ b/src/coqLsp/coqLspBuilders.ts @@ -8,12 +8,14 @@ import { CoqLspClientConfig, CoqLspConfig } from "./coqLspConfig"; export async function createCoqLspClient( coqLspServerPath: string, logOutputChannel?: OutputChannel, - eventLogger?: EventLogger + eventLogger?: EventLogger, + abortController?: AbortController ): Promise { return createAbstractCoqLspClient( CoqLspConfig.createClientConfig(coqLspServerPath), logOutputChannel, - eventLogger + eventLogger, + abortController ); } @@ -33,13 +35,15 @@ async function createAbstractCoqLspClient( logOutputChannel: OutputChannel = window.createOutputChannel( "CoqPilot: coq-lsp events" ), - eventLogger?: EventLogger + eventLogger?: EventLogger, + abortController?: AbortController ): Promise { const coqLspServerConfig = CoqLspConfig.createServerConfig(); return await CoqLspClientImpl.create( coqLspServerConfig, coqLspClientConfig, logOutputChannel, - eventLogger + eventLogger, + abortController ); } diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index 08ad600c..a5098d1f 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -19,8 +19,8 @@ import { VersionedTextDocumentIdentifier, } from "vscode-languageclient"; +import { throwOnAbort } from "../extension/extensionAbortUtils"; import { EventLogger } from "../logging/eventLogger"; -import { sleep } from "../utils/sleep"; import { Uri } from "../utils/uri"; import { CoqLspClientConfig, CoqLspServerConfig } from "./coqLspConfig"; @@ -85,7 +85,8 @@ export class CoqLspClientImpl implements CoqLspClient { private constructor( coqLspConnector: CoqLspConnector, - public readonly eventLogger?: EventLogger + public readonly eventLogger?: EventLogger, + public readonly abortController?: AbortController ) { this.client = coqLspConnector; } @@ -94,7 +95,8 @@ export class CoqLspClientImpl implements CoqLspClient { serverConfig: CoqLspServerConfig, clientConfig: CoqLspClientConfig, logOutputChannel: OutputChannel, - eventLogger?: EventLogger + eventLogger?: EventLogger, + abortController?: AbortController ): Promise { const connector = new CoqLspConnector( serverConfig, @@ -107,7 +109,7 @@ export class CoqLspClientImpl implements CoqLspClient { clientConfig.coq_lsp_server_path ); }); - return new CoqLspClientImpl(connector, eventLogger); + return new CoqLspClientImpl(connector, eventLogger, abortController); } async getGoalsAtPoint( @@ -117,6 +119,7 @@ export class CoqLspClientImpl implements CoqLspClient { command?: string ): Promise[], Error>> { return await this.mutex.runExclusive(async () => { + throwOnAbort(this.abortController?.signal); return this.getGoalsAtPointUnsafe( position, documentUri, @@ -131,18 +134,21 @@ export class CoqLspClientImpl implements CoqLspClient { version: number = 1 ): Promise { return await this.mutex.runExclusive(async () => { + throwOnAbort(this.abortController?.signal); return this.openTextDocumentUnsafe(uri, version); }); } async closeTextDocument(uri: Uri): Promise { return await this.mutex.runExclusive(async () => { + throwOnAbort(this.abortController?.signal); return this.closeTextDocumentUnsafe(uri); }); } async getFlecheDocument(uri: Uri): Promise { return await this.mutex.runExclusive(async () => { + throwOnAbort(this.abortController?.signal); return this.getFlecheDocumentUnsafe(uri); }); } @@ -237,6 +243,10 @@ export class CoqLspClientImpl implements CoqLspClient { } } + private sleep(ms: number): Promise> { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + private async waitUntilFileFullyChecked( requestType: ProtocolNotificationType, params: any, @@ -285,7 +295,7 @@ export class CoqLspClientImpl implements CoqLspClient { ); while (timeout > 0 && (pendingProgress || pendingDiagnostic)) { - await sleep(100); + await this.sleep(100); timeout -= 100; } diff --git a/src/coqParser/parsedTypes.ts b/src/coqParser/parsedTypes.ts index 54314092..a844f07e 100644 --- a/src/coqParser/parsedTypes.ts +++ b/src/coqParser/parsedTypes.ts @@ -138,7 +138,6 @@ export class TheoremProof { } } -// TODO: [LspCoreRefactor] Refactor Theorem -> Premise // TODO: [LspCoreRefactor] Make Theorem.proof non nullable export class Theorem { constructor( diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index eb329afe..33c173ff 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -23,7 +23,6 @@ import { inspectSourceFile } from "../core/inspectSourceFile"; import { ProofStep } from "../coqParser/parsedTypes"; import { buildErrorCompleteLog } from "../utils/errorsUtils"; -import { sleep } from "../utils/sleep"; import { Uri } from "../utils/uri"; import { @@ -131,12 +130,9 @@ export class CoqPilot { async toggleCurrentSession() { if (this.globalExtensionState.hasActiveSession) { + this.globalExtensionState.hasActiveSession = false; this.sessionExtensionState.abort(); - // TODO: [LspCoreRefactor] Change this. Need to reach the throwOnAbort - // checkpoint before disposing the LSP client - await sleep(500); this.sessionExtensionState.dispose(); - this.globalExtensionState.hasActiveSession = false; this.healthStatusIndicator.updateStatusBar(false); } else { this.sessionExtensionState = await SessionExtensionState.create( @@ -157,7 +153,6 @@ export class CoqPilot { showMessageToUser(EditorMessages.extensionIsPaused, "warning"); return; } - let isSessionHealthy = true; try { this.healthStatusIndicator.showSpinner(); @@ -175,9 +170,11 @@ export class CoqPilot { "error", `${pluginId}.coqLspServerPath` ); - } else if (e instanceof CompletionAbortError) { + } else if ( + e instanceof CompletionAbortError || + !this.globalExtensionState.hasActiveSession + ) { showMessageToUser(EditorMessages.completionAborted, "info"); - isSessionHealthy = false; } else { showMessageToUser( e instanceof Error @@ -188,7 +185,9 @@ export class CoqPilot { console.error(buildErrorCompleteLog(e)); } } finally { - this.healthStatusIndicator.hideSpinner(isSessionHealthy); + this.healthStatusIndicator.hideSpinner( + this.globalExtensionState.hasActiveSession + ); } } diff --git a/src/extension/extensionAbortUtils.ts b/src/extension/extensionAbortUtils.ts index 81c6975b..47731152 100644 --- a/src/extension/extensionAbortUtils.ts +++ b/src/extension/extensionAbortUtils.ts @@ -4,11 +4,12 @@ export class CompletionAbortError extends Error { constructor() { super(CompletionAbortError.abortMessage); + this.name = "CompletionAbortError"; } } -export function throwOnAbort(abortSignal: AbortSignal) { - if (abortSignal.aborted) { +export function throwOnAbort(abortSignal?: AbortSignal) { + if (abortSignal?.aborted) { throw abortSignal.reason; } } diff --git a/src/extension/sessionExtensionState.ts b/src/extension/sessionExtensionState.ts index 063d5a37..463d26fe 100644 --- a/src/extension/sessionExtensionState.ts +++ b/src/extension/sessionExtensionState.ts @@ -9,22 +9,26 @@ import { parseCoqLspServerPath } from "./configReaders"; import { CompletionAbortError } from "./extensionAbortUtils"; export class SessionExtensionState implements Disposable { - public abortController: AbortController = new AbortController(); - - private constructor(public readonly coqLspClient: CoqLspClient) {} + private constructor( + readonly coqLspClient: CoqLspClient, + readonly abortController: AbortController + ) {} static async create( logOutputChannel: OutputChannel, eventLogger: EventLogger ): Promise { + const abortController = new AbortController(); const coqLspServerPath = parseCoqLspServerPath(); + const coqLspClient = await createCoqLspClient( coqLspServerPath, logOutputChannel, - eventLogger + eventLogger, + abortController ); - return new SessionExtensionState(coqLspClient); + return new SessionExtensionState(coqLspClient, abortController); } abort(): void { diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts deleted file mode 100644 index 994de271..00000000 --- a/src/utils/sleep.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function sleep(ms: number): Promise> { - return new Promise((resolve) => setTimeout(resolve, ms)); -} From e12267533be463f1a4aeb2b3b8eca137264714b3 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Mon, 28 Oct 2024 00:53:18 +0100 Subject: [PATCH 23/30] Prepare for v2.3.1 release --- CHANGELOG.md | 14 ++++++++++++++ README.md | 4 +--- package-lock.json | 35 +++++++++-------------------------- package.json | 4 ++-- 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ff17b15..7ea7d76f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 2.3.1 + +The version increment is minor, yet the changes are significant. The main focus of this release was to improve interaction with the `coq-lsp` server. We now completely got rid of temporary files in the `extension` part of `CoqPilot`. This was done thanks to [ejgallego](https://github.com/ejgallego) and his improvements in `coq-lsp`. Now checking of proofs is done via the `proof/goals` request via `command` parameter, which is much more reliable and faster. Now a single `lsp` server is created for the plugin until being explicitly closed, and it tracks changes in the file. If you are using `coq-lsp` plugin itself, it will help you to always keep track of whether file is checked or not. When `CoqPilot` is ran on a completely checked file, it will not bring any significant overhead apart from requests to the Completion Services. + +### Public changes +- Added a toggle button to enable/disable the extension +- Using the toggle switch, user can now abort the proof generation process +- Significant performance improvements in proof checking that are especially easy to see for large files + +### Internal changes +- Get rid of temporary files in the `extension` part of `CoqPilot` +- Refactor `CoqLspClient` +- Make computation of `initialGoal` for premises optional to avoid unnecessary requests to `coq-lsp` + ## 2.3.0 **A major upgrade of the benchmarking system**: At the moment, only a little **new** functionality is provided; moreover, the ability to run benchmarks on Tactician/CoqHammer is temporarily unavailable. However, it will soon be restored and improved. Excessive work has been done to make the benchmarking system more flexible, secure, robust, self-contained, and easy to use. Experiments via our benchmarking framework have been made more accessible than ever. The configurability and reliability of the pipeline have been improved drastically. In a nutshell, the main features of the improved benchmarking system include: diff --git a/README.md b/README.md index 1ea5f4df..f8eb3ce8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# CoqPilot - -![Version](https://img.shields.io/badge/version-v2.3.0-blue?style=flat-square) +# CoqPilot ![Version](https://img.shields.io/badge/version-v2.3.1-blue?style=flat-square) *Authors:* Andrei Kozyrev, Gleb Solovev, Nikita Khramov, and Anton Podkopaev, [Programming Languages and Tools Lab](https://lp.jetbrains.com/research/plt_lab/) at JetBrains Research. diff --git a/package-lock.json b/package-lock.json index 51a9c245..1a236c72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "coqpilot", - "version": "2.3.0", + "version": "2.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "coqpilot", - "version": "2.3.0", + "version": "2.3.1", "dependencies": { "@codemirror/autocomplete": "^6.9.1", "ajv": "^8.12.0", @@ -17,7 +17,7 @@ "event-source-polyfill": "^1.0.31", "i": "^0.3.7", "mocha-param": "^2.0.1", - "node-ipc": "^11.1.0", + "node-ipc": "^12.0.0", "npm": "^10.4.0", "openai": "^4.6.0", "path": "^0.12.7", @@ -3093,15 +3093,13 @@ } }, "node_modules/node-ipc": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-11.1.0.tgz", - "integrity": "sha512-BfE098Uyx06O//uatDNbSNxa5HLejwd2gV33yU0VfYv5wm4e22Um3VnNTOacqSxcl02v4fx6ZNUqJG9ZK+0Ufg==", - "license": "MIT", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-12.0.0.tgz", + "integrity": "sha512-QHJ2gAJiqA3cM7cQiRjLsfCOBRB0TwQ6axYD4FSllQWipEbP6i7Se1dP8EzPKk5J1nCe27W69eqPmCoKyQ61Vg==", "dependencies": { "event-pubsub": "5.0.3", "js-message": "1.0.7", "js-queue": "2.0.2", - "peacenotwar": "^9.1.5", "strong-type": "^1.0.1" }, "engines": { @@ -5690,15 +5688,6 @@ "node": ">=8" } }, - "node_modules/peacenotwar": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/peacenotwar/-/peacenotwar-9.1.7.tgz", - "integrity": "sha512-tMTCcK/MFZQXg6cJPYv1rYAU5V4G1jw7vyJ6cxX35BZ+Jb58of7TTTwktK6LCGouPlyEkIDFLT0KYq/hE1Lstg==", - "license": "GPL-3.0-or-later", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -8911,14 +8900,13 @@ } }, "node-ipc": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-11.1.0.tgz", - "integrity": "sha512-BfE098Uyx06O//uatDNbSNxa5HLejwd2gV33yU0VfYv5wm4e22Um3VnNTOacqSxcl02v4fx6ZNUqJG9ZK+0Ufg==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-12.0.0.tgz", + "integrity": "sha512-QHJ2gAJiqA3cM7cQiRjLsfCOBRB0TwQ6axYD4FSllQWipEbP6i7Se1dP8EzPKk5J1nCe27W69eqPmCoKyQ61Vg==", "requires": { "event-pubsub": "5.0.3", "js-message": "1.0.7", "js-queue": "2.0.2", - "peacenotwar": "^9.1.5", "strong-type": "^1.0.1" } }, @@ -10607,11 +10595,6 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "peacenotwar": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/peacenotwar/-/peacenotwar-9.1.7.tgz", - "integrity": "sha512-tMTCcK/MFZQXg6cJPYv1rYAU5V4G1jw7vyJ6cxX35BZ+Jb58of7TTTwktK6LCGouPlyEkIDFLT0KYq/hE1Lstg==" - }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", diff --git a/package.json b/package.json index f5cce704..1c93e85a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "url": "https://github.com/JetBrains-Research/coqpilot" }, "publisher": "JetBrains-Research", - "version": "2.3.0", + "version": "2.3.1", "engines": { "vscode": "^1.82.0" }, @@ -442,7 +442,7 @@ "event-source-polyfill": "^1.0.31", "i": "^0.3.7", "mocha-param": "^2.0.1", - "node-ipc": "^11.1.0", + "node-ipc": "^12.0.0", "npm": "^10.4.0", "openai": "^4.6.0", "path": "^0.12.7", From 33d11979944c61ee545e5e0f61cf2cc12b381e04 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Wed, 6 Nov 2024 17:00:12 +0100 Subject: [PATCH 24/30] Fix cosmetics from review in PR#46 --- .../proofsCheckers/implementation/internalSignature.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts index 1c5b7d6d..deb6dbb3 100644 --- a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts +++ b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts @@ -7,9 +7,6 @@ export namespace CheckProofsInternalSignature { export interface Args { workspaceRootPath: string | undefined; - // sourceFileDirPath: string; - // sourceFileContentPrefix: string[]; - // prefixEndPosition: Position; fileUri: string; documentVersion: number; checkAtPosition: Position; @@ -17,11 +14,6 @@ export namespace CheckProofsInternalSignature { preparedProofs: string[]; } - // sourceDirPath: string, - // sourceFileContentPrefix: string[], - // prefixEndPosition: Position, - // proofs: Proof[] - export interface Position { line: number; character: number; @@ -105,7 +97,6 @@ export namespace CheckProofsInternalSignature { required: [ "fileUri", "documentVersion", - "documentVersion", "preparedProofs", ], additionalProperties: false, From d1d4952de25abc5354bc3049deb0c22b54150fc7 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Wed, 6 Nov 2024 19:11:16 +0100 Subject: [PATCH 25/30] Fix more issues from review in PR#46 --- .../implementation/internalSignature.ts | 6 +-- .../implementation/parseCoqProject.ts | 4 +- .../parsedCoqFile/parsedCoqFileData.ts | 2 +- .../structures/parsedCoqFile/theoremData.ts | 33 ++++++-------- src/coqLsp/coqLspClient.ts | 22 ++++++++++ src/coqParser/parseCoqFile.ts | 43 ++++++++----------- src/coqParser/parsedTypes.ts | 11 ++--- src/core/inspectSourceFile.ts | 15 ++++--- src/extension/coqPilot.ts | 3 +- src/test/coqParser/parseCoqFile.test.ts | 7 +-- .../legacyBenchmark/benchmarkingFramework.ts | 2 +- 11 files changed, 76 insertions(+), 72 deletions(-) diff --git a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts index deb6dbb3..92e67a59 100644 --- a/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts +++ b/src/benchmark/framework/benchmarkingCore/singleCompletionGeneration/proofsCheckers/implementation/internalSignature.ts @@ -94,11 +94,7 @@ export namespace CheckProofsInternalSignature { }, }, }, - required: [ - "fileUri", - "documentVersion", - "preparedProofs", - ], + required: ["fileUri", "documentVersion", "preparedProofs"], additionalProperties: false, }; diff --git a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts index e742aa35..3343edc2 100644 --- a/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts +++ b/src/benchmark/framework/parseDataset/coqProjectParser/implementation/parseCoqProject.ts @@ -77,6 +77,8 @@ export namespace ParseCoqProjectImpl { const mockDocumentVersion = 1; const sourceFileUri = Uri.fromPath(filePath); await coqLspClient.openTextDocument(sourceFileUri); + // TODO: [@Gleb Solovev] Do not create this Abort Controller but pass + // the one created at the top const abortController = new AbortController(); const sourceFileEnvironment = await createSourceFileEnvironment( mockDocumentVersion, @@ -222,7 +224,7 @@ export namespace ParseCoqProjectImpl { ); throw goal; } else { - const goal = goals.val[0]; + const goal = coqLspClient.getFirstGoalOrThrow(goals); logger.debug( `Successfully retrieved target goal at point: "${goal.ty}" at ${startPosition}, "${serializedParsedFile.filePath}"` ); diff --git a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts index 2394efbe..d9a509f9 100644 --- a/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts +++ b/src/benchmark/framework/structures/parsedCoqFile/parsedCoqFileData.ts @@ -42,7 +42,7 @@ export class ParsedCoqFileData { constructSourceFileEnvironment(): SourceFileEnvironment { return { fileTheorems: this.getOrderedFileTheorems().filter( - (theorem) => theorem.proof && !theorem.proof.is_incomplete + (theorem) => !theorem.proof.is_incomplete ), documentVersion: this.documentVersion, fileUri: Uri.fromPath(this.filePath), diff --git a/src/benchmark/framework/structures/parsedCoqFile/theoremData.ts b/src/benchmark/framework/structures/parsedCoqFile/theoremData.ts index 92d45f34..be01b871 100644 --- a/src/benchmark/framework/structures/parsedCoqFile/theoremData.ts +++ b/src/benchmark/framework/structures/parsedCoqFile/theoremData.ts @@ -33,7 +33,7 @@ export interface SerializedTheorem { name: string; statement_range: SerializedCodeElementRange; statement: string; - proof: SerializedTheoremProof | undefined; + proof: SerializedTheoremProof; initial_goal: SerializedGoal | undefined; fileTheoremsIndex: number; } @@ -100,7 +100,6 @@ export const serializedTheoremSchema: JSONSchemaType = { proof: { type: "object", oneOf: [serializedTheoremProofSchema], - nullable: true, }, initial_goal: { type: "string", @@ -118,15 +117,12 @@ export function deserializeTheoremData( serializedTheorem: SerializedTheorem ): TheoremData { const serializedTheoremProof = serializedTheorem.proof; - let theoremProof: TheoremProof | null = null; - if (serializedTheoremProof !== undefined) { - theoremProof = new TheoremProof( - serializedTheoremProof.proof_steps.map(deserializeProofStep), - deserializeCodeElementRange(serializedTheoremProof.end_pos), - serializedTheoremProof.is_incomplete, - serializedTheoremProof.holes.map(deserializeProofStep) - ); - } + const theoremProof = new TheoremProof( + serializedTheoremProof.proof_steps.map(deserializeProofStep), + deserializeCodeElementRange(serializedTheoremProof.end_pos), + serializedTheoremProof.is_incomplete, + serializedTheoremProof.holes.map(deserializeProofStep) + ); const serializedInitialGoal = serializedTheorem.initial_goal; const initialGoal = serializedInitialGoal === undefined @@ -148,15 +144,12 @@ export function serializeTheoremData( theoremData: TheoremData ): SerializedTheorem { const theoremProof = theoremData.proof; - let serializedTheoremProof: SerializedTheoremProof | undefined = undefined; - if (theoremProof !== null) { - serializedTheoremProof = { - proof_steps: theoremProof.proof_steps.map(serializeProofStep), - end_pos: serializeCodeElementRange(theoremProof.end_pos), - is_incomplete: theoremProof.is_incomplete, - holes: theoremProof.holes.map(serializeProofStep), - }; - } + const serializedTheoremProof = { + proof_steps: theoremProof.proof_steps.map(serializeProofStep), + end_pos: serializeCodeElementRange(theoremProof.end_pos), + is_incomplete: theoremProof.is_incomplete, + holes: theoremProof.holes.map(serializeProofStep), + }; const initialGoal = theoremData.sourceTheorem.initial_goal; const serializedInitialGoal = initialGoal === null ? undefined : serializeGoal(initialGoal); diff --git a/src/coqLsp/coqLspClient.ts b/src/coqLsp/coqLspClient.ts index a5098d1f..6bf7b16f 100644 --- a/src/coqLsp/coqLspClient.ts +++ b/src/coqLsp/coqLspClient.ts @@ -64,6 +64,12 @@ export interface CoqLspClient extends Disposable { openTextDocument(uri: Uri, version?: number): Promise; closeTextDocument(uri: Uri): Promise; + + /** + * + * @param goals + */ + getFirstGoalOrThrow(goals: Result[], Error>): Goal; } const goalReqType = new RequestType, void>( @@ -153,6 +159,22 @@ export class CoqLspClientImpl implements CoqLspClient { }); } + getFirstGoalOrThrow( + goals: Result[], Error> + ): Goal { + if (goals.err) { + throw new CoqLspError( + "Call to `getFirstGoalOrThrow` with a Error type" + ); + } else if (goals.val.length === 0) { + throw new CoqLspError( + "Call to `getFirstGoalOrThrow` with an empty set of goals" + ); + } + + return goals.val[0]; + } + /** * Dirty due to the fact that the client sends no pure * error: https://github.com/ejgallego/coq-lsp/blob/f98b65344c961d1aad1e0c3785199258f21c3abc/controller/request.ml#L29 diff --git a/src/coqParser/parseCoqFile.ts b/src/coqParser/parseCoqFile.ts index 8f9e458c..d306139b 100644 --- a/src/coqParser/parseCoqFile.ts +++ b/src/coqParser/parseCoqFile.ts @@ -12,19 +12,22 @@ import { } from "../coqLsp/coqLspTypes"; import { throwOnAbort } from "../extension/extensionAbortUtils"; +import { EventLogger } from "../logging/eventLogger"; import { Uri } from "../utils/uri"; import { ProofStep, Theorem, TheoremProof, Vernacexpr } from "./parsedTypes"; /** - * TODO: [@Gleb Solovev] Refactor retrieveInitialGoal param - * to be something more reasonable and readable. + * As we have decided that premises = only theorems/definitions + * with existing proofs, parseCoqFile ignores items without proofs + * and does not add them into the resulting array. */ export async function parseCoqFile( uri: Uri, client: CoqLspClient, abortSignal: AbortSignal, - retrieveInitialGoal: boolean = true + extractTheoremInitialGoal: boolean = true, + eventLogger?: EventLogger ): Promise { return client .getFlecheDocument(uri) @@ -38,7 +41,8 @@ export async function parseCoqFile( client, uri, abortSignal, - retrieveInitialGoal + extractTheoremInitialGoal, + eventLogger ); }) .catch((error) => { @@ -54,7 +58,8 @@ async function parseFlecheDocument( client: CoqLspClient, uri: Uri, abortSignal: AbortSignal, - retrieveInitialGoal: boolean + extractTheoremInitialGoal: boolean, + eventLogger?: EventLogger ): Promise { if (doc === null) { throw Error("could not parse file"); @@ -84,14 +89,9 @@ async function parseFlecheDocument( const nextExprVernac = getVernacexpr(getExpr(doc.spans[i + 1])); if (i + 1 >= doc.spans.length) { - theorems.push( - new Theorem( - thrName, - doc.spans[i].range, - thrStatement, - null, - null - ) + eventLogger?.log( + "premise-has-no-proof", + `Could not parse the proof in theorem/definition ${thrName}.` ); } else if (!nextExprVernac) { throw new CoqParsingError("unable to parse proof"); @@ -102,21 +102,16 @@ async function parseFlecheDocument( Vernacexpr.VernacEndProof, ].includes(nextExprVernac) ) { - theorems.push( - new Theorem( - thrName, - doc.spans[i].range, - thrStatement, - null, - null - ) + eventLogger?.log( + "premise-has-no-proof", + `Could not parse the proof in theorem/definition ${thrName}.` ); } else { - // TODO: Might be a source of bugs if somewhere absense of initialGoal - // is not handled properly or invariants are broken + // TODO: Cover with tests, might be a source of bugs if somewhere + // absense of initialGoal is not handled properly or invariants are broken let initialGoal: Result[], Error> | null = null; - if (retrieveInitialGoal) { + if (extractTheoremInitialGoal) { initialGoal = await client.getGoalsAtPoint( doc.spans[i + 1].range.start, uri, diff --git a/src/coqParser/parsedTypes.ts b/src/coqParser/parsedTypes.ts index a844f07e..530aa504 100644 --- a/src/coqParser/parsedTypes.ts +++ b/src/coqParser/parsedTypes.ts @@ -138,29 +138,24 @@ export class TheoremProof { } } -// TODO: [LspCoreRefactor] Make Theorem.proof non nullable export class Theorem { constructor( public name: string, public statement_range: Range, public statement: string, - public proof: TheoremProof | null = null, + public proof: TheoremProof, public initial_goal: Goal | null = null ) {} public toString(): string { let text = this.statement; - if (this.proof !== null) { - text += "\n" + this.proof.toString(); - } + text += "\n" + this.proof.toString(); return text; } public onlyText(): string { let text = this.statement; - if (this.proof !== null) { - text += "\n" + this.proof.onlyText(); - } + text += "\n" + this.proof.onlyText(); return text; } } diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index ef89bd23..3a73199b 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -3,6 +3,7 @@ import { CoqLspClient } from "../coqLsp/coqLspClient"; import { parseCoqFile } from "../coqParser/parseCoqFile"; import { ProofStep, Theorem } from "../coqParser/parsedTypes"; import { throwOnAbort } from "../extension/extensionAbortUtils"; +import { EventLogger } from "../logging/eventLogger"; import { Uri } from "../utils/uri"; import { @@ -18,14 +19,16 @@ export async function inspectSourceFile( fileUri: Uri, client: CoqLspClient, abortSignal: AbortSignal, - rankerNeedsInitialGoals: boolean = true + rankerNeedsInitialGoals: boolean = true, + eventLogger?: EventLogger ): Promise { const sourceFileEnvironment = await createSourceFileEnvironment( documentVersion, fileUri, client, abortSignal, - rankerNeedsInitialGoals + rankerNeedsInitialGoals, + eventLogger ); const completionContexts = await createCompletionContexts( documentVersion, @@ -38,7 +41,7 @@ export async function inspectSourceFile( const sourceFileEnvironmentWithCompleteProofs: SourceFileEnvironment = { ...sourceFileEnvironment, fileTheorems: sourceFileEnvironment.fileTheorems.filter( - (thr) => thr.proof && !thr.proof.is_incomplete + (thr) => !thr.proof.is_incomplete ), }; @@ -86,13 +89,15 @@ export async function createSourceFileEnvironment( fileUri: Uri, client: CoqLspClient, abortSignal: AbortSignal, - rankerNeedsInitialGoals: boolean = true + rankerNeedsInitialGoals: boolean = true, + eventLogger?: EventLogger ): Promise { const fileTheorems = await parseCoqFile( fileUri, client, abortSignal, - rankerNeedsInitialGoals + rankerNeedsInitialGoals, + eventLogger ); return { diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 33c173ff..1551ca48 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -339,7 +339,8 @@ export class CoqPilot { fileUri, this.sessionExtensionState.coqLspClient, abortSignal, - contextTheoremsRanker.needsUnwrappedNotations + contextTheoremsRanker.needsUnwrappedNotations, + this.globalExtensionState.eventLogger ); const processEnvironment: ProcessEnvironment = { coqProofChecker: coqProofChecker, diff --git a/src/test/coqParser/parseCoqFile.test.ts b/src/test/coqParser/parseCoqFile.test.ts index 1d00ce1d..cc4c06fd 100644 --- a/src/test/coqParser/parseCoqFile.test.ts +++ b/src/test/coqParser/parseCoqFile.test.ts @@ -100,13 +100,8 @@ suite("Coq file parser tests", () => { "test_7", ]; - const theoremsWithoutProof = doc.filter((theorem) => !theorem.proof); - const theoremsWithProof = doc.filter((theorem) => theorem.proof); - const theoremNames = theoremsWithProof.map((theorem) => theorem.name); - + const theoremNames = doc.map((theorem) => theorem.name); expect(theoremNames).toEqual(theoremData); - expect(theoremsWithoutProof).toHaveLength(1); - expect(theoremsWithoutProof[0].name).toEqual("test_5"); }); test("Test parse file which is part of project", async () => { diff --git a/src/test/legacyBenchmark/benchmarkingFramework.ts b/src/test/legacyBenchmark/benchmarkingFramework.ts index 3d485604..84374d8a 100644 --- a/src/test/legacyBenchmark/benchmarkingFramework.ts +++ b/src/test/legacyBenchmark/benchmarkingFramework.ts @@ -441,7 +441,7 @@ async function extractCompletionTargets( const sourceFileEnvironmentWithCompleteProofs: SourceFileEnvironment = { ...sourceFileEnvironment, fileTheorems: sourceFileEnvironment.fileTheorems.filter( - (thr) => thr.proof && !thr.proof.is_incomplete + (thr) => !thr.proof.is_incomplete ), }; From 09a34200273aa2f2c903840615e0317fb6eaf5f5 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Wed, 6 Nov 2024 19:25:48 +0100 Subject: [PATCH 26/30] Code style fixes and core in rankers from review in PR#46 --- src/core/completionGenerator.ts | 21 +++++++------------ .../distanceContextTheoremsRanker.ts | 2 +- .../euclidContextTheoremRanker.ts | 2 +- .../jaccardIndexContextTheoremsRanker.ts | 2 +- .../randomContextTheoremsRanker.ts | 2 +- .../weightedJaccardIndexTheoremRanker.ts | 2 +- .../\321\201osineContextTheoremRanker.ts" | 2 +- src/extension/coqPilot.ts | 4 ++-- 8 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/core/completionGenerator.ts b/src/core/completionGenerator.ts index 09bb292c..48068684 100644 --- a/src/core/completionGenerator.ts +++ b/src/core/completionGenerator.ts @@ -86,6 +86,7 @@ export async function generateCompletion( completionContext, sourceFileEnvironment, processEnvironment, + abortSignal, eventLogger, perProofTimeoutMillis ); @@ -96,11 +97,13 @@ export async function generateCompletion( } while (newlyGeneratedProofs.length > 0) { + throwOnAbort(abortSignal); const fixedProofsOrCompletion = await checkAndFixProofs( newlyGeneratedProofs, completionContext, sourceFileEnvironment, processEnvironment, + abortSignal, eventLogger, perProofTimeoutMillis ); @@ -143,6 +146,7 @@ async function checkAndFixProofs( completionContext: CompletionContext, sourceFileEnvironment: SourceFileEnvironment, processEnvironment: ProcessEnvironment, + abortSignal: AbortSignal, eventLogger?: EventLogger, perProofTimeoutMillis: number = 15000 ): Promise { @@ -169,7 +173,7 @@ async function checkAndFixProofs( }; } ); - const fixedProofs = await fixProofs(proofsWithFeedback); + const fixedProofs = await fixProofs(proofsWithFeedback, abortSignal); eventLogger?.log( "core-proofs-fixed", "Proofs were fixed", @@ -193,17 +197,6 @@ async function checkGeneratedProofs( prepareProofToCheck(generatedProof.proof()) ); - // TODO: Why is it happening every time? - // if (workspaceRootPath) { - // processEnvironment.coqProofChecker.dispose(); - // const client = await createCoqLspClient( - // workspaceRootPath, - // logOutputChannel - // ); - // const coqProofChecker = new CoqProofChecker(client); - // processEnvironment.coqProofChecker = coqProofChecker; - // } - return processEnvironment.coqProofChecker.checkProofs( sourceFileEnvironment.fileUri, sourceFileEnvironment.documentVersion, @@ -219,12 +212,14 @@ interface ProofWithFeedback { } async function fixProofs( - proofsWithFeedback: ProofWithFeedback[] + proofsWithFeedback: ProofWithFeedback[], + abortSignal: AbortSignal ): Promise { const fixProofsPromises = []; // build fix promises for (const proofWithFeedback of proofsWithFeedback) { + throwOnAbort(abortSignal); const generatedProof = proofWithFeedback.generatedProof; if (!generatedProof.canBeFixed()) { continue; diff --git a/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts b/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts index 769377a0..b48d52f4 100644 --- a/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts +++ b/src/core/contextTheoremRanker/distanceContextTheoremsRanker.ts @@ -4,7 +4,7 @@ import { CompletionContext } from "../completionGenerationContext"; import { ContextTheoremsRanker } from "./contextTheoremsRanker"; export class DistanceContextTheoremsRanker implements ContextTheoremsRanker { - public readonly needsUnwrappedNotations = false; + readonly needsUnwrappedNotations = false; rankContextTheorems( theorems: Theorem[], diff --git a/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts b/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts index 1e366c2c..0be8058d 100644 --- a/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts +++ b/src/core/contextTheoremRanker/euclidContextTheoremRanker.ts @@ -11,7 +11,7 @@ import { goalAsTheoremString } from "./tokenUtils"; * */ export class EuclidContextTheoremsRanker implements ContextTheoremsRanker { - public readonly needsUnwrappedNotations = true; + readonly needsUnwrappedNotations = true; rankContextTheorems( theorems: Theorem[], diff --git a/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts b/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts index cbd3b4b9..389bf6ca 100644 --- a/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts +++ b/src/core/contextTheoremRanker/jaccardIndexContextTheoremsRanker.ts @@ -14,7 +14,7 @@ import { goalAsTheoremString } from "./tokenUtils"; export class JaccardIndexContextTheoremsRanker implements ContextTheoremsRanker { - public readonly needsUnwrappedNotations = true; + readonly needsUnwrappedNotations = true; rankContextTheorems( theorems: Theorem[], diff --git a/src/core/contextTheoremRanker/randomContextTheoremsRanker.ts b/src/core/contextTheoremRanker/randomContextTheoremsRanker.ts index aa2e548f..97b90c31 100644 --- a/src/core/contextTheoremRanker/randomContextTheoremsRanker.ts +++ b/src/core/contextTheoremRanker/randomContextTheoremsRanker.ts @@ -4,7 +4,7 @@ import { CompletionContext } from "../completionGenerationContext"; import { ContextTheoremsRanker } from "./contextTheoremsRanker"; export class RandomContextTheoremsRanker implements ContextTheoremsRanker { - public readonly needsUnwrappedNotations = false; + readonly needsUnwrappedNotations = false; private shuffleArray(array: any[]) { for (let i = array.length - 1; i > 0; i--) { diff --git a/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts b/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts index a4eab92b..03db43dd 100644 --- a/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts +++ b/src/core/contextTheoremRanker/weightedJaccardIndexTheoremRanker.ts @@ -14,7 +14,7 @@ import { goalAsTheoremString } from "./tokenUtils"; export class WeightedJaccardIndexContextTheoremsRanker implements ContextTheoremsRanker { - public readonly needsUnwrappedNotations = true; + readonly needsUnwrappedNotations = true; rankContextTheorems( theorems: Theorem[], diff --git "a/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" "b/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" index 0ec9c825..cd31d402 100644 --- "a/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" +++ "b/src/core/contextTheoremRanker/\321\201osineContextTheoremRanker.ts" @@ -12,7 +12,7 @@ import { goalAsTheoremString } from "./tokenUtils"; * ```cosine(A, B) = |A ∩ B| / sqrt(|A| * |B|)``` */ export class CosineContextTheoremsRanker implements ContextTheoremsRanker { - public readonly needsUnwrappedNotations = true; + readonly needsUnwrappedNotations = true; rankContextTheorems( theorems: Theorem[], diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 1551ca48..54fa27b6 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -1,7 +1,7 @@ import { - ExtensionContext, // ProgressLocation, + ExtensionContext, TextEditor, - commands, // window, + commands, workspace, } from "vscode"; From c58c89ffb3a3055e1830854ae493d3fa1fad749a Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Wed, 6 Nov 2024 19:34:08 +0100 Subject: [PATCH 27/30] Refactoring `healthStatusIndicator` according to review in PR#46 --- src/core/inspectSourceFile.ts | 8 ++------ src/extension/coqPilot.ts | 16 ++++++++-------- ...atusIndicator.ts => pluginStatusIndicator.ts} | 8 ++++---- 3 files changed, 14 insertions(+), 18 deletions(-) rename src/extension/{healthStatusIndicator.ts => pluginStatusIndicator.ts} (87%) diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index 3a73199b..62f3033d 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -2,7 +2,6 @@ import { CoqLspClient } from "../coqLsp/coqLspClient"; import { parseCoqFile } from "../coqParser/parseCoqFile"; import { ProofStep, Theorem } from "../coqParser/parsedTypes"; -import { throwOnAbort } from "../extension/extensionAbortUtils"; import { EventLogger } from "../logging/eventLogger"; import { Uri } from "../utils/uri"; @@ -35,8 +34,7 @@ export async function inspectSourceFile( shouldCompleteHole, sourceFileEnvironment.fileTheorems, fileUri, - client, - abortSignal + client ); const sourceFileEnvironmentWithCompleteProofs: SourceFileEnvironment = { ...sourceFileEnvironment, @@ -53,8 +51,7 @@ async function createCompletionContexts( shouldCompleteHole: (hole: ProofStep) => boolean, fileTheorems: Theorem[], fileUri: Uri, - client: CoqLspClient, - abortSignal: AbortSignal + client: CoqLspClient ): Promise { const holesToComplete = fileTheorems .filter((thr) => thr.proof) @@ -64,7 +61,6 @@ async function createCompletionContexts( let completionContexts: CompletionContext[] = []; for (const hole of holesToComplete) { - throwOnAbort(abortSignal); const goals = await client.getGoalsAtPoint( hole.range.start, fileUri, diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 54fa27b6..408bfd5c 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -41,7 +41,7 @@ import { } from "./editorMessages"; import { CompletionAbortError, throwOnAbort } from "./extensionAbortUtils"; import { GlobalExtensionState } from "./globalExtensionState"; -import { HealthStatusIndicator } from "./healthStatusIndicator"; +import { PluginStatusIndicator } from "./pluginStatusIndicator"; import { subscribeToHandleLLMServicesEvents } from "./llmServicesEventsHandler"; import { positionInRange, @@ -55,7 +55,7 @@ export const pluginId = "coqpilot"; export const pluginName = "CoqPilot"; export class CoqPilot { - private readonly healthStatusIndicator: HealthStatusIndicator; + private readonly pluginStatusIndicator: PluginStatusIndicator; private constructor( private readonly vscodeExtensionContext: ExtensionContext, @@ -80,11 +80,11 @@ export class CoqPilot { toggleCommand, this.toggleCurrentSession.bind(this) ); - this.healthStatusIndicator = new HealthStatusIndicator( + this.pluginStatusIndicator = new PluginStatusIndicator( `${pluginId}.${toggleCommand}`, vscodeExtensionContext ); - this.healthStatusIndicator.updateStatusBar(true); + this.pluginStatusIndicator.updateStatusBar(true); this.vscodeExtensionContext.subscriptions.push(this); } @@ -133,14 +133,14 @@ export class CoqPilot { this.globalExtensionState.hasActiveSession = false; this.sessionExtensionState.abort(); this.sessionExtensionState.dispose(); - this.healthStatusIndicator.updateStatusBar(false); + this.pluginStatusIndicator.updateStatusBar(false); } else { this.sessionExtensionState = await SessionExtensionState.create( this.globalExtensionState.logOutputChannel, this.globalExtensionState.eventLogger ); this.globalExtensionState.hasActiveSession = true; - this.healthStatusIndicator.updateStatusBar(true); + this.pluginStatusIndicator.updateStatusBar(true); } } @@ -154,7 +154,7 @@ export class CoqPilot { return; } try { - this.healthStatusIndicator.showSpinner(); + this.pluginStatusIndicator.showInProgressSpinner(); await this.performSpecificCompletions( shouldCompleteHole, @@ -185,7 +185,7 @@ export class CoqPilot { console.error(buildErrorCompleteLog(e)); } } finally { - this.healthStatusIndicator.hideSpinner( + this.pluginStatusIndicator.hideInProgressSpinner( this.globalExtensionState.hasActiveSession ); } diff --git a/src/extension/healthStatusIndicator.ts b/src/extension/pluginStatusIndicator.ts similarity index 87% rename from src/extension/healthStatusIndicator.ts rename to src/extension/pluginStatusIndicator.ts index 5f582ec4..30182c7f 100644 --- a/src/extension/healthStatusIndicator.ts +++ b/src/extension/pluginStatusIndicator.ts @@ -7,8 +7,8 @@ import { import { pluginName } from "./coqPilot"; -export class HealthStatusIndicator { - private statusBarItem: StatusBarItem; +export class PluginStatusIndicator { + private readonly statusBarItem: StatusBarItem; constructor( invocationCommand: string, @@ -34,12 +34,12 @@ export class HealthStatusIndicator { } } - showSpinner() { + showInProgressSpinner() { this.statusBarItem.text = `$(sync~spin) ${pluginName}: In progress`; this.statusBarItem.tooltip = "Operation in progress..."; } - hideSpinner(isActive: boolean) { + hideInProgressSpinner(isActive: boolean) { this.updateStatusBar(isActive); } From a8ac5b8e04f5d59e0ebbc943c85f9973d6d754d1 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Wed, 6 Nov 2024 19:45:15 +0100 Subject: [PATCH 28/30] Renamings according to review in PR#46 --- src/core/inspectSourceFile.ts | 8 +-- src/extension/coqPilot.ts | 78 +++++++++++++------------- src/extension/editorMessages.ts | 2 +- src/extension/extensionAbortUtils.ts | 4 +- src/extension/globalExtensionState.ts | 2 +- src/extension/sessionExtensionState.ts | 6 +- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/core/inspectSourceFile.ts b/src/core/inspectSourceFile.ts index 62f3033d..befd0914 100644 --- a/src/core/inspectSourceFile.ts +++ b/src/core/inspectSourceFile.ts @@ -18,7 +18,7 @@ export async function inspectSourceFile( fileUri: Uri, client: CoqLspClient, abortSignal: AbortSignal, - rankerNeedsInitialGoals: boolean = true, + needsTheoremInitialGoals: boolean = true, eventLogger?: EventLogger ): Promise { const sourceFileEnvironment = await createSourceFileEnvironment( @@ -26,7 +26,7 @@ export async function inspectSourceFile( fileUri, client, abortSignal, - rankerNeedsInitialGoals, + needsTheoremInitialGoals, eventLogger ); const completionContexts = await createCompletionContexts( @@ -85,14 +85,14 @@ export async function createSourceFileEnvironment( fileUri: Uri, client: CoqLspClient, abortSignal: AbortSignal, - rankerNeedsInitialGoals: boolean = true, + needsTheoremInitialGoals: boolean = true, eventLogger?: EventLogger ): Promise { const fileTheorems = await parseCoqFile( fileUri, client, abortSignal, - rankerNeedsInitialGoals, + needsTheoremInitialGoals, eventLogger ); diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 408bfd5c..6f160786 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -1,5 +1,5 @@ import { - ExtensionContext, + ExtensionContext as VSCodeContext, TextEditor, commands, workspace, @@ -40,7 +40,7 @@ import { showMessageToUserWithSettingsHint, } from "./editorMessages"; import { CompletionAbortError, throwOnAbort } from "./extensionAbortUtils"; -import { GlobalExtensionState } from "./globalExtensionState"; +import { PluginState } from "./globalExtensionState"; import { PluginStatusIndicator } from "./pluginStatusIndicator"; import { subscribeToHandleLLMServicesEvents } from "./llmServicesEventsHandler"; import { @@ -48,7 +48,7 @@ import { toVSCodePosition, toVSCodeRange, } from "./positionRangeUtils"; -import { SessionExtensionState } from "./sessionExtensionState"; +import { SessionState } from "./sessionExtensionState"; import { SettingsValidationError } from "./settingsValidationError"; export const pluginId = "coqpilot"; @@ -58,9 +58,9 @@ export class CoqPilot { private readonly pluginStatusIndicator: PluginStatusIndicator; private constructor( - private readonly vscodeExtensionContext: ExtensionContext, - private readonly globalExtensionState: GlobalExtensionState, - private sessionExtensionState: SessionExtensionState + private readonly vscodeContext: VSCodeContext, + private readonly pluginState: PluginState, + private sessionState: SessionState ) { this.registerEditorCommand( "perform_completion_under_cursor", @@ -82,16 +82,16 @@ export class CoqPilot { ); this.pluginStatusIndicator = new PluginStatusIndicator( `${pluginId}.${toggleCommand}`, - vscodeExtensionContext + vscodeContext ); this.pluginStatusIndicator.updateStatusBar(true); - this.vscodeExtensionContext.subscriptions.push(this); + this.vscodeContext.subscriptions.push(this); } - static async create(vscodeExtensionContext: ExtensionContext) { - const globalExtensionState = new GlobalExtensionState(); - const sessionExtensionState = await SessionExtensionState.create( + static async create(vscodeExtensionContext: VSCodeContext) { + const globalExtensionState = new PluginState(); + const sessionExtensionState = await SessionState.create( globalExtensionState.logOutputChannel, globalExtensionState.eventLogger ); @@ -107,7 +107,7 @@ export class CoqPilot { this.performSpecificCompletionsWithProgress( (hole) => positionInRange(cursorPosition, hole.range), editor, - this.sessionExtensionState.abortController.signal + this.sessionState.abortController.signal ); } @@ -116,7 +116,7 @@ export class CoqPilot { this.performSpecificCompletionsWithProgress( (hole) => selection.contains(toVSCodePosition(hole.range.start)), editor, - this.sessionExtensionState.abortController.signal + this.sessionState.abortController.signal ); } @@ -124,22 +124,22 @@ export class CoqPilot { this.performSpecificCompletionsWithProgress( (_hole) => true, editor, - this.sessionExtensionState.abortController.signal + this.sessionState.abortController.signal ); } async toggleCurrentSession() { - if (this.globalExtensionState.hasActiveSession) { - this.globalExtensionState.hasActiveSession = false; - this.sessionExtensionState.abort(); - this.sessionExtensionState.dispose(); + if (this.pluginState.hasActiveSession) { + this.pluginState.hasActiveSession = false; + this.sessionState.abort(); + this.sessionState.dispose(); this.pluginStatusIndicator.updateStatusBar(false); } else { - this.sessionExtensionState = await SessionExtensionState.create( - this.globalExtensionState.logOutputChannel, - this.globalExtensionState.eventLogger + this.sessionState = await SessionState.create( + this.pluginState.logOutputChannel, + this.pluginState.eventLogger ); - this.globalExtensionState.hasActiveSession = true; + this.pluginState.hasActiveSession = true; this.pluginStatusIndicator.updateStatusBar(true); } } @@ -149,7 +149,7 @@ export class CoqPilot { editor: TextEditor, abortSignal: AbortSignal ) { - if (!this.globalExtensionState.hasActiveSession) { + if (!this.pluginState.hasActiveSession) { showMessageToUser(EditorMessages.extensionIsPaused, "warning"); return; } @@ -172,7 +172,7 @@ export class CoqPilot { ); } else if ( e instanceof CompletionAbortError || - !this.globalExtensionState.hasActiveSession + !this.pluginState.hasActiveSession ) { showMessageToUser(EditorMessages.completionAborted, "info"); } else { @@ -186,7 +186,7 @@ export class CoqPilot { } } finally { this.pluginStatusIndicator.hideInProgressSpinner( - this.globalExtensionState.hasActiveSession + this.pluginState.hasActiveSession ); } } @@ -196,7 +196,7 @@ export class CoqPilot { editor: TextEditor, abortSignal: AbortSignal ) { - this.globalExtensionState.eventLogger.log( + this.pluginState.eventLogger.log( "completion-started", "CoqPilot has started the completion process" ); @@ -216,7 +216,7 @@ export class CoqPilot { editor.document.uri.fsPath, abortSignal ); - this.globalExtensionState.eventLogger.log( + this.pluginState.eventLogger.log( "completion-preparation-finished", `CoqPilot has successfully parsed the file with ${sourceFileEnvironment.fileTheorems.length} theorems and has found ${completionContexts.length} admits inside chosen selection` ); @@ -228,8 +228,8 @@ export class CoqPilot { const unsubscribeFromLLMServicesEventsCallback = subscribeToHandleLLMServicesEvents( - this.globalExtensionState.llmServices, - this.globalExtensionState.eventLogger + this.pluginState.llmServices, + this.pluginState.eventLogger ); try { @@ -264,7 +264,7 @@ export class CoqPilot { sourceFileEnvironment, processEnvironment, abortSignal, - this.globalExtensionState.eventLogger + this.pluginState.eventLogger ); if (result instanceof SuccessGenerationResult) { @@ -330,25 +330,25 @@ export class CoqPilot { const contextTheoremsRanker = buildTheoremsRankerFromConfig(); const coqProofChecker = new CoqProofChecker( - this.sessionExtensionState.coqLspClient + this.sessionState.coqLspClient ); const [completionContexts, sourceFileEnvironment] = await inspectSourceFile( documentVersion, shouldCompleteHole, fileUri, - this.sessionExtensionState.coqLspClient, + this.sessionState.coqLspClient, abortSignal, contextTheoremsRanker.needsUnwrappedNotations, - this.globalExtensionState.eventLogger + this.pluginState.eventLogger ); const processEnvironment: ProcessEnvironment = { coqProofChecker: coqProofChecker, modelsParams: readAndValidateUserModelsParams( workspace.getConfiguration(pluginId), - this.globalExtensionState.llmServices + this.pluginState.llmServices ), - services: this.globalExtensionState.llmServices, + services: this.pluginState.llmServices, theoremRanker: contextTheoremsRanker, }; @@ -363,12 +363,12 @@ export class CoqPilot { `${pluginId}.` + command, fn ); - this.vscodeExtensionContext.subscriptions.push(disposable); + this.vscodeContext.subscriptions.push(disposable); } dispose(): void { - this.vscodeExtensionContext.subscriptions.forEach((d) => d.dispose()); - this.globalExtensionState.dispose(); - this.sessionExtensionState.dispose(); + this.vscodeContext.subscriptions.forEach((d) => d.dispose()); + this.sessionState.dispose(); + this.pluginState.dispose(); } } diff --git a/src/extension/editorMessages.ts b/src/extension/editorMessages.ts index 4641d2c0..cd22d2c1 100644 --- a/src/extension/editorMessages.ts +++ b/src/extension/editorMessages.ts @@ -32,7 +32,7 @@ export namespace EditorMessages { `Coqpilot got an unexpected error: ${errorDescription}. Please report this crash by opening an issue in the Coqpilot GitHub repository.`; export const completionAborted = - "Completion generation was forcefully aborted, Coq-LSP server stopping. Please hit the CoqPilot Status Bar button again to restart the server and proceed with completions."; + "Completion generation was forcefully aborted, stopping Coq-LSP server. Please hit the CoqPilot Status Bar button again to restart the server and continue using CoqPilot."; export const extensionIsPaused = "You have stopped CoqPilot. To resume, click on the CoqPilot icon in the status bar."; diff --git a/src/extension/extensionAbortUtils.ts b/src/extension/extensionAbortUtils.ts index 47731152..27486216 100644 --- a/src/extension/extensionAbortUtils.ts +++ b/src/extension/extensionAbortUtils.ts @@ -1,6 +1,6 @@ export class CompletionAbortError extends Error { - static readonly abortMessage = - "User has triggered a sesion abort: Stopping all completions"; + private static readonly abortMessage = + "User has triggered a session abort: Stopping all completions"; constructor() { super(CompletionAbortError.abortMessage); diff --git a/src/extension/globalExtensionState.ts b/src/extension/globalExtensionState.ts index 4974e8ba..806b5ba6 100644 --- a/src/extension/globalExtensionState.ts +++ b/src/extension/globalExtensionState.ts @@ -14,7 +14,7 @@ import { EventLogger, Severity } from "../logging/eventLogger"; import { pluginId } from "./coqPilot"; import VSCodeLogWriter from "./vscodeLogWriter"; -export class GlobalExtensionState implements Disposable { +export class PluginState implements Disposable { public readonly eventLogger: EventLogger = new EventLogger(); public hasActiveSession = true; public readonly logWriter: VSCodeLogWriter = new VSCodeLogWriter( diff --git a/src/extension/sessionExtensionState.ts b/src/extension/sessionExtensionState.ts index 463d26fe..f49b4f4b 100644 --- a/src/extension/sessionExtensionState.ts +++ b/src/extension/sessionExtensionState.ts @@ -8,7 +8,7 @@ import { EventLogger } from "../logging/eventLogger"; import { parseCoqLspServerPath } from "./configReaders"; import { CompletionAbortError } from "./extensionAbortUtils"; -export class SessionExtensionState implements Disposable { +export class SessionState implements Disposable { private constructor( readonly coqLspClient: CoqLspClient, readonly abortController: AbortController @@ -17,7 +17,7 @@ export class SessionExtensionState implements Disposable { static async create( logOutputChannel: OutputChannel, eventLogger: EventLogger - ): Promise { + ): Promise { const abortController = new AbortController(); const coqLspServerPath = parseCoqLspServerPath(); @@ -28,7 +28,7 @@ export class SessionExtensionState implements Disposable { abortController ); - return new SessionExtensionState(coqLspClient, abortController); + return new SessionState(coqLspClient, abortController); } abort(): void { From 0b36cc38f1a511c71cdf4fceac0a0e1615f5175d Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Wed, 6 Nov 2024 23:56:49 +0100 Subject: [PATCH 29/30] More accurate `completionAbortError` handling --- src/core/completionGenerator.ts | 4 +- src/extension/coqPilot.ts | 115 ++++++--------- ...obalExtensionState.ts => pluginContext.ts} | 3 +- src/extension/sessionExtensionState.ts | 42 ------ src/extension/sessionState.ts | 137 ++++++++++++++++++ src/test/resources/small_document.v | 6 +- 6 files changed, 190 insertions(+), 117 deletions(-) rename src/extension/{globalExtensionState.ts => pluginContext.ts} (96%) delete mode 100644 src/extension/sessionExtensionState.ts create mode 100644 src/extension/sessionState.ts diff --git a/src/core/completionGenerator.ts b/src/core/completionGenerator.ts index 48068684..9a6bc0b3 100644 --- a/src/core/completionGenerator.ts +++ b/src/core/completionGenerator.ts @@ -3,7 +3,7 @@ import { GeneratedProof } from "../llm/llmServices/generatedProof"; import { CoqLspTimeoutError } from "../coqLsp/coqLspTypes"; -import { throwOnAbort } from "../extension/extensionAbortUtils"; +import { CompletionAbortError, throwOnAbort } from "../extension/extensionAbortUtils"; import { EventLogger } from "../logging/eventLogger"; import { asErrorOrRethrow, buildErrorCompleteLog } from "../utils/errorsUtils"; import { stringifyAnyValue } from "../utils/printers"; @@ -132,6 +132,8 @@ export async function generateCompletion( FailureGenerationStatus.TIMEOUT_EXCEEDED, error.message ); + } else if (error instanceof CompletionAbortError) { + throw error; } else { return new FailureGenerationResult( FailureGenerationStatus.ERROR_OCCURRED, diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 6f160786..7fa4854a 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -1,6 +1,6 @@ import { - ExtensionContext as VSCodeContext, TextEditor, + ExtensionContext as VSCodeContext, commands, workspace, } from "vscode"; @@ -39,28 +39,27 @@ import { showMessageToUser, showMessageToUserWithSettingsHint, } from "./editorMessages"; -import { CompletionAbortError, throwOnAbort } from "./extensionAbortUtils"; -import { PluginState } from "./globalExtensionState"; -import { PluginStatusIndicator } from "./pluginStatusIndicator"; import { subscribeToHandleLLMServicesEvents } from "./llmServicesEventsHandler"; +import { PluginContext } from "./pluginContext"; +import { PluginStatusIndicator } from "./pluginStatusIndicator"; import { positionInRange, toVSCodePosition, toVSCodeRange, } from "./positionRangeUtils"; -import { SessionState } from "./sessionExtensionState"; +import { SessionState } from "./sessionState"; import { SettingsValidationError } from "./settingsValidationError"; +import { CompletionAbortError } from "./extensionAbortUtils"; export const pluginId = "coqpilot"; export const pluginName = "CoqPilot"; export class CoqPilot { - private readonly pluginStatusIndicator: PluginStatusIndicator; - private constructor( private readonly vscodeContext: VSCodeContext, - private readonly pluginState: PluginState, - private sessionState: SessionState + private readonly pluginContext: PluginContext, + private sessionState: SessionState, + toggleCommand: string ) { this.registerEditorCommand( "perform_completion_under_cursor", @@ -75,30 +74,34 @@ export class CoqPilot { this.performCompletionForAllAdmits.bind(this) ); - const toggleCommand = `toggle_current_session`; this.registerEditorCommand( toggleCommand, - this.toggleCurrentSession.bind(this) - ); - this.pluginStatusIndicator = new PluginStatusIndicator( - `${pluginId}.${toggleCommand}`, - vscodeContext + this.sessionState.toggleCurrentSession.bind(this.sessionState) ); - this.pluginStatusIndicator.updateStatusBar(true); this.vscodeContext.subscriptions.push(this); } - static async create(vscodeExtensionContext: VSCodeContext) { - const globalExtensionState = new PluginState(); + static async create(vscodeContext: VSCodeContext) { + const globalExtensionState = new PluginContext(); + + const toggleCommand = `toggle_current_session`; + const pluginStatusIndicator = new PluginStatusIndicator( + `${pluginId}.${toggleCommand}`, + vscodeContext + ); + const sessionExtensionState = await SessionState.create( globalExtensionState.logOutputChannel, - globalExtensionState.eventLogger + globalExtensionState.eventLogger, + pluginStatusIndicator ); + return new CoqPilot( - vscodeExtensionContext, + vscodeContext, globalExtensionState, - sessionExtensionState + sessionExtensionState, + toggleCommand ); } @@ -106,8 +109,7 @@ export class CoqPilot { const cursorPosition = editor.selection.active; this.performSpecificCompletionsWithProgress( (hole) => positionInRange(cursorPosition, hole.range), - editor, - this.sessionState.abortController.signal + editor ); } @@ -115,51 +117,29 @@ export class CoqPilot { const selection = editor.selection; this.performSpecificCompletionsWithProgress( (hole) => selection.contains(toVSCodePosition(hole.range.start)), - editor, - this.sessionState.abortController.signal + editor ); } async performCompletionForAllAdmits(editor: TextEditor) { - this.performSpecificCompletionsWithProgress( - (_hole) => true, - editor, - this.sessionState.abortController.signal - ); - } - - async toggleCurrentSession() { - if (this.pluginState.hasActiveSession) { - this.pluginState.hasActiveSession = false; - this.sessionState.abort(); - this.sessionState.dispose(); - this.pluginStatusIndicator.updateStatusBar(false); - } else { - this.sessionState = await SessionState.create( - this.pluginState.logOutputChannel, - this.pluginState.eventLogger - ); - this.pluginState.hasActiveSession = true; - this.pluginStatusIndicator.updateStatusBar(true); - } + this.performSpecificCompletionsWithProgress((_hole) => true, editor); } private async performSpecificCompletionsWithProgress( shouldCompleteHole: (hole: ProofStep) => boolean, - editor: TextEditor, - abortSignal: AbortSignal + editor: TextEditor ) { - if (!this.pluginState.hasActiveSession) { + if (!this.sessionState.isSessionActive) { showMessageToUser(EditorMessages.extensionIsPaused, "warning"); return; } try { - this.pluginStatusIndicator.showInProgressSpinner(); + this.sessionState.showInProgressSpinner(); await this.performSpecificCompletions( shouldCompleteHole, editor, - abortSignal + this.sessionState.abortController.signal ); } catch (e) { if (e instanceof SettingsValidationError) { @@ -170,11 +150,11 @@ export class CoqPilot { "error", `${pluginId}.coqLspServerPath` ); - } else if ( - e instanceof CompletionAbortError || - !this.pluginState.hasActiveSession - ) { - showMessageToUser(EditorMessages.completionAborted, "info"); + } else if (e instanceof CompletionAbortError) { + if (!this.sessionState.userNotifiedAboutAbort) { + showMessageToUser(EditorMessages.completionAborted, "info"); + this.sessionState.userReceivedAbortNotification(); + } } else { showMessageToUser( e instanceof Error @@ -185,9 +165,7 @@ export class CoqPilot { console.error(buildErrorCompleteLog(e)); } } finally { - this.pluginStatusIndicator.hideInProgressSpinner( - this.pluginState.hasActiveSession - ); + this.sessionState.hideInProgressSpinner(); } } @@ -196,7 +174,7 @@ export class CoqPilot { editor: TextEditor, abortSignal: AbortSignal ) { - this.pluginState.eventLogger.log( + this.pluginContext.eventLogger.log( "completion-started", "CoqPilot has started the completion process" ); @@ -216,7 +194,7 @@ export class CoqPilot { editor.document.uri.fsPath, abortSignal ); - this.pluginState.eventLogger.log( + this.pluginContext.eventLogger.log( "completion-preparation-finished", `CoqPilot has successfully parsed the file with ${sourceFileEnvironment.fileTheorems.length} theorems and has found ${completionContexts.length} admits inside chosen selection` ); @@ -228,8 +206,8 @@ export class CoqPilot { const unsubscribeFromLLMServicesEventsCallback = subscribeToHandleLLMServicesEvents( - this.pluginState.llmServices, - this.pluginState.eventLogger + this.pluginContext.llmServices, + this.pluginContext.eventLogger ); try { @@ -258,13 +236,12 @@ export class CoqPilot { editor: TextEditor, abortSignal: AbortSignal ) { - throwOnAbort(abortSignal); const result = await generateCompletion( completionContext, sourceFileEnvironment, processEnvironment, abortSignal, - this.pluginState.eventLogger + this.pluginContext.eventLogger ); if (result instanceof SuccessGenerationResult) { @@ -340,15 +317,15 @@ export class CoqPilot { this.sessionState.coqLspClient, abortSignal, contextTheoremsRanker.needsUnwrappedNotations, - this.pluginState.eventLogger + this.pluginContext.eventLogger ); const processEnvironment: ProcessEnvironment = { coqProofChecker: coqProofChecker, modelsParams: readAndValidateUserModelsParams( workspace.getConfiguration(pluginId), - this.pluginState.llmServices + this.pluginContext.llmServices ), - services: this.pluginState.llmServices, + services: this.pluginContext.llmServices, theoremRanker: contextTheoremsRanker, }; @@ -369,6 +346,6 @@ export class CoqPilot { dispose(): void { this.vscodeContext.subscriptions.forEach((d) => d.dispose()); this.sessionState.dispose(); - this.pluginState.dispose(); + this.pluginContext.dispose(); } } diff --git a/src/extension/globalExtensionState.ts b/src/extension/pluginContext.ts similarity index 96% rename from src/extension/globalExtensionState.ts rename to src/extension/pluginContext.ts index 806b5ba6..727bcb93 100644 --- a/src/extension/globalExtensionState.ts +++ b/src/extension/pluginContext.ts @@ -14,9 +14,8 @@ import { EventLogger, Severity } from "../logging/eventLogger"; import { pluginId } from "./coqPilot"; import VSCodeLogWriter from "./vscodeLogWriter"; -export class PluginState implements Disposable { +export class PluginContext implements Disposable { public readonly eventLogger: EventLogger = new EventLogger(); - public hasActiveSession = true; public readonly logWriter: VSCodeLogWriter = new VSCodeLogWriter( this.eventLogger, this.parseLoggingVerbosity(workspace.getConfiguration(pluginId)) diff --git a/src/extension/sessionExtensionState.ts b/src/extension/sessionExtensionState.ts deleted file mode 100644 index f49b4f4b..00000000 --- a/src/extension/sessionExtensionState.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Disposable, OutputChannel } from "vscode"; - -import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; -import { CoqLspClient } from "../coqLsp/coqLspClient"; - -import { EventLogger } from "../logging/eventLogger"; - -import { parseCoqLspServerPath } from "./configReaders"; -import { CompletionAbortError } from "./extensionAbortUtils"; - -export class SessionState implements Disposable { - private constructor( - readonly coqLspClient: CoqLspClient, - readonly abortController: AbortController - ) {} - - static async create( - logOutputChannel: OutputChannel, - eventLogger: EventLogger - ): Promise { - const abortController = new AbortController(); - const coqLspServerPath = parseCoqLspServerPath(); - - const coqLspClient = await createCoqLspClient( - coqLspServerPath, - logOutputChannel, - eventLogger, - abortController - ); - - return new SessionState(coqLspClient, abortController); - } - - abort(): void { - this.abortController.abort(new CompletionAbortError()); - } - - dispose(): void { - this.abortController.abort(); - this.coqLspClient.dispose(); - } -} diff --git a/src/extension/sessionState.ts b/src/extension/sessionState.ts new file mode 100644 index 00000000..41b7dedc --- /dev/null +++ b/src/extension/sessionState.ts @@ -0,0 +1,137 @@ +import { Disposable, OutputChannel } from "vscode"; + +import { createCoqLspClient } from "../coqLsp/coqLspBuilders"; +import { CoqLspClient } from "../coqLsp/coqLspClient"; + +import { EventLogger } from "../logging/eventLogger"; + +import { parseCoqLspServerPath } from "./configReaders"; +import { CompletionAbortError } from "./extensionAbortUtils"; +import { PluginStatusIndicator } from "./pluginStatusIndicator"; + +export class SessionState implements Disposable { + private _isSessionActive = true; + private _coqLspClient: CoqLspClient; + private _abortController: AbortController; + + // When user triggers abort for completions + // and there are multiple completions in progress, + // we want to notify the user only once. + private _userNotifiedAboutAbort = false; + + throwOnInactiveSession(): void { + if (!this._isSessionActive) { + throw new Error("Trying to access a disposed session state"); + } + } + + get isSessionActive(): boolean { + return this._isSessionActive; + } + + get userNotifiedAboutAbort(): boolean { + return this._userNotifiedAboutAbort; + } + + get coqLspClient(): CoqLspClient { + this.throwOnInactiveSession(); + return this._coqLspClient; + } + + get abortController(): AbortController { + this.throwOnInactiveSession(); + return this._abortController; + } + + private constructor( + coqLspClient: CoqLspClient, + abortController: AbortController, + private readonly logOutputChannel: OutputChannel, + private readonly eventLogger: EventLogger, + private readonly pluginStatusIndicator: PluginStatusIndicator + ) { + this._coqLspClient = coqLspClient; + this._abortController = abortController; + } + + static async create( + logOutputChannel: OutputChannel, + eventLogger: EventLogger, + pluginStatusIndicator: PluginStatusIndicator + ): Promise { + const abortController = new AbortController(); + const coqLspServerPath = parseCoqLspServerPath(); + + const coqLspClient = await createCoqLspClient( + coqLspServerPath, + logOutputChannel, + eventLogger, + abortController + ); + + pluginStatusIndicator.updateStatusBar(true); + + return new SessionState( + coqLspClient, + abortController, + logOutputChannel, + eventLogger, + pluginStatusIndicator + ); + } + + userReceivedAbortNotification(): void { + this._userNotifiedAboutAbort = true; + } + + showInProgressSpinner(): void { + this.pluginStatusIndicator.showInProgressSpinner(); + } + + hideInProgressSpinner(): void { + this.pluginStatusIndicator.hideInProgressSpinner(this._isSessionActive); + } + + async toggleCurrentSession(): Promise { + if (this._isSessionActive) { + this.abort(); + } else { + await this.startNewSession(); + } + } + + abort(): void { + this._isSessionActive = false; + this._abortController.abort(new CompletionAbortError()); + this._coqLspClient.dispose(); + + this.pluginStatusIndicator.updateStatusBar(false); + + this.eventLogger.log( + "session-abort", + "User has triggered a session abort: Stopping all completions" + ); + } + + async startNewSession(): Promise { + this._isSessionActive = true; + this._abortController = new AbortController(); + + const coqLspServerPath = parseCoqLspServerPath(); + this._coqLspClient = await createCoqLspClient( + coqLspServerPath, + this.logOutputChannel, + this.eventLogger, + this._abortController + ); + + this.pluginStatusIndicator.updateStatusBar(true); + + this.eventLogger.log("session-start", "User has started a new session"); + } + + dispose(): void { + this._abortController.abort(); + this._coqLspClient.dispose(); + } +} diff --git a/src/test/resources/small_document.v b/src/test/resources/small_document.v index 66dc5b41..522cfeb7 100644 --- a/src/test/resources/small_document.v +++ b/src/test/resources/small_document.v @@ -1,11 +1,11 @@ Theorem test_thr : forall n:nat, 0 + n = n. Proof. intros n. Print plus. - reflexivity. -Qed. + auto. +Admitted. Lemma test_thr1 : forall n:nat, 0 + n + 0 = n. Proof. intros n. Print plus. - admit. + auto. Admitted. \ No newline at end of file From f5122cd39983e2f5603ecb28fd97abcd9870df92 Mon Sep 17 00:00:00 2001 From: Andrei Kozyrev Date: Thu, 7 Nov 2024 00:23:39 +0100 Subject: [PATCH 30/30] Refactor `coqLsp` tests according to review of PR#46 --- src/core/completionGenerator.ts | 5 +- src/extension/coqPilot.ts | 2 +- src/extension/sessionState.ts | 2 +- .../coqLsp/coqLspCommandInGoalsReq.test.ts | 239 ------------------ .../coqLsp/coqLspGoalsWithCommandExec.test.ts | 225 +++++++++++++++++ src/test/core/completionGenerator.test.ts | 7 +- src/test/resources/small_document.v | 6 +- 7 files changed, 237 insertions(+), 249 deletions(-) delete mode 100644 src/test/coqLsp/coqLspCommandInGoalsReq.test.ts create mode 100644 src/test/coqLsp/coqLspGoalsWithCommandExec.test.ts diff --git a/src/core/completionGenerator.ts b/src/core/completionGenerator.ts index 9a6bc0b3..2574262c 100644 --- a/src/core/completionGenerator.ts +++ b/src/core/completionGenerator.ts @@ -3,7 +3,10 @@ import { GeneratedProof } from "../llm/llmServices/generatedProof"; import { CoqLspTimeoutError } from "../coqLsp/coqLspTypes"; -import { CompletionAbortError, throwOnAbort } from "../extension/extensionAbortUtils"; +import { + CompletionAbortError, + throwOnAbort, +} from "../extension/extensionAbortUtils"; import { EventLogger } from "../logging/eventLogger"; import { asErrorOrRethrow, buildErrorCompleteLog } from "../utils/errorsUtils"; import { stringifyAnyValue } from "../utils/printers"; diff --git a/src/extension/coqPilot.ts b/src/extension/coqPilot.ts index 7fa4854a..10c8ab4e 100644 --- a/src/extension/coqPilot.ts +++ b/src/extension/coqPilot.ts @@ -39,6 +39,7 @@ import { showMessageToUser, showMessageToUserWithSettingsHint, } from "./editorMessages"; +import { CompletionAbortError } from "./extensionAbortUtils"; import { subscribeToHandleLLMServicesEvents } from "./llmServicesEventsHandler"; import { PluginContext } from "./pluginContext"; import { PluginStatusIndicator } from "./pluginStatusIndicator"; @@ -49,7 +50,6 @@ import { } from "./positionRangeUtils"; import { SessionState } from "./sessionState"; import { SettingsValidationError } from "./settingsValidationError"; -import { CompletionAbortError } from "./extensionAbortUtils"; export const pluginId = "coqpilot"; export const pluginName = "CoqPilot"; diff --git a/src/extension/sessionState.ts b/src/extension/sessionState.ts index 41b7dedc..c9621cba 100644 --- a/src/extension/sessionState.ts +++ b/src/extension/sessionState.ts @@ -14,7 +14,7 @@ export class SessionState implements Disposable { private _coqLspClient: CoqLspClient; private _abortController: AbortController; - // When user triggers abort for completions + // When user triggers abort for completions // and there are multiple completions in progress, // we want to notify the user only once. private _userNotifiedAboutAbort = false; diff --git a/src/test/coqLsp/coqLspCommandInGoalsReq.test.ts b/src/test/coqLsp/coqLspCommandInGoalsReq.test.ts deleted file mode 100644 index 6596e4a4..00000000 --- a/src/test/coqLsp/coqLspCommandInGoalsReq.test.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { expect } from "earl"; -import { Result } from "ts-results"; - -import { createTestCoqLspClient } from "../../coqLsp/coqLspBuilders"; -import { Goal, PpString } from "../../coqLsp/coqLspTypes"; - -import { Uri } from "../../utils/uri"; -import { resolveResourcesDir } from "../commonTestFunctions/pathsResolver"; - -suite("Request goals with `command/pretac` argument", () => { - async function getGoalAtPosition( - position: { line: number; character: number }, - resourcePath: string[], - command: string, - projectRootPath?: string[] - ): Promise[], Error>> { - const [filePath, rootDir] = resolveResourcesDir( - resourcePath, - projectRootPath - ); - const fileUri = Uri.fromPath(filePath); - - const client = await createTestCoqLspClient(rootDir); - await client.openTextDocument(fileUri); - const goals = await client.getGoalsAtPoint( - position, - fileUri, - 1, - command - ); - await client.closeTextDocument(fileUri); - client.dispose(); - - return goals; - } - - test("One Coq sentence: fail to solve with bad goal", async () => { - const goal = await getGoalAtPosition( - { line: 9, character: 4 }, - ["small_document.v"], - "reflexivity." - ); - - expect(goal.err).toEqual(true); - expect(goal.val).toBeA(Error); - if (goal.val instanceof Error) { - expect(goal.val.message).toEqual( - 'In environment\nn : nat\nUnable to unify "n" with\n "0 + n + 0".' - ); - } - }); - - test("One Coq sentence: fail to solve with bad tactic", async () => { - const goal = await getGoalAtPosition( - { line: 9, character: 4 }, - ["small_document.v"], - "reflexivit." - ); - - expect(goal.err).toEqual(true); - expect(goal.val).toBeA(Error); - if (goal.err) { - expect(goal.val.message).toEqual( - "The reference reflexivit was not found in the current\nenvironment." - ); - } - }); - - test("One Coq sentence: successfully solve no goals left", async () => { - const goals = await getGoalAtPosition( - { line: 9, character: 4 }, - ["small_document.v"], - "auto." - ); - - expect(goals.ok).toEqual(true); - if (goals.ok) { - expect(goals.val).toBeEmpty(); - } - }); - - test("One Coq sentence: successfully solve 1 goal left", async () => { - const goals = await getGoalAtPosition( - { line: 9, character: 4 }, - ["test_many_subgoals.v"], - "auto." - ); - - expect(goals.ok).toEqual(true); - if (goals.ok) { - expect(goals.val).toHaveLength(1); - expect(goals.val[0].ty).toEqual("S n + 0 = S n"); - } - }); - - test("One Coq sentence: successfully solve 2 goals left", async () => { - const goals = await getGoalAtPosition( - { line: 22, character: 4 }, - ["test_many_subgoals.v"], - "auto." - ); - - expect(goals.ok).toEqual(true); - if (goals.ok) { - expect(goals.val).toHaveLength(2); - expect(goals.val[0].ty).toEqual( - "Second = First \\/ Second = Second \\/ Second = Third" - ); - expect(goals.val[1].ty).toEqual( - "Third = First \\/ Third = Second \\/ Third = Third" - ); - } - }); - - test("One Coq sentence wrapped into curly braces: solve successfully", async () => { - const goals = await getGoalAtPosition( - { line: 9, character: 4 }, - ["small_document.v"], - " { auto. }" - ); - - expect(goals.ok).toEqual(true); - if (goals.ok) { - expect(goals.val).toBeEmpty(); - } - }); - - test("One Coq sentence wrapped into curly braces: bad proof", async () => { - const goals = await getGoalAtPosition( - { line: 9, character: 4 }, - ["small_document.v"], - " { kek. }" - ); - - expect(goals.err).toEqual(true); - if (goals.err) { - expect(goals.val.message).toEqual( - "The reference kek was not found in the current\nenvironment." - ); - } - }); - - test("One Coq sentence wrapped into curly braces: good proof, test indent", async () => { - const goals = await getGoalAtPosition( - { line: 2, character: 12 }, - ["test_many_subgoals.v"], - "{ auto. }" - ); - - expect(goals.ok).toEqual(true); - if (goals.ok) { - expect(goals.val).toBeEmpty(); - } - }); - - test("Many Coq sentences: good proof", async () => { - const goals = await getGoalAtPosition( - { line: 2, character: 12 }, - ["test_many_subgoals.v"], - "simpl. induction n. reflexivity. auto." - ); - - expect(goals.ok).toEqual(true); - if (goals.ok) { - expect(goals.val).toBeEmpty(); - } - }); - - test("Many Coq sentences: bad proof", async () => { - const goals = await getGoalAtPosition( - { line: 2, character: 12 }, - ["test_many_subgoals.v"], - "simpl. induction n. reflexivity. reflexivity." - ); - - expect(goals.err).toEqual(true); - if (goals.err) { - expect(goals.val.message).toEqual( - 'In environment\nn : nat\nIHn : n + 0 = n\nUnable to unify "S n" with\n "S n + 0".' - ); - } - }); - - test("Many Coq sentences: good proof with multiple curly braces", async () => { - const goals = await getGoalAtPosition( - { line: 2, character: 12 }, - ["test_many_subgoals.v"], - "{ simpl. \n induction n. \n { reflexivity. }\n auto. \n }" - ); - - expect(goals.ok).toEqual(true); - if (goals.ok) { - expect(goals.val).toBeEmpty(); - } - }); - - test("Many Coq sentences: bad proof with multiple curly braces", async () => { - const goals = await getGoalAtPosition( - { line: 2, character: 12 }, - ["test_many_subgoals.v"], - "{ simpl. \n induction n. \n { reflexivity. }\n reflexivity. \n }" - ); - - expect(goals.err).toEqual(true); - if (goals.err) { - expect(goals.val.message).toEqual( - 'In environment\nn : nat\nIHn : n + 0 = n\nUnable to unify "S n" with\n "S n + 0".' - ); - } - }); - - test("Many Coq sentences: good proof with bullets", async () => { - const goals = await getGoalAtPosition( - { line: 2, character: 12 }, - ["test_many_subgoals.v"], - "{ \n simpl. \n induction n. \n - reflexivity.\n - auto. \n }" - ); - - expect(goals.ok).toEqual(true); - if (goals.ok) { - expect(goals.val).toBeEmpty(); - } - }); - - test("Many Coq sentences: bad proof with bullets", async () => { - const goals = await getGoalAtPosition( - { line: 2, character: 12 }, - ["test_many_subgoals.v"], - "{ \n simpl. \n induction n. \n - reflexivity.\n - reflexivity. \n }" - ); - - expect(goals.err).toEqual(true); - if (goals.err) { - expect(goals.val.message).toEqual( - 'In environment\nn : nat\nIHn : n + 0 = n\nUnable to unify "S n" with\n "S n + 0".' - ); - } - }); -}); diff --git a/src/test/coqLsp/coqLspGoalsWithCommandExec.test.ts b/src/test/coqLsp/coqLspGoalsWithCommandExec.test.ts new file mode 100644 index 00000000..1c284985 --- /dev/null +++ b/src/test/coqLsp/coqLspGoalsWithCommandExec.test.ts @@ -0,0 +1,225 @@ +import { expect } from "earl"; +import { Result } from "ts-results"; + +import { createTestCoqLspClient } from "../../coqLsp/coqLspBuilders"; +import { Goal, PpString } from "../../coqLsp/coqLspTypes"; + +import { Uri } from "../../utils/uri"; +import { resolveResourcesDir } from "../commonTestFunctions/pathsResolver"; + +suite("Request goals with `command/pretac` argument", () => { + async function getGoalsAtPosition( + position: { line: number; character: number }, + resourcePath: string[], + command: string, + projectRootPath?: string[] + ): Promise[], Error>> { + const [filePath, rootDir] = resolveResourcesDir( + resourcePath, + projectRootPath + ); + const fileUri = Uri.fromPath(filePath); + + const client = await createTestCoqLspClient(rootDir); + let goals: Result[], Error> | undefined; + + try { + await client.openTextDocument(fileUri); + goals = await client.getGoalsAtPoint(position, fileUri, 1, command); + } finally { + await client.closeTextDocument(fileUri); + client.dispose(); + } + + if (goals === undefined) { + throw new Error("The goals were not received."); + } + + return goals; + } + + function checkSuccessfullGoalConcls( + goals: Result[], Error>, + goalConclusions: string[] + ): void { + expect(goals.ok).toEqual(true); + if (goals.ok) { + expect(goals.val).toHaveLength(goalConclusions.length); + for (let i = 0; i < goalConclusions.length; i++) { + expect(goals.val[i].ty).toEqual(goalConclusions[i]); + } + } + } + + function checkCommandApplicationError( + goals: Result[], Error>, + expectedError: string + ): void { + expect(goals.err).toEqual(true); + expect(goals.val).toBeA(Error); + if (goals.err) { + expect(goals.val.message).toEqual(expectedError); + } + } + + test("One Coq sentence: fail to solve with invalid goal", async () => { + const goals = await getGoalsAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + "reflexivity." + ); + + checkCommandApplicationError( + goals, + 'In environment\nn : nat\nUnable to unify "n" with\n "0 + n + 0".' + ); + }); + + test("One Coq sentence: fail to solve with invalid tactic", async () => { + const goals = await getGoalsAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + "reflexivit." + ); + + checkCommandApplicationError( + goals, + "The reference reflexivit was not found in the current\nenvironment." + ); + }); + + test("One Coq sentence: successfully solve no goals left", async () => { + const goals = await getGoalsAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + "auto." + ); + + checkSuccessfullGoalConcls(goals, []); + }); + + test("One Coq sentence: successfully solve 1 goal left", async () => { + const goals = await getGoalsAtPosition( + { line: 9, character: 4 }, + ["test_many_subgoals.v"], + "auto." + ); + + checkSuccessfullGoalConcls(goals, ["S n + 0 = S n"]); + }); + + test("One Coq sentence: successfully solve 2 goals left", async () => { + const goals = await getGoalsAtPosition( + { line: 22, character: 4 }, + ["test_many_subgoals.v"], + "auto." + ); + + checkSuccessfullGoalConcls(goals, [ + "Second = First \\/ Second = Second \\/ Second = Third", + "Third = First \\/ Third = Second \\/ Third = Third", + ]); + }); + + test("One Coq sentence wrapped into curly braces: solve successfully", async () => { + const goals = await getGoalsAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + " { auto. }" + ); + + checkSuccessfullGoalConcls(goals, []); + }); + + test("One Coq sentence wrapped into curly braces: invalid proof", async () => { + const goals = await getGoalsAtPosition( + { line: 9, character: 4 }, + ["small_document.v"], + " { kek. }" + ); + + checkCommandApplicationError( + goals, + "The reference kek was not found in the current\nenvironment." + ); + }); + + test("One Coq sentence wrapped into curly braces: valid proof, test indent", async () => { + const goals = await getGoalsAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ auto. }" + ); + + checkSuccessfullGoalConcls(goals, []); + }); + + test("Many Coq sentences: valid proof", async () => { + const goals = await getGoalsAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "simpl. induction n. reflexivity. auto." + ); + + checkSuccessfullGoalConcls(goals, []); + }); + + test("Many Coq sentences: invalid proof", async () => { + const goals = await getGoalsAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "simpl. induction n. reflexivity. reflexivity." + ); + + checkCommandApplicationError( + goals, + 'In environment\nn : nat\nIHn : n + 0 = n\nUnable to unify "S n" with\n "S n + 0".' + ); + }); + + test("Many Coq sentences: valid proof with multiple curly braces", async () => { + const goals = await getGoalsAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ simpl. \n induction n. \n { reflexivity. }\n auto. \n }" + ); + + checkSuccessfullGoalConcls(goals, []); + }); + + test("Many Coq sentences: invalid proof with multiple curly braces", async () => { + const goals = await getGoalsAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ simpl. \n induction n. \n { reflexivity. }\n reflexivity. \n }" + ); + + checkCommandApplicationError( + goals, + 'In environment\nn : nat\nIHn : n + 0 = n\nUnable to unify "S n" with\n "S n + 0".' + ); + }); + + test("Many Coq sentences: valid proof with bullets", async () => { + const goals = await getGoalsAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ \n simpl. \n induction n. \n - reflexivity.\n - auto. \n }" + ); + + checkSuccessfullGoalConcls(goals, []); + }); + + test("Many Coq sentences: invalid proof with bullets", async () => { + const goals = await getGoalsAtPosition( + { line: 2, character: 12 }, + ["test_many_subgoals.v"], + "{ \n simpl. \n induction n. \n - reflexivity.\n - reflexivity. \n }" + ); + + checkCommandApplicationError( + goals, + 'In environment\nn : nat\nIHn : n + 0 = n\nUnable to unify "S n" with\n "S n + 0".' + ); + }); +}); diff --git a/src/test/core/completionGenerator.test.ts b/src/test/core/completionGenerator.test.ts index 5c963dc7..d7fddc75 100644 --- a/src/test/core/completionGenerator.test.ts +++ b/src/test/core/completionGenerator.test.ts @@ -52,13 +52,12 @@ suite("Completion generation tests", () => { ) ); - await environment.coqLspClient.closeTextDocument( - environment.sourceFileEnvironment.fileUri - ); - return generationResult; } finally { disposeServices(processEnvironment.services); + await environment.coqLspClient.closeTextDocument( + environment.sourceFileEnvironment.fileUri + ); } } diff --git a/src/test/resources/small_document.v b/src/test/resources/small_document.v index 522cfeb7..66dc5b41 100644 --- a/src/test/resources/small_document.v +++ b/src/test/resources/small_document.v @@ -1,11 +1,11 @@ Theorem test_thr : forall n:nat, 0 + n = n. Proof. intros n. Print plus. - auto. -Admitted. + reflexivity. +Qed. Lemma test_thr1 : forall n:nat, 0 + n + 0 = n. Proof. intros n. Print plus. - auto. + admit. Admitted. \ No newline at end of file