From c900a7eaa8f276c2062c61f7ceb071b694ce8a08 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sun, 20 Sep 2020 10:57:38 -0700 Subject: [PATCH] Nodeless entry for Vim extension (#5130) Add a new entry for running Vim in nodeless environment and load platform-specific modules based on the target. This PR includes changes to: - fs. In node, it's node's native fs; in nodeless, it uses vscode.workspace.fs. - historyFile. In node, it stores the history in file system; in nodeless, it stores in memory. - logger. In node, it uses winson; in nodeless, it uses browser console. - lastly, it relies on Webpack to tree shake unwanted code paths (for example, remove nvim related code paths from the bundle in nodeless environment). --- extensionBase.ts | 2 +- extensionWeb.ts | 22 ++++ gulpfile.js | 37 +++++- package-lock.json | 63 +++++++++ package.json | 6 +- src/actions/plugins/imswitcher.ts | 23 +++- src/cmd_line/commandLine.ts | 17 +-- src/cmd_line/commands/file.ts | 19 +-- src/cmd_line/commands/read.ts | 37 +++--- src/cmd_line/commands/write.ts | 2 +- .../inputMethodSwitcherValidator.ts | 2 +- src/configuration/vimrc.ts | 2 +- src/history/historyFile.ts | 106 +++------------- src/jumps/jumpTracker.ts | 2 +- src/platform/browser/constants.ts | 3 + src/platform/browser/fs.ts | 110 ++++++++++++++++ src/platform/browser/history.ts | 78 ++++++++++++ src/platform/browser/loggerImpl.ts | 73 +++++++++++ src/platform/common/logger.ts | 6 + src/platform/node/constants.ts | 3 + src/{util => platform/node}/fs.ts | 25 +++- src/platform/node/history.ts | 120 ++++++++++++++++++ src/platform/node/loggerImpl.ts | 83 ++++++++++++ src/state/globalState.ts | 4 +- src/state/vimState.ts | 26 ++-- src/util/clipboard.ts | 2 +- src/util/externalCommand.ts | 8 +- src/util/logger.ts | 83 +----------- src/util/util.ts | 20 --- test/actions/baseAction.test.ts | 1 + test/cmd_line/historyFile.test.ts | 4 +- test/register/register.test.ts | 1 + test/state/vimState.test.ts | 6 +- test/testUtils.ts | 2 +- tsconfig.json | 4 + webpack.config.js | 63 ++++++++- webpack.dev.js | 12 +- 37 files changed, 809 insertions(+), 268 deletions(-) create mode 100644 extensionWeb.ts create mode 100644 src/platform/browser/constants.ts create mode 100644 src/platform/browser/fs.ts create mode 100644 src/platform/browser/history.ts create mode 100644 src/platform/browser/loggerImpl.ts create mode 100644 src/platform/common/logger.ts create mode 100644 src/platform/node/constants.ts rename src/{util => platform/node}/fs.ts (78%) create mode 100644 src/platform/node/history.ts create mode 100644 src/platform/node/loggerImpl.ts diff --git a/extensionBase.ts b/extensionBase.ts index 8459ba521..d2fdab450 100644 --- a/extensionBase.ts +++ b/extensionBase.ts @@ -107,7 +107,7 @@ export async function activate( // Load state Register.loadFromDisk(extensionContext); - await Promise.all([commandLine.load(), globalState.load()]); + await Promise.all([commandLine.load(extensionContext), globalState.load(extensionContext)]); if (vscode.window.activeTextEditor) { const filepathComponents = vscode.window.activeTextEditor.document.fileName.split(/\\|\//); diff --git a/extensionWeb.ts b/extensionWeb.ts new file mode 100644 index 000000000..3605827ba --- /dev/null +++ b/extensionWeb.ts @@ -0,0 +1,22 @@ +/** + * Extension.ts is a lightweight wrapper around ModeHandler. It converts key + * events to their string names and passes them on to ModeHandler via + * handleKeyEvent(). + */ +import './src/actions/include-main'; + +/** + * Load configuration validator + */ + +import './src/configuration/validators/inputMethodSwitcherValidator'; +import './src/configuration/validators/remappingValidator'; +import './src/configuration/validators/vimrcValidator'; + +import * as vscode from 'vscode'; +import { activate as activateFunc } from './extensionBase'; +export { getAndUpdateModeHandler } from './extensionBase'; + +export async function activate(context: vscode.ExtensionContext) { + activateFunc(context, false); +} diff --git a/gulpfile.js b/gulpfile.js index 68218a097..e8b2318d3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,7 +9,8 @@ var gulp = require('gulp'), minimist = require('minimist'), path = require('path'), webpack_stream = require('webpack-stream'), - webpack_config = require('./webpack.config.js'); + webpack_config = require('./webpack.config.js'), + es = require('event-stream'); webpack_dev_config = require('./webpack.dev.js'); const exec = require('child_process').exec; @@ -135,6 +136,29 @@ function updateVersion(done) { }); } +function updatePath() { + const input = es.through(); + const output = input.pipe( + es.mapSync((f) => { + const contents = f.contents.toString('utf8'); + const filePath = f.path; + let platformRelativepath = path.relative( + path.dirname(filePath), + path.resolve(process.cwd(), 'out/src/platform/node') + ); + f.contents = Buffer.from( + contents.replace( + /\(\"platform\/([^"]*)\"\)/g, + '("' + (platformRelativepath === '' ? './' : platformRelativepath + '/') + '$1")' + ), + 'utf8' + ); + return f; + }) + ); + return es.duplex(input, output); +} + function copyPackageJson() { return gulp.src('./package.json').pipe(gulp.dest('out')); } @@ -156,15 +180,22 @@ gulp.task('tsc', function () { return tsResult.js .pipe(sourcemaps.write('.', { includeContent: false, sourceRoot: '' })) + .pipe(updatePath()) .pipe(gulp.dest('out')); }); gulp.task('webpack', function () { - return gulp.src('./extension.ts').pipe(webpack_stream(webpack_config)).pipe(gulp.dest('out')); + return webpack_stream({ + config: webpack_config, + entry: ['./extension.ts', './extensionWeb.ts'], + }).pipe(gulp.dest('out')); }); gulp.task('webpack-dev', function () { - return gulp.src('./extension.ts').pipe(webpack_stream(webpack_dev_config)).pipe(gulp.dest('out')); + return webpack_stream({ + config: webpack_dev_config, + entry: ['./extension.ts', './extensionWeb.ts'], + }).pipe(gulp.dest('out')); }); gulp.task('tslint', function () { diff --git a/package-lock.json b/package-lock.json index 20888c823..7289659a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2047,6 +2047,12 @@ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -2382,6 +2388,29 @@ "es5-ext": "~0.10.14" } }, + "event-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", + "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" + }, + "dependencies": { + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + } + } + }, "events": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", @@ -2853,6 +2882,12 @@ "map-cache": "^0.2.2" } }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -6455,6 +6490,15 @@ "pinkie-promise": "^2.0.0" } }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "~2.3" + } + }, "pbkdf2": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", @@ -7431,6 +7475,15 @@ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -7523,6 +7576,16 @@ } } }, + "stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", diff --git a/package.json b/package.json index 6b603d2bd..7c56cf139 100644 --- a/package.json +++ b/package.json @@ -32,14 +32,17 @@ ], "extensionKind": [ "ui", - "workspace" + "workspace", + "web" ], + "sideEffects": false, "activationEvents": [ "*", "onCommand:type" ], "qna": "https://vscodevim.herokuapp.com/", "main": "./out/extension", + "browser": "./outWeb/extensionWeb", "contributes": { "commands": [ { @@ -1056,6 +1059,7 @@ "@types/sinon": "9.0.5", "@types/vscode": "1.37.0", "clean-webpack-plugin": "3.0.0", + "event-stream": "^4.0.1", "gulp": "4.0.2", "gulp-bump": "3.2.0", "gulp-git": "2.10.1", diff --git a/src/actions/plugins/imswitcher.ts b/src/actions/plugins/imswitcher.ts index 58465c020..b049b2499 100644 --- a/src/actions/plugins/imswitcher.ts +++ b/src/actions/plugins/imswitcher.ts @@ -1,7 +1,26 @@ -import * as util from '../../util/util'; import { Logger } from '../../util/logger'; import { Mode } from '../../mode/mode'; import { configuration } from '../../configuration/configuration'; +import { exec } from 'child_process'; + +/** + * This function executes a shell command and returns the standard output as a string. + */ +function executeShell(cmd: string): Promise { + return new Promise((resolve, reject) => { + try { + exec(cmd, (err, stdout, stderr) => { + if (err) { + reject(err); + } else { + resolve(stdout); + } + }); + } catch (error) { + reject(error); + } + }); +} /** * InputMethodSwitcher changes input method when mode changed @@ -11,7 +30,7 @@ export class InputMethodSwitcher { private execute: (cmd: string) => Promise; private savedIMKey = ''; - constructor(execute: (cmd: string) => Promise = util.executeShell) { + constructor(execute: (cmd: string) => Promise = executeShell) { this.execute = execute; } diff --git a/src/cmd_line/commandLine.ts b/src/cmd_line/commandLine.ts index 6d63a2cbe..2610cd3b9 100644 --- a/src/cmd_line/commandLine.ts +++ b/src/cmd_line/commandLine.ts @@ -30,16 +30,13 @@ class CommandLine { public preCompleteCommand = ''; public get historyEntries() { - return this._history.get(); + return this._history?.get() || []; } public previousMode = Mode.Normal; - constructor() { - this._history = new CommandLineHistory(); - } - - public async load(): Promise { + public async load(context: vscode.ExtensionContext): Promise { + this._history = new CommandLineHistory(context); return this._history.load(); } @@ -66,7 +63,7 @@ class CommandLine { const cmd = parser.parse(command); const useNeovim = configuration.enableNeovim && cmd.command && cmd.command.neovimCapable(); - if (useNeovim) { + if (useNeovim && vimState.nvim) { const { statusBarText, error } = await vimState.nvim.run(vimState, command); StatusBar.setText(vimState, statusBarText, error); } else { @@ -74,7 +71,11 @@ class CommandLine { } } catch (e) { if (e instanceof VimError) { - if (e.code === ErrorCode.NotAnEditorCommand && configuration.enableNeovim) { + if ( + e.code === ErrorCode.NotAnEditorCommand && + configuration.enableNeovim && + vimState.nvim + ) { const { statusBarText } = await vimState.nvim.run(vimState, command); StatusBar.setText(vimState, statusBarText, true); } else { diff --git a/src/cmd_line/commands/file.ts b/src/cmd_line/commands/file.ts index 28f79f965..b6ab40ed9 100644 --- a/src/cmd_line/commands/file.ts +++ b/src/cmd_line/commands/file.ts @@ -1,27 +1,10 @@ -import * as fs from 'fs'; -import * as util from 'util'; import * as vscode from 'vscode'; import { Logger } from '../../util/logger'; import { getPathDetails, resolveUri } from '../../util/path'; import * as node from '../node'; +import { doesFileExist } from 'platform/fs'; import untildify = require('untildify'); -async function doesFileExist(fileUri: vscode.Uri) { - const activeTextEditor = vscode.window.activeTextEditor; - if (activeTextEditor) { - try { - await vscode.workspace.fs.stat(fileUri); - return true; - } catch { - return false; - } - } else { - // fallback to local fs - const fsExists = util.promisify(fs.exists); - return fsExists(fileUri.fsPath); - } -} - export enum FilePosition { NewWindowVerticalSplit, NewWindowHorizontalSplit, diff --git a/src/cmd_line/commands/read.ts b/src/cmd_line/commands/read.ts index 1e35883ca..31c162890 100644 --- a/src/cmd_line/commands/read.ts +++ b/src/cmd_line/commands/read.ts @@ -1,8 +1,7 @@ -import { exec } from 'child_process'; -import { readFileAsync } from '../../util/fs'; - import { TextEditor } from '../../textEditor'; import * as node from '../node'; +import { readFileAsync } from 'platform/fs'; +import { SUPPORT_READ_COMMAND } from 'platform/constants'; export interface IReadCommandArguments extends node.ICommandArgs { file?: string; @@ -58,18 +57,24 @@ export class ReadCommand extends node.CommandBase { } async getTextToInsertFromCmd(): Promise { - return new Promise((resolve, reject) => { - try { - exec(this.arguments.cmd as string, (err, stdout, stderr) => { - if (err) { - reject(err); - } else { - resolve(stdout); - } - }); - } catch (e) { - reject(e); - } - }); + if (SUPPORT_READ_COMMAND) { + return new Promise((resolve, reject) => { + try { + import('child_process').then((cp) => { + return cp.exec(this.arguments.cmd as string, (err, stdout, stderr) => { + if (err) { + reject(err); + } else { + resolve(stdout); + } + }); + }); + } catch (e) { + reject(e); + } + }); + } else { + return ''; + } } } diff --git a/src/cmd_line/commands/write.ts b/src/cmd_line/commands/write.ts index 174b75916..4223e3184 100644 --- a/src/cmd_line/commands/write.ts +++ b/src/cmd_line/commands/write.ts @@ -1,4 +1,4 @@ -import * as fs from '../../util/fs'; +import * as fs from 'platform/fs'; import * as node from '../node'; import * as path from 'path'; import * as vscode from 'vscode'; diff --git a/src/configuration/validators/inputMethodSwitcherValidator.ts b/src/configuration/validators/inputMethodSwitcherValidator.ts index aff87b5aa..57474b9a2 100644 --- a/src/configuration/validators/inputMethodSwitcherValidator.ts +++ b/src/configuration/validators/inputMethodSwitcherValidator.ts @@ -1,6 +1,6 @@ import { IConfigurationValidator, ValidatorResults } from '../iconfigurationValidator'; import { IConfiguration } from '../iconfiguration'; -import { existsAsync } from '../../util/fs'; +import { existsAsync } from 'platform/fs'; import { Globals } from '../../globals'; import { configurationValidator } from '../configurationValidator'; diff --git a/src/configuration/vimrc.ts b/src/configuration/vimrc.ts index 118edbdc3..ac1fd655d 100644 --- a/src/configuration/vimrc.ts +++ b/src/configuration/vimrc.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import * as fs from '../util/fs'; +import * as fs from 'platform/fs'; import * as os from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; diff --git a/src/history/historyFile.ts b/src/history/historyFile.ts index d2be92412..8b4bdc41b 100644 --- a/src/history/historyFile.ts +++ b/src/history/historyFile.ts @@ -1,120 +1,56 @@ -import * as path from 'path'; +import * as vscode from 'vscode'; import { Logger } from '../util/logger'; import { configuration } from '../configuration/configuration'; import { Globals } from '../globals'; -import { readFileAsync, mkdirAsync, writeFileAsync, unlinkSync } from '../util/fs'; +import { HistoryBase } from 'platform/history'; export class HistoryFile { private readonly _logger = Logger.get('HistoryFile'); - private _historyFileName: string; + private _base: HistoryBase; private _history: string[] = []; - public get historyFilePath(): string { - return path.join(Globals.extensionStoragePath, this._historyFileName); + get historyFilePath(): string { + return this._base.historyKey; } - constructor(historyFileName: string) { - this._historyFileName = historyFileName; + constructor(context: vscode.ExtensionContext, historyFileName: string) { + this._base = new HistoryBase( + context, + historyFileName, + Globals.extensionStoragePath, + this._logger + ); } public async add(value: string | undefined): Promise { - if (!value || value.length === 0) { - return; - } - - // remove duplicates - let index: number = this._history.indexOf(value); - if (index !== -1) { - this._history.splice(index, 1); - } - - // append to the end - this._history.push(value); - - // resize array if necessary - if (this._history.length > configuration.history) { - this._history = this._history.slice(this._history.length - configuration.history); - } - - return this.save(); + return this._base.add(value, configuration.history); } public get(): string[] { - // resize array if necessary - if (this._history.length > configuration.history) { - this._history = this._history.slice(this._history.length - configuration.history); - } - - return this._history; + return this._base.get(configuration.history); } public clear() { - try { - this._history = []; - unlinkSync(this.historyFilePath); - } catch (err) { - this._logger.warn(`Unable to delete ${this.historyFilePath}. err=${err}.`); - } + this._base.clear(); } public async load(): Promise { - let data = ''; - - try { - data = await readFileAsync(this.historyFilePath, 'utf-8'); - } catch (err) { - if (err.code === 'ENOENT') { - this._logger.debug(`History does not exist. path=${this.historyFilePath}`); - } else { - this._logger.warn(`Failed to load history. path=${this.historyFilePath} err=${err}.`); - } - return; - } - - if (data.length === 0) { - return; - } - - try { - let parsedData = JSON.parse(data); - if (!Array.isArray(parsedData)) { - throw Error('Unexpected format in history file. Expected JSON.'); - } - this._history = parsedData; - } catch (e) { - this._logger.warn(`Deleting corrupted history file. path=${this.historyFilePath} err=${e}.`); - this.clear(); - } + await this._base.load(); } private async save(): Promise { - try { - // create supplied directory. if directory already exists, do nothing and move on - try { - await mkdirAsync(Globals.extensionStoragePath, { recursive: true }); - } catch (createDirectoryErr) { - if (createDirectoryErr.code !== 'EEXIST') { - throw createDirectoryErr; - } - } - - // create file - await writeFileAsync(this.historyFilePath, JSON.stringify(this._history), 'utf-8'); - } catch (err) { - this._logger.error(`Failed to save history. filepath=${this.historyFilePath}. err=${err}.`); - throw err; - } + await this._base.save(); } } export class SearchHistory extends HistoryFile { - constructor() { - super('.search_history'); + constructor(context: vscode.ExtensionContext) { + super(context, '.search_history'); } } export class CommandLineHistory extends HistoryFile { - constructor() { - super('.cmdline_history'); + constructor(context: vscode.ExtensionContext) { + super(context, '.cmdline_history'); } } diff --git a/src/jumps/jumpTracker.ts b/src/jumps/jumpTracker.ts index f92139173..e0798e2a4 100644 --- a/src/jumps/jumpTracker.ts +++ b/src/jumps/jumpTracker.ts @@ -6,7 +6,7 @@ import { VimState } from '../state/vimState'; import { Jump } from './jump'; import { getCursorsAfterSync } from '../util/util'; -import { existsAsync } from '../util/fs'; +import { existsAsync } from 'platform/fs'; /** * JumpTracker is a handrolled version of VSCode's TextEditorState diff --git a/src/platform/browser/constants.ts b/src/platform/browser/constants.ts new file mode 100644 index 000000000..0a9f82b1c --- /dev/null +++ b/src/platform/browser/constants.ts @@ -0,0 +1,3 @@ +export const SUPPORT_NVIM = false; +export const SUPPORT_IME_SWITCHER = false; +export const SUPPORT_READ_COMMAND = false; diff --git a/src/platform/browser/fs.ts b/src/platform/browser/fs.ts new file mode 100644 index 000000000..37ea9620b --- /dev/null +++ b/src/platform/browser/fs.ts @@ -0,0 +1,110 @@ +import * as vscode from 'vscode'; + +export const constants = { + UV_FS_SYMLINK_DIR: 1, + UV_FS_SYMLINK_JUNCTION: 2, + O_RDONLY: 0, + O_WRONLY: 1, + O_RDWR: 2, + UV_DIRENT_UNKNOWN: 0, + UV_DIRENT_FILE: 1, + UV_DIRENT_DIR: 2, + UV_DIRENT_LINK: 3, + UV_DIRENT_FIFO: 4, + UV_DIRENT_SOCKET: 5, + UV_DIRENT_CHAR: 6, + UV_DIRENT_BLOCK: 7, + S_IFMT: 61440, + S_IFREG: 32768, + S_IFDIR: 16384, + S_IFCHR: 8192, + S_IFBLK: 24576, + S_IFIFO: 4096, + S_IFLNK: 40960, + S_IFSOCK: 49152, + O_CREAT: 512, + O_EXCL: 2048, + UV_FS_O_FILEMAP: 0, + O_NOCTTY: 131072, + O_TRUNC: 1024, + O_APPEND: 8, + O_DIRECTORY: 1048576, + O_NOFOLLOW: 256, + O_SYNC: 128, + O_DSYNC: 4194304, + O_SYMLINK: 2097152, + O_NONBLOCK: 4, + S_IRWXU: 448, + S_IRUSR: 256, + S_IWUSR: 128, + S_IXUSR: 64, + S_IRWXG: 56, + S_IRGRP: 32, + S_IWGRP: 16, + S_IXGRP: 8, + S_IRWXO: 7, + S_IROTH: 4, + S_IWOTH: 2, + S_IXOTH: 1, + F_OK: 0, + R_OK: 4, + W_OK: 2, + X_OK: 1, + UV_FS_COPYFILE_EXCL: 1, + COPYFILE_EXCL: 1, + UV_FS_COPYFILE_FICLONE: 2, + COPYFILE_FICLONE: 2, + UV_FS_COPYFILE_FICLONE_FORCE: 4, + COPYFILE_FICLONE_FORCE: 4, +}; + +export async function doesFileExist(fileUri: vscode.Uri) { + try { + await vscode.workspace.fs.stat(fileUri); + return true; + } catch { + return false; + } +} + +export async function existsAsync(path: string): Promise { + try { + await vscode.workspace.fs.stat(vscode.Uri.parse(path)); + return true; + } catch (_e) { + return false; + } +} + +export async function unlink(path): Promise { + await vscode.workspace.fs.delete(vscode.Uri.parse(path)); +} + +export async function readFileAsync(path: string, encoding: string): Promise { + const ret = await vscode.workspace.fs.readFile(vscode.Uri.parse(path)); + return ret.toString(); +} + +export async function mkdirAsync(path: string, options: any): Promise { + return vscode.workspace.fs.createDirectory(vscode.Uri.parse(path)); +} + +export async function writeFileAsync( + path: string, + content: string, + encoding: string +): Promise { + return vscode.workspace.fs.writeFile(vscode.Uri.parse(path), Buffer.from(content)); +} + +export async function accessAsync(path: string, mode: number) { + // no op in nodeless +} + +export async function chmodAsync(path: string, mode: string | number) { + // no op in nodeless +} + +export function unlinkSync(path: string) { + // no op in nodeless +} diff --git a/src/platform/browser/history.ts b/src/platform/browser/history.ts new file mode 100644 index 000000000..3954727b7 --- /dev/null +++ b/src/platform/browser/history.ts @@ -0,0 +1,78 @@ +import * as vscode from 'vscode'; +import { ILogger } from '../common/logger'; + +export class HistoryBase { + private _historyFileName: string; + private _history: string[] = []; + get historyKey(): string { + return `vim.${this._historyFileName}`; + } + private _context: vscode.ExtensionContext; + private _extensionStoragePath: string; + private _logger: ILogger; + + constructor( + context: vscode.ExtensionContext, + historyFileName: string, + extensionStoragePath: string, + logger: ILogger + ) { + this._context = context; + this._historyFileName = historyFileName; + this._extensionStoragePath = extensionStoragePath; + this._logger = logger; + } + + public async add(value: string | undefined, history: number): Promise { + if (!value || value.length === 0) { + return; + } + + // remove duplicates + let index: number = this._history.indexOf(value); + if (index !== -1) { + this._history.splice(index, 1); + } + + // append to the end + this._history.push(value); + + // resize array if necessary + if (this._history.length > history) { + this._history = this._history.slice(this._history.length - history); + } + + return this.save(); + } + + public get(history: number): string[] { + // resize array if necessary + if (this._history.length > history) { + this._history = this._history.slice(this._history.length - history); + } + + return this._history; + } + + public async clear() { + this._context.workspaceState.update(this.historyKey, undefined); + this._history = []; + } + + public async load(): Promise { + let data = this._context.workspaceState.get(this.historyKey) || ''; + if (data.length === 0) { + return; + } + + let parsedData = JSON.parse(data); + if (!Array.isArray(parsedData)) { + throw Error('Unexpected format in history. Expected JSON.'); + } + this._history = parsedData; + } + + async save(): Promise { + this._context.workspaceState.update(this.historyKey, JSON.stringify(this._history)); + } +} diff --git a/src/platform/browser/loggerImpl.ts b/src/platform/browser/loggerImpl.ts new file mode 100644 index 000000000..4eff8e6a1 --- /dev/null +++ b/src/platform/browser/loggerImpl.ts @@ -0,0 +1,73 @@ +import { configuration } from '../../configuration/configuration'; +import { ILogger } from 'src/platform/common/logger'; + +/** + * Displays VSCode message to user + */ +export class VsCodeMessage implements ILogger { + actionMessages = ['Dismiss', 'Suppress Errors']; + private prefix: string; + + constructor(prefix: string) { + this.prefix = prefix; + } + + error(errorMessage: string): void { + this.log({ level: 'error', message: errorMessage }); + } + + debug(debugMessage: string): void { + this.log({ level: 'debug', message: debugMessage }); + } + + warn(warnMessage: string): void { + this.log({ level: 'warn', message: warnMessage }); + } + + verbose(verboseMessage: string): void { + this.log({ level: 'verbose', message: verboseMessage }); + } + + private async log(info: { level: string; message: string }) { + if (configuration.debug.silent) { + return; + } + let showMessage: (message: string, ...items: string[]) => void; + switch (info.level) { + case 'error': + showMessage = console.error; + break; + case 'warn': + showMessage = console.warn; + break; + case 'info': + case 'verbose': + case 'debug': + showMessage = console.log; + break; + default: + throw 'Unsupported ' + info.level; + } + + let message = info.message; + if (this.prefix) { + message = this.prefix + ': ' + message; + } + + showMessage(message, ...this.actionMessages); + } +} + +export class LoggerImpl { + static mapping: Map = new Map(); + static get(prefix?: string): ILogger { + prefix = prefix || 'default'; + if (LoggerImpl.mapping.has(prefix)) { + return LoggerImpl.mapping.get(prefix)!; + } + + const logger = new VsCodeMessage(prefix); + LoggerImpl.mapping.set(prefix, logger); + return logger; + } +} diff --git a/src/platform/common/logger.ts b/src/platform/common/logger.ts new file mode 100644 index 000000000..4da9f953e --- /dev/null +++ b/src/platform/common/logger.ts @@ -0,0 +1,6 @@ +export interface ILogger { + error(errorMessage: string): void; + debug(debugMessage: string): void; + warn(warnMessage: string): void; + verbose(verboseMessage: string): void; +} diff --git a/src/platform/node/constants.ts b/src/platform/node/constants.ts new file mode 100644 index 000000000..e0ebc57aa --- /dev/null +++ b/src/platform/node/constants.ts @@ -0,0 +1,3 @@ +export const SUPPORT_NVIM = true; +export const SUPPORT_IME_SWITCHER = true; +export const SUPPORT_READ_COMMAND = true; diff --git a/src/util/fs.ts b/src/platform/node/fs.ts similarity index 78% rename from src/util/fs.ts rename to src/platform/node/fs.ts index 86de84cf3..74433e680 100644 --- a/src/util/fs.ts +++ b/src/platform/node/fs.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { promisify } from 'util'; +import * as util from 'util'; import * as fs from 'fs'; export const constants = { @@ -60,19 +61,33 @@ export const constants = { COPYFILE_FICLONE_FORCE: 4, }; +export async function doesFileExist(fileUri: vscode.Uri) { + const activeTextEditor = vscode.window.activeTextEditor; + if (activeTextEditor) { + try { + await vscode.workspace.fs.stat(fileUri); + return true; + } catch { + return false; + } + } else { + // fallback to local fs + const fsExists = util.promisify(fs.exists); + return fsExists(fileUri.fsPath); + } +} + export async function existsAsync(path: string): Promise { try { - const uri = vscode.Uri.parse(`file:${path}`); - await vscode.workspace.fs.stat(uri); + await vscode.workspace.fs.stat(vscode.Uri.parse(path)); return true; } catch (_e) { return false; } } -export async function unlink(path: string): Promise { - const uri = vscode.Uri.parse(`file:${path}`); - await vscode.workspace.fs.delete(uri); +export async function unlink(path): Promise { + fs.unlinkSync(path); } export async function readFileAsync(path: string, encoding: string): Promise { diff --git a/src/platform/node/history.ts b/src/platform/node/history.ts new file mode 100644 index 000000000..ca4495de6 --- /dev/null +++ b/src/platform/node/history.ts @@ -0,0 +1,120 @@ +import * as path from 'path'; +import * as vscode from 'vscode'; +import { readFileAsync, mkdirAsync, writeFileAsync, unlinkSync } from 'platform/fs'; +import { ILogger } from '../common/logger'; +import { Globals } from '../../globals'; + +export class HistoryBase { + private _historyFileName: string; + private _history: string[] = []; + + get historyKey(): string { + return path.join(this._extensionStoragePath, this._historyFileName); + } + + private _context: vscode.ExtensionContext; + private _extensionStoragePath: string; + private _logger: ILogger; + + constructor( + context: vscode.ExtensionContext, + historyFileName: string, + extensionStoragePath: string, + logger: ILogger + ) { + this._historyFileName = historyFileName; + this._context = context; + this._extensionStoragePath = extensionStoragePath; + this._logger = logger; + } + + public async add(value: string | undefined, history: number): Promise { + if (!value || value.length === 0) { + return; + } + + // remove duplicates + let index: number = this._history.indexOf(value); + if (index !== -1) { + this._history.splice(index, 1); + } + + // append to the end + this._history.push(value); + + // resize array if necessary + if (this._history.length > history) { + this._history = this._history.slice(this._history.length - history); + } + + return this.save(); + } + + public get(history: number): string[] { + // resize array if necessary + if (this._history.length > history) { + this._history = this._history.slice(this._history.length - history); + } + + return this._history; + } + + public clear() { + try { + this._history = []; + unlinkSync(this.historyKey); + } catch (err) { + this._logger.warn(`Unable to delete ${this.historyKey}. err=${err}.`); + } + } + + public async load(): Promise { + // await this._base.load(); + let data = ''; + + try { + data = await readFileAsync(this.historyKey, 'utf-8'); + } catch (err) { + if (err.code === 'ENOENT') { + this._logger.debug(`History does not exist. path=${this.historyKey}`); + } else { + this._logger.warn(`Failed to load history. path=${this.historyKey} err=${err}.`); + } + return; + } + + if (data.length === 0) { + return; + } + + try { + let parsedData = JSON.parse(data); + if (!Array.isArray(parsedData)) { + throw Error('Unexpected format in history file. Expected JSON.'); + } + this._history = parsedData; + } catch (e) { + this._logger.warn(`Deleting corrupted history file. path=${this.historyKey} err=${e}.`); + this.clear(); + } + } + + async save(): Promise { + try { + // create supplied directory. if directory already exists, do nothing and move on + try { + await mkdirAsync(Globals.extensionStoragePath, { recursive: true }); + } catch (createDirectoryErr) { + if (createDirectoryErr.code !== 'EEXIST') { + throw createDirectoryErr; + } + } + + // create file + await writeFileAsync(this.historyKey, JSON.stringify(this._history), 'utf-8'); + } catch (err) { + this._logger.error(`Failed to save history. filepath=${this.historyKey}. err=${err}.`); + throw err; + } + } +} diff --git a/src/platform/node/loggerImpl.ts b/src/platform/node/loggerImpl.ts new file mode 100644 index 000000000..33a2295fd --- /dev/null +++ b/src/platform/node/loggerImpl.ts @@ -0,0 +1,83 @@ +import * as TransportStream from 'winston-transport'; +import * as vscode from 'vscode'; +import * as winston from 'winston'; +import { ConsoleForElectron } from 'winston-console-for-electron'; +import { configuration } from '../../configuration/configuration'; + +interface VsCodeMessageOptions extends TransportStream.TransportStreamOptions { + prefix?: string; +} + +/** + * Implementation of Winston transport + * Displays VSCode message to user + */ +class VsCodeMessage extends TransportStream { + prefix?: string; + actionMessages = ['Dismiss', 'Suppress Errors']; + + constructor(options: VsCodeMessageOptions) { + super(options); + + this.prefix = options.prefix; + } + + public async log(info: { level: string; message: string }, callback: Function) { + if (configuration.debug.silent) { + return; + } + let showMessage: (message: string, ...items: string[]) => Thenable; + switch (info.level) { + case 'error': + showMessage = vscode.window.showErrorMessage; + break; + case 'warn': + showMessage = vscode.window.showWarningMessage; + break; + case 'info': + case 'verbose': + case 'debug': + showMessage = vscode.window.showInformationMessage; + break; + default: + throw 'Unsupported ' + info.level; + } + + let message = info.message; + if (this.prefix) { + message = this.prefix + ': ' + message; + } + + let selectedAction = await showMessage(message, ...this.actionMessages); + if (selectedAction === 'Suppress Errors') { + vscode.window.showInformationMessage( + 'Ignorance is bliss; temporarily suppressing log messages. For more permanence, please configure `vim.debug.silent`.' + ); + configuration.debug.silent = true; + } + + if (callback) { + callback(); + } + } +} + +export class LoggerImpl { + static get(prefix?: string): winston.Logger { + return winston.createLogger({ + format: winston.format.simple(), + transports: [ + // TODO: update these when configuration changes + new ConsoleForElectron({ + level: configuration.debug.loggingLevelForConsole, + silent: configuration.debug.silent, + prefix: prefix, + }), + new VsCodeMessage({ + level: configuration.debug.loggingLevelForAlert, + prefix: prefix, + }), + ], + }); + } +} diff --git a/src/state/globalState.ts b/src/state/globalState.ts index 5f38e89dd..1726d6927 100644 --- a/src/state/globalState.ts +++ b/src/state/globalState.ts @@ -53,8 +53,8 @@ class GlobalState { */ public hl = true; - public async load() { - this._searchHistory = new SearchHistory(); + public async load(context: vscode.ExtensionContext) { + this._searchHistory = new SearchHistory(context); this._searchHistory .get() .forEach((val) => diff --git a/src/state/vimState.ts b/src/state/vimState.ts index 037d42e28..e7a51f418 100644 --- a/src/state/vimState.ts +++ b/src/state/vimState.ts @@ -5,7 +5,6 @@ import { configuration } from '../configuration/configuration'; import { EasyMotion } from './../actions/plugins/easymotion/easymotion'; import { EditorIdentity } from './../editorIdentity'; import { HistoryTracker } from './../history/historyTracker'; -import { InputMethodSwitcher } from '../actions/plugins/imswitcher'; import { Logger } from '../util/logger'; import { Mode } from '../mode/mode'; import { Position } from './../common/motion/position'; @@ -15,6 +14,11 @@ import { RegisterMode } from './../register/register'; import { ReplaceState } from './../state/replaceState'; import { IKeyRemapping } from '../configuration/iconfiguration'; import { SurroundState } from '../actions/plugins/surround'; +import { SUPPORT_NVIM, SUPPORT_IME_SWITCHER } from 'platform/constants'; + +interface IInputMethodSwitcher { + switchInputMethod(prevMode: Mode, newMode: Mode); +} interface INVim { run(vimState: VimState, command: string): Promise<{ statusBarText: string; error: boolean }>; @@ -331,6 +335,7 @@ export class VimState implements vscode.Disposable { return this._currentMode; } + private _inputMethodSwitcher?: IInputMethodSwitcher; /** * The mode Vim is currently including pseudo-modes like OperatorPendingMode * This is to be used only by the Remappers when getting the remappings so don't @@ -342,9 +347,8 @@ export class VimState implements vscode.Disposable { : this._currentMode; } - private _inputMethodSwitcher: InputMethodSwitcher; public async setCurrentMode(mode: Mode): Promise { - await this._inputMethodSwitcher.switchInputMethod(this._currentMode, mode); + await this._inputMethodSwitcher?.switchInputMethod(this._currentMode, mode); if (this.returnToInsertAfterCommand && mode === Mode.Insert) { this.returnToInsertAfterCommand = false; } @@ -393,23 +397,29 @@ export class VimState implements vscode.Disposable { /** The macro currently being recorded, if one exists. */ public macro: RecordedState | undefined; - public nvim: INVim; + public nvim?: INVim; public constructor(editor: vscode.TextEditor) { this.editor = editor; this.identity = EditorIdentity.fromEditor(editor); this.historyTracker = new HistoryTracker(this); this.easyMotion = new EasyMotion(); - this._inputMethodSwitcher = new InputMethodSwitcher(); } async load() { - const m = await import('../neovim/neovim'); - this.nvim = new m.NeovimWrapper(); + if (SUPPORT_NVIM) { + const m = await import('../neovim/neovim'); + this.nvim = new m.NeovimWrapper(); + } + + if (SUPPORT_IME_SWITCHER) { + const ime = await import('../actions/plugins/imswitcher'); + this._inputMethodSwitcher = new ime.InputMethodSwitcher(); + } } dispose() { - this.nvim.dispose(); + this.nvim?.dispose(); } } diff --git a/src/util/clipboard.ts b/src/util/clipboard.ts index ab311b5ec..450ed734e 100644 --- a/src/util/clipboard.ts +++ b/src/util/clipboard.ts @@ -11,7 +11,7 @@ export class Clipboard { try { await vscode.env.clipboard.writeText(text); } catch (e) { - this.logger.error(e, `Error copying to clipboard. err=${e}`); + this.logger.error(`Error copying to clipboard. err=${e}`); } } diff --git a/src/util/externalCommand.ts b/src/util/externalCommand.ts index e59f56442..6968a1263 100644 --- a/src/util/externalCommand.ts +++ b/src/util/externalCommand.ts @@ -1,8 +1,8 @@ -import { exec } from '../util/child_process'; -import { readFileAsync, writeFileAsync, unlink } from '../util/fs'; +import { readFileAsync, writeFileAsync, unlink } from 'platform/fs'; import { tmpdir } from '../util/os'; import { join } from '../util/path'; import { VimError, ErrorCode } from '../error'; +import { promisify } from 'util'; class ExternalCommand { private previousExternalCommand: string | undefined; @@ -96,7 +96,9 @@ class ExternalCommand { this.previousExternalCommand = command; command = this.redirectCommand(command, inputFile, outputFile); try { - await exec(command); + await import('child_process').then((cp) => { + return promisify(cp.exec)(command); + }); } catch (e) { // exec throws an error if exit code != 0 // keep going and read the output anyway (just like vim) diff --git a/src/util/logger.ts b/src/util/logger.ts index 468d46c14..a47106c0d 100644 --- a/src/util/logger.ts +++ b/src/util/logger.ts @@ -1,83 +1,8 @@ -import * as TransportStream from 'winston-transport'; -import * as vscode from 'vscode'; -import * as winston from 'winston'; -import { ConsoleForElectron } from 'winston-console-for-electron'; -import { configuration } from '../configuration/configuration'; - -interface VsCodeMessageOptions extends TransportStream.TransportStreamOptions { - prefix?: string; -} - -/** - * Implementation of Winston transport - * Displays VSCode message to user - */ -class VsCodeMessage extends TransportStream { - prefix?: string; - actionMessages = ['Dismiss', 'Suppress Errors']; - - constructor(options: VsCodeMessageOptions) { - super(options); - - this.prefix = options.prefix; - } - - public async log(info: { level: string; message: string }, callback: Function) { - if (configuration.debug.silent) { - return; - } - let showMessage: (message: string, ...items: string[]) => Thenable; - switch (info.level) { - case 'error': - showMessage = vscode.window.showErrorMessage; - break; - case 'warn': - showMessage = vscode.window.showWarningMessage; - break; - case 'info': - case 'verbose': - case 'debug': - showMessage = vscode.window.showInformationMessage; - break; - default: - throw 'Unsupported ' + info.level; - } - - let message = info.message; - if (this.prefix) { - message = this.prefix + ': ' + message; - } - - let selectedAction = await showMessage(message, ...this.actionMessages); - if (selectedAction === 'Suppress Errors') { - vscode.window.showInformationMessage( - 'Ignorance is bliss; temporarily suppressing log messages. For more permanence, please configure `vim.debug.silent`.' - ); - configuration.debug.silent = true; - } - - if (callback) { - callback(); - } - } -} +import { ILogger } from 'src/platform/common/logger'; +import { LoggerImpl } from 'platform/loggerImpl'; export class Logger { - static get(prefix?: string): winston.Logger { - return winston.createLogger({ - format: winston.format.simple(), - transports: [ - // TODO: update these when configuration changes - new ConsoleForElectron({ - level: configuration.debug.loggingLevelForConsole, - silent: configuration.debug.silent, - prefix: prefix, - }), - new VsCodeMessage({ - level: configuration.debug.loggingLevelForAlert, - prefix: prefix, - }), - ], - }); + static get(prefix?: string): ILogger { + return LoggerImpl.get(prefix); } } diff --git a/src/util/util.ts b/src/util/util.ts index da18d04d9..890e589fa 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -1,6 +1,5 @@ import * as vscode from 'vscode'; import { Range } from '../common/motion/range'; -import { exec } from 'child_process'; import { VimState } from '../state/vimState'; /** @@ -13,25 +12,6 @@ export function getCursorsAfterSync(): Range[] { return vscode.window.activeTextEditor!.selections.map((x) => Range.FromVSCodeSelection(x)); } -/** - * This function executes a shell command and returns the standard output as a string. - */ -export function executeShell(cmd: string): Promise { - return new Promise((resolve, reject) => { - try { - exec(cmd, (err, stdout, stderr) => { - if (err) { - reject(err); - } else { - resolve(stdout); - } - }); - } catch (error) { - reject(error); - } - }); -} - export function clamp(num: number, min: number, max: number) { return Math.min(Math.max(num, min), max); } diff --git a/test/actions/baseAction.test.ts b/test/actions/baseAction.test.ts index f566219cc..cbcaa4d3f 100644 --- a/test/actions/baseAction.test.ts +++ b/test/actions/baseAction.test.ts @@ -27,6 +27,7 @@ suite('base action', () => { suiteSetup(async () => { await setupWorkspace(); vimState = new VimState(vscode.window.activeTextEditor!); + await vimState.load(); }); suiteTeardown(cleanUpWorkspace); diff --git a/test/cmd_line/historyFile.test.ts b/test/cmd_line/historyFile.test.ts index ad7f6ad6e..af8b152f3 100644 --- a/test/cmd_line/historyFile.test.ts +++ b/test/cmd_line/historyFile.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as os from 'os'; import { HistoryFile } from '../../src/history/historyFile'; -import { setupWorkspace, cleanUpWorkspace, rndName } from '../testUtils'; +import { setupWorkspace, cleanUpWorkspace, rndName, TestExtensionContext } from '../testUtils'; import { configuration } from '../../src/configuration/configuration'; import { Globals } from '../../src/globals'; @@ -26,7 +26,7 @@ suite('HistoryFile', () => { } Globals.extensionStoragePath = os.tmpdir(); - history = new HistoryFile(rndName()); + history = new HistoryFile(new TestExtensionContext(), rndName()); await history.load(); }); diff --git a/test/register/register.test.ts b/test/register/register.test.ts index 418a56841..6ed7a5479 100644 --- a/test/register/register.test.ts +++ b/test/register/register.test.ts @@ -245,6 +245,7 @@ suite('register', () => { test('Can put and get to register', async () => { const expected = 'text-to-put-on-register'; const vimState = new VimState(vscode.window.activeTextEditor!); + await vimState.load(); vimState.recordedState.registerName = '0'; try { diff --git a/test/state/vimState.test.ts b/test/state/vimState.test.ts index 69ecc7082..0d46dfc3f 100644 --- a/test/state/vimState.test.ts +++ b/test/state/vimState.test.ts @@ -12,9 +12,10 @@ suite('VimState', () => { teardown(cleanUpWorkspace); - test('de-dupes cursors', () => { + test('de-dupes cursors', async () => { // setup const vimState = new VimState(vscode.window.activeTextEditor!); + await vimState.load(); const cursorStart = new Position(0, 0); const cursorStop = new Position(0, 1); const initialCursors = [new Range(cursorStart, cursorStop), new Range(cursorStart, cursorStop)]; @@ -26,9 +27,10 @@ suite('VimState', () => { assert.strictEqual(vimState.cursors.length, 1); }); - test('cursorStart/cursorStop should be first cursor in cursors', () => { + test('cursorStart/cursorStop should be first cursor in cursors', async () => { // setup const vimState = new VimState(vscode.window.activeTextEditor!); + await vimState.load(); const cursorStart = new Position(0, 0); const cursorStop = new Position(0, 1); const initialCursors = [ diff --git a/test/testUtils.ts b/test/testUtils.ts index ddb156386..420ebfacb 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -124,7 +124,7 @@ export async function setupWorkspace( config: IConfiguration = new Configuration(), fileExtension: string = '' ): Promise { - await commandLine.load(); + await commandLine.load(new TestExtensionContext()); const filePath = await createRandomFile('', fileExtension); const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); diff --git a/tsconfig.json b/tsconfig.json index 6febefe2f..9da58c74a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,10 @@ "strictNullChecks": true, "experimentalDecorators": true, "alwaysStrict": true, + "baseUrl": ".", + "paths": { + "platform/*": ["src/platform/node/*"] + }, "resolveJsonModule": true }, "exclude": ["node_modules", "!node_modules/@types"] diff --git a/webpack.config.js b/webpack.config.js index 287896313..247bad629 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,6 +3,7 @@ 'use strict'; const path = require('path'); +const webpack = require('webpack'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); /**@type {import('webpack').Configuration}*/ @@ -26,6 +27,9 @@ const config = { resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader extensions: ['.ts', '.js'], + alias: { + platform: path.resolve(__dirname, 'src', 'platform', 'node'), + }, }, module: { rules: [ @@ -36,6 +40,61 @@ const config = { }, ], }, - plugins: [new CleanWebpackPlugin()], + plugins: [ + new CleanWebpackPlugin({ + cleanOnceBeforeBuildPatterns: [], // disable initial clean + }), + ], }; -module.exports = config; + +/**@type {import('webpack').Configuration}*/ +const nodelessConfig = { + target: 'webworker', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ + + mode: 'development', + + entry: './extensionWeb.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ + output: { + // the bundle is stored in the 'out' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ + path: path.resolve(__dirname, 'out'), + filename: 'extensionWeb.js', + libraryTarget: 'umd', + }, + devtool: 'inline-source-map', + externals: { + vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ + }, + resolve: { + // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader + extensions: ['.ts', '.js'], + alias: { + platform: path.resolve(__dirname, 'src', 'platform', 'browser'), + }, + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + loader: 'ts-loader', + }, + ], + }, + ], + }, + plugins: [ + new webpack.IgnorePlugin({ + resourceRegExp: /\/neovim$/, + }), + new webpack.IgnorePlugin({ + resourceRegExp: /\/imswitcher$/, + }), + new webpack.IgnorePlugin({ + resourceRegExp: /child_process$/, + }), + ], +}; + +module.exports = [config, nodelessConfig]; diff --git a/webpack.dev.js b/webpack.dev.js index 3ac968c48..d8f046249 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -1,7 +1,9 @@ const merge = require('webpack-merge'); -const prod_config = require('./webpack.config.js'); +const prod_configs = require('./webpack.config.js'); -module.exports = merge.merge(prod_config, { - mode: 'development', - devtool: 'inline-source-map', -}); +module.exports = prod_configs.map((config) => + merge.merge(prod_configs[0], { + mode: 'development', + devtool: 'inline-source-map', + }) +);