Skip to content

Commit 0de59b8

Browse files
Add support for multiple vscode workspaces (#1250)
## Changes <!-- Summary of your changes that are easy to understand --> ## Tests <!-- How is this tested? -->
1 parent a2d7f14 commit 0de59b8

22 files changed

+465
-106
lines changed

packages/databricks-vscode/.vscodeignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ logs/
1818
extension/
1919
**/*.vsix
2020
.build/
21+
tmp/**

packages/databricks-vscode/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@
544544
{
545545
"submenu": "databricks.run",
546546
"group": "navigation@0",
547-
"when": "resourceLangId == python || resourceLangId == scala || resourceLangId == r || resourceLangId == sql || resourceExtname == .ipynb"
547+
"when": "databricks.context.isActiveFileInActiveWorkspace && resourceLangId =~ /^(python|scala|r|sql)$/ || databricks.context.isActiveFileInActiveWorkspace && resourceExtname == .ipynb"
548548
}
549549
],
550550
"databricks.run": [
@@ -881,7 +881,7 @@
881881
"useYarn": false
882882
},
883883
"cli": {
884-
"version": "0.219.0"
884+
"version": "0.221.0"
885885
},
886886
"scripts": {
887887
"vscode:prepublish": "rm -rf out && yarn run package:compile && yarn run package:wrappers:write && yarn run package:jupyter-init-script:write && yarn run package:copy-webview-toolkit && yarn run generate-telemetry",

packages/databricks-vscode/src/bundle/BundleFileSet.test.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import {Uri} from "vscode";
1+
import {Uri, WorkspaceFolder} from "vscode";
22
import {BundleFileSet, getAbsoluteGlobPath} from "./BundleFileSet";
33
import {expect} from "chai";
44
import path from "path";
55
import * as tmp from "tmp-promise";
66
import * as fs from "fs/promises";
77
import {BundleSchema} from "./types";
88
import * as yaml from "yaml";
9+
import {instance, mock, when} from "ts-mockito";
10+
import {WorkspaceFolderManager} from "../vscode-objs/WorkspaceFolderManager";
911

1012
describe(__filename, async function () {
1113
let tmpdir: tmp.DirectoryResult;
@@ -18,6 +20,16 @@ describe(__filename, async function () {
1820
await tmpdir.cleanup();
1921
});
2022

23+
function getWorkspaceFolderManagerMock() {
24+
const mockWorkspaceFolderManager = mock<WorkspaceFolderManager>();
25+
const mockWorkspaceFolder = mock<WorkspaceFolder>();
26+
when(mockWorkspaceFolder.uri).thenReturn(Uri.file(tmpdir.path));
27+
when(mockWorkspaceFolderManager.activeWorkspaceFolder).thenReturn(
28+
instance(mockWorkspaceFolder)
29+
);
30+
return instance(mockWorkspaceFolderManager);
31+
}
32+
2133
it("should return the correct absolute glob path", () => {
2234
const tmpdirUri = Uri.file(tmpdir.path);
2335
let expectedGlob = path.join(tmpdirUri.fsPath, "test.txt");
@@ -34,7 +46,9 @@ describe(__filename, async function () {
3446

3547
it("should find the correct root bundle yaml", async () => {
3648
const tmpdirUri = Uri.file(tmpdir.path);
37-
const bundleFileSet = new BundleFileSet(tmpdirUri);
49+
const bundleFileSet = new BundleFileSet(
50+
getWorkspaceFolderManagerMock()
51+
);
3852

3953
expect(await bundleFileSet.getRootFile()).to.be.undefined;
4054

@@ -47,7 +61,9 @@ describe(__filename, async function () {
4761

4862
it("should return undefined if more than one root bundle yaml is found", async () => {
4963
const tmpdirUri = Uri.file(tmpdir.path);
50-
const bundleFileSet = new BundleFileSet(tmpdirUri);
64+
const bundleFileSet = new BundleFileSet(
65+
getWorkspaceFolderManagerMock()
66+
);
5167

5268
await fs.writeFile(path.join(tmpdirUri.fsPath, "bundle.yaml"), "");
5369
await fs.writeFile(path.join(tmpdirUri.fsPath, "databricks.yaml"), "");
@@ -80,7 +96,9 @@ describe(__filename, async function () {
8096

8197
it("should return correct included files", async () => {
8298
const tmpdirUri = Uri.file(tmpdir.path);
83-
const bundleFileSet = new BundleFileSet(tmpdirUri);
99+
const bundleFileSet = new BundleFileSet(
100+
getWorkspaceFolderManagerMock()
101+
);
84102

85103
expect(await bundleFileSet.getIncludedFilesGlob()).to.equal(
86104
`{included.yaml,${path.join("includes", "**", "*.yaml")}}`
@@ -102,7 +120,9 @@ describe(__filename, async function () {
102120

103121
it("should return all bundle files", async () => {
104122
const tmpdirUri = Uri.file(tmpdir.path);
105-
const bundleFileSet = new BundleFileSet(tmpdirUri);
123+
const bundleFileSet = new BundleFileSet(
124+
getWorkspaceFolderManagerMock()
125+
);
106126

107127
const actual = (await bundleFileSet.allFiles()).map(
108128
(v) => v.fsPath
@@ -117,7 +137,9 @@ describe(__filename, async function () {
117137

118138
it("isRootBundleFile should return true only for root bundle file", async () => {
119139
const tmpdirUri = Uri.file(tmpdir.path);
120-
const bundleFileSet = new BundleFileSet(tmpdirUri);
140+
const bundleFileSet = new BundleFileSet(
141+
getWorkspaceFolderManagerMock()
142+
);
121143

122144
const possibleRoots = [
123145
"bundle.yaml",
@@ -143,7 +165,9 @@ describe(__filename, async function () {
143165

144166
it("isIncludedBundleFile should return true only for included files", async () => {
145167
const tmpdirUri = Uri.file(tmpdir.path);
146-
const bundleFileSet = new BundleFileSet(tmpdirUri);
168+
const bundleFileSet = new BundleFileSet(
169+
getWorkspaceFolderManagerMock()
170+
);
147171

148172
expect(
149173
await bundleFileSet.isIncludedBundleFile(
@@ -168,7 +192,9 @@ describe(__filename, async function () {
168192

169193
it("isBundleFile should return true only for bundle files", async () => {
170194
const tmpdirUri = Uri.file(tmpdir.path);
171-
const bundleFileSet = new BundleFileSet(tmpdirUri);
195+
const bundleFileSet = new BundleFileSet(
196+
getWorkspaceFolderManagerMock()
197+
);
172198

173199
const possibleBundleFiles = [
174200
"bundle.yaml",

packages/databricks-vscode/src/bundle/BundleFileSet.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {BundleSchema} from "./types";
77
import {readFile, writeFile} from "fs/promises";
88
import {CachedValue} from "../locking/CachedValue";
99
import minimatch from "minimatch";
10+
import {WorkspaceFolderManager} from "../vscode-objs/WorkspaceFolderManager";
1011

1112
const rootFilePattern: string = "{bundle,databricks}.{yaml,yml}";
1213
const subProjectFilePattern: string = path.join("**", rootFilePattern);
@@ -61,7 +62,17 @@ export class BundleFileSet {
6162
return bundle as BundleSchema;
6263
});
6364

64-
constructor(private readonly workspaceRoot: Uri) {}
65+
private get workspaceRoot() {
66+
return this.workspaceFolderManager.activeWorkspaceFolder.uri;
67+
}
68+
69+
constructor(
70+
private readonly workspaceFolderManager: WorkspaceFolderManager
71+
) {
72+
workspaceFolderManager.onDidChangeActiveWorkspaceFolder(() => {
73+
this.bundleDataCache.invalidate();
74+
});
75+
}
6576

6677
async getRootFile() {
6778
const rootFile = await glob.glob(

packages/databricks-vscode/src/bundle/BundleProjectManager.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {randomUUID} from "crypto";
1818
import {onError} from "../utils/onErrorDecorator";
1919
import {BundleInitWizard, promptToOpenSubProjects} from "./BundleInitWizard";
2020
import {EventReporter, Events, Telemetry} from "../telemetry";
21+
import {WorkspaceFolderManager} from "../vscode-objs/WorkspaceFolderManager";
2122

2223
export class BundleProjectManager {
2324
private logger = logging.NamedLogger.getOrCreate(Loggers.Extension);
@@ -36,17 +37,26 @@ export class BundleProjectManager {
3637
private subProjects?: {relative: string; absolute: Uri}[];
3738
private legacyProjectConfig?: ProjectConfigFile;
3839

40+
get workspaceUri() {
41+
return this.workspaceFolderManager.activeWorkspaceFolder.uri;
42+
}
43+
3944
constructor(
4045
private context: ExtensionContext,
4146
private cli: CliWrapper,
4247
private customWhenContext: CustomWhenContext,
4348
private connectionManager: ConnectionManager,
4449
private configModel: ConfigModel,
4550
private bundleFileSet: BundleFileSet,
46-
private workspaceUri: Uri,
51+
private workspaceFolderManager: WorkspaceFolderManager,
4752
private telemetry: Telemetry
4853
) {
4954
this.disposables.push(
55+
this.workspaceFolderManager.onDidChangeActiveWorkspaceFolder(
56+
async () => {
57+
await this.isBundleProjectCache.refresh();
58+
}
59+
),
5060
this.bundleFileSet.bundleDataCache.onDidChange(async () => {
5161
try {
5262
await this.isBundleProjectCache.refresh();

packages/databricks-vscode/src/bundle/BundleWatcher.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {Disposable, EventEmitter, Uri, workspace} from "vscode";
22
import {BundleFileSet, getAbsoluteGlobPath} from "./BundleFileSet";
3-
import {WithMutex} from "../locking";
43
import path from "path";
4+
import {WorkspaceFolderManager} from "../vscode-objs/WorkspaceFolderManager";
55

66
export class BundleWatcher implements Disposable {
77
private disposables: Disposable[] = [];
@@ -18,15 +18,30 @@ export class BundleWatcher implements Disposable {
1818
private readonly _onDidDelete = new EventEmitter<Uri>();
1919
public readonly onDidDelete = this._onDidDelete.event;
2020

21-
private bundleFileSet: WithMutex<BundleFileSet>;
21+
private initCleanup: Disposable;
22+
constructor(
23+
private readonly bundleFileSet: BundleFileSet,
24+
private readonly workspaceFolderManager: WorkspaceFolderManager
25+
) {
26+
this.initCleanup = this.init();
27+
this.disposables.push(
28+
this.workspaceFolderManager.onDidChangeActiveWorkspaceFolder(() => {
29+
this.initCleanup.dispose();
30+
this.initCleanup = this.init();
31+
this.bundleFileSet.bundleDataCache.invalidate();
32+
})
33+
);
34+
}
2235

23-
constructor(bundleFileSet: BundleFileSet, workspaceUri: Uri) {
24-
this.bundleFileSet = new WithMutex(bundleFileSet);
36+
private init() {
2537
const yamlWatcher = workspace.createFileSystemWatcher(
26-
getAbsoluteGlobPath(path.join("**", "*.{yaml,yml}"), workspaceUri)
38+
getAbsoluteGlobPath(
39+
path.join("**", "*.{yaml,yml}"),
40+
this.workspaceFolderManager.activeWorkspaceFolder.uri
41+
)
2742
);
2843

29-
this.disposables.push(
44+
const disposables: Disposable[] = [
3045
yamlWatcher,
3146
yamlWatcher.onDidCreate((e) => {
3247
this.yamlFileChangeHandler(e, "CREATE");
@@ -36,22 +51,28 @@ export class BundleWatcher implements Disposable {
3651
}),
3752
yamlWatcher.onDidDelete((e) => {
3853
this.yamlFileChangeHandler(e, "DELETE");
39-
})
40-
);
54+
}),
55+
];
56+
57+
return {
58+
dispose: () => {
59+
disposables.forEach((i) => i.dispose());
60+
},
61+
};
4162
}
4263

4364
private async yamlFileChangeHandler(
4465
e: Uri,
4566
type: "CREATE" | "CHANGE" | "DELETE"
4667
) {
47-
if (!(await this.bundleFileSet.value.isBundleFile(e))) {
68+
if (!(await this.bundleFileSet.isBundleFile(e))) {
4869
return;
4970
}
5071

51-
this.bundleFileSet.value.bundleDataCache.invalidate();
72+
this.bundleFileSet.bundleDataCache.invalidate();
5273
this._onDidChange.fire();
5374
// to provide additional granularity, we also fire an event when the root bundle file changes
54-
if (this.bundleFileSet.value.isRootBundleFile(e)) {
75+
if (this.bundleFileSet.isRootBundleFile(e)) {
5576
this._onDidChangeRootFile.fire();
5677
}
5778
switch (type) {
@@ -66,5 +87,6 @@ export class BundleWatcher implements Disposable {
6687

6788
dispose() {
6889
this.disposables.forEach((i) => i.dispose());
90+
this.initCleanup.dispose();
6991
}
7092
}

packages/databricks-vscode/src/bundle/models/BundleRemoteStateModel.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {Uri} from "vscode";
21
import {CliWrapper} from "../../cli/CliWrapper";
32
import {BaseModelWithStateCache} from "../../configuration/models/BaseModelWithStateCache";
43
import {Mutex} from "../../locking";
@@ -9,6 +8,7 @@ import lodash from "lodash";
98
import {WorkspaceConfigs} from "../../vscode-objs/WorkspaceConfigs";
109
import {logging} from "@databricks/databricks-sdk";
1110
import {Loggers} from "../../logger";
11+
import {WorkspaceFolderManager} from "../../vscode-objs/WorkspaceFolderManager";
1212

1313
/* eslint-disable @typescript-eslint/naming-convention */
1414
export type BundleResourceModifiedStatus = "created" | "deleted" | "updated";
@@ -45,9 +45,13 @@ export class BundleRemoteStateModel extends BaseModelWithStateCache<BundleRemote
4545
protected mutex = new Mutex();
4646
private logger = logging.NamedLogger.getOrCreate(Loggers.Bundle);
4747

48+
get workspaceFolder() {
49+
return this.workspaceFolderManager.activeWorkspaceFolder.uri;
50+
}
51+
4852
constructor(
4953
private readonly cli: CliWrapper,
50-
private readonly workspaceFolder: Uri,
54+
private readonly workspaceFolderManager: WorkspaceFolderManager,
5155
private readonly workspaceConfigs: WorkspaceConfigs
5256
) {
5357
super();

packages/databricks-vscode/src/bundle/models/BundleValidateModel.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {Uri} from "vscode";
21
import {BundleWatcher} from "../BundleWatcher";
32
import {AuthProvider} from "../../configuration/auth/AuthProvider";
43
import {Mutex} from "../../locking";
@@ -10,6 +9,7 @@ import {BaseModelWithStateCache} from "../../configuration/models/BaseModelWithS
109
import {withOnErrorHandler} from "../../utils/onErrorDecorator";
1110
import {logging} from "@databricks/databricks-sdk";
1211
import {Loggers} from "../../logger";
12+
import {WorkspaceFolderManager} from "../../vscode-objs/WorkspaceFolderManager";
1313

1414
export type BundleValidateState = {
1515
clusterId?: string;
@@ -25,7 +25,7 @@ export class BundleValidateModel extends BaseModelWithStateCache<BundleValidateS
2525
constructor(
2626
private readonly bundleWatcher: BundleWatcher,
2727
private readonly cli: CliWrapper,
28-
private readonly workspaceFolder: Uri
28+
private readonly workspaceFolderManager: WorkspaceFolderManager
2929
) {
3030
super();
3131
this.disposables.push(
@@ -62,7 +62,11 @@ export class BundleValidateModel extends BaseModelWithStateCache<BundleValidateS
6262
}
6363

6464
protected async readState(): Promise<BundleValidateState> {
65-
if (this.target === undefined || this.authProvider === undefined) {
65+
if (
66+
!this.target ||
67+
!this.authProvider ||
68+
!this.workspaceFolderManager.activeWorkspaceFolder
69+
) {
6670
return {};
6771
}
6872

@@ -71,7 +75,7 @@ export class BundleValidateModel extends BaseModelWithStateCache<BundleValidateS
7175
await this.cli.bundleValidate(
7276
this.target,
7377
this.authProvider,
74-
this.workspaceFolder,
78+
this.workspaceFolderManager.activeWorkspaceFolder?.uri,
7579
workspaceConfigs.databrickscfgLocation,
7680
this.logger
7781
)

packages/databricks-vscode/src/bundle/models/BundleVariableModel.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {NamedLogger} from "@databricks/databricks-sdk/dist/logging";
88
import {Loggers} from "../../logger";
99
import {onError} from "../../utils/onErrorDecorator";
1010
import {BundleValidateModel} from "./BundleValidateModel";
11+
import {WorkspaceFolderManager} from "../../vscode-objs/WorkspaceFolderManager";
1112

1213
export type BundleVariable = Required<BundleSchema>["variables"][string] & {
1314
valueInTarget?: string;
@@ -23,10 +24,15 @@ export class BundleVariableModel extends BaseModelWithStateCache<BundleVariableM
2324
protected mutex: Mutex = new Mutex();
2425
private target: string | undefined;
2526
private overrideFileWatcher: FileSystemWatcher | undefined;
27+
28+
get workspaceRoot() {
29+
return this.workspaceFolderManager.activeWorkspaceFolder.uri;
30+
}
31+
2632
constructor(
2733
private readonly configModel: ConfigModel,
2834
private readonly bundleValidateModel: BundleValidateModel,
29-
private readonly workspaceRoot: Uri
35+
private readonly workspaceFolderManager: WorkspaceFolderManager
3036
) {
3137
super();
3238
this.disposables.push(

0 commit comments

Comments
 (0)