Skip to content

Commit df5d9c1

Browse files
adam-fowler0xTim
andauthored
Add focus, unfocus events for workspace folders (#84)
* Add `focus`, `unfocus` events for workspace folders * Add WorkspaceContext.fireEvent, rename operation to event * Ensure we are fire multiple focus events, fire unfocus on remove * Add await to fireEvent("add") * Update src/WorkspaceContext.ts Co-authored-by: Tim Condon <[email protected]> * Make FolderEvent a class based enum Co-authored-by: Tim Condon <[email protected]>
1 parent 1216ed1 commit df5d9c1

File tree

2 files changed

+144
-31
lines changed

2 files changed

+144
-31
lines changed

src/WorkspaceContext.ts

Lines changed: 131 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ import { SwiftOutputChannel } from "./SwiftOutputChannel";
1919
import { execSwift, getSwiftExecutable, getXCTestPath } from "./utilities";
2020
import { getLLDBLibPath } from "./lldb";
2121

22-
// Context for whole workspace. Holds array of contexts for each workspace folder
23-
// and the ExtensionContext
22+
/**
23+
* Context for whole workspace. Holds array of contexts for each workspace folder
24+
* and the ExtensionContext
25+
*/
2426
export class WorkspaceContext implements vscode.Disposable {
2527
public folders: FolderContext[] = [];
28+
public currentFolder?: FolderContext;
2629
public outputChannel: SwiftOutputChannel;
2730
public statusItem: StatusItem;
2831
public xcTestPath?: string;
@@ -38,8 +41,70 @@ export class WorkspaceContext implements vscode.Disposable {
3841
this.statusItem.dispose();
3942
}
4043

41-
// catch workspace folder changes and add/remove folders based on those changes
42-
public async onDidChangeWorkspaceFolders(event: vscode.WorkspaceFoldersChangeEvent) {
44+
/** Setup the vscode event listeners to catch folder changes and active window changes */
45+
setupEventListeners() {
46+
// add event listener for when a workspace folder is added/removed
47+
const onWorkspaceChange = vscode.workspace.onDidChangeWorkspaceFolders(event => {
48+
if (this === undefined) {
49+
console.log("Trying to run onDidChangeWorkspaceFolders on deleted context");
50+
return;
51+
}
52+
this.onDidChangeWorkspaceFolders(event);
53+
});
54+
// add event listener for when the active edited text document changes
55+
const onDidChangeActiveWindow = vscode.window.onDidChangeActiveTextEditor(editor => {
56+
if (this === undefined) {
57+
console.log("Trying to run onDidChangeWorkspaceFolders on deleted context");
58+
return;
59+
}
60+
61+
const workspaceFolder = this.getWorkspaceFolder(editor);
62+
if (workspaceFolder) {
63+
this.focusFolder(workspaceFolder);
64+
}
65+
});
66+
this.extensionContext.subscriptions.push(onWorkspaceChange, onDidChangeActiveWindow);
67+
}
68+
69+
/**
70+
* Fire an event to all folder observers
71+
* @param folder folder to fire event for
72+
* @param event event type
73+
*/
74+
async fireEvent(folder: FolderContext, event: FolderEvent) {
75+
for (const observer of this.observers) {
76+
await observer(folder, event, this);
77+
}
78+
}
79+
80+
/**
81+
* set the focus folder
82+
* @param folder folder that has gained focus
83+
*/
84+
async focusFolder(folder?: vscode.WorkspaceFolder) {
85+
const folderContext = this.folders.find(context => context.folder === folder);
86+
if (folderContext === this.currentFolder) {
87+
return;
88+
}
89+
90+
// send unfocus event for previous folder observers
91+
if (this.currentFolder) {
92+
await this.fireEvent(this.currentFolder, FolderEvent.unfocus);
93+
}
94+
this.currentFolder = folderContext;
95+
if (!folderContext) {
96+
return;
97+
}
98+
99+
// send focus event to all observers
100+
await this.fireEvent(folderContext, FolderEvent.focus);
101+
}
102+
103+
/**
104+
* catch workspace folder changes and add or remove folders based on those changes
105+
* @param event workspace folder event
106+
*/
107+
async onDidChangeWorkspaceFolders(event: vscode.WorkspaceFoldersChangeEvent) {
43108
for (const folder of event.added) {
44109
await this.addFolder(folder);
45110
}
@@ -49,8 +114,11 @@ export class WorkspaceContext implements vscode.Disposable {
49114
}
50115
}
51116

52-
// add folder to workspace
53-
public async addFolder(folder: vscode.WorkspaceFolder) {
117+
/**
118+
* Called whenever a folder is added to the workspace
119+
* @param folder folder being added
120+
*/
121+
async addFolder(folder: vscode.WorkspaceFolder) {
54122
const isRootFolder = this.folders.length === 0;
55123
const folderContext = await FolderContext.create(folder, isRootFolder, this);
56124
this.folders.push(folderContext);
@@ -65,12 +133,21 @@ export class WorkspaceContext implements vscode.Disposable {
65133
);
66134
}
67135
}
68-
for (const observer of this.observers) {
69-
await observer(folderContext, "add");
136+
await this.fireEvent(folderContext, FolderEvent.add);
137+
138+
// if this is the first folder then set a focus event
139+
if (
140+
this.folders.length === 1 ||
141+
this.getWorkspaceFolder(vscode.window.activeTextEditor) === folder
142+
) {
143+
this.focusFolder(folder);
70144
}
71145
}
72146

73-
// remove folder from workspace
147+
/**
148+
* called when a folder is removed from workspace
149+
* @param folder folder being removed
150+
*/
74151
async removeFolder(folder: vscode.WorkspaceFolder) {
75152
// find context with root folder
76153
const index = this.folders.findIndex(context => context.folder === folder);
@@ -79,23 +156,33 @@ export class WorkspaceContext implements vscode.Disposable {
79156
return;
80157
}
81158
const context = this.folders[index];
159+
// if current folder is this folder send unfocus event by setting
160+
// current folder to undefined
161+
if (this.currentFolder === context) {
162+
this.focusFolder(undefined);
163+
}
82164
// run observer functions in reverse order when removing
83165
const observersReversed = [...this.observers];
84166
observersReversed.reverse();
85167
for (const observer of observersReversed) {
86-
await observer(context, "remove");
168+
await observer(context, FolderEvent.remove, this);
87169
}
88170
context.dispose();
89171
// remove context
90172
this.folders.splice(index, 1);
91173
}
92174

93-
observerFolders(fn: WorkspaceFoldersObserver): vscode.Disposable {
175+
/**
176+
* Add workspace folder event observer
177+
* @param fn observer function to be called when event occurs
178+
* @returns disposable object
179+
*/
180+
observeFolders(fn: WorkspaceFoldersObserver): vscode.Disposable {
94181
this.observers.add(fn);
95182
return { dispose: () => this.observers.delete(fn) };
96183
}
97184

98-
// report swift version and throw error if it failed to find swift
185+
/** report swift version and throw error if it failed to find swift */
99186
async reportSwiftVersion() {
100187
try {
101188
const { stdout } = await execSwift(["--version"]);
@@ -106,7 +193,7 @@ export class WorkspaceContext implements vscode.Disposable {
106193
}
107194
}
108195

109-
// find LLDB version and setup path in CodeLLDB
196+
/** find LLDB version and setup path in CodeLLDB */
110197
async setLLDBVersion() {
111198
// don't set LLDB on windows as swift version is not working at the moment
112199
if (process.platform === "win32") {
@@ -149,10 +236,40 @@ export class WorkspaceContext implements vscode.Disposable {
149236
});
150237
}
151238

239+
// return workspace folder from text editor
240+
private getWorkspaceFolder(editor?: vscode.TextEditor): vscode.WorkspaceFolder | undefined {
241+
if (!editor || !editor.document) {
242+
return;
243+
}
244+
return vscode.workspace.getWorkspaceFolder(editor.document.uri);
245+
}
246+
152247
private observers: Set<WorkspaceFoldersObserver> = new Set();
153248
}
154249

250+
/** Workspace Folder events */
251+
export class FolderEvent {
252+
/** Workspace folder has been added */
253+
static add = new FolderEvent("add");
254+
/** Workspace folder has been removed */
255+
static remove = new FolderEvent("remove");
256+
/** Workspace folder has gained focus via a file inside the folder becoming the actively edited file */
257+
static focus = new FolderEvent("focus");
258+
/** Workspace folder loses focus because another workspace folder gained it */
259+
static unfocus = new FolderEvent("unfocus");
260+
261+
constructor(private readonly name: string) {
262+
this.name = name;
263+
}
264+
265+
toString() {
266+
return this.name;
267+
}
268+
}
269+
270+
/** Workspace Folder observer function */
155271
export type WorkspaceFoldersObserver = (
156272
folder: FolderContext,
157-
operation: "add" | "remove"
273+
operation: FolderEvent,
274+
workspace: WorkspaceContext
158275
) => unknown;

src/extension.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import * as commands from "./commands";
1717
import * as debug from "./debug";
1818
import { PackageDependenciesProvider } from "./PackageDependencyProvider";
1919
import { SwiftTaskProvider } from "./SwiftTaskProvider";
20-
import { WorkspaceContext } from "./WorkspaceContext";
20+
import { FolderEvent, WorkspaceContext } from "./WorkspaceContext";
2121
import { activate as activateSourceKitLSP } from "./sourcekit-lsp/extension";
2222

2323
/**
@@ -31,15 +31,12 @@ export async function activate(context: vscode.ExtensionContext) {
3131

3232
// report swift version and throw error
3333
await workspaceContext.reportSwiftVersion();
34+
35+
// setup swift version of LLDB. Don't await on this as it can run in the background
3436
workspaceContext.setLLDBVersion();
3537

36-
const onWorkspaceChange = vscode.workspace.onDidChangeWorkspaceFolders(event => {
37-
if (workspaceContext === undefined) {
38-
console.log("Trying to run onDidChangeWorkspaceFolders on deleted context");
39-
return;
40-
}
41-
workspaceContext.onDidChangeWorkspaceFolders(event);
42-
});
38+
// listen for workspace folder changes and active text editor changes
39+
workspaceContext.setupEventListeners();
4340

4441
await activateSourceKitLSP(context);
4542

@@ -51,16 +48,16 @@ export async function activate(context: vscode.ExtensionContext) {
5148
commands.register(workspaceContext);
5249

5350
// observer for logging workspace folder addition/removal
54-
const logObserver = workspaceContext.observerFolders((folderContext, operation) => {
51+
const logObserver = workspaceContext.observeFolders((folderContext, event) => {
5552
workspaceContext.outputChannel.log(
56-
`${operation}: ${folderContext.folder.uri.fsPath}`,
53+
`${event}: ${folderContext.folder.uri.fsPath}`,
5754
folderContext.folder.name
5855
);
5956
});
6057

6158
// observer that will add dependency view based on whether a root workspace folder has been added
62-
const addDependencyViewObserver = workspaceContext.observerFolders((folder, operation) => {
63-
if (folder.isRootFolder && operation === "add") {
59+
const addDependencyViewObserver = workspaceContext.observeFolders((folder, event) => {
60+
if (folder.isRootFolder && event === FolderEvent.add) {
6461
const dependenciesProvider = new PackageDependenciesProvider(folder);
6562
const dependenciesView = vscode.window.createTreeView("packageDependencies", {
6663
treeDataProvider: dependenciesProvider,
@@ -71,12 +68,12 @@ export async function activate(context: vscode.ExtensionContext) {
7168
});
7269

7370
// observer that will resolve package for root folder
74-
const resolvePackageObserver = workspaceContext.observerFolders(async (folder, operation) => {
75-
if (operation === "add" && folder.swiftPackage.foundPackage) {
71+
const resolvePackageObserver = workspaceContext.observeFolders(async (folder, operation) => {
72+
if (operation === FolderEvent.add && folder.swiftPackage.foundPackage) {
7673
// Create launch.json files based on package description.
7774
await debug.makeDebugConfigurations(folder);
7875
if (folder.isRootFolder) {
79-
await commands.resolveDependencies(workspaceContext);
76+
commands.resolveDependencies(workspaceContext);
8077
}
8178
}
8279
});
@@ -93,8 +90,7 @@ export async function activate(context: vscode.ExtensionContext) {
9390
resolvePackageObserver,
9491
addDependencyViewObserver,
9592
logObserver,
96-
taskProvider,
97-
onWorkspaceChange
93+
taskProvider
9894
);
9995
}
10096

0 commit comments

Comments
 (0)