From 3f0ab4d8ef46f27958e4c528c6f4aa486aede63b Mon Sep 17 00:00:00 2001 From: YoRolling Date: Thu, 6 Dec 2018 12:18:59 +0800 Subject: [PATCH] =?UTF-8?q?feature:=20:sparkles:=20=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 完成基本功能 --- .vscode/settings.json | 3 +- package.json | 6 +- src/extension.ts | 163 ++++++++++++++++++++++++++++++++---------- src/utils/utils.ts | 126 ++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+), 43 deletions(-) create mode 100644 src/utils/utils.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 30bf8c2..de5d132 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "out": true // set this to false to include "out" folder in search results }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "typescript.tsc.autoDetect": "off", + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/package.json b/package.json index 9fe1f4e..4442ffc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "exclude-it", "displayName": "Exclude It", "description": "exclude files or folders with right click ", - "version": "0.0.1", + "version": "1.0.0", "engines": { "vscode": "^1.29.0" }, @@ -17,11 +17,11 @@ "commands": [ { "command": "extension.excludeWs", - "title": "exclude it as workspace" + "title": "Exclude: workspace" }, { "command": "extension.excludeglobal", - "title": "exclude it as global" + "title": "Exclude: global" } ], "menus": { diff --git a/src/extension.ts b/src/extension.ts index 1d1e3b2..47941ea 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,69 +1,154 @@ -'use strict'; +"use strict"; // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below -import * as vscode from 'vscode'; -const Path = require('path'); -const fs = require('fs'); +import * as vscode from "vscode"; +import * as utils from "./utils/utils"; +import { isNullOrUndefined } from "util"; +const rootPath = vscode.workspace.rootPath; // this method is called when your extension is activated // your extension is activated the very first time the command is executed export function activate(context: vscode.ExtensionContext) { - // Use the console to output diagnostic information (console.log) and errors (console.error) - // This line of code will only be executed once when your extension is activated - console.log('Congratulations, your extension "exclude-it" is now active!'); - // The command has been defined in the package.json file // Now provide the implementation of the command with registerCommand // The commandId parameter must match the command field in package.json let disposable = vscode.commands.registerCommand( - 'extension.excludeWs', - () => { - // The code you place here will be executed every time your command is executed - - // Display a message box to the user - vscode.window.showInformationMessage('Hello World!'); + "extension.excludeWs", + (uri: vscode.Uri) => { + globMatch(uri, false); } ); // excludeglobal context.subscriptions.push(disposable); - context.subscriptions.push(registerGlobal()); + context.subscriptions.push(registerGlobal(context)); } -function registerGlobal() { +function registerGlobal(context: vscode.ExtensionContext) { let disposable = vscode.commands.registerCommand( - 'extension.excludeglobal', + "extension.excludeglobal", (uri: vscode.Uri) => { - // The code you place here will be executed every time your command is executed - // Display a message box to the user - const parseMeta = parseFilePath(uri.path); - vscode.window.showInformationMessage(JSON.stringify(uri)); + globMatch(uri, true); } ); return disposable; } /** + * show file glob quick pick to decide which `glob` will be add to exluded * @author YoRolling - * @param filePath + * @since 1.0.0 + * @param {string[]} items + * @returns */ -function parseFilePath(filePath: String): Promise<{} | Error> { - return new Promise((res, rej) => { - fs.exists(filePath, (error: Error, stat) => { - if (error) { - rej(error); - return; - } - const extname = Path.extname(filePath); - const basename = Path.basename(filePath); - const dirname = Path.dirname(filePath); - res({ - path: filePath, - extname, - basename, - dirname - }); - }); +function shouldShowPicker(items: string[]) { + return vscode.window.showQuickPick(items, { + canPickMany: true }); } // this method is called when your extension is deactivated export function deactivate() {} + +/** + * flush config to settings.json + * @author YoRolling + * @since 1.0.0 + * @param {string} key + * @param {*} values + * @returns + */ +function flushConf(key: string, values: string[], global: boolean) { + if (isNullOrUndefined(key)) { + vscode.window.showErrorMessage(`E1000001: Internal error`); + return; + } + if (isNullOrUndefined(values)) { + vscode.window.showErrorMessage(`E1000002: Internal error`); + return; + } + const config = vscode.workspace.getConfiguration("files"); + let clude: any = config.get("exclude"); + if (!clude) { + clude = {}; + } + try { + Array.from(new Set(values)).filter((v) => v !== '*' ).forEach((glob: string) => { + clude[glob] = true; + }); + + config.update("exclude", clude, global).then(() => { + vscode.window.showInformationMessage("♡ You got it!"); + }); + } catch (error) { + + } +} + +/** + * + * + * @param {vscode.Uri} uri + * @param {boolean} [isGlobal=true] + */ +async function globMatch(uri: vscode.Uri, isGlobal = true) { + try { + const fileMeta = ( + await utils.parseFilePath(uri.path, rootPath) + ); + const isFile = await utils.isFile(uri.path); + const isFolder = await utils.isFolder(uri.path); + let result: string[] | undefined; + let glob: string[] = []; + if (isFile) { + + Object.keys(fileMeta).forEach(key => { + let r = undefined; + switch (key) { + case "path": + break; + case "extname": + r = fileMeta[key] ? `**/*${fileMeta[key]}` : undefined ; + break; + case "basename": + r = fileMeta[key]; + break; + case "dirname": + r = fileMeta[key] + ? `${fileMeta[key] + "/"}*.*` + : undefined; + break; + } + if (r) { + glob.push(r); + } + }); + if(fileMeta["dirname"]) { + if(fileMeta["extname"]) { + glob.push( + `${ fileMeta["dirname"]}/*${fileMeta["extname"]}` + ); + } + } else { + if(fileMeta["extname"]) { + glob.push( + `*${fileMeta["extname"]}` + ); + } + } + + if (fileMeta["basename"]) { + glob.push(`**/${fileMeta["basename"]}`); + if ( fileMeta['dirname'] ) { + glob.push(`${fileMeta['dirname']}/${fileMeta["basename"]}`); + } + } + result = await shouldShowPicker(glob); + } else if (isFolder) { + result = [`${fileMeta.basename}`]; + } + if (result) { + flushConf("files.exclude", result, isGlobal); + } + } catch (error) { + vscode.window.showErrorMessage(error.message || error); + } +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..7afd5a1 --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,126 @@ +import { Stats, lstat, access } from "fs"; +import { isNullOrUndefined } from "util"; + +import * as path from 'path'; + +/** + *  检测文件或者目录是否存在 + * @author YoRolling + * @export + * @param {string} _path + * @returns {(Promise)} + * @version 0.0.1 + */ +export function exist(_path: string): Promise { + if (isUnavail(_path)) { + return Promise.reject(new Error(`${_path} should has a falsy value`)); + } + return new Promise((res, rej) => { + access(_path, (error: Error) => { + if (isNullOrUndefined(error)) { + res(true); + } else { + rej(error); + } + }); + }); +} + +/** + * 检测传入路径是否合法 + * @author YoRolling + * @param {string} _path file path or folder's + * @returns {boolean} + */ +function isUnavail(_path: string): boolean { + return isNullOrUndefined(_path) || _path === ''; +} + +/** + * get stats for _path + * @author YoRolling` + * @export + * @param {string} _path + * @returns {(Promise)} + */ +export async function lsStat(_path: string): Promise { + if (isUnavail(_path)) { + return Promise.reject(false); + } + try { + await exist(_path); + return new Promise((res, rej) => { + lstat(_path, (error: Error, stats: Stats) => { + if (!isNullOrUndefined(error)) { + rej(error); + } else { + res(stats); + } + }); + }); + } catch (error) { + return Promise.reject(error); + } +} + +/** + * @author YoRolling + * @version 0.1.1 + * @export + * @param {string} _path + * @returns {(Promise)} + */ +export async function isFile(_path: string): Promise { + if (isUnavail(_path)) { + return false; + } + try { + const stats = await lsStat(_path); + return stats.isFile(); + } catch (error) { + return false; + } +} + + +export async function isFolder(_path: string): Promise { + if (isUnavail(_path)) { + return false; + } + try { + const stats = await lsStat(_path); + return stats.isDirectory(); + } catch (error) { + return false; + } +} +/** + * @author YoRolling + * @param filePath + */ +export async function parseFilePath(filePath: string,rootPath = ''): Promise { + if (isUnavail(filePath)) { + return Promise.reject(new Error(`${filePath} should have a fasly value`)); + } + try { + await exist(filePath); + const extname = path.extname(filePath); + const basename = path.basename(filePath); + const dirname = path.relative(rootPath, path.dirname(filePath) ); + return { + path: filePath, + extname, + basename, + dirname + }; + } catch (error) { + return Promise.reject(error); + } +} + +export interface Meta { + path: string; + extname: string; + basename: string; + dirname: string; +} \ No newline at end of file