Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 74 additions & 42 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ async function handleError({ message, actions }: { message: string; actions: Act
}

export async function activate(context: vscode.ExtensionContext, launchOptions: LaunchOptions = defaultLaunchOptions) {
if (!isProjectMode()) {
vscode.window.showWarningMessage(USER_MESSAGE.SINGLE_FILE_MODE())
}

// activate state
initialiseState(context)

Expand Down Expand Up @@ -205,50 +209,64 @@ export async function activate(context: vscode.ExtensionContext, launchOptions:
// While most other commands can be awaited directly, this is useful for stuff like file creation, which indirectely triggers an asynchronous job.
registerCommand('flix.allJobsFinished', handlers.allJobsFinished(client, eventEmitter))

// watch for changes on the file system (delete, create, rename .flix files)
flixWatcher = vscode.workspace.createFileSystemWatcher(getFlixGlobPattern())
flixWatcher.onDidDelete((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiRemUri, { uri })
})
flixWatcher.onDidCreate((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiAddUri, { uri })
})
if (isProjectMode()) {
// In project mode, watch the file system for .flix/.fpkg/.jar/flix.toml changes.

// watch for changes on the file system (delete, create .fpkg files)
pkgWatcher = vscode.workspace.createFileSystemWatcher(getFpkgGlobPattern())
pkgWatcher.onDidDelete((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiRemPkg, { uri })
})
pkgWatcher.onDidCreate((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiAddPkg, { uri })
})
flixWatcher = vscode.workspace.createFileSystemWatcher(getFlixGlobPattern())
flixWatcher.onDidDelete((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiRemUri, { uri })
})
flixWatcher.onDidCreate((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiAddUri, { uri })
})

// watch for changes on the file system (delete, create .jar files)
pkgWatcher = vscode.workspace.createFileSystemWatcher(getJarGlobPattern())
pkgWatcher.onDidDelete((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiRemJar, { uri })
})
pkgWatcher.onDidCreate((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiAddJar, { uri })
})
pkgWatcher = vscode.workspace.createFileSystemWatcher(getFpkgGlobPattern())
pkgWatcher.onDidDelete((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiRemPkg, { uri })
})
pkgWatcher.onDidCreate((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiAddPkg, { uri })
})

