Skip to content

Commit 5348bca

Browse files
feat: add multi-root workspace support (#41)
1 parent e8b1122 commit 5348bca

File tree

41 files changed

+584
-174
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+584
-174
lines changed

.vscode/launch.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,34 @@
8585
],
8686
"outFiles": ["${workspaceFolder}/out/**/*.js"],
8787
"sourceMapRenames": true
88+
},
89+
{
90+
"name": "🧩 Debug Extension (Multi-Root Workspace)",
91+
"type": "extensionHost",
92+
"request": "launch",
93+
"args": [
94+
"--disable-extensions",
95+
"--extensionDevelopmentKind=node",
96+
"--extensionDevelopmentPath=${workspaceFolder}",
97+
"--log=supabase.postgrestools:debug",
98+
"${workspaceFolder}/test/fixtures/multi-root/test.code-workspace"
99+
],
100+
"outFiles": ["${workspaceFolder}/out/**/*.js"],
101+
"sourceMapRenames": true
102+
},
103+
{
104+
"name": "🧩 Debug Extension (Multi-Root Workspace With Overwrites)",
105+
"type": "extensionHost",
106+
"request": "launch",
107+
"args": [
108+
"--disable-extensions",
109+
"--extensionDevelopmentKind=node",
110+
"--extensionDevelopmentPath=${workspaceFolder}",
111+
"--log=supabase.postgrestools:debug",
112+
"${workspaceFolder}/test/fixtures/multi-root-with-overwrites/test.code-workspace"
113+
],
114+
"outFiles": ["${workspaceFolder}/out/**/*.js"],
115+
"sourceMapRenames": true
88116
}
89117
]
90118
}

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ The extension adds seven commands to your VS Code Command Palette. They are all
6060
- `PostgresTools: Restart` runs stop and start in succession.
6161
- `PostgresTools: Copy Latest Server Logfile` copies the latest server log file to your currently opened repo. The log file is meant to be attached to GitHub issues, it can sometimes help us to debug.
6262

63+
## Multi-Root Workspaces
64+
65+
You can use the extension in a multi-root workspace setting, but there are a few caveats:
66+
67+
- You should use at least version 0.8.0 of the binary. You can upgrade it in your `package.json` or run `PostgresTools: Hard Reset (..)`.
68+
- You can specify a `postgrestools.bin` and a `postgrestools.configFile` in your `.code-workspace` file, but you need to use an absolute path. The binary and the setting will then be used for all your workspace folders.
69+
- If you don't specify a config file, the binary will look for a `postgrestools.jsonc` file at the every workspace folder's root level. If the file isn't there, the extension will be disabled for the folder. The individual `configFile` settings of the folders are ignored.
70+
- The binary will currently only change database connections when a new file is opened, not when you alternate focus between two files. So, you might get linting/completions from a different database than you'd expect. For now, you can simply reopen the file, but we'll fix this soon 🙌
71+
6372
## Troubleshooting
6473

6574
1. First, try restarting the extension via the `PostgresTools: Hard Reset (...)` command mentioned above.

package-lock.json

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"url": "https://github.com/supabase-community/postgres-language-server"
1212
},
1313
"engines": {
14-
"vscode": "^1.96.2"
14+
"vscode": "^1.96.2",
15+
"node": "^18"
1516
},
1617
"icon": "icon.png",
1718
"categories": [
@@ -123,6 +124,7 @@
123124
},
124125
"devDependencies": {
125126
"@types/node": "20.17.23",
127+
"@types/semver": "7.7.0",
126128
"@types/vscode": "^1.96.2",
127129
"@typescript-eslint/eslint-plugin": "^8.22.0",
128130
"@typescript-eslint/parser": "^8.22.0",
@@ -131,6 +133,7 @@
131133
"typescript": "^5.7.3"
132134
},
133135
"dependencies": {
136+
"semver": "7.7.2",
134137
"vscode-languageclient": "9.0.1",
135138
"vscode-uri": "3.1.0"
136139
}

