Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor the existing file opening and auto completion #4032

Merged
merged 18 commits into from
Sep 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"url": "https://github.com/VSCodeVim/Vim/issues"
},
"engines": {
"vscode": "^1.31.0"
"vscode": "^1.37.0"
},
"categories": [
"Other",
Expand Down
148 changes: 90 additions & 58 deletions src/actions/commands/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as vscode from 'vscode';
import * as fs from 'fs';

import { RecordedState } from '../../state/recordedState';
import { ReplaceState } from '../../state/replaceState';
Expand All @@ -14,7 +13,7 @@ import { Position, PositionDiff } from './../../common/motion/position';
import { Range } from './../../common/motion/range';
import { NumericString } from './../../common/number/numericString';
import { configuration } from './../../configuration/configuration';
import { ModeName } from './../../mode/mode';
import { ModeName, Mode } from './../../mode/mode';
import { VisualBlockMode } from './../../mode/modes';
import { Register, RegisterMode } from './../../register/register';
import { SearchDirection, SearchState } from './../../state/searchState';
Expand All @@ -27,7 +26,7 @@ import * as operator from './../operator';
import { Jump } from '../../jumps/jump';
import { commandParsers } from '../../cmd_line/subparser';
import { StatusBar } from '../../statusBar';
import { GetAbsolutePath } from '../../util/path';
import { readDirectory, getPathDetails } from '../../util/path';
import {
ReportLinesChanged,
ReportClear,
Expand Down Expand Up @@ -1956,83 +1955,113 @@ class CommandNavigateInCommandlineOrSearchMode extends BaseCommand {
} else if (key === '<left>') {
vimState.statusBarCursorCharacterPos = Math.max(vimState.statusBarCursorCharacterPos - 1, 0);
}

commandLine.lastKeyPressed = key;
return vimState;
}
}

