From 97495056b3c7b29cbcc5955dd5a6a2fcf178c2ed Mon Sep 17 00:00:00 2001 From: KamikazeZirou Date: Sun, 6 May 2018 15:16:12 +0900 Subject: [PATCH 1/9] feat: add "q:" command --- extension.ts | 2 + src/actions/base.ts | 15 +++++ src/actions/commands/actions.ts | 27 +++++++- src/cmd_line/commandLine.ts | 89 ++++++++++++++++++++++++++ src/mode/modeHandler.ts | 8 +++ src/transformations/transformations.ts | 8 +++ 6 files changed, 148 insertions(+), 1 deletion(-) diff --git a/extension.ts b/extension.ts index c89f6901702..78eff41d9f4 100644 --- a/extension.ts +++ b/extension.ts @@ -265,6 +265,8 @@ export async function activate(context: vscode.ExtensionContext) { } } + CommandLine.SetHistoryDirPath(context.extensionPath); + registerCommand(context, 'toggleVim', async () => { configuration.disableExt = !configuration.disableExt; toggleExtension(configuration.disableExt); diff --git a/src/actions/base.ts b/src/actions/base.ts index a7f55ca09c6..5484f9daba7 100644 --- a/src/actions/base.ts +++ b/src/actions/base.ts @@ -38,6 +38,14 @@ export let compareKeypressSequence = function(one: string[] | string[][], two: s ); }; + const isSingleAlpha = (s: string): boolean => { + if (s.length === 1 && (('a' <= s && s <= 'z') || ('A' <= s && s <= 'Z'))) { + return true; + } else { + return false; + } + }; + for (let i = 0, j = 0; i < one.length; i++, j++) { const left = one[i], right = two[j]; @@ -56,6 +64,13 @@ export let compareKeypressSequence = function(one: string[] | string[][], two: s continue; } + if (left === '' && isSingleAlpha(right)) { + continue; + } + if (right === '' && isSingleAlpha(left)) { + continue; + } + if (left === '' && !containsControlKey(right)) { continue; } diff --git a/src/actions/commands/actions.ts b/src/actions/commands/actions.ts index 32f16a57bbf..1c801e36eec 100644 --- a/src/actions/commands/actions.ts +++ b/src/actions/commands/actions.ts @@ -371,7 +371,7 @@ class CommandInsertRegisterContentInSearchMode extends BaseCommand { @RegisterAction class CommandRecordMacro extends BaseCommand { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; - keys = ['q', '']; + keys = [['q', ''], ['q', '']]; public async exec(position: Position, vimState: VimState): Promise { const register = this.keysPressed[1]; @@ -1714,6 +1714,31 @@ class CommandShowCommandLine extends BaseCommand { } } +@RegisterAction +class CommandShowCommandHistory extends BaseCommand { + modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine, ModeName.VisualBlock]; + keys = ['q', ':']; + + runsOnceForEveryCursor() { + return false; + } + + public async exec(position: Position, vimState: VimState): Promise { + vimState.recordedState.transformations.push({ + type: 'showCommandHistory', + }); + + if (vimState.currentMode === ModeName.Normal) { + vimState.commandInitialText = ''; + } else { + vimState.commandInitialText = "'<,'>"; + } + vimState.currentMode = ModeName.Normal; + + return vimState; + } +} + @RegisterAction class CommandDot extends BaseCommand { modes = [ModeName.Normal]; diff --git a/src/cmd_line/commandLine.ts b/src/cmd_line/commandLine.ts index dd79a607737..224aed70f3f 100644 --- a/src/cmd_line/commandLine.ts +++ b/src/cmd_line/commandLine.ts @@ -8,7 +8,68 @@ import * as parser from './parser'; import * as util from '../util'; import { VimError, ErrorCode } from '../error'; +class CommandLineHistory { + private _history: string[] = []; + private _is_loaded: boolean = false; + private _filePath: string = ''; + + public add(command: string | undefined): void { + if (!command || command.length === 0) { + return; + } + + let index: number = this._history.indexOf(command); + if (index !== -1) { + this._history.splice(index, 1); + } + this._history.unshift(command); + + if (this._history.length > configuration.history) { + this._history.pop(); + } + } + + public get(): string[] { + if (!this._is_loaded) { + this.load(); + this._is_loaded = true; + } + return this._history; + } + + public setFilePath(filePath: string): void { + this._filePath = filePath; + } + + private load(): void { + const fs = require('fs'); + + if (!fs.existsSync(this._filePath)) { + return; + } + + try { + let data = fs.readFileSync(this._filePath, 'utf-8'); + let parsedData = JSON.parse(data); + if (Array.isArray(parsedData)) { + this._history = parsedData; + } else { + console.log('CommandLine: Failed to load history.'); + } + } catch (e) { + console.error(e); + } + } + + public save(): void { + const fs = require('fs'); + fs.writeFileSync(this._filePath, JSON.stringify(this._history), 'utf-8'); + } +} + export class CommandLine { + private static _history: CommandLineHistory = new CommandLineHistory(); + public static async PromptAndRun(initialText: string, vimState: VimState): Promise { if (!vscode.window.activeTextEditor) { console.log('CommandLine: No active document.'); @@ -20,6 +81,9 @@ export class CommandLine { cmd = cmd.slice(1); } + this._history.add(cmd); + this._history.save(); + await CommandLine.Run(cmd!, vimState); } @@ -66,4 +130,29 @@ export class CommandLine { ], }; } + + public static async ShowHistory( + initialText: string, + vimState: VimState + ): Promise { + if (!vscode.window.activeTextEditor) { + console.log('CommandLine: No active document.'); + return ''; + } + + this._history.add(initialText); + + let cmd = await vscode.window.showQuickPick(this._history.get(), { + placeHolder: 'Vim command history', + ignoreFocusOut: false, + }); + + return cmd; + } + + public static SetHistoryDirPath(historyDirPath: string): void { + const path = require('path'); + const filePath: string = path.join(historyDirPath, '.cmdline_history'); + this._history.setFilePath(filePath); + } } diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 2febd535f9e..db81d3d4f0a 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -911,6 +911,14 @@ export class ModeHandler implements vscode.Disposable { this.updateView(this.vimState); break; + case 'showCommandHistory': + let cmd = await CommandLine.ShowHistory(vimState.commandInitialText, this.vimState); + if (cmd && cmd.length !== 0) { + await CommandLine.PromptAndRun(cmd, this.vimState); + this.updateView(this.vimState); + } + break; + case 'dot': if (!vimState.globalState.previousFullAction) { return vimState; // TODO(bell) diff --git a/src/transformations/transformations.ts b/src/transformations/transformations.ts index da8e93697e2..1a9610a04a1 100644 --- a/src/transformations/transformations.ts +++ b/src/transformations/transformations.ts @@ -188,6 +188,13 @@ export interface ShowCommandLine { type: 'showCommandLine'; } +/** + * Represents pressing ':' + */ +export interface ShowCommandHistory { + type: 'showCommandHistory'; +} + /** * Represents pressing '.' */ @@ -234,6 +241,7 @@ export type Transformation = | DeleteTextTransformation | MoveCursorTransformation | ShowCommandLine + | ShowCommandHistory | Dot | Macro | ContentChangeTransformation From 8068c5ff9cae30990681f44204b3c98847fe3e2d Mon Sep 17 00:00:00 2001 From: KamikazeZirou Date: Mon, 7 May 2018 22:07:23 +0900 Subject: [PATCH 2/9] fix: Changing vim.history isn't reflected --- src/cmd_line/commandLine.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cmd_line/commandLine.ts b/src/cmd_line/commandLine.ts index 224aed70f3f..495157fe831 100644 --- a/src/cmd_line/commandLine.ts +++ b/src/cmd_line/commandLine.ts @@ -34,6 +34,13 @@ class CommandLineHistory { this.load(); this._is_loaded = true; } + + if (this._history.length > configuration.history) { + // resize history because "vim.history" is updated. + this._history = this._history.slice(0, configuration.history); + this.save(); + } + return this._history; } From 22aabc8f7e7667120fd9cea0dcdf8c639e7df98d Mon Sep 17 00:00:00 2001 From: KamikazeZirou Date: Tue, 8 May 2018 19:54:54 +0900 Subject: [PATCH 3/9] fix: Recording macro into double-quote doen't work --- src/actions/commands/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/commands/actions.ts b/src/actions/commands/actions.ts index 1c801e36eec..53b44b4a4f0 100644 --- a/src/actions/commands/actions.ts +++ b/src/actions/commands/actions.ts @@ -371,7 +371,7 @@ class CommandInsertRegisterContentInSearchMode extends BaseCommand { @RegisterAction class CommandRecordMacro extends BaseCommand { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; - keys = [['q', ''], ['q', '']]; + keys = [['q', ''], ['q', ''], ['q', '"']]; public async exec(position: Position, vimState: VimState): Promise { const register = this.keysPressed[1]; From 0a635c35a225c39ed1dc2a8b6d1e5e6010183368 Mon Sep 17 00:00:00 2001 From: KamikazeZirou Date: Tue, 8 May 2018 22:30:55 +0900 Subject: [PATCH 4/9] update: Change file operation to asynchronous --- src/cmd_line/commandLine.ts | 63 +++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/cmd_line/commandLine.ts b/src/cmd_line/commandLine.ts index 495157fe831..a67765a8d0f 100644 --- a/src/cmd_line/commandLine.ts +++ b/src/cmd_line/commandLine.ts @@ -30,11 +30,6 @@ class CommandLineHistory { } public get(): string[] { - if (!this._is_loaded) { - this.load(); - this._is_loaded = true; - } - if (this._history.length > configuration.history) { // resize history because "vim.history" is updated. this._history = this._history.slice(0, configuration.history); @@ -48,29 +43,56 @@ class CommandLineHistory { this._filePath = filePath; } - private load(): void { + public load(): void { const fs = require('fs'); - if (!fs.existsSync(this._filePath)) { - return; - } + fs.readFile(this._filePath, 'utf-8', (err: Error, data: string) => { + this._is_loaded = true; - try { - let data = fs.readFileSync(this._filePath, 'utf-8'); - let parsedData = JSON.parse(data); - if (Array.isArray(parsedData)) { - this._history = parsedData; - } else { - console.log('CommandLine: Failed to load history.'); + if (err) { + console.log(err); + + if (this._history.length > 0) { + this.save(); + } + return; } - } catch (e) { - console.error(e); - } + + try { + let parsedData = JSON.parse(data); + if (Array.isArray(parsedData)) { + let not_saved_history: string[] = this._history; + this._history = parsedData; + + // add ccommands that were run before history was loaded. + if (not_saved_history.length > 0) { + for (let cmd of not_saved_history.reverse()) { + this.add(cmd); + } + this.save(); + } + } else { + console.log('CommandLine: Failed to load history.'); + } + } catch (e) { + console.error(e); + } + }); } public save(): void { + if (!this._is_loaded) { + console.log('CommandLine: Failed to save history because history is unloaded.'); + return; + } + const fs = require('fs'); - fs.writeFileSync(this._filePath, JSON.stringify(this._history), 'utf-8'); + + fs.writeFile(this._filePath, JSON.stringify(this._history), 'utf-8', (err: Error) => { + if (err) { + console.log(err); + } + }); } } @@ -161,5 +183,6 @@ export class CommandLine { const path = require('path'); const filePath: string = path.join(historyDirPath, '.cmdline_history'); this._history.setFilePath(filePath); + this._history.load(); } } From e43686d36bbf17158c96c20533b8fe1cf771aaf1 Mon Sep 17 00:00:00 2001 From: KamikazeZirou Date: Tue, 8 May 2018 22:41:54 +0900 Subject: [PATCH 5/9] update: put CommandLineHistory in it's own separate file. --- src/cmd_line/commandLine.ts | 89 +---------------------------- src/cmd_line/commandLineHistory.ts | 92 ++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 88 deletions(-) create mode 100644 src/cmd_line/commandLineHistory.ts diff --git a/src/cmd_line/commandLine.ts b/src/cmd_line/commandLine.ts index a67765a8d0f..c8d67bd0119 100644 --- a/src/cmd_line/commandLine.ts +++ b/src/cmd_line/commandLine.ts @@ -7,94 +7,7 @@ import { StatusBar } from '../statusBar'; import * as parser from './parser'; import * as util from '../util'; import { VimError, ErrorCode } from '../error'; - -class CommandLineHistory { - private _history: string[] = []; - private _is_loaded: boolean = false; - private _filePath: string = ''; - - public add(command: string | undefined): void { - if (!command || command.length === 0) { - return; - } - - let index: number = this._history.indexOf(command); - if (index !== -1) { - this._history.splice(index, 1); - } - this._history.unshift(command); - - if (this._history.length > configuration.history) { - this._history.pop(); - } - } - - public get(): string[] { - if (this._history.length > configuration.history) { - // resize history because "vim.history" is updated. - this._history = this._history.slice(0, configuration.history); - this.save(); - } - - return this._history; - } - - public setFilePath(filePath: string): void { - this._filePath = filePath; - } - - public load(): void { - const fs = require('fs'); - - fs.readFile(this._filePath, 'utf-8', (err: Error, data: string) => { - this._is_loaded = true; - - if (err) { - console.log(err); - - if (this._history.length > 0) { - this.save(); - } - return; - } - - try { - let parsedData = JSON.parse(data); - if (Array.isArray(parsedData)) { - let not_saved_history: string[] = this._history; - this._history = parsedData; - - // add ccommands that were run before history was loaded. - if (not_saved_history.length > 0) { - for (let cmd of not_saved_history.reverse()) { - this.add(cmd); - } - this.save(); - } - } else { - console.log('CommandLine: Failed to load history.'); - } - } catch (e) { - console.error(e); - } - }); - } - - public save(): void { - if (!this._is_loaded) { - console.log('CommandLine: Failed to save history because history is unloaded.'); - return; - } - - const fs = require('fs'); - - fs.writeFile(this._filePath, JSON.stringify(this._history), 'utf-8', (err: Error) => { - if (err) { - console.log(err); - } - }); - } -} +import { CommandLineHistory } from './commandLineHistory'; export class CommandLine { private static _history: CommandLineHistory = new CommandLineHistory(); diff --git a/src/cmd_line/commandLineHistory.ts b/src/cmd_line/commandLineHistory.ts new file mode 100644 index 00000000000..dec0af30b63 --- /dev/null +++ b/src/cmd_line/commandLineHistory.ts @@ -0,0 +1,92 @@ +import * as vscode from 'vscode'; + +import { configuration } from '../configuration/configuration'; + +export class CommandLineHistory { + private _history: string[] = []; + private _is_loaded: boolean = false; + private _filePath: string = ''; + + public add(command: string | undefined): void { + if (!command || command.length === 0) { + return; + } + + let index: number = this._history.indexOf(command); + if (index !== -1) { + this._history.splice(index, 1); + } + this._history.unshift(command); + + if (this._history.length > configuration.history) { + this._history.pop(); + } + } + + public get(): string[] { + if (this._history.length > configuration.history) { + // resize history because "vim.history" is updated. + this._history = this._history.slice(0, configuration.history); + this.save(); + } + + return this._history; + } + + public setFilePath(filePath: string): void { + this._filePath = filePath; + } + + public load(): void { + const fs = require('fs'); + + fs.readFile(this._filePath, 'utf-8', (err: Error, data: string) => { + this._is_loaded = true; + + if (err) { + console.log(err); + + // add ccommands that were run before history was loaded. + if (this._history.length > 0) { + this.save(); + } + return; + } + + try { + let parsedData = JSON.parse(data); + if (Array.isArray(parsedData)) { + let not_saved_history: string[] = this._history; + this._history = parsedData; + + // add ccommands that were run before history was loaded. + if (not_saved_history.length > 0) { + for (let cmd of not_saved_history.reverse()) { + this.add(cmd); + } + this.save(); + } + } else { + console.log('CommandLine: Failed to load history.'); + } + } catch (e) { + console.error(e); + } + }); + } + + public save(): void { + if (!this._is_loaded) { + console.log('CommandLine: Failed to save history because history is unloaded.'); + return; + } + + const fs = require('fs'); + + fs.writeFile(this._filePath, JSON.stringify(this._history), 'utf-8', (err: Error) => { + if (err) { + console.log(err); + } + }); + } +} From 1cb61f1f724e058e51774d25acfaf18b4525ef29 Mon Sep 17 00:00:00 2001 From: KamikazeZirou Date: Wed, 9 May 2018 20:25:18 +0900 Subject: [PATCH 6/9] update: improve method for determining one alphabet --- src/actions/base.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/actions/base.ts b/src/actions/base.ts index 5484f9daba7..7bd554204cf 100644 --- a/src/actions/base.ts +++ b/src/actions/base.ts @@ -38,13 +38,7 @@ export let compareKeypressSequence = function(one: string[] | string[][], two: s ); }; - const isSingleAlpha = (s: string): boolean => { - if (s.length === 1 && (('a' <= s && s <= 'z') || ('A' <= s && s <= 'Z'))) { - return true; - } else { - return false; - } - }; + const singleAlpha: RegExp = /^[a-zA-Z]$/; for (let i = 0, j = 0; i < one.length; i++, j++) { const left = one[i], @@ -64,10 +58,10 @@ export let compareKeypressSequence = function(one: string[] | string[][], two: s continue; } - if (left === '' && isSingleAlpha(right)) { + if (left === '' && singleAlpha.test(right)) { continue; } - if (right === '' && isSingleAlpha(left)) { + if (right === '' && singleAlpha.test(left)) { continue; } From a59b00aae406dcb6758d9841f433fb877714d4a0 Mon Sep 17 00:00:00 2001 From: KamikazeZirou Date: Wed, 9 May 2018 22:15:28 +0900 Subject: [PATCH 7/9] update: Change the location of history file. --- extension.ts | 2 +- src/cmd_line/commandLine.ts | 13 ++++++++----- src/util.ts | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/extension.ts b/extension.ts index 78eff41d9f4..fe46d29f08d 100644 --- a/extension.ts +++ b/extension.ts @@ -265,7 +265,7 @@ export async function activate(context: vscode.ExtensionContext) { } } - CommandLine.SetHistoryDirPath(context.extensionPath); + CommandLine.LoadHistory(); registerCommand(context, 'toggleVim', async () => { configuration.disableExt = !configuration.disableExt; diff --git a/src/cmd_line/commandLine.ts b/src/cmd_line/commandLine.ts index c8d67bd0119..7e59bc2be71 100644 --- a/src/cmd_line/commandLine.ts +++ b/src/cmd_line/commandLine.ts @@ -92,10 +92,13 @@ export class CommandLine { return cmd; } - public static SetHistoryDirPath(historyDirPath: string): void { - const path = require('path'); - const filePath: string = path.join(historyDirPath, '.cmdline_history'); - this._history.setFilePath(filePath); - this._history.load(); + public static LoadHistory(): void { + util.getExternalExtensionDirPath().then(externalExtensionDirPath => { + const path = require('path'); + const filePath: string = path.join(externalExtensionDirPath, '.cmdline_history'); + + this._history.setFilePath(filePath); + this._history.load(); + }); } } diff --git a/src/util.ts b/src/util.ts index 9d800a6e979..5575c2453b4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -67,3 +67,22 @@ export async function allowVSCodeToPropagateCursorUpdatesAndReturnThem( x => new Range(Position.FromVSCodePosition(x.start), Position.FromVSCodePosition(x.end)) ); } + +export async function getExternalExtensionDirPath(): Promise { + return new Promise((resolve, reject) => { + const os = require('os'); + const homeDir: string = os.homedir(); + const path = require('path'); + const extensionFolder = path.join(homeDir, '.VSCodeVim'); + const fs = require('fs'); + + fs.mkdir(extensionFolder, 0o775, (err: any) => { + if (!err || err.code === 'EEXIST') { + resolve(extensionFolder); + } else { + console.log(err); + reject(err); + } + }); + }); +} From a5b2971d0a11bdbb33eb1939691beb98f9e75fc4 Mon Sep 17 00:00:00 2001 From: KamikazeZirou Date: Thu, 10 May 2018 22:09:32 +0900 Subject: [PATCH 8/9] update: Use Logger instead of console.log(). --- src/cmd_line/commandLine.ts | 2 +- src/cmd_line/commandLineHistory.ts | 27 +++++++++++++++++---------- src/util.ts | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/cmd_line/commandLine.ts b/src/cmd_line/commandLine.ts index 51403140efe..4506d43a0fb 100644 --- a/src/cmd_line/commandLine.ts +++ b/src/cmd_line/commandLine.ts @@ -79,7 +79,7 @@ export class CommandLine { vimState: VimState ): Promise { if (!vscode.window.activeTextEditor) { - console.log('CommandLine: No active document.'); + Logger.debug('CommandLine: No active document.'); return ''; } diff --git a/src/cmd_line/commandLineHistory.ts b/src/cmd_line/commandLineHistory.ts index dec0af30b63..437b82d0dcd 100644 --- a/src/cmd_line/commandLineHistory.ts +++ b/src/cmd_line/commandLineHistory.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import { configuration } from '../configuration/configuration'; +import { Logger } from '../util/logger'; export class CommandLineHistory { private _history: string[] = []; @@ -40,15 +41,18 @@ export class CommandLineHistory { public load(): void { const fs = require('fs'); - fs.readFile(this._filePath, 'utf-8', (err: Error, data: string) => { + fs.readFile(this._filePath, 'utf-8', (err: any, data: string) => { this._is_loaded = true; if (err) { - console.log(err); - - // add ccommands that were run before history was loaded. - if (this._history.length > 0) { - this.save(); + if (err.code === 'ENOENT') { + Logger.debug('CommandLineHistory: History does not exist.'); + // add ccommands that were run before history was loaded. + if (this._history.length > 0) { + this.save(); + } + } else { + Logger.error(err.message, 'Failed to load history.'); } return; } @@ -67,17 +71,20 @@ export class CommandLineHistory { this.save(); } } else { - console.log('CommandLine: Failed to load history.'); + Logger.error( + 'CommandLineHistory: The history format is unknown.', + 'Failed to load history.' + ); } } catch (e) { - console.error(e); + Logger.error(e.message, 'Failed to load history.'); } }); } public save(): void { if (!this._is_loaded) { - console.log('CommandLine: Failed to save history because history is unloaded.'); + Logger.debug('CommandLineHistory: Failed to save history because history is unloaded.'); return; } @@ -85,7 +92,7 @@ export class CommandLineHistory { fs.writeFile(this._filePath, JSON.stringify(this._history), 'utf-8', (err: Error) => { if (err) { - console.log(err); + Logger.error(err.message, 'Failed to save history.'); } }); } diff --git a/src/util.ts b/src/util.ts index 8f0704c8476..bd54c73b2fc 100644 --- a/src/util.ts +++ b/src/util.ts @@ -85,7 +85,7 @@ export async function getExternalExtensionDirPath(): Promise { if (!err || err.code === 'EEXIST') { resolve(extensionFolder); } else { - console.log(err); + Logger.debug(err.message); reject(err); } }); From 011e6ff49d39eb7f761c3d7a6fc80deafe6acc36 Mon Sep 17 00:00:00 2001 From: KamikazeZirou Date: Sat, 12 May 2018 00:47:25 +0900 Subject: [PATCH 9/9] update: add test code of commandLineHistory --- src/cmd_line/commandLineHistory.ts | 107 ++++++++++++--------- test/cmd_line/commandLineHistory.test.ts | 114 +++++++++++++++++++++++ 2 files changed, 175 insertions(+), 46 deletions(-) create mode 100644 test/cmd_line/commandLineHistory.test.ts diff --git a/src/cmd_line/commandLineHistory.ts b/src/cmd_line/commandLineHistory.ts index 437b82d0dcd..6f202863d61 100644 --- a/src/cmd_line/commandLineHistory.ts +++ b/src/cmd_line/commandLineHistory.ts @@ -5,7 +5,7 @@ import { Logger } from '../util/logger'; export class CommandLineHistory { private _history: string[] = []; - private _is_loaded: boolean = false; + private _is_loading: boolean = false; private _filePath: string = ''; public add(command: string | undefined): void { @@ -38,62 +38,77 @@ export class CommandLineHistory { this._filePath = filePath; } - public load(): void { - const fs = require('fs'); - - fs.readFile(this._filePath, 'utf-8', (err: any, data: string) => { - this._is_loaded = true; - - if (err) { - if (err.code === 'ENOENT') { - Logger.debug('CommandLineHistory: History does not exist.'); - // add ccommands that were run before history was loaded. - if (this._history.length > 0) { - this.save(); + public async load(): Promise { + this._history = []; + this._is_loading = true; + + return new Promise((resolve, reject) => { + const fs = require('fs'); + fs.readFile(this._filePath, 'utf-8', (err: any, data: string) => { + this._is_loading = false; + + if (err) { + if (err.code === 'ENOENT') { + Logger.debug('CommandLineHistory: History does not exist.'); + // add ccommands that were run before history was loaded. + if (this._history.length > 0) { + this.save(); + } + resolve(); + } else { + Logger.error(err.message, 'Failed to load history.'); + reject(); } - } else { - Logger.error(err.message, 'Failed to load history.'); + return; } - return; - } - try { - let parsedData = JSON.parse(data); - if (Array.isArray(parsedData)) { - let not_saved_history: string[] = this._history; - this._history = parsedData; - - // add ccommands that were run before history was loaded. - if (not_saved_history.length > 0) { - for (let cmd of not_saved_history.reverse()) { - this.add(cmd); + try { + let parsedData = JSON.parse(data); + if (Array.isArray(parsedData)) { + let not_saved_history: string[] = this._history; + this._history = parsedData; + + // add ccommands that were run before history was loaded. + if (not_saved_history.length > 0) { + for (let cmd of not_saved_history.reverse()) { + this.add(cmd); + } + this.save(); } - this.save(); + resolve(); + } else { + Logger.error( + 'CommandLineHistory: The history format is unknown.', + 'Failed to load history.' + ); + reject(); } - } else { - Logger.error( - 'CommandLineHistory: The history format is unknown.', - 'Failed to load history.' - ); + } catch (e) { + Logger.error(e.message, 'Failed to load history.'); + reject(); } - } catch (e) { - Logger.error(e.message, 'Failed to load history.'); - } + }); }); } - public save(): void { - if (!this._is_loaded) { - Logger.debug('CommandLineHistory: Failed to save history because history is unloaded.'); - return; - } + public async save(): Promise { + return new Promise((resolve, reject) => { + if (this._is_loading) { + Logger.debug('CommandLineHistory: Failed to save history because history is loading.'); + resolve(); + return; + } - const fs = require('fs'); + const fs = require('fs'); - fs.writeFile(this._filePath, JSON.stringify(this._history), 'utf-8', (err: Error) => { - if (err) { - Logger.error(err.message, 'Failed to save history.'); - } + fs.writeFile(this._filePath, JSON.stringify(this._history), 'utf-8', (err: Error) => { + if (!err) { + resolve(); + } else { + Logger.error(err.message, 'Failed to save history.'); + reject(); + } + }); }); } } diff --git a/test/cmd_line/commandLineHistory.test.ts b/test/cmd_line/commandLineHistory.test.ts new file mode 100644 index 00000000000..1a6f0864e09 --- /dev/null +++ b/test/cmd_line/commandLineHistory.test.ts @@ -0,0 +1,114 @@ +import * as assert from 'assert'; +import { CommandLineHistory } from '../../src/cmd_line/commandLineHistory'; +import { assertEqual, setupWorkspace, cleanUpWorkspace } from '../testUtils'; +import { Uri } from 'vscode'; +import { Configuration } from '../testConfiguration'; +import { configuration } from '../../src/configuration/configuration'; + +suite('command-line history', () => { + const _fs = require('fs'); + const _os = require('os'); + const _path = require('path'); + let history: CommandLineHistory; + let run_cmds: string[]; + + const assertArrayEquals = (expected: any, actual: any) => { + assertEqual(expected.length, actual.length); + for (let i : number = 0; i < expected.length; i++) { + assertEqual(expected[i], actual[i]); + } + }; + + const rndName = () => { + return Math.random() + .toString(36) + .replace(/[^a-z]+/g, '') + .substr(0, 10); + }; + + setup(async () => { + let _configuration: Configuration = new Configuration(); + await setupWorkspace(_configuration); + + run_cmds = new Array(); + for (let i : number = 0; i < _configuration.history; i++) { + run_cmds.push(i.toString()); + } + history = new CommandLineHistory(); + }); + + teardown(async () => { + cleanUpWorkspace(); + }); + + test('add command', async () => { + for (let cmd of run_cmds) { + history.add(cmd); + } + assertArrayEquals(run_cmds.slice().reverse(), history.get()); + }); + + test('add empty command', async () => { + for (let cmd of run_cmds) { + history.add(cmd); + } + history.add(""); + assertArrayEquals(run_cmds.slice().reverse(), history.get()); + history.add(undefined); + assertArrayEquals(run_cmds.slice().reverse(), history.get()); + }); + + test('add command over configuration.history', async () => { + for (let cmd of run_cmds) { + history.add(cmd); + } + let added_cmd: string = String(configuration.history); + run_cmds.push(added_cmd); + history.add(added_cmd); + assertArrayEquals(run_cmds.slice().splice(1, configuration.history).reverse(), history.get()); + }); + + test('add command that exists in history', async () => { + for (let cmd of run_cmds) { + history.add(cmd); + } + let existed_cmd: string = "0"; + history.add(existed_cmd); + let expected_raw_history: string[] = run_cmds.slice().reverse(); + expected_raw_history.splice(expected_raw_history.indexOf(existed_cmd), 1); + expected_raw_history.unshift(existed_cmd); + assertArrayEquals(expected_raw_history, history.get()); + }); + + test('load and save history', async () => { + let filePath: string = _path.join(_os.tmpdir(), rndName()); + history.setFilePath(filePath); + + await history.load(); + for (let cmd of run_cmds) { + history.add(cmd); + } + await history.save(); + + await history.load(); + assertArrayEquals(run_cmds.slice().reverse(), history.get()); + await history.save(); + }); + + test('change configuration.history', async() => { + let filePath: string = _path.join(_os.tmpdir(), rndName()); + history.setFilePath(filePath); + + await history.load(); + for (let cmd of run_cmds) { + history.add(cmd); + } + configuration.history = 10; + assertArrayEquals(run_cmds.slice().splice(run_cmds.length - 10).reverse(), history.get()); + await history.save(); + + await history.load(); + assertArrayEquals(run_cmds.slice().splice(run_cmds.length - 10).reverse(), history.get()); + await history.save(); + }); +});