mirror of
https://github.com/VSCodeVim/Vim.git
synced 2024-10-26 20:23:50 +03:00
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).
This commit is contained in:
parent
dad9c847ce
commit
c900a7eaa8
@ -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(/\\|\//);
|
||||
|
22
extensionWeb.ts
Normal file
22
extensionWeb.ts
Normal file
@ -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);
|
||||
}
|
37
gulpfile.js
37
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 () {
|
||||
|
63
package-lock.json
generated
63
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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<string> {
|
||||
return new Promise<string>((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<string>;
|
||||
private savedIMKey = '';
|
||||
|
||||
constructor(execute: (cmd: string) => Promise<string> = util.executeShell) {
|
||||
constructor(execute: (cmd: string) => Promise<string> = executeShell) {
|
||||
this.execute = execute;
|
||||
}
|
||||
|
||||
|
@ -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<void> {
|
||||
public async load(context: vscode.ExtensionContext): Promise<void> {
|
||||
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 {
|
||||
|
@ -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,
|
||||
|
@ -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<string> {
|
||||
return new Promise<string>((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<string>((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 '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
3
src/platform/browser/constants.ts
Normal file
3
src/platform/browser/constants.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const SUPPORT_NVIM = false;
|
||||
export const SUPPORT_IME_SWITCHER = false;
|
||||
export const SUPPORT_READ_COMMAND = false;
|
110
src/platform/browser/fs.ts
Normal file
110
src/platform/browser/fs.ts
Normal file
@ -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<boolean> {
|
||||
try {
|
||||
await vscode.workspace.fs.stat(vscode.Uri.parse(path));
|
||||
return true;
|
||||
} catch (_e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function unlink(path): Promise<void> {
|
||||
await vscode.workspace.fs.delete(vscode.Uri.parse(path));
|
||||
}
|
||||
|
||||
export async function readFileAsync(path: string, encoding: string): Promise<string> {
|
||||
const ret = await vscode.workspace.fs.readFile(vscode.Uri.parse(path));
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
export async function mkdirAsync(path: string, options: any): Promise<void> {
|
||||
return vscode.workspace.fs.createDirectory(vscode.Uri.parse(path));
|
||||
}
|
||||
|
||||
export async function writeFileAsync(
|
||||
path: string,
|
||||
content: string,
|
||||
encoding: string
|
||||
): Promise<void> {
|
||||
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
|
||||
}
|
78
src/platform/browser/history.ts
Normal file
78
src/platform/browser/history.ts
Normal file
@ -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<void> {
|
||||
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<void> {
|
||||
let data = this._context.workspaceState.get<string>(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<void> {
|
||||
this._context.workspaceState.update(this.historyKey, JSON.stringify(this._history));
|
||||
}
|
||||
}
|
73
src/platform/browser/loggerImpl.ts
Normal file
73
src/platform/browser/loggerImpl.ts
Normal file
@ -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<string, ILogger> = new Map<string, ILogger>();
|
||||
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;
|
||||
}
|
||||
}
|
6
src/platform/common/logger.ts
Normal file
6
src/platform/common/logger.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface ILogger {
|
||||
error(errorMessage: string): void;
|
||||
debug(debugMessage: string): void;
|
||||
warn(warnMessage: string): void;
|
||||
verbose(verboseMessage: string): void;
|
||||
}
|
3
src/platform/node/constants.ts
Normal file
3
src/platform/node/constants.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const SUPPORT_NVIM = true;
|
||||
export const SUPPORT_IME_SWITCHER = true;
|
||||
export const SUPPORT_READ_COMMAND = true;
|
@ -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<boolean> {
|
||||
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<void> {
|
||||
const uri = vscode.Uri.parse(`file:${path}`);
|
||||
await vscode.workspace.fs.delete(uri);
|
||||
export async function unlink(path): Promise<void> {
|
||||
fs.unlinkSync(path);
|
||||
}
|
||||
|
||||
export async function readFileAsync(path: string, encoding: string): Promise<string> {
|
120
src/platform/node/history.ts
Normal file
120
src/platform/node/history.ts
Normal file
@ -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<void> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
83
src/platform/node/loggerImpl.ts
Normal file
83
src/platform/node/loggerImpl.ts
Normal file
@ -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<string | undefined>;
|
||||
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,
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
@ -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) =>
|
||||
|
@ -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<void> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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<string | undefined>;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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<string> {
|
||||
return new Promise<string>((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);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ suite('base action', () => {
|
||||
suiteSetup(async () => {
|
||||
await setupWorkspace();
|
||||
vimState = new VimState(vscode.window.activeTextEditor!);
|
||||
await vimState.load();
|
||||
});
|
||||
|
||||
suiteTeardown(cleanUpWorkspace);
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 = [
|
||||
|
@ -124,7 +124,7 @@ export async function setupWorkspace(
|
||||
config: IConfiguration = new Configuration(),
|
||||
fileExtension: string = ''
|
||||
): Promise<void> {
|
||||
await commandLine.load();
|
||||
await commandLine.load(new TestExtensionContext());
|
||||
const filePath = await createRandomFile('', fileExtension);
|
||||
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath));
|
||||
|
||||
|
@ -12,6 +12,10 @@
|
||||
"strictNullChecks": true,
|
||||
"experimentalDecorators": true,
|
||||
"alwaysStrict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"platform/*": ["src/platform/node/*"]
|
||||
},
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": ["node_modules", "!node_modules/@types"]
|
||||
|
@ -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];
|
||||
|
@ -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',
|
||||
})
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user