Skip to content

Commit 091c1bd

Browse files
authored
Set platform based on server (#32)
* Set platform based on server Had to refactor a bit to ensure our values get set before VS Code tries to use them. * Pave the way for mnemonics on all platforms * Fix context menus on Mac * Fix a bunch of things on Mac including menu bar * Set keybindings based on client's OS
1 parent 57fc27b commit 091c1bd

18 files changed

+976
-444
lines changed

packages/ide/src/client.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { upload } from "./upload";
55
import { client } from "./fill/client";
66
import { clipboard } from "./fill/clipboard";
77
import { INotificationService, IProgressService } from "./fill/notification";
8+
import "./fill/os"; // Ensure it fills before anything else waiting on initData.
89

910
/**
1011
* A general abstraction of an IDE client.

packages/ide/src/fill/dialog.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export class Dialog {
7878
this.buttons = this.options.buttons.map((buttonText, buttonIndex) => {
7979
const button = document.createElement("button");
8080
// TODO: support mnemonics.
81-
button.innerText = buttonText.replace("_", "");
81+
button.innerText = buttonText.replace("&&", "");
8282
button.addEventListener("click", () => {
8383
this.actionEmitter.emit({
8484
buttonIndex,

packages/ide/src/fill/electron.ts

+8
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ class Clipboard {
145145
return false;
146146
}
147147

148+
public readFindText(): string {
149+
return "";
150+
}
151+
152+
public writeFindText(_text: string): void {
153+
// Nothing.
154+
}
155+
148156
public writeText(value: string): Promise<void> {
149157
return clipboard.writeText(value);
150158
}

packages/ide/src/fill/os.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import { InitData } from "@coder/protocol";
1+
import { OperatingSystem, InitData } from "@coder/protocol";
22
import { client } from "./client";
33

44
class OS {
55
private _homedir: string | undefined;
66
private _tmpdir: string | undefined;
7+
private _platform: NodeJS.Platform | undefined;
78

89
public constructor() {
9-
client.initData.then((data) => {
10-
this.initialize(data);
11-
});
10+
client.initData.then((d) => this.initialize(d));
1211
}
1312

1413
public homedir(): string {
@@ -30,21 +29,23 @@ class OS {
3029
public initialize(data: InitData): void {
3130
this._homedir = data.homeDirectory;
3231
this._tmpdir = data.tmpDirectory;
32+
switch (data.os) {
33+
case OperatingSystem.Windows: this._platform = "win32"; break;
34+
case OperatingSystem.Mac: this._platform = "darwin"; break;
35+
default: this._platform = "linux"; break;
36+
}
3337
}
3438

3539
public release(): string {
3640
return "Unknown";
3741
}
3842

3943
public platform(): NodeJS.Platform {
40-
if (navigator.appVersion.indexOf("Win") != -1) {
41-
return "win32";
42-
}
43-
if (navigator.appVersion.indexOf("Mac") != -1) {
44-
return "darwin";
44+
if (typeof this._platform === "undefined") {
45+
throw new Error("trying to access platform before it has been set");
4546
}
4647

47-
return "linux";
48+
return this._platform;
4849
}
4950
}
5051

packages/server/yarn.lock

+7
Original file line numberDiff line numberDiff line change
@@ -3336,6 +3336,13 @@ source-map@~0.1.38:
33363336
dependencies:
33373337
amdefine ">=0.0.4"
33383338

3339+
3340+
version "1.0.5"
3341+
resolved "https://registry.yarnpkg.com/sourcemap-blender/-/sourcemap-blender-1.0.5.tgz#d361f3d12381c4e477178113878fdf984a91bdbc"
3342+
integrity sha512-GPhjCmDtJ8YY6zt1L6kP6WtBg6WrdWt5hw2Wmgt9rwC3yiwLo9vEuabh/YYSZ5KmFV20hVkGdkTwpXtT2E65TA==
3343+
dependencies:
3344+
source-map "^0.7.3"
3345+
33393346
split-string@^3.0.1, split-string@^3.0.2:
33403347
version "3.1.0"
33413348
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"

packages/vscode/src/client.ts

+10-205
Original file line numberDiff line numberDiff line change
@@ -1,216 +1,21 @@
1+
import { IdeClient } from "@coder/ide";
12
import * as paths from "./fill/paths";
2-
import "./fill/platform";
3-
import "./fill/storageDatabase";
4-
import "./fill/windowsService";
5-
import "./fill/workspacesService";
6-
import "./fill/environmentService";
7-
import "./fill/vscodeTextmate";
8-
import "./fill/codeEditor";
9-
import "./fill/mouseEvent";
10-
import "./fill/menuRegistry";
11-
import "./fill/workbenchRegistry";
12-
import { PasteAction } from "./fill/paste";
13-
import "./fill/dom";
143
import "./vscode.scss";
15-
import { IdeClient, IProgress, INotificationHandle } from "@coder/ide";
16-
import { registerContextMenuListener } from "vs/base/parts/contextmenu/electron-main/contextmenu";
17-
import { LogLevel } from "vs/platform/log/common/log";
18-
import { URI } from "vs/base/common/uri";
19-
import { INotificationService } from "vs/platform/notification/common/notification";
20-
import { IProgressService2, ProgressLocation } from "vs/platform/progress/common/progress";
21-
import { ExplorerItem, ExplorerModel } from "vs/workbench/parts/files/common/explorerModel";
22-
import { DragMouseEvent } from "vs/base/browser/mouseEvent";
23-
import { IEditorService, IResourceEditor } from "vs/workbench/services/editor/common/editorService";
24-
import { IEditorGroup } from "vs/workbench/services/group/common/editorGroupsService";
25-
import { IWindowsService, IWindowConfiguration } from "vs/platform/windows/common/windows";
26-
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
27-
import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey";
28-
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces";
29-
30-
export class Client extends IdeClient {
31-
private readonly windowId = parseInt(new Date().toISOString().replace(/[-:.TZ]/g, ""), 10);
32-
private _serviceCollection: ServiceCollection | undefined;
33-
private _clipboardContextKey: RawContextKey<boolean> | undefined;
34-
private _builtInExtensionsDirectory: string | undefined;
35-
36-
public get builtInExtensionsDirectory(): string {
37-
if (!this._builtInExtensionsDirectory) {
38-
throw new Error("trying to access builtin extensions directory before it has been set");
39-
}
40-
41-
return this._builtInExtensionsDirectory;
42-
}
43-
44-
public async handleExternalDrop(target: ExplorerItem | ExplorerModel, originalEvent: DragEvent): Promise<void> {
45-
await this.upload.uploadDropped(
46-
originalEvent,
47-
(target instanceof ExplorerItem ? target : target.roots[0]).resource,
48-
);
49-
}
50-
51-
public handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup, afterDrop: (targetGroup: IEditorGroup) => void, targetIndex?: number): void {
52-
this.initData.then((d) => {
53-
this.upload.uploadDropped(event, URI.file(d.workingDirectory)).then((paths) => {
54-
const uris = paths.map((p) => URI.file(p));
55-
if (uris.length) {
56-
(this.serviceCollection.get(IWindowsService) as IWindowsService).addRecentlyOpened(uris);
57-
}
58-
59-
const editors: IResourceEditor[] = uris.map(uri => ({
60-
resource: uri,
61-
options: {
62-
pinned: true,
63-
index: targetIndex,
64-
},
65-
}));
66-
67-
const targetGroup = resolveTargetGroup();
68-
69-
(this.serviceCollection.get(IEditorService) as IEditorService).openEditors(editors, targetGroup).then(() => {
70-
afterDrop(targetGroup);
71-
});
72-
});
73-
});
74-
}
75-
76-
/**
77-
* Use to toggle the paste option inside editors based on the native clipboard.
78-
*/
79-
public get clipboardContextKey(): RawContextKey<boolean> {
80-
if (!this._clipboardContextKey) {
81-
throw new Error("Trying to access clipboard context key before it has been set");
82-
}
83-
84-
return this._clipboardContextKey;
85-
}
86-
87-
public get clipboardText(): Promise<string> {
88-
return this.clipboard.readText();
89-
}
90-
91-
/**
92-
* Create a paste action for use in text inputs.
93-
*/
94-
public get pasteAction(): PasteAction {
95-
return new PasteAction();
96-
}
97-
98-
public set workspace(ws: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined) {
99-
if (typeof ws === "undefined") {
100-
window.localStorage.removeItem("workspace");
101-
} else {
102-
window.localStorage.setItem("workspace", JSON.stringify(ws));
103-
}
104-
105-
location.reload();
106-
}
107-
108-
public get workspace(): undefined | IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier {
109-
const ws = window.localStorage.getItem("workspace");
110-
try {
111-
return JSON.parse(ws!);
112-
} catch (ex) {
113-
return undefined;
114-
}
115-
}
116-
117-
public get serviceCollection(): ServiceCollection {
118-
if (!this._serviceCollection) {
119-
throw new Error("Trying to access service collection before it has been set");
120-
}
121-
122-
return this._serviceCollection;
123-
}
124-
125-
public set serviceCollection(collection: ServiceCollection) {
126-
this._serviceCollection = collection;
127-
this.progressService = {
128-
start: <T>(title: string, task: (progress: IProgress) => Promise<T>, onCancel: () => void): Promise<T> => {
129-
let lastProgress = 0;
130-
131-
return (this.serviceCollection.get(IProgressService2) as IProgressService2).withProgress({
132-
location: ProgressLocation.Notification,
133-
title,
134-
cancellable: true,
135-
}, (progress) => {
136-
return task({
137-
report: (p): void => {
138-
progress.report({ increment: p - lastProgress });
139-
lastProgress = p;
140-
},
141-
});
142-
}, () => {
143-
onCancel();
144-
});
145-
},
146-
};
147-
148-
this.notificationService = {
149-
error: (error: Error): void => (this.serviceCollection.get(INotificationService) as INotificationService).error(error),
150-
prompt: (severity, message, buttons, onCancel): INotificationHandle => {
151-
const handle = (this.serviceCollection.get(INotificationService) as INotificationService).prompt(
152-
severity, message, buttons, { onCancel },
153-
);
154-
155-
return {
156-
close: (): void => handle.close(),
157-
updateMessage: (message): void => handle.updateMessage(message),
158-
updateButtons: (buttons): void => handle.updateActions({
159-
primary: buttons.map((button) => ({
160-
id: "",
161-
label: button.label,
162-
tooltip: "",
163-
class: undefined,
164-
enabled: true,
165-
checked: false,
166-
radio: false,
167-
dispose: (): void => undefined,
168-
run: (): Promise<void> => Promise.resolve(button.run()),
169-
})),
170-
}),
171-
};
172-
},
173-
};
174-
}
4+
// NOTE: shouldn't import anything from VS Code here or anything that will
5+
// depend on a synchronous fill like `os`.
1756

7+
class VSClient extends IdeClient {
1768
protected initialize(): Promise<void> {
177-
registerContextMenuListener();
178-
179-
this._clipboardContextKey = new RawContextKey("nativeClipboard", this.clipboard.isEnabled);
180-
1819
return this.task("Start workbench", 1000, async (data, sharedData) => {
18210
paths._paths.initialize(data, sharedData);
183-
this._builtInExtensionsDirectory = data.builtInExtensionsDirectory;
18411
process.env.SHELL = data.shell;
185-
186-
const workspace = this.workspace || URI.file(data.workingDirectory);
187-
const { startup } = require("./startup") as typeof import("vs/workbench/electron-browser/main");
188-
const config: IWindowConfiguration = {
189-
machineId: "1",
190-
windowId: this.windowId,
191-
logLevel: LogLevel.Info,
192-
mainPid: 1,
193-
appRoot: data.dataDirectory,
194-
execPath: data.tmpDirectory,
195-
userEnv: {},
196-
nodeCachedDataDir: data.tmpDirectory,
197-
perfEntries: [],
198-
_: [],
199-
};
200-
if ((workspace as IWorkspaceIdentifier).configPath) {
201-
config.workspace = workspace as IWorkspaceIdentifier;
202-
} else {
203-
config.folderUri = workspace as URI;
204-
}
205-
await startup(config);
206-
const contextKeys = this.serviceCollection.get(IContextKeyService) as IContextKeyService;
207-
const bounded = this.clipboardContextKey.bindTo(contextKeys);
208-
this.clipboard.onPermissionChange((enabled) => {
209-
bounded.set(enabled);
210-
});
211-
this.clipboard.initialize();
12+
// At this point everything should be filled, including `os`. `os` also
13+
// relies on `initData` but it listens first so it initialize before this
14+
// callback, meaning we are safe to include everything from VS Code now.
15+
const { workbench } = require("./workbench") as typeof import("./workbench");
16+
await workbench.initialize();
21217
}, this.initData, this.sharedProcessData);
21318
}
21419
}
21520

216-
export const client = new Client();
21+
export const client = new VSClient();

packages/vscode/src/fill/environmentService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as environment from "vs/platform/environment/node/environmentService";
33

44
export class EnvironmentService extends environment.EnvironmentService {
55
public get sharedIPCHandle(): string {
6-
return paths._paths.socketPath || super.sharedIPCHandle;
6+
return paths.getSocketPath() || super.sharedIPCHandle;
77
}
88
}
99

packages/vscode/src/fill/labels.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as labels from "vs/base/common/labels";
2+
3+
// Here we simply disable translation of mnemonics and leave everything as &&.
4+
// Since we're in the browser, we can handle all platforms in the same way.
5+
const target = labels as typeof labels;
6+
target.mnemonicMenuLabel = (label: string, forceDisable?: boolean): string => {
7+
return forceDisable ? label.replace(/\(&&\w\)|&&/g, "") : label;
8+
};
9+
target.mnemonicButtonLabel = (label: string): string => { return label; };
10+
target.unmnemonicLabel = (label: string): string => { return label; };

packages/vscode/src/fill/paste.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TERMINAL_COMMAND_ID } from "vs/workbench/parts/terminal/common/terminal
44
import { ITerminalService } from "vs/workbench/parts/terminal/common/terminal";
55
import * as actions from "vs/workbench/parts/terminal/electron-browser/terminalActions";
66
import * as instance from "vs/workbench/parts/terminal/electron-browser/terminalInstance";
7-
import { clipboard } from "@coder/ide";
7+
import { client } from "../client";
88

