Skip to content

Commit 1590e3b

Browse files
authored
feat: activate lean features on all files in a project (#637)
This PR ensures that the extension will attempt to activate all Lean-specific features when opening any file in a Lean project, not just when opening a Lean file. The came up at [#general > Updating Mathlib via VSCode menu does not work @ 💬](https://leanprover.zulipchat.com/#narrow/channel/113488-general/topic/Updating.20Mathlib.20via.20VSCode.20menu.20does.20not.20work/near/526969295).
1 parent 9d5c0f2 commit 1590e3b

File tree

3 files changed

+161
-84
lines changed

3 files changed

+161
-84
lines changed

vscode-lean4/src/extension.ts

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as os from 'os'
22
import * as path from 'path'
3-
import { commands, ExtensionContext, extensions, TextDocument, window, workspace } from 'vscode'
3+
import { commands, ExtensionContext, extensions, TextDocument, TextEditor, window, workspace } from 'vscode'
44
import { AbbreviationFeature } from './abbreviation/AbbreviationFeature'
55
import { AbbreviationView } from './abbreviationview'
66
import { getDefaultLeanVersion } from './config'
@@ -20,11 +20,11 @@ import { LeanClientProvider } from './utils/clientProvider'
2020
import { LeanConfigWatchService } from './utils/configwatchservice'
2121
import { ElanCommandProvider } from './utils/elanCommands'
2222
import { PATH, setProcessEnvPATH } from './utils/envPath'
23-
import { onEventWhile, withoutReentrancy } from './utils/events'
24-
import { ExtUri, extUriToCwdUri, FileUri } from './utils/exturi'
23+
import { combine, onEventWhile, withoutReentrancy } from './utils/events'
24+
import { ExtUri, extUriToCwdUri, FileUri, toExtUri } from './utils/exturi'
2525
import { displayInternalErrorsIn } from './utils/internalErrors'
2626
import { registerLeanCommandRunner } from './utils/leanCmdRunner'
27-
import { lean, registerLeanEditorProvider } from './utils/leanEditorProvider'
27+
import { lean, registerLeanEditorProviders, text } from './utils/leanEditorProvider'
2828
import { LeanInstaller } from './utils/leanInstaller'
2929
import { ModuleTreeViewProvider } from './utils/moduleTreeViewProvider'
3030
import {
@@ -41,24 +41,51 @@ async function setLeanFeatureSetActive(isActive: boolean) {
4141
await commands.executeCommand('setContext', 'lean4.isLeanFeatureSetActive', isActive)
4242
}
4343

44-
async function findOpenLeanProjectUri(): Promise<ExtUri | 'NoValidDocument'> {
45-
const activeEditor = lean.activeLeanEditor
46-
if (activeEditor !== undefined) {
47-
const info = await findLeanProjectRootInfo(activeEditor.documentExtUri)
48-
if (info.kind !== 'FileNotFound') {
49-
return info.projectRootUri
50-
}
44+
async function findInitialLeanProjectUri(editor: TextEditor): Promise<ExtUri | undefined> {
45+
const uri = toExtUri(editor.document.uri)
46+
if (uri === undefined) {
47+
return undefined
48+
}
49+
const info = await findLeanProjectRootInfo(uri)
50+
if (info.kind === 'FileNotFound') {
51+
return undefined
52+
}
53+
if (editor.document.languageId !== 'lean4' && info.kind === 'Success' && info.toolchainUri === undefined) {
54+
return undefined
5155
}
56+
return info.projectRootUri
57+
}
5258

59+
async function findActiveLeanProjectUri(): Promise<ExtUri | undefined> {
60+
const activeEditor = window.activeTextEditor
61+
if (activeEditor === undefined) {
62+
return undefined
63+
}
64+
return await findInitialLeanProjectUri(activeEditor)
65+
}
66+
67+
async function findVisibleLeanProjectUri(): Promise<ExtUri | undefined> {
5368
// This happens if vscode starts with a lean file open
5469
// but the "Getting Started" page is active.
55-
for (const editor of lean.visibleLeanEditors) {
56-
const info = await findLeanProjectRootInfo(editor.documentExtUri)
57-
if (info.kind !== 'FileNotFound') {
58-
return info.projectRootUri
70+
for (const editor of window.visibleTextEditors) {
71+
const projectUri = await findInitialLeanProjectUri(editor)
72+
if (projectUri === undefined) {
73+
continue
5974
}
75+
return projectUri
6076
}
77+
return undefined
78+
}
6179

80+
async function findOpenLeanProjectUri(): Promise<ExtUri | 'NoValidDocument'> {
81+
const activeProjectUri = await findActiveLeanProjectUri()
82+
if (activeProjectUri !== undefined) {
83+
return activeProjectUri
84+
}
85+
const visibleProjectUri = await findVisibleLeanProjectUri()
86+
if (visibleProjectUri !== undefined) {
87+
return visibleProjectUri
88+
}
6289
return 'NoValidDocument'
6390
}
6491

@@ -257,12 +284,23 @@ async function tryActivatingLean4Features(
257284
'No visible Lean document - cannot retry activating the extension. Please select a Lean document.',
258285
)
259286
}
287+
// We try activating the Lean features in two cases:
288+
// 1. When revealing a new editor with the `lean4` language ID (e.g.: switching tabs, opening a new Lean document, changing the language ID to `lean4`)
289+
// 2. When revealing a new editor in a Lean project that doesn't have the `lean4` language ID (e.g.: switching tabs, opening a new document)
290+
// These two events are disjoint, so combining them won't cause duplicate triggers.
291+
const combinedEvent = combine(
292+
lean.onDidRevealLeanEditor,
293+
_ => true,
294+
text.onDidRevealLeanEditor,
295+
editor => editor.editor.document.languageId !== 'lean4',
296+
)
297+
context.subscriptions.push(combinedEvent.disposable)
260298
context.subscriptions.push(
261299
onEventWhile(
262-
lean.onDidRevealLeanEditor,
263-
withoutReentrancy('Continue', async editor => {
264-
const info = await findLeanProjectRootInfo(editor.documentExtUri)
265-
if (info.kind === 'FileNotFound') {
300+
combinedEvent.event,
301+
withoutReentrancy('Continue', async leanEditor => {
302+
const projectUri = await findInitialLeanProjectUri(leanEditor.editor)
303+
if (projectUri === undefined) {
266304
return 'Continue'
267305
}
268306
await tryActivatingLean4FeaturesInProject(
@@ -271,7 +309,7 @@ async function tryActivatingLean4Features(
271309
elanCommandProvider,
272310
resolve,
273311
d,
274-
info.projectRootUri,
312+
projectUri,
275313
)
276314
return 'Stop'
277315
}),
@@ -281,7 +319,7 @@ async function tryActivatingLean4Features(
281319

282320
export async function activate(context: ExtensionContext): Promise<Exports> {
283321
await setLeanFeatureSetActive(false)
284-
registerLeanEditorProvider(context)
322+
registerLeanEditorProviders(context)
285323
await setStickyNotificationActiveButHidden(false)
286324
context.subscriptions.push(
287325
commands.registerCommand('lean4.redisplaySetupError', async () => displayActiveStickyNotification()),

vscode-lean4/src/utils/events.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Disposable, Event } from 'vscode'
1+
import { Disposable, Event, EventEmitter } from 'vscode'
22

33
export function onNextEvent<T>(ev: Event<T>, listener: (e: T) => any): Disposable {
44
const d = ev(e => {
@@ -49,3 +49,26 @@ export function actionWithoutReentrancy<T>(f: (v: T) => Promise<void>): (v: T) =
4949
}
5050
}
5151
}
52+
53+
export function combine<T>(
54+
ev1: Event<T>,
55+
filter1: (e1: T) => boolean,
56+
ev2: Event<T>,
57+
filter2: (e2: T) => boolean,
58+
): { disposable: Disposable; event: Event<T> } {
59+
const emitter = new EventEmitter<T>()
60+
const d1 = ev1(e1 => {
61+
if (filter1(e1)) {
62+
emitter.fire(e1)
63+
}
64+
})
65+
const d2 = ev2(e2 => {
66+
if (filter2(e2)) {
67+
emitter.fire(e2)
68+
}
69+
})
70+
return {
71+
disposable: Disposable.from(d1, d2),
72+
event: emitter.event,
73+
}
74+
}

0 commit comments

Comments
 (0)