From c1bf1e41ae4699a06538c4f30519da51fdecbafa Mon Sep 17 00:00:00 2001 From: thautwarm Date: Tue, 29 Mar 2022 11:39:45 +0800 Subject: [PATCH] fix cross-platform issue & add createFile 0. add createFile (ctrl+x =) 1. autocompletionInputBox 2. allow create Files and make createDir async --- .vscode/launch.json | 1 + package.json | 7 ++- src/autocompletedInputBox.ts | 61 ++++++++++++++++++++ src/extension.ts | 104 ++++++++++++++++++++++++++++++++--- src/fileItem.ts | 28 +++++----- src/idResolver.ts | 19 +++---- src/provider.ts | 47 ++++++++++------ 7 files changed, 217 insertions(+), 50 deletions(-) create mode 100644 src/autocompletedInputBox.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index c77b2ad..c3e5d44 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,6 +2,7 @@ { "version": "0.1.0", "configurations": [ + { "name": "Launch Extension", "type": "extensionHost", diff --git a/package.json b/package.json index fc52170..eb93b18 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-dired", "displayName": "vscode-dired", "description": "dired(File Manager) for VSCode, like Emacs", - "version": "0.0.6", + "version": "0.0.7", "publisher": "rrudi", "license": "Apache-2.0", "repository": { @@ -54,6 +54,11 @@ "command": "extension.dired.createDir", "when": "dired.open && !findWidgetVisible && !inQuickOpen" }, + { + "key": "ctrl+x =", + "command": "extension.dired.createFile", + "when": "dired.open && !findWidgetVisible && !inQuickOpen" + }, { "key": "shift+r", "command": "extension.dired.rename", diff --git a/src/autocompletedInputBox.ts b/src/autocompletedInputBox.ts new file mode 100644 index 0000000..ca2258d --- /dev/null +++ b/src/autocompletedInputBox.ts @@ -0,0 +1,61 @@ +import * as vscode from 'vscode'; + +export function defaultFinishCondition(self: vscode.QuickPick) { + if (self.selectedItems.length == 0 || self.selectedItems[0].label == self.value) { + return true; + } + else { + self.value = self.selectedItems[0].label; + return false; + } +} + +export async function autocompletedInputBox( + arg: { + completion: (userinput: string) => Iterable, + withSelf?: undefined | ((self: vscode.QuickPick) => any), + stopWhen?: undefined | ((self: vscode.QuickPick) => boolean) + }) { + const completionFunc = arg.completion; + const processSelf = arg.withSelf; + + let finishCondition = defaultFinishCondition; + if (arg.stopWhen != undefined) + finishCondition = defaultFinishCondition + + + const quickPick = vscode.window.createQuickPick(); + quickPick.canSelectMany = false; + let disposables: vscode.Disposable[] = []; + let result = quickPick.value; + if (processSelf !== undefined) + processSelf(quickPick); + + let makeTask = () => new Promise(resolve => { + disposables.push( + quickPick.onDidChangeValue(directoryOrFile => { + quickPick.items = Array.from(completionFunc(quickPick.value)) + return 0; + }), + quickPick.onDidAccept(() => { + if (finishCondition(quickPick)) { + result = quickPick.value; + quickPick.hide(); + resolve(); + } + }), + quickPick.onDidHide(() => { + quickPick.dispose(); + resolve(); + }) + ); + quickPick.show(); + }); + try { + await makeTask(); + } + finally { + disposables.forEach(d => d.dispose()); + } + return quickPick.value; +} diff --git a/src/extension.ts b/src/extension.ts index 3336a47..2340347 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,6 +6,7 @@ import FileItem from './fileItem'; import * as fs from 'fs'; import * as path from 'path'; +import { autocompletedInputBox } from './autocompletedInputBox'; export interface ExtensionInternal { DiredProvider: DiredProvider, @@ -70,14 +71,12 @@ export function activate(context: vscode.ExtensionContext): ExtensionInternal { provider.toggleDotFiles(); }); - const commandCreateDir = vscode.commands.registerCommand("extension.dired.createDir", () => { - vscode.window.showInputBox() - .then((dirName) => { - if (!dirName) { - return; - } - provider.createDir(dirName); - }); + const commandCreateDir = vscode.commands.registerCommand("extension.dired.createDir", async () => { + let dirName = await vscode.window.showInputBox({ prompt: "Directory name" }); + if (!dirName) { + return; + } + await provider.createDir(dirName); }); const commandRename = vscode.commands.registerCommand("extension.dired.rename", () => { vscode.window.showInputBox() @@ -107,12 +106,101 @@ export function activate(context: vscode.ExtensionContext): ExtensionInternal { vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); + const commandCreateFile = vscode.commands.registerCommand("extension.dired.createFile", async () => { + function* completionFunc(filePathOrDirPath: string): IterableIterator { + let dirname: string; + if (!path.isAbsolute(filePathOrDirPath)) { + if (provider.dirname == undefined) + return + filePathOrDirPath = path.join(provider.dirname, filePathOrDirPath); + } + try { + let stat = fs.statSync(filePathOrDirPath); + if (stat.isDirectory()) { + dirname = filePathOrDirPath; + yield { + detail: "Open " + path.basename(filePathOrDirPath) + "/", + label: filePathOrDirPath, + buttons: [ { iconPath: vscode.ThemeIcon.Folder } ] + }; + } + else { + yield { + detail: "Open " + path.basename(filePathOrDirPath), + label: filePathOrDirPath, + buttons: [ { iconPath: vscode.ThemeIcon.File } ] + }; + + dirname = path.dirname(filePathOrDirPath); + } + } + catch + { + yield { + detail: "Create " + path.basename(filePathOrDirPath), + label: filePathOrDirPath, + buttons: [ { iconPath: vscode.ThemeIcon.File } ] + } + dirname = path.dirname(filePathOrDirPath); + try { + fs.accessSync(filePathOrDirPath, fs.constants.F_OK); + } + catch + { + return; + } + } + for (let name of fs.readdirSync(dirname)) { + const fullpath = path.join(dirname, name); + if (fs.statSync(fullpath).isDirectory()) + yield { + label: fullpath, detail: "Open " + name + "/", + buttons: [ { iconPath: vscode.ThemeIcon.Folder } ] + } + else + yield { + label: fullpath, detail: "Open" + name, + buttons: [ { iconPath: vscode.ThemeIcon.File } ] + } + } + } + function processSelf(self: vscode.QuickPick) { + self.placeholder = "Create File or Open" + } + let fileName = await autocompletedInputBox( + { + completion: completionFunc, + withSelf: processSelf, + }); + vscode.window.showInformationMessage(fileName); + let isDirectory = false; + + try { + let stat = await fs.promises.stat(fileName); + if (stat.isDirectory()) + isDirectory = true; + } + catch { + await fs.promises.mkdir(path.dirname(fileName), { recursive: true }) + await fs.promises.writeFile(fileName, ""); + } + + if (isDirectory) { + provider.openDir(fileName) + } + else { + await provider.createFile(fileName) + } + + }); + context.subscriptions.push( provider, commandOpen, commandEnter, commandToggleDotFiles, commandCreateDir, + commandCreateFile, commandRename, commandCopy, commandGoUpDir, diff --git a/src/fileItem.ts b/src/fileItem.ts index 237c2c3..5343918 100644 --- a/src/fileItem.ts +++ b/src/fileItem.ts @@ -59,8 +59,8 @@ export default class FileItem { } public line(): string { - const u = (this._username + " ").substr(0, 8); - const g = (this._groupname + " ").substr(0, 8); + const u = (this._username + " ").substring(0, 8); + const g = (this._groupname + " ").substring(0, 8); const size = this.pad(this._size, 8, " "); const month = this.pad(this._month, 2, "0"); const day = this.pad(this._day, 2, "0"); @@ -74,18 +74,18 @@ export default class FileItem { } public static parseLine(dir: string, line: string): FileItem { - const filename = line.substr(52); - const username = line.substr(13, 8); - const groupname = line.substr(22, 8); - const size = parseInt(line.substr(31, 8)); - const month = parseInt(line.substr(40, 2)); - const day = parseInt(line.substr(43, 2)); - const hour = parseInt(line.substr(46, 2)); - const min = parseInt(line.substr(49, 2)); - const modeStr = line.substr(2, 10); - const isDirectory = (modeStr.substr(0, 1) === "d"); - const isFile = (modeStr.substr(0, 1) === "-"); - const isSelected = (line.substr(0, 1) === "*"); + const filename = line.substring(52); + const username = line.substring(13, 13 + 8); + const groupname = line.substring(22, 22 + 8); + const size = parseInt(line.substring(31, 31 + 8)); + const month = parseInt(line.substring(40, 40 + 2)); + const day = parseInt(line.substring(43, 43 + 2)); + const hour = parseInt(line.substring(46, 46 + 2)); + const min = parseInt(line.substring(49, 49 + 2)); + const modeStr = line.substring(2, 2 + 10); + const isDirectory = (modeStr.substring(0, 0 + 1) === "d"); + const isFile = (modeStr.substring(0, 1) === "-"); + const isSelected = (line.substring(0, 1) === "*"); return new FileItem( dir, diff --git a/src/idResolver.ts b/src/idResolver.ts index 79435e8..d6b1b3e 100644 --- a/src/idResolver.ts +++ b/src/idResolver.ts @@ -3,6 +3,7 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as readline from 'readline'; +import * as path from 'path' export class IDResolver { private _user_cache = new Map(); @@ -20,19 +21,17 @@ export class IDResolver { } private create(user: boolean){ - let path: string; - if (user) { - path = '/etc/passwd'; - } else { - path = '/etc/group'; - } + // create a cache file in the user's home directory for Windows and Unix + const home = require('os').homedir(); + const cache_file = user ? '.vscode-dired-user-cache' : '.vscode-dired-group-cache'; + const cache_path = path.join(home, cache_file); - if (fs.existsSync(path) === false) { - vscode.window.showErrorMessage(`Could not get stat of ${path}`); - return; + if (fs.existsSync(cache_file) === false) { + // create empty file + fs.writeFileSync(cache_path, ''); } const rl = readline.createInterface({ - input: fs.createReadStream(path), + input: fs.createReadStream(cache_file), }); rl.on('line', (line:string) => { const l = line.split(":", 3); diff --git a/src/provider.ts b/src/provider.ts index 8514161..dcac18b 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -5,6 +5,7 @@ import * as fs from 'fs'; import * as path from 'path'; import FileItem from './fileItem'; +import * as autoBox from './autocompletedInputBox' const FIXED_URI: vscode.Uri = vscode.Uri.parse('dired://fixed_window'); @@ -13,10 +14,11 @@ export default class DiredProvider implements vscode.TextDocumentContentProvider private _onDidChange = new vscode.EventEmitter(); private _fixed_window: boolean; - private _show_dot_files: boolean = true; - private _buffer: string[]; // This is a temporary buffer. Reused by multiple tabs. + private _show_dot_files: boolean = true; + private _buffers: string[]; // This is a temporary buffer. Reused by multiple tabs. - constructor(fixed_window: boolean) { + constructor(fixed_window: boolean) + { this._fixed_window = fixed_window; } @@ -38,7 +40,7 @@ export default class DiredProvider implements vscode.TextDocumentContentProvider return undefined; } const line0 = doc.lineAt(0).text; - const dir = line0.substr(0, line0.length - 1); + const dir = line0.substring(0, line0.length - 1); return dir; } @@ -71,15 +73,23 @@ export default class DiredProvider implements vscode.TextDocumentContentProvider .then(() => this._onDidChange.fire(this.uri)); } - createDir(dirname: string) { + async createDir(dirname: string) { if (this.dirname) { const p = path.join(this.dirname, dirname); - fs.mkdirSync(p); + let uri = vscode.Uri.file(p); + await vscode.workspace.fs.createDirectory(uri); this.reload(); - vscode.window.showInformationMessage(`${p} is created.`); } } + async createFile(filename: string) + { + const uri = vscode.Uri.file(filename); + const document = await vscode.workspace.openTextDocument(uri); + await vscode.window.showTextDocument(document, { preview: false }); + this.reload(); + } + rename(newName: string) { const f = this.getFile(); if (!f) { @@ -125,13 +135,16 @@ export default class DiredProvider implements vscode.TextDocumentContentProvider if (uri) { this.createBuffer(path) .then(() => vscode.workspace.openTextDocument(uri)) - .then(doc => vscode.window.showTextDocument(doc, this.getTextDocumentShowOptions(this._fixed_window))); + .then(doc => vscode.window.showTextDocument( + doc, + this.getTextDocumentShowOptions(true) + )); } } showFile(uri: vscode.Uri) { vscode.workspace.openTextDocument(uri).then(doc => { - vscode.window.showTextDocument(doc, this.getTextDocumentShowOptions(this._fixed_window)); + vscode.window.showTextDocument(doc, this.getTextDocumentShowOptions(false)); }); // TODO: show warning when open file failed // vscode.window.showErrorMessage(`Could not open file ${uri.fsPath}: ${err}`); @@ -154,7 +167,7 @@ export default class DiredProvider implements vscode.TextDocumentContentProvider private render(): Thenable { return new Promise((resolve) => { - resolve(this._buffer.join('\n')); + resolve(this._buffers.join('\n')); }); } @@ -169,12 +182,12 @@ export default class DiredProvider implements vscode.TextDocumentContentProvider } } - this._buffer = [ + this._buffers = [ dirname + ":", // header line ]; - this._buffer = this._buffer.concat(files.map((f) => f.line())); + this._buffers = this._buffers.concat(files.map((f) => f.line())); - resolve(this._buffer); + resolve(this._buffers); }); } @@ -229,9 +242,9 @@ export default class DiredProvider implements vscode.TextDocumentContentProvider if (!doc) { return; } - this._buffer = []; + this._buffers = []; for (let i = 0; i < doc.lineCount; i++) { - this._buffer.push(doc.lineAt(i).text); + this._buffers.push(doc.lineAt(i).text); } let start = 0; @@ -255,14 +268,14 @@ export default class DiredProvider implements vscode.TextDocumentContentProvider } for (let i = start; i < end; i++) { - const f = FileItem.parseLine(this.dirname, this._buffer[i]); + const f = FileItem.parseLine(this.dirname, this._buffers[i]); if (f.fileName === "." || f.fileName === "..") { if (!allowSelectDot) { continue; } } f.select(value); - this._buffer[i] = f.line(); + this._buffers[i] = f.line(); } const uri = this.uri; this._onDidChange.fire(uri);