99
const getLabel = (key: string, enabled: boolean): string => {
1010
return enabled
@@ -18,13 +18,13 @@ export class PasteAction extends Action {
1818
public constructor() {
1919
super(
2020
"editor.action.clipboardPasteAction",
21-
getLabel(PasteAction.KEY, clipboard.isEnabled),
21+
getLabel(PasteAction.KEY, client.clipboard.isEnabled),
2222
undefined,
23-
clipboard.isEnabled,
24-
async (): Promise<boolean> => clipboard.paste(),
23+
client.clipboard.isEnabled,
24+
async (): Promise<boolean> => client.clipboard.paste(),
2525
);
2626

27-
clipboard.onPermissionChange((enabled) => {
27+
client.clipboard.onPermissionChange((enabled) => {
2828
this.label = getLabel(PasteAction.KEY, enabled);
2929
this.enabled = enabled;
3030
});
@@ -36,17 +36,17 @@ class TerminalPasteAction extends Action {
3636

3737
public static readonly ID = TERMINAL_COMMAND_ID.PASTE;
3838
public static readonly LABEL = nls.localize("workbench.action.terminal.paste", "Paste into Active Terminal");
39-
public static readonly SHORT_LABEL = getLabel(TerminalPasteAction.KEY, clipboard.isEnabled);
39+
public static readonly SHORT_LABEL = getLabel(TerminalPasteAction.KEY, client.clipboard.isEnabled);
4040

4141
public constructor(
4242
id: string, label: string,
4343
@ITerminalService private terminalService: ITerminalService,
4444
) {
4545
super(id, label);
46-
clipboard.onPermissionChange((enabled) => {
46+
client.clipboard.onPermissionChange((enabled) => {
4747
this._setLabel(getLabel(TerminalPasteAction.KEY, enabled));
4848
});
49-
this._setLabel(getLabel(TerminalPasteAction.KEY, clipboard.isEnabled));
49+
this._setLabel(getLabel(TerminalPasteAction.KEY, client.clipboard.isEnabled));
5050
}
5151

5252
public run(): Promise<void> {
@@ -63,8 +63,8 @@ class TerminalPasteAction extends Action {
6363
class TerminalInstance extends instance.TerminalInstance {
6464
public async paste(): Promise<void> {
6565
this.focus();
66-
if (clipboard.isEnabled) {
67-
const text = await clipboard.readText();
66+
if (client.clipboard.isEnabled) {
67+
const text = await client.clipboard.readText();
6868
this.sendText(text, false);
6969
} else {
7070
document.execCommand("paste");

0 commit comments

Comments
 (0)