Skip to content

Commit

Permalink
Add remote file completion
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenguh committed Aug 27, 2019
1 parent defef21 commit e295404
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 107 deletions.
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
161 changes: 100 additions & 61 deletions src/actions/commands/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

import { RecordedState } from '../../state/recordedState';
import { ReplaceState } from '../../state/replaceState';
Expand All @@ -16,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 @@ -29,7 +26,10 @@ 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, getFullPath } from '../../util/path';
import * as path from 'path';
import untildify = require('untildify');

import {
ReportLinesChanged,
ReportClear,
Expand Down Expand Up @@ -1933,83 +1933,110 @@ 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, tabForward: boolean) {
const autoCompleteItems = commandLine.autoCompleteItems;
if (autoCompleteItems.length === 0) {
return;
}

completionItems = completionItems.filter(completionItem =>
completionItem.startsWith(commandLine.autoCompleteText)
);
commandLine.autoCompleteIndex = tabForward
? (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;
}
const lastPos = commandLine.preCompleteCharacterPos;
const lastCmd = commandLine.preCompleteCommand;
const evalCmd = lastCmd.slice(0, lastPos);
const restCmd = lastCmd.slice(lastPos);

let result = completionItems[commandLine.autoCompleteIndex];
if (result === vimState.currentCommandlineText) {
result = completionItems[++commandLine.autoCompleteIndex % completionItems.length];
}

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];
vimState.statusBarCursorCharacterPos = vimState.currentCommandlineText.length;
vimState.currentCommandlineText += restCmd;
}

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

if (!/ /g.test(vimState.currentCommandlineText)) {
if (
commandLine.autoCompleteItems.length !== 0 &&
(commandLine.lastKeyPressed === '<tab>' || commandLine.lastKeyPressed === '<shift+tab>')
) {
this.cycleCompletion(vimState, tabForward);
commandLine.lastKeyPressed = key;
return vimState;
}

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

// Sub string since vim do 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 [fullPath, fullDirPath, dirName, baseName, updatedFilePathInCmd, p] = getFullPath(
filePathInCmd
);
filePathInCmd = updatedFilePathInCmd; // Update incase of windows

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

const newIndex = tabForward ? 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;
vimState.statusBarCursorCharacterPos = vimState.currentCommandlineText.length;
vimState.currentCommandlineText += restCmd;

commandLine.lastKeyPressed = key;
return vimState;
}
Expand Down Expand Up @@ -2124,8 +2151,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 Expand Up @@ -2164,6 +2194,7 @@ class CommandCtrlVInCommandline extends BaseCommand {
}

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

let modifiedString = vimState.currentCommandlineText.split('');
Expand All @@ -2172,6 +2203,7 @@ class CommandCtrlVInCommandline extends BaseCommand {

vimState.statusBarCursorCharacterPos += textFromClipboard.length;

commandLine.lastKeyPressed = key;
return vimState;
}
}
Expand All @@ -2185,6 +2217,7 @@ class CommandCmdVInCommandline extends BaseCommand {
}

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