// watch for changes to the flix.toml file
tomlWatcher = vscode.workspace.createFileSystemWatcher(getFlixTomlGlobPattern())
tomlWatcher.onDidChange(() => {
const { msg, option1, option2 } = USER_MESSAGE.ASK_RELOAD_TOML()
const doReload = vscode.window.showInformationMessage(msg, option1, option2)
doReload.then(res => {
if (res === 'Yes') {
makeHandleRestartClient(context, launchOptions)()
pkgWatcher = vscode.workspace.createFileSystemWatcher(getJarGlobPattern())
pkgWatcher.onDidDelete((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiRemJar, { uri })
})
pkgWatcher.onDidCreate((vsCodeUri: vscode.Uri) => {
const uri = vsCodeUriToUriString(vsCodeUri)
client.sendNotification(jobs.Request.apiAddJar, { uri })
})

tomlWatcher = vscode.workspace.createFileSystemWatcher(getFlixTomlGlobPattern())
tomlWatcher.onDidChange(() => {
const { msg, option1, option2 } = USER_MESSAGE.ASK_RELOAD_TOML()
const doReload = vscode.window.showInformationMessage(msg, option1, option2)
doReload.then(res => {
if (res === 'Yes') {
makeHandleRestartClient(context, launchOptions)()
}
})
})
} else {
// In single-file mode there is no workspace folder to watch.
// Instead, track document open/close to add/remove .flix files from the compiler.
// Content changes are already handled by the LSP TextDocumentSync mechanism.
vscode.workspace.onDidOpenTextDocument(doc => {
if (doc.uri.path.endsWith('.flix')) {
client.sendNotification(jobs.Request.apiAddUri, { uri: vsCodeUriToUriString(doc.uri) })
}
})
})
vscode.workspace.onDidCloseTextDocument(doc => {
if (doc.uri.path.endsWith('.flix')) {
client.sendNotification(jobs.Request.apiRemUri, { uri: vsCodeUriToUriString(doc.uri) })
}
})
}

vscode.window.onDidChangeActiveTextEditor(handlers.handleChangeEditor)
vscode.workspace.onDidChangeConfiguration(() => {
Expand All @@ -274,9 +292,23 @@ async function startSession(

const globalStoragePath = context.globalStorageUri.fsPath
const workspaceFolders = vscode.workspace.workspaceFolders?.map(ws => ws.uri.fsPath)
const workspaceFiles = (await vscode.workspace.findFiles(getFlixGlobPattern())).map(vsCodeUriToUriString)
const workspacePkgs = (await vscode.workspace.findFiles(getFpkgGlobPattern())).map(vsCodeUriToUriString)
const workspaceJars = (await vscode.workspace.findFiles(getJarGlobPattern())).map(vsCodeUriToUriString)

// In project mode, discover files via workspace glob patterns.
// In single-file mode, use the currently open .flix documents instead.
let workspaceFiles: string[]
let workspacePkgs: string[]
let workspaceJars: string[]
if (isProjectMode()) {
workspaceFiles = (await vscode.workspace.findFiles(getFlixGlobPattern())).map(vsCodeUriToUriString)
workspacePkgs = (await vscode.workspace.findFiles(getFpkgGlobPattern())).map(vsCodeUriToUriString)
workspaceJars = (await vscode.workspace.findFiles(getJarGlobPattern())).map(vsCodeUriToUriString)
} else {
workspaceFiles = vscode.workspace.textDocuments
.filter(doc => doc.uri.path.endsWith('.flix'))
.map(doc => vsCodeUriToUriString(doc.uri))
workspacePkgs = []
workspaceJars = []
}

// Wait until we're sure flix exists
const flixFilename = await ensureFlixExists({
Expand Down
15 changes: 14 additions & 1 deletion client/src/handlers/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as path from 'path'
import * as vscode from 'vscode'
import { LanguageClient } from 'vscode-languageclient/node'
import { EventEmitter } from 'events'

import * as jobs from '../engine/jobs'
import ensureFlixExists from './../util/ensureFlixExists'
import { LaunchOptions, defaultLaunchOptions, getFlixGlobPattern } from './../extension'
import { LaunchOptions, defaultLaunchOptions, getFlixGlobPattern, isProjectMode } from './../extension'
import { USER_MESSAGE } from '../util/userMessages'

let flixTerminal: vscode.Terminal | null = null
Expand Down Expand Up @@ -57,10 +58,17 @@ async function launchRepl(context: vscode.ExtensionContext, launchOptions: Launc
const { cmd, args } = await getJvmCmd(context, launchOptions)
args.push('repl')
args.push(...getExtraFlixArgs())

// In single-file mode, set cwd to the active file's directory so the REPL
// can find the file. In project mode, VS Code defaults to the workspace root.
const activeFilePath = vscode.window.activeTextEditor?.document.uri.fsPath
const cwd = !isProjectMode() && activeFilePath ? path.dirname(activeFilePath) : undefined

flixTerminal = vscode.window.createTerminal({
name: 'Flix REPL',
shellPath: cmd,
shellArgs: args,
cwd,

// The terminal will not be kept alive when restarting VSCode.
// This is necessary in the case where flix.jar has been removed while VSCode has been closed.
Expand Down Expand Up @@ -155,6 +163,11 @@ export function handleChangeEditor(editor: vscode.TextEditor | undefined) {
return
}

// In single-file mode every .flix file is valid — there is no project boundary.
if (!isProjectMode()) {
return
}

const included = vscode.languages.match({ pattern: getFlixGlobPattern() }, editor.document)
if (!included) {
vscode.window.showWarningMessage(USER_MESSAGE.FILE_NOT_PART_OF_PROJECT())
Expand Down
4 changes: 4 additions & 0 deletions client/src/util/userMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,8 @@ export class USER_MESSAGE {
static FILE_NOT_PART_OF_PROJECT() {
return `Flix will only load source files from \`*.flix\`, \`src/**\`, and \`test/**\``
}

static SINGLE_FILE_MODE() {
return 'Running in single-file mode. Open a folder for full project support.'
}
}
Loading