// Command tab backward from behind shift tab
@RegisterAction
class CommandTabInCommandline extends BaseCommand {
modes = [ModeName.CommandlineInProgress];
keys = ['<tab>'];
keys = [['<tab>'], ['<shift+tab>']];
runsOnceForEveryCursor() {
return this.keysPressed[0] === '\n';
}

private autoComplete(completionItems: string[], vimState: VimState) {
if (commandLine.lastKeyPressed !== '<tab>') {
if (/ /g.test(vimState.currentCommandlineText)) {
// The regex here will match any text after the space or any text after the last / if it is present
const search = <RegExpExecArray>(
/(?:.* .*\/|.* )(.*)/g.exec(vimState.currentCommandlineText)
);
commandLine.autoCompleteText = search[1];
commandLine.autoCompleteIndex = 0;
} else {
commandLine.autoCompleteText = vimState.currentCommandlineText;
commandLine.autoCompleteIndex = 0;
}
private cycleCompletion(vimState: VimState, isTabForward: boolean) {
const autoCompleteItems = commandLine.autoCompleteItems;
if (autoCompleteItems.length === 0) {
return;
}

completionItems = completionItems.filter(completionItem =>
completionItem.startsWith(commandLine.autoCompleteText)
);
commandLine.autoCompleteIndex = isTabForward
? (commandLine.autoCompleteIndex + 1) % autoCompleteItems.length
: (commandLine.autoCompleteIndex - 1 + autoCompleteItems.length) % autoCompleteItems.length;

if (
commandLine.lastKeyPressed === '<tab>' &&
commandLine.autoCompleteIndex < completionItems.length
) {
commandLine.autoCompleteIndex += 1;
}
if (commandLine.autoCompleteIndex >= completionItems.length) {
commandLine.autoCompleteIndex = 0;
}

let result = completionItems[commandLine.autoCompleteIndex];
if (result === vimState.currentCommandlineText) {
result = completionItems[++commandLine.autoCompleteIndex % completionItems.length];
}
const lastPos = commandLine.preCompleteCharacterPos;
const lastCmd = commandLine.preCompleteCommand;
const evalCmd = lastCmd.slice(0, lastPos);
const restCmd = lastCmd.slice(lastPos);

if (result !== undefined && !/ /g.test(vimState.currentCommandlineText)) {
vimState.currentCommandlineText = result;
vimState.statusBarCursorCharacterPos = result.length;
} else if (result !== undefined) {
const searchArray = <RegExpExecArray>/(.* .*\/|.* )/g.exec(vimState.currentCommandlineText);
vimState.currentCommandlineText = searchArray[0] + result;
vimState.statusBarCursorCharacterPos = vimState.currentCommandlineText.length;
}
return vimState;
vimState.currentCommandlineText =
evalCmd + autoCompleteItems[commandLine.autoCompleteIndex] + restCmd;
vimState.statusBarCursorCharacterPos = vimState.currentCommandlineText.length - restCmd.length;
}

public async exec(position: Position, vimState: VimState): Promise<VimState> {
const key = this.keysPressed[0];
const isTabForward = key === '<tab>';

if (!/ /g.test(vimState.currentCommandlineText)) {
if (
commandLine.autoCompleteItems.length !== 0 &&
this.keys.some(k => commandLine.lastKeyPressed === k[0])
) {
this.cycleCompletion(vimState, isTabForward);
commandLine.lastKeyPressed = key;
return vimState;
}

let newCompletionItems: string[] = [];
const currentCmd = vimState.currentCommandlineText;
const cursorPos = vimState.statusBarCursorCharacterPos;

// Sub string since vim does completion before the cursor
let evalCmd = currentCmd.slice(0, cursorPos);
let restCmd = currentCmd.slice(cursorPos);

// \s* is the match the extra space before any character like ': edit'
const cmdRegex = /^\s*\w+$/;
const fileRegex = /^\s*\w+\s+/g;
if (cmdRegex.test(evalCmd)) {
// Command completion
const commands = Object.keys(commandParsers).sort();
vimState = this.autoComplete(commands, vimState);
} else {
// File Completion
const search = <RegExpExecArray>/.* (.*\/)/g.exec(vimState.currentCommandlineText);
const searchString = search !== null ? search[1] : '';
const fullPath = GetAbsolutePath(searchString);
const fileNames = fs
.readdirSync(fullPath, { withFileTypes: true })
.filter(fileEnt => fileEnt.isFile())
.map(fileEnt => fileEnt.name);

vimState = this.autoComplete(fileNames, vimState);
newCompletionItems = Object.keys(commandParsers)
.filter(cmd => cmd.startsWith(evalCmd))
// Remove the already typed portion in the array
.map(cmd => cmd.slice(cmd.search(evalCmd) + evalCmd.length))
.sort();
} else if (fileRegex.exec(evalCmd)) {
// File completion by searching if there is a space after the first word/command
// ideally it should be a process of white-listing to selected commands like :e and :vsp
let filePathInCmd = evalCmd.substring(fileRegex.lastIndex);
const currentUri = vscode.window.activeTextEditor!.document.uri;
const isRemote = !!vscode.env.remoteName;

const { fullDirPath, baseName, partialPath, path: p } = getPathDetails(
filePathInCmd,
currentUri,
isRemote
);
// Update the evalCmd in case of windows, where we change / to \
evalCmd = evalCmd.slice(0, fileRegex.lastIndex) + partialPath;

// test if the baseName is . or ..
const shouldAddDotItems = /^\.\.?$/g.test(baseName);
const dirItems = await readDirectory(
fullDirPath,
p.sep,
currentUri,
isRemote,
shouldAddDotItems
);
newCompletionItems = dirItems
.filter(name => name.startsWith(baseName))
.map(name => name.slice(name.search(baseName) + baseName.length))
.sort();
}

const newIndex = isTabForward ? 0 : newCompletionItems.length - 1;
commandLine.autoCompleteIndex = newIndex;
// If here only one items we fill cmd direct, so the next tab will not cycle the one item array
commandLine.autoCompleteItems = newCompletionItems.length <= 1 ? [] : newCompletionItems;
commandLine.preCompleteCharacterPos = cursorPos;
commandLine.preCompleteCommand = evalCmd + restCmd;

const completion = newCompletionItems.length === 0 ? '' : newCompletionItems[newIndex];
vimState.currentCommandlineText = evalCmd + completion + restCmd;
vimState.statusBarCursorCharacterPos = vimState.currentCommandlineText.length - restCmd.length;

commandLine.lastKeyPressed = key;
return vimState;
}
Expand Down Expand Up @@ -2090,7 +2119,7 @@ class CommandInsertInCommandline extends BaseCommand {
} else if (key === '<End>' || key === '<C-e>') {
vimState.statusBarCursorCharacterPos = vimState.currentCommandlineText.length;
} else if (key === '\n') {
await commandLine.Run(vimState.currentCommandlineText, vimState);
await commandLine.Run(vimState.currentCommandlineText.trim(), vimState);
await vimState.setCurrentMode(ModeName.Normal);
return vimState;
} else if (key === '<up>' || key === '<C-p>') {
Expand Down Expand Up @@ -2147,8 +2176,11 @@ class CommandEscInCommandline extends BaseCommand {
}

public async exec(position: Position, vimState: VimState): Promise<VimState> {
const key = this.keysPressed[0];

await vimState.setCurrentMode(ModeName.Normal);

commandLine.lastKeyPressed = key;
return vimState;
}
}
Expand Down
13 changes: 3 additions & 10 deletions src/cmd_line/commandLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,10 @@ class CommandLine {
*/
public lastKeyPressed = '';

/**
* for checking the last pressed key in command mode
*
*/
public autoCompleteIndex = 0;

/**
* for checking the last pressed key in command mode
*
*/
public autoCompleteText = '';
public autoCompleteItems: string[] = [];
public preCompleteCharacterPos = 0;
public preCompleteCommand = '';

public get commandlineHistoryIndex(): number {
return this._commandLineHistoryIndex;
Expand Down
Loading