src/binary-finder-strategies.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { Uri, window } from "vscode";
22
import { logger } from "./logger";
33
import { delimiter, dirname, join } from "node:path";
4-
import { CONSTANTS } from "./constants";
4+
import { CONSTANTS, OperatingMode } from "./constants";
55
import { fileExists } from "./utils";
66
import { createRequire } from "node:module";
77
import { getConfig } from "./config";
88
import { downloadPglt, getDownloadedBinary } from "./downloader";
99

1010
export interface BinaryFindStrategy {
1111
name: string;
12-
find(path: Uri): Promise<Uri | null>;
12+
find(path?: Uri): Promise<Uri | null>;
1313
}
1414

1515
/**
@@ -34,7 +34,7 @@ export interface BinaryFindStrategy {
3434
*/
3535
export const vsCodeSettingsStrategy: BinaryFindStrategy = {
3636
name: "VSCode Settings Strategy",
37-
async find(path: Uri) {
37+
async find(path?: Uri) {
3838
logger.debug("Trying to find PostgresTools binary via VSCode Settings");
3939

4040
type BinSetting = string | Record<string, string> | undefined;
@@ -69,9 +69,30 @@ export const vsCodeSettingsStrategy: BinaryFindStrategy = {
6969
if (typeof binSetting === "string") {
7070
logger.debug("Binary Setting is a string", { binSetting });
7171

72-
const resolvedPath = binSetting.startsWith(".")
73-
? Uri.joinPath(path, binSetting).fsPath
74-
: binSetting;
72+
let resolvedPath: string;
73+
74+
if (binSetting.startsWith(".")) {
75+
if (CONSTANTS.operatingMode === OperatingMode.MultiRoot) {
76+
window.showErrorMessage(
77+
"Relative paths for the postgrestools binary in a multi-root workspace setting are not supported. Please use an absolute path in your `*.code-workspace` file."
78+
);
79+
return null;
80+
} else if (path) {
81+
resolvedPath = Uri.joinPath(path, binSetting).fsPath;
82+
} else {
83+
// can't really happen.
84+
logger.error(
85+
`User picked a relative path for setting and is not in multi-root workspace mode. Somehow, we couldn't form a path to the binary.`
86+
);
87+
return null;
88+
}
89+
} else {
90+
resolvedPath = binSetting;
91+
}
92+
93+
if (!resolvedPath) {
94+
return null;
95+
}
7596

7697
logger.debug("Looking for binary at path", { resolvedPath });
7798

@@ -98,7 +119,7 @@ export const vsCodeSettingsStrategy: BinaryFindStrategy = {
98119
*/
99120
export const nodeModulesStrategy: BinaryFindStrategy = {
100121
name: "Node Modules Strategy",
101-
async find(path: Uri) {
122+
async find(path?: Uri) {
102123
logger.debug("Trying to find PostgresTools binary in Node Modules");
103124

104125
if (!path) {
@@ -178,7 +199,7 @@ export const nodeModulesStrategy: BinaryFindStrategy = {
178199

179200
export const yarnPnpStrategy: BinaryFindStrategy = {
180201
name: "Yarn PnP Strategy",
181-
async find(path: Uri) {
202+
async find(path?: Uri) {
182203
logger.debug("Trying to find PostgresTools binary in Yarn Plug'n'Play");
183204

184205
if (!path) {

src/binary-finder.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,47 @@ const LOCAL_STRATEGIES: Strategy[] = [
5858
}),
5959
},
6060
];
61+
const GLOBAL_STRATEGIES: Strategy[] = [
62+
{
63+
label: "VSCode Settings",
64+
strategy: vsCodeSettingsStrategy,
65+
onSuccess: (uri) =>
66+
logger.debug(`Found Binary in VSCode Settings (postgrestools.bin)`, {
67+
path: uri.fsPath,
68+
}),
69+
},
70+
{
71+
label: "PATH Environment Variable",
72+
strategy: pathEnvironmentVariableStrategy,
73+
onSuccess: (uri) =>
74+
logger.debug(`Found Binary in PATH Environment Variable`, {
75+
path: uri.fsPath,
76+
}),
77+
},
78+
{
79+
label: "Downloaded Binary",
80+
strategy: downloadPgltStrategy,
81+
onSuccess: (uri) =>
82+
logger.debug(`Found downloaded binary`, {
83+
path: uri.fsPath,
84+
}),
85+
},
86+
];
6187

6288
export class BinaryFinder {
63-
static async find(path: Uri) {
89+
static async findGlobally() {
90+
logger.info("Using Global Strategies to find binary");
91+
const binary = await this.attemptFind(GLOBAL_STRATEGIES);
92+
93+
if (!binary) {
94+
logger.debug("Unable to find binary globally.");
95+
}
96+
97+
return binary;
98+
}
99+
100+
static async findLocally(path: Uri) {
101+
logger.info("Using Local Strategies to find binary");
64102
const binary = await this.attemptFind(LOCAL_STRATEGIES, path);
65103

66104
if (!binary) {
@@ -70,7 +108,7 @@ export class BinaryFinder {
70108
return binary;
71109
}
72110

73-
private static async attemptFind(strategies: Strategy[], path: Uri) {
111+
private static async attemptFind(strategies: Strategy[], path?: Uri) {
74112
for (const { strategy, onSuccess, condition, label } of strategies) {
75113
if (condition && !(await condition(path))) {
76114
continue;

src/commands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class UserFacingCommands {
8888
logdir = Uri.file(process.env.PGT_LOG_PATH);
8989
} else {
9090
/*
91-
* Looks are placed at different locations based on the platform.
91+
* Logs are placed at different locations based on the platform.
9292
* Linux: /home/alice/.cache/pgt
9393
* Win: C:\Users\Alice\AppData\Local\supabase-community\pgt\cache
9494
* Mac: /Users/Alice/Library/Caches/dev.supabase-community.pgt

src/config.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ export const getConfig = <T>(
3434
return workspace.getConfiguration("postgrestools", options.scope).get<T>(key);
3535
};
3636

37-
/**
38-
* TODO: Can the "state.activeProject" also refer to a workspace, or just to a workspace-folder?
39-
*/
4037
export const isEnabledForFolder = (folder: WorkspaceFolder): boolean => {
4138
return !!getConfig<boolean>("enabled", { scope: folder.uri });
4239
};

src/extension.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,42 @@ const listenForConfigurationChanges = () => {
8989
*/
9090
const listenForActiveTextEditorChange = () => {
9191
state.context.subscriptions.push(
92-
window.onDidChangeActiveTextEditor((editor) => {
92+
window.onDidChangeActiveTextEditor(async () => {
93+
const editor = window.activeTextEditor;
94+
logger.debug(`User changed active text editor.`);
95+
9396
updateHidden(editor);
97+
98+
const documentUri = editor?.document.uri;
99+
if (!documentUri) {
100+
logger.debug(
101+
`User changed active text editor, but document uri could not be found.`
102+
);
103+
return;
104+
}
105+
106+
const folder = workspace.getWorkspaceFolder(documentUri);
107+
if (!folder) {
108+
logger.debug(
109+
`User changed active text editor, but matching workspace folder could not be found.`,
110+
{ uri: documentUri }
111+
);
112+
return;
113+
}
114+
115+
const matchingProject = state.allProjects?.get(folder.uri);
116+
if (!matchingProject) {
117+
logger.debug(
118+
`User changed active text editor, but matching project could not be found.`,
119+
{ uri: folder.uri, projects: state.allProjects }
120+
);
121+
return;
122+
}
123+
124+
logger.debug(`Found matching active project.`, {
125+
project: matchingProject.path,
126+
});
127+
state.activeProject = matchingProject;
94128
})
95129
);
96130

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { logger } from "./logger";
55
import { state } from "./state";
66
import { createExtension, destroyExtension } from "./extension";
77
import { getConfig, getFullConfig } from "./config";
8+
import { CONSTANTS } from "./constants";
89

910
// This method is called when your extension is activated
1011
// Your extension is activated the very first time the command is executed
@@ -18,6 +19,7 @@ export async function activate(context: vscode.ExtensionContext) {
1819
const config = getFullConfig();
1920

2021
logger.info(`Starting with config…`, { config });
22+
logger.info(`In mode…`, { mode: CONSTANTS.operatingMode });
2123

2224
await createExtension();
2325
}

0 commit comments

Comments
 (0)