Skip to content

Commit 9cf599c

Browse files
authored
feat: error when opening project that contains lakefile but no lean-toolchain (#610)
This PR ensures that an error is displayed when users open a project that has a lakefile but no lean-toolchain file.
1 parent 17ffa26 commit 9cf599c

File tree

11 files changed

+86
-77
lines changed

11 files changed

+86
-77
lines changed

vscode-lean4/src/diagnostics/fullDiagnostics.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { SemVer } from 'semver'
2-
import { Disposable, OutputChannel, commands, env } from 'vscode'
2+
import { commands, Disposable, env, OutputChannel } from 'vscode'
33
import { ExecutionExitCode, ExecutionResult } from '../utils/batch'
44
import { ElanInstalledToolchain, ElanToolchains, ElanUnresolvedToolchain } from '../utils/elan'
5-
import { FileUri } from '../utils/exturi'
5+
import { extUriToCwdUri, FileUri } from '../utils/exturi'
66
import { lean } from '../utils/leanEditorProvider'
77
import { displayNotification, displayNotificationWithInput } from '../utils/notifs'
8-
import { findLeanProjectRoot } from '../utils/projectInfo'
8+
import { findLeanProjectRootInfo } from '../utils/projectInfo'
99
import {
1010
ElanDumpStateWithoutNetQueryResult,
1111
ElanVersionDiagnosis,
@@ -228,17 +228,25 @@ export class FullDiagnosticsProvider implements Disposable {
228228
// in which case we still want to provide adequate diagnostics. Hence, we use the last active Lean document,
229229
// regardless of whether there is an actual `LeanClient` managing it.
230230
const lastActiveLeanDocumentUri = lean.lastActiveLeanDocument?.extUri
231-
const projectUri =
232-
lastActiveLeanDocumentUri !== undefined && lastActiveLeanDocumentUri.scheme === 'file'
233-
? await findLeanProjectRoot(lastActiveLeanDocumentUri)
231+
const projectInfo =
232+
lastActiveLeanDocumentUri !== undefined
233+
? await findLeanProjectRootInfo(lastActiveLeanDocumentUri)
234234
: undefined
235-
if (projectUri === 'FileNotFound') {
235+
if (projectInfo?.kind === 'FileNotFound') {
236236
displayNotification(
237237
'Error',
238238
`Cannot display setup information for file that does not exist in the file system: ${lastActiveLeanDocumentUri}. Please choose a different file to display the setup information for.`,
239239
)
240240
return
241241
}
242+
if (projectInfo?.kind === 'LakefileWithoutToolchain') {
243+
displayNotification(
244+
'Error',
245+
`Cannot display setup information for file in invalid project: Project at ${projectInfo.projectRootUri} has a Lakefile, but no 'lean-toolchain' file. Please create one with the Lean version that you would like the project to use.`,
246+
)
247+
return
248+
}
249+
const projectUri = projectInfo !== undefined ? extUriToCwdUri(projectInfo.projectRootUri) : undefined
242250
const fullDiagnostics = await performFullDiagnosis(this.outputChannel, projectUri)
243251
const formattedFullDiagnostics = formatFullDiagnostics(fullDiagnostics)
244252
const copyToClipboardInput = 'Copy to Clipboard'

vscode-lean4/src/diagnostics/setupDiagnostics.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import path from 'path'
21
import { SemVer } from 'semver'
32
import { OutputChannel, commands } from 'vscode'
43
import { ExtUri, FileUri, extUriToCwdUri } from '../utils/exturi'
@@ -39,15 +38,15 @@ const oldServerFolderContainsNewServerFolderErrorMessage = (
3938
fileUri: FileUri,
4039
clientFolderUri: FileUri,
4140
) =>
42-
`Error while starting language server: The project at '${folderUri.fsPath}' of the file '${path.basename(fileUri.fsPath)}' is contained inside of another project at '${clientFolderUri.fsPath}', for which a language server is already running.
41+
`Error while starting language server: The project at '${folderUri.fsPath}' of the file '${fileUri.baseName()}' is contained inside of another project at '${clientFolderUri.fsPath}', for which a language server is already running.
4342
The Lean 4 VS Code extension does not support nested Lean projects.`
4443

4544
const newServerFolderContainsOldServerFolderErrorMessage = (
4645
folderUri: FileUri,
4746
fileUri: FileUri,
4847
clientFolderUri: FileUri,
4948
) =>
50-
`Error while starting language server: The project at '${folderUri.fsPath}' of the file '${path.basename(fileUri.fsPath)}' contains another project at '${clientFolderUri.fsPath}', for which a language server is already running.
49+
`Error while starting language server: The project at '${folderUri.fsPath}' of the file '${fileUri.baseName()}' contains another project at '${clientFolderUri.fsPath}', for which a language server is already running.
5150
The Lean 4 VS Code extension does not support nested Lean projects.`
5251

5352
export class SetupDiagnostics {

vscode-lean4/src/extension.ts

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import { LeanConfigWatchService } from './utils/configwatchservice'
2121
import { ElanCommandProvider } from './utils/elanCommands'
2222
import { PATH, setProcessEnvPATH } from './utils/envPath'
2323
import { onEventWhile, withoutReentrancy } from './utils/events'
24-
import { FileUri } from './utils/exturi'
24+
import { ExtUri, extUriToCwdUri, FileUri } from './utils/exturi'
2525
import { displayInternalErrorsIn } from './utils/internalErrors'
2626
import { registerLeanCommandRunner } from './utils/leanCmdRunner'
27-
import { lean, LeanEditor, registerLeanEditorProvider } from './utils/leanEditorProvider'
27+
import { lean, registerLeanEditorProvider } from './utils/leanEditorProvider'
2828
import { LeanInstaller } from './utils/leanInstaller'
2929
import {
3030
displayActiveStickyNotification,
@@ -33,37 +33,28 @@ import {
3333
setStickyNotificationActiveButHidden,
3434
} from './utils/notifs'
3535
import { PathExtensionProvider } from './utils/pathExtensionProvider'
36-
import { findLeanProjectRoot } from './utils/projectInfo'
36+
import { findLeanProjectRootInfo } from './utils/projectInfo'
3737
import { UriHandlerService } from './utils/uriHandlerService'
3838

3939
async function setLeanFeatureSetActive(isActive: boolean) {
4040
await commands.executeCommand('setContext', 'lean4.isLeanFeatureSetActive', isActive)
4141
}
4242

43-
async function findLeanEditorProjectUri(editor: LeanEditor): Promise<FileUri | undefined | 'InvalidProject'> {
44-
const docUri = editor.documentExtUri
45-
const projectUri = docUri.scheme === 'file' ? await findLeanProjectRoot(docUri) : undefined
46-
if (projectUri === 'FileNotFound') {
47-
return 'InvalidProject'
48-
}
49-
return projectUri
50-
}
51-
52-
async function findOpenLeanProjectUri(): Promise<FileUri | undefined | 'NoValidDocument'> {
43+
async function findOpenLeanProjectUri(): Promise<ExtUri | 'NoValidDocument'> {
5344
const activeEditor = lean.activeLeanEditor
5445
if (activeEditor !== undefined) {
55-
const projectUri = await findLeanEditorProjectUri(activeEditor)
56-
if (projectUri !== 'InvalidProject') {
57-
return projectUri
46+
const info = await findLeanProjectRootInfo(activeEditor.documentExtUri)
47+
if (info.kind !== 'FileNotFound') {
48+
return info.projectRootUri
5849
}
5950
}
6051

6152
// This happens if vscode starts with a lean file open
6253
// but the "Getting Started" page is active.
6354
for (const editor of lean.visibleLeanEditors) {
64-
const projectUri = await findLeanEditorProjectUri(editor)
65-
if (projectUri !== 'InvalidProject') {
66-
return projectUri
55+
const info = await findLeanProjectRootInfo(editor.documentExtUri)
56+
if (info.kind !== 'FileNotFound') {
57+
return info.projectRootUri
6758
}
6859
}
6960

@@ -220,12 +211,12 @@ async function tryActivatingLean4FeaturesInProject(
220211
elanCommandProvider: ElanCommandProvider,
221212
resolve: (value: Lean4EnabledFeatures) => void,
222213
d: SetupDiagnostics,
223-
projectUri: FileUri | undefined,
214+
projectUri: ExtUri,
224215
) {
225216
const preconditionCheckResult = await checkLean4FeaturePreconditions(
226217
installer,
227218
'Activate Lean 4 Extension',
228-
projectUri,
219+
extUriToCwdUri(projectUri),
229220
d,
230221
)
231222
if (preconditionCheckResult === 'Fatal') {
@@ -260,8 +251,8 @@ async function tryActivatingLean4Features(
260251
onEventWhile(
261252
lean.onDidRevealLeanEditor,
262253
withoutReentrancy('Continue', async editor => {
263-
const projectUri = await findLeanEditorProjectUri(editor)
264-
if (projectUri === 'InvalidProject') {
254+
const info = await findLeanProjectRootInfo(editor.documentExtUri)
255+
if (info.kind === 'FileNotFound') {
265256
return 'Continue'
266257
}
267258
await tryActivatingLean4FeaturesInProject(
@@ -270,7 +261,7 @@ async function tryActivatingLean4Features(
270261
elanCommandProvider,
271262
resolve,
272263
d,
273-
projectUri,
264+
info.projectRootUri,
274265
)
275266
return 'Stop'
276267
}),

vscode-lean4/src/leanclient.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import {
4343
} from './config'
4444
import { logger } from './utils/logger'
4545
// @ts-ignore
46-
import path from 'path'
4746
import { SemVer } from 'semver'
4847
import {
4948
c2pConverter,
@@ -368,7 +367,7 @@ export class LeanClient implements Disposable {
368367
return
369368
}
370369

371-
const fileName = fileUri.scheme === 'file' ? path.basename(fileUri.fsPath) : 'untitled'
370+
const fileName = fileUri.scheme === 'file' ? fileUri.baseName() : 'untitled'
372371
const isImportsOutdatedError = params.diagnostics.some(
373372
d =>
374373
d.severity === DiagnosticSeverity.Error &&
@@ -472,7 +471,7 @@ export class LeanClient implements Disposable {
472471

473472
async restartFile(leanDoc: LeanDocument): Promise<void> {
474473
const extUri = leanDoc.extUri
475-
const formattedFileName = extUri.scheme === 'file' ? path.basename(extUri.fsPath) : extUri.toString()
474+
const formattedFileName = extUri.scheme === 'file' ? extUri.baseName() : extUri.toString()
476475
const formattedProjectName =
477476
this.folderUri.scheme === 'file' ? this.folderUri.fsPath : this.folderUri.toString()
478477
if (this.client === undefined || !this.running) {
@@ -592,7 +591,7 @@ export class LeanClient implements Disposable {
592591
documentSelector.pattern = `${escapedPath}/**/*`
593592
workspaceFolder = {
594593
uri: this.folderUri.asUri(),
595-
name: path.basename(this.folderUri.fsPath),
594+
name: this.folderUri.baseName(),
596595
index: 0, // the language client library does not actually need this index
597596
}
598597
}

vscode-lean4/src/projectinit.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { lake } from './utils/lake'
1414
import { LeanInstaller } from './utils/leanInstaller'
1515
import { displayNotification, displayNotificationWithInput } from './utils/notifs'
1616
import { checkParentFoldersForLeanProject, isValidLeanProject } from './utils/projectInfo'
17-
import path from 'path'
1817

1918
const projectInitNotificationOptions: SetupNotificationOptions = {
2019
errorMode: { mode: 'NonModal' },
@@ -192,7 +191,7 @@ export class ProjectInitializationProvider implements Disposable {
192191
return 'DidNotComplete'
193192
}
194193

195-
const projectName: string = path.basename(projectFolder.fsPath)
194+
const projectName: string = projectFolder.baseName()
196195
const result: ExecutionResult = await lake({
197196
channel: this.channel,
198197
cwdUri: projectFolder,
@@ -431,7 +430,7 @@ Open this project instead?`
431430
}
432431

433432
private static async openNewFolder(projectFolder: FileUri) {
434-
const message = `Project initialized. Open new project folder '${path.basename(projectFolder.fsPath)}'?`
433+
const message = `Project initialized. Open new project folder '${projectFolder.baseName()}'?`
435434
const input = 'Open project folder'
436435
const choice: string | undefined = await displayNotificationWithInput('Information', message, [input])
437436
if (choice === input) {

vscode-lean4/src/utils/clientProvider.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import { LeanFileProgressProcessingInfo, ServerStoppedReason } from '@leanprover/infoview-api'
2-
import path from 'path'
32
import { Disposable, EventEmitter, OutputChannel, commands } from 'vscode'
43
import { SetupDiagnostics, checkAll } from '../diagnostics/setupDiagnostics'
54
import { PreconditionCheckResult, SetupNotificationOptions } from '../diagnostics/setupNotifs'
65
import { LeanClient } from '../leanclient'
76
import { LeanPublishDiagnosticsParams } from './converters'
8-
import { ExtUri, FileUri, UntitledUri } from './exturi'
7+
import { ExtUri, FileUri } from './exturi'
98
import { lean } from './leanEditorProvider'
109
import { LeanInstaller } from './leanInstaller'
1110
import { logger } from './logger'
1211
import { displayNotification } from './notifs'
13-
import { findLeanProjectRoot, willUseLakeServer } from './projectInfo'
12+
import { findLeanProjectRootInfo, willUseLakeServer } from './projectInfo'
1413

1514
async function checkLean4ProjectPreconditions(
1615
channel: OutputChannel,
@@ -113,11 +112,6 @@ export class LeanClientProvider implements Disposable {
113112
break
114113
}
115114
try {
116-
const projectUri = await findLeanProjectRoot(uri)
117-
if (projectUri === 'FileNotFound') {
118-
continue
119-
}
120-
121115
const [cached, client] = await this.ensureClient(uri)
122116
if (cached && client) {
123117
await client.restart()
@@ -130,7 +124,7 @@ export class LeanClientProvider implements Disposable {
130124
}
131125

132126
restartFile(uri: ExtUri) {
133-
const fileName = uri.scheme === 'file' ? path.basename(uri.fsPath) : 'untitled file'
127+
const fileName = uri.scheme === 'file' ? uri.baseName() : 'untitled file'
134128

135129
const client: LeanClient | undefined = this.findClient(uri)
136130
if (!client || !client.isRunning()) {
@@ -243,10 +237,18 @@ export class LeanClientProvider implements Disposable {
243237
}
244238

245239
async ensureClient(uri: ExtUri): Promise<[boolean, LeanClient | undefined]> {
246-
const folderUri = uri.scheme === 'file' ? await findLeanProjectRoot(uri) : new UntitledUri()
247-
if (folderUri === 'FileNotFound') {
240+
const projectInfo = await findLeanProjectRootInfo(uri)
241+
if (projectInfo.kind === 'FileNotFound') {
242+
return [false, undefined]
243+
}
244+
if (projectInfo.kind === 'LakefileWithoutToolchain') {
245+
displayNotification(
246+
'Error',
247+
`Project at ${projectInfo.projectRootUri} has a Lakefile, but lacks a 'lean-toolchain' file. Please create one with the Lean version that you would like the project to use.`,
248+
)
248249
return [false, undefined]
249250
}
251+
const folderUri = projectInfo.projectRootUri
250252
let client = this.getClientForFolder(folderUri)
251253
if (client) {
252254
this.activeClient = client

vscode-lean4/src/utils/configwatchservice.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from 'path'
22
import { Disposable, EventEmitter, Uri, window, workspace } from 'vscode'
33
import { FileUri } from './exturi'
4-
import { findLeanProjectInfo, findLeanProjectRoot } from './projectInfo'
4+
import { findLeanProjectInfo, findLeanProjectRootInfo } from './projectInfo'
55

66
// This service monitors the Lean package root folders for changes to any
77
// lean-toolchail, lakefile.lean or lakefile.toml files found there.
@@ -78,8 +78,12 @@ export class LeanConfigWatchService implements Disposable {
7878
// Note: just opening the file fires this event sometimes which is annoying, so
7979
// we compare the contents just to be sure and normalize whitespace so that
8080
// just adding a new line doesn't trigger the prompt.
81-
const projectUri = await findLeanProjectRoot(fileUri)
82-
if (projectUri === 'FileNotFound') {
81+
const info = await findLeanProjectRootInfo(fileUri)
82+
if (
83+
info.kind === 'FileNotFound' ||
84+
info.kind === 'LakefileWithoutToolchain' ||
85+
info.projectRootUri.scheme === 'untitled'
86+
) {
8387
return
8488
}
8589

@@ -96,7 +100,7 @@ export class LeanConfigWatchService implements Disposable {
96100
this.normalizedLakeFileContents.set(key, contents)
97101
if (raiseEvent) {
98102
// raise an event so the extension triggers handleLakeFileChanged.
99-
this.lakeFileChangedEmitter.fire(projectUri)
103+
this.lakeFileChangedEmitter.fire(info.projectRootUri)
100104
}
101105
}
102106

@@ -111,6 +115,8 @@ export class LeanConfigWatchService implements Disposable {
111115
const projectInfo = await findLeanProjectInfo(fileUri)
112116
if (
113117
projectInfo.kind === 'FileNotFound' ||
118+
projectInfo.kind === 'LakefileWithoutToolchain' ||
119+
projectInfo.projectRootUri.scheme === 'untitled' ||
114120
projectInfo.toolchainInfo === undefined ||
115121
projectInfo.toolchainInfo.toolchain === undefined
116122
) {

vscode-lean4/src/utils/exturi.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'path'
12
import { Uri, workspace } from 'vscode'
23
import { isFileInFolder, relativeFilePathInFolder } from './fsHelper'
34

@@ -49,6 +50,10 @@ export class FileUri {
4950
return this.asUri().toString()
5051
}
5152

53+
baseName(): string {
54+
return path.basename(this.fsPath)
55+
}
56+
5257
join(...pathSegments: string[]): FileUri {
5358
return FileUri.fromUriOrError(Uri.joinPath(this.asUri(), ...pathSegments))
5459
}

vscode-lean4/src/utils/lake.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import path from 'path'
21
import { OutputChannel } from 'vscode'
32
import { ExecutionExitCode, ExecutionResult } from './batch'
43
import { FileUri } from './exturi'
@@ -50,7 +49,7 @@ export class LakeRunner {
5049
}
5150

5251
async fetchMathlibCacheForFile(projectRelativeFileUri: FileUri): Promise<ExecutionResult> {
53-
const prompt = `Fetching Mathlib build artifact cache for ${path.basename(projectRelativeFileUri.fsPath)}`
52+
const prompt = `Fetching Mathlib build artifact cache for ${projectRelativeFileUri.baseName()}`
5453
return this.runLakeCommandWithProgress('exe', ['cache', 'get', projectRelativeFileUri.fsPath], prompt)
5554
}
5655

vscode-lean4/src/utils/leanCmdRunner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function overrideReason(activeOverride: ElanOverrideReason | undefined): string
4343
case 'Environment':
4444
return undefined
4545
case 'Manual':
46-
return `set by \`elan override\` in folder '${path.basename(activeOverride.directoryPath.fsPath)}'`
46+
return `set by \`elan override\` in folder '${activeOverride.directoryPath.baseName()}'`
4747
case 'ToolchainFile':
4848
return `of Lean project '${path.dirname(activeOverride.toolchainPath.fsPath)}'`
4949
case 'LeanpkgFile':

0 commit comments

Comments
 (0)