Featureful (#31)

* Fix loading within the CLI

* Remove app

* Remove promise handle

* Add initial travis file

* Add libxkbfile dependency

* Add libxkbfile-dev

* Add build script

* Fix malformed bash statement

* Remove yarn from script

* Improve build script

* Extract upx before usage

* Only run upx if on linux

* Ensure resource directory exists

* Pack runnable binary

* Export binary with platform

* Improve build process

* Install upx before running install script

* Update typescript version before running nexe

* Add os.release() function for multi-platform support

* Update travis.yml to improve deployment

* Add on CI

* Update to v1.31.0

* Add libsecret

* Update build target

* Skip cleanup

* Fix built-in extensions

* Add basics for apps

* Create custom DNS server

* Fix forking within CLI. Fixes TS language features

* Fix filename resolve

* Fix default extensions path

* Add custom dialog

* Store workspace path

* Remove outfiles

* Cleanup

* Always authed outside of CLI

* Use location.host for client

* Remove useless app interface

* Remove debug file for building wordlist

* Use chromes tcp host

* Update patch

* Build browser app before packaging

* Replace all css containing file:// URLs, fix webviews

* Fix save

* Fix mkdir
This commit is contained in:
Kyle Carberry
2019-02-21 11:55:42 -06:00
committed by Asher
parent bdd24081ab
commit 85d2225e0c
84 changed files with 5204 additions and 264 deletions

View File

@@ -1,7 +1,7 @@
import { logger } from "@coder/logger";
import { IDisposable } from "vs/base/common/lifecycle";
import * as actions from "vs/platform/actions/common/actions";
import { ToggleDevToolsAction } from "vs/workbench/electron-browser/actions";
import { ToggleDevToolsAction } from "vs/workbench/electron-browser/actions/developerActions";
// Intercept appending menu items so we can skip items that won't work.
const originalAppend = actions.MenuRegistry.appendMenuItem.bind(actions.MenuRegistry);

View File

@@ -1,4 +1,4 @@
import { readFile, writeFile } from "fs";
import { readFile, writeFile, mkdir } from "fs";
import * as path from "path";
import { promisify } from "util";
import { IDisposable } from "@coder/disposable";
@@ -7,6 +7,8 @@ import * as workspaceStorage from "vs/base/node/storage";
import * as globalStorage from "vs/platform/storage/node/storageIpc";
import * as paths from "./paths";
import { logger, field } from "@coder/logger";
import { client } from "@coder/vscode/src/client";
import { IStorageService, WillSaveStateReason } from "vs/platform/storage/common/storage";
class StorageDatabase implements workspaceStorage.IStorageDatabase {
public readonly onDidChangeItemsExternal = Event.None;
@@ -20,11 +22,9 @@ class StorageDatabase implements workspaceStorage.IStorageDatabase {
if (!navigator.sendBeacon) {
throw new Error("cannot save state");
}
// TODO: Need to use navigator.sendBeacon instead of the web socket, or we
// need to save when there is a change. Should we save as a sqlite3
// database instead of JSON? Could send to the server the way the global
// storage works. Or maybe fill `vscode-sqlite3` to do that.
this.save();
this.triggerFlush(WillSaveStateReason.SHUTDOWN);
navigator.sendBeacon(`/resource${this.path}`, this.content);
});
}
@@ -58,7 +58,7 @@ class StorageDatabase implements workspaceStorage.IStorageDatabase {
request.delete.forEach(key => this.items.delete(key));
}
return Promise.resolve();
return this.save();
}
public close(): Promise<void> {
@@ -69,13 +69,38 @@ class StorageDatabase implements workspaceStorage.IStorageDatabase {
return Promise.resolve("ok");
}
private save(): Promise<void> {
private async save(): Promise<void> {
try {
await promisify(mkdir)(path.dirname(this.path));
} catch (ex) {}
return promisify(writeFile)(this.path, this.content);
}
private triggerFlush(reason: WillSaveStateReason = WillSaveStateReason.NONE): boolean {
// tslint:disable-next-line:no-any
const storageService = client.serviceCollection.get<IStorageService>(IStorageService) as any;
if (reason === WillSaveStateReason.SHUTDOWN && storageService.close) {
storageService.close();
return true;
}
if (storageService._onWillSaveState) {
storageService._onWillSaveState.fire({ reason });
return true;
}
return false;
}
private get content(): string {
const json: { [key: string]: string } = {};
this.items.forEach((value, key) => {
json[key] = value;
});
return promisify(writeFile)(this.path, JSON.stringify(json));
return JSON.stringify(json);
}
}

View File

@@ -1,13 +1,14 @@
import * as electron from "electron";
import { Emitter } from "@coder/events";
import * as windowsIpc from "vs/platform/windows/node/windowsIpc";
import { IWindowsService, INativeOpenDialogOptions, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IMessageBoxResult, IDevToolsOptions, IEnterWorkspaceResult, CrashReporterStartOptions, INewWindowOptions } from "vs/platform/windows/common/windows";
import { IWindowsService, INativeOpenDialogOptions, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IMessageBoxResult, IDevToolsOptions, IEnterWorkspaceResult, CrashReporterStartOptions, INewWindowOptions, IOpenFileRequest, IAddFoldersRequest } from "vs/platform/windows/common/windows";
import { ParsedArgs } from "vs/platform/environment/common/environment";
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces";
import { URI } from "vs/base/common/uri";
import { IRecentlyOpened } from "vs/platform/history/common/history";
import { ISerializableCommandAction } from "vs/platform/actions/common/actions";
import { client } from "../client";
import { showOpenDialog } from "../dialog";
/**
* Instead of going to the shared process, we'll directly run these methods on
@@ -34,20 +35,70 @@ class WindowsService implements IWindowsService {
private readonly window = new electron.BrowserWindow();
// Dialogs
public pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
public async pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
showOpenDialog({
...(_options.dialogOptions || {}),
properties: {
openFile: true,
openDirectory: true,
},
}).then((path) => {
// tslint:disable-next-line:no-any
(<any>electron.ipcMain).send("vscode:openFiles", {
filesToOpen: [{
fileUri: URI.file(path),
}],
} as IOpenFileRequest);
}).catch((ex) => {
//
});
}
public pickFileAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
public async pickFileAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
showOpenDialog({
...(_options.dialogOptions || {}),
properties: {
openFile: true,
},
}).then((path) => {
// tslint:disable-next-line:no-any
(<any>electron.ipcMain).send("vscode:openFiles", {
filesToOpen: [{
fileUri: URI.file(path),
}],
} as IOpenFileRequest);
}).catch((ex) => {
//
});
}
public pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
public async pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
showOpenDialog({
...(_options.dialogOptions || {}),
properties: {
openDirectory: true,
},
}).then((path) => {
client.workspace = URI.file(path);
}).catch((ex) => {
//
});
}
public pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
public async pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
showOpenDialog({
...(_options.dialogOptions || {}),
properties: {
openDirectory: true,
},
}).then((path) => {
// tslint:disable-next-line:no-any
(<any>electron.ipcMain).send("vscode:addFolders", {
foldersToAdd: [URI.file(path)],
} as IAddFoldersRequest);
}).catch((ex) => {
//
});
}
public showMessageBox(windowId: number, options: MessageBoxOptions): Promise<IMessageBoxResult> {
@@ -70,10 +121,14 @@ class WindowsService implements IWindowsService {
}
public showOpenDialog(windowId: number, options: OpenDialogOptions): Promise<string[]> {
return new Promise((resolve): void => {
electron.dialog.showOpenDialog(this.getWindowById(windowId), options, (filePaths, _bookmarks) => {
resolve(filePaths);
});
return showOpenDialog({
...(options || {}),
properties: {
openDirectory: true,
openFile: true,
},
}).then((path) => {
return [path];
});
}
@@ -93,8 +148,17 @@ class WindowsService implements IWindowsService {
throw new Error("not implemented");
}
public enterWorkspace(_windowId: number, _path: string): Promise<IEnterWorkspaceResult> {
throw new Error("not implemented");
public enterWorkspace(_windowId: number, _path: URI): Promise<IEnterWorkspaceResult> {
if (_path.path.endsWith(".json")) {
client.workspace = {
id: "Untitled",
configPath: _path.path,
};
} else {
client.workspace = _path;
}
return undefined!;
}
public createAndEnterWorkspace(_windowId: number, _folders?: IWorkspaceFolderCreationData[], _path?: string): Promise<IEnterWorkspaceResult> {
@@ -251,8 +315,8 @@ class WindowsService implements IWindowsService {
return Promise.resolve(1);
}
public openExternal(_url: string): Promise<boolean> {
throw new Error("not implemented");
public async openExternal(_url: string): Promise<boolean> {
return typeof window.open(_url, "_blank") !== "undefined";
}
public startCrashReporter(_config: CrashReporterStartOptions): Promise<void> {

View File

@@ -4,7 +4,7 @@ import { Registry } from "vs/platform/registry/common/platform";
import { IWorkbenchActionRegistry, Extensions } from "vs/workbench/common/actions";
import { SyncActionDescriptor } from "vs/platform/actions/common/actions";
import { ContextKeyExpr } from "vs/platform/contextkey/common/contextkey";
import { ToggleDevToolsAction } from "vs/workbench/electron-browser/actions";
import { ToggleDevToolsAction } from "vs/workbench/electron-browser/actions/developerActions";
import { TerminalPasteAction } from "vs/workbench/parts/terminal/electron-browser/terminalActions";
import { KEYBINDING_CONTEXT_TERMINAL_FOCUS } from "vs/workbench/parts/terminal/common/terminal";
import { KeyCode, KeyMod } from "vs/base/common/keyCodes";

View File

@@ -0,0 +1,40 @@
import { URI } from "vs/base/common/uri";
import { IEnvironmentService } from "vs/platform/environment/common/environment";
import { ILogService } from "vs/platform/log/common/log";
import { IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from "vs/platform/workspaces/common/workspaces";
import { WorkspacesMainService } from "vs/platform/workspaces/electron-main/workspacesMainService";
import * as workspacesIpc from "vs/platform/workspaces/node/workspacesIpc";
import { client } from "../client";
/**
* Instead of going to the shared process, we'll directly run these methods on
* the client. This setup means we can only control the current window.
*/
class WorkspacesService implements IWorkspacesService {
// tslint:disable-next-line:no-any
public _serviceBrand: any;
public createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[] | undefined): Promise<IWorkspaceIdentifier> {
const mainService = new WorkspacesMainService(
client.serviceCollection.get<IEnvironmentService>(IEnvironmentService) as IEnvironmentService,
client.serviceCollection.get<ILogService>(ILogService) as ILogService,
);
// lib/vscode/src/vs/platform/workspaces/node/workspacesIpc.ts
const rawFolders: IWorkspaceFolderCreationData[] = folders!;
if (Array.isArray(rawFolders)) {
folders = rawFolders.map(rawFolder => {
return {
uri: URI.revive(rawFolder.uri), // convert raw URI back to real URI
name: rawFolder.name!,
} as IWorkspaceFolderCreationData;
});
}
return mainService.createUntitledWorkspace(folders);
}
}
const target = workspacesIpc as typeof workspacesIpc;
// @ts-ignore TODO: don't ignore it.
target.WorkspacesChannelClient = WorkspacesService;