let modifiedString = vimState.currentCommandlineText.split('');
Expand All @@ -2193,6 +2226,7 @@ class CommandCmdVInCommandline extends BaseCommand {

vimState.statusBarCursorCharacterPos += textFromClipboard.length;

commandLine.lastKeyPressed = key;
return vimState;
}
}
Expand Down Expand Up @@ -2892,9 +2926,14 @@ class CommandGoToDefinition extends BaseCommand {
isJump = true;

public async exec(position: Position, vimState: VimState): Promise<VimState> {
await vscode.commands.executeCommand('editor.action.goToDeclaration');
const oldActiveEditor = vimState.editor;

if (vimState.editor === vscode.window.activeTextEditor) {
await vscode.commands.executeCommand('editor.action.goToDeclaration');
// `executeCommand` returns immediately before cursor is updated
// wait for the editor to update before updating the vim state
// https://github.com/VSCodeVim/Vim/issues/3277
await waitForCursorSync(1000);
if (oldActiveEditor === vimState.editor) {
vimState.cursorStopPosition = Position.FromVSCodePosition(vimState.editor.selection.start);
}

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
59 changes: 48 additions & 11 deletions src/cmd_line/commands/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,45 @@ import * as node from '../node';
import * as path from 'path';
import * as util from 'util';
import * as vscode from 'vscode';
import { getFullPath } from '../../util/path';
import untildify = require('untildify');
import { Logger } from '../../util/logger';

const doesFileExist = util.promisify(fs.exists);
async function doesFileExist(filePath: string) {
const activeTextEditor = vscode.window.activeTextEditor;
if (activeTextEditor) {
// Use the Uri from the active text editor so
// we can use the correct scheme and authority
// for remote fs like remote ssh
const activeUri = activeTextEditor.document.uri;
const updatedUri = activeUri.with({ path: filePath });
try {
await vscode.workspace.fs.stat(updatedUri);
return true;
} catch {
return false;
}
} else {
// fallback to local fs
const fsExists = util.promisify(fs.exists);
return fsExists(filePath);
}
}

async function createNewFile(filePath: string) {
const activeTextEditor = vscode.window.activeTextEditor;
if (activeTextEditor) {
// Use the Uri from the active text editor so
// we can use the correct scheme and authority
// for remote fs like remote ssh
const activeUri = activeTextEditor.document.uri;
const updatedUri = activeUri.with({ path: filePath });
await vscode.workspace.fs.writeFile(updatedUri, new Uint8Array());
} else {
// fallback to local fs
await util.promisify(fs.close)(await util.promisify(fs.open)(filePath, 'w'));
}
}

export enum FilePosition {
NewWindowVerticalSplit,
Expand Down Expand Up @@ -83,6 +118,7 @@ export class FileCommand extends node.CommandBase {
this._arguments.name = <string>untildify(this.arguments.name);
}

let fileUrl;
// Using the empty string will request to open a file
if (this._arguments.name === '') {
// No name on split is fine and just return
Expand All @@ -92,22 +128,24 @@ export class FileCommand extends node.CommandBase {

const fileList = await vscode.window.showOpenDialog({});
if (fileList) {
filePath = fileList[0].fsPath;
fileUrl = fileList[0];
}
} else {
// Using a filename, open or create the file
// remove file://
this._arguments.name = this._arguments.name.replace(/^file:\/\//, '');

filePath = path.isAbsolute(this._arguments.name)
? path.normalize(this._arguments.name)
: path.join(path.dirname(editorFilePath), this._arguments.name);
const [fullPath, fullDirPath, dirName, baseName, updatedFilePathInCmd, p] = getFullPath(
this._arguments.name
);

filePath = fullPath;
if (filePath !== editorFilePath) {
let fileExists = await doesFileExist(filePath);
if (!fileExists) {
// if file does not exist
// try to find it with the same extension as the current file
const pathWithExt = filePath + path.extname(editorFilePath);
const pathWithExt = filePath + p.extname(editorFilePath);
fileExists = await doesFileExist(pathWithExt);
if (fileExists) {
filePath = pathWithExt;
Expand All @@ -116,16 +154,15 @@ export class FileCommand extends node.CommandBase {

if (!fileExists) {
if (this._arguments.createFileIfNotExists) {
await util.promisify(fs.close)(await util.promisify(fs.open)(filePath, 'w'));
} else {
this._logger.error(`${filePath} does not exist.`);
return;
await createNewFile(filePath);
}
}

fileUrl = editorFileUri.with({ path: filePath });
}
}

const doc = await vscode.workspace.openTextDocument(filePath);
const doc = await vscode.workspace.openTextDocument(fileUrl);
vscode.window.showTextDocument(doc);

if (this.arguments.lineNumber) {
Expand Down
Loading

0 comments on commit e295404

Please sign in to comment.