From c665acf0df6c59f600e1a72f1f59455579c29aff Mon Sep 17 00:00:00 2001 From: sz-p Date: Fri, 23 Aug 2024 09:53:54 +0800 Subject: [PATCH] feat: enable comment in file and get .gitignore as directoryIgnore default --- README.md | 2 +- package.json | 11 +++- src/config.ts | 1 + src/extension.ts | 7 +-- src/lib/directory.ts | 12 ++++- src/lib/generator.ts | 17 ++++-- src/lib/interface.ts | 4 ++ src/utils.ts | 121 ++++++++++++++++++++++++++++++++++++++++++- yarn.lock | 12 +++++ 9 files changed, 174 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 5074ce0..18c2210 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ In addition to being able to format pre-formatted tree strings, you may also gen The walking process through files is performed asynchronously. Therefore, selecting heavily-nested folders (e.g. `node_modules`) will directly affect performance speed. -By default, `node_modules` and `.git` are ignored while generating tree string for directories. However, this can be customized by setting `asciiTreeGenerator.directoryIgnore` in configurations. Also, setting `asciiTreeGenerator.directoryMaxDepth` can limit the depth of directory walking-through. +At first,`asciiTreeGenerator.directoryIgnore` will get `.gitignore` as default. However, this can be customized by setting `asciiTreeGenerator.directoryIgnore` in configurations. if no `.gitignore` file and `asciiTreeGenerator.directoryIgnore` setting `node_modules` and `.git` are ignored while generating tree string for directories. Also, setting `asciiTreeGenerator.directoryMaxDepth` can limit the depth of directory walking-through. ## Configuration diff --git a/package.json b/package.json index b7b5eb7..ee6da22 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,11 @@ { "title": "Ascii Tree Generator Configuration", "properties": { + "asciiTreeGenerator.enableCommentInFile": { + "type": "boolean", + "default": false, + "description": "Enable comment in file by @file" + }, "asciiTreeGenerator.rootElement": { "type": "integer", "default": 46, @@ -69,7 +74,7 @@ "items": { "type": "string" }, - "default": ["node_modules", ".git"], + "default": [], "description": "The glob patterns of ignored path while generate tree string for directory" }, "asciiTreeGenerator.directoryMaxDepth": { @@ -136,6 +141,7 @@ "@types/glob": "^7.1.1", "@types/mocha": "8", "@types/node": "12", + "@types/parse-gitignore": "^1.0.2", "@types/vscode": "^1.50.0", "@typescript-eslint/eslint-plugin": "^4.16.0", "@typescript-eslint/parser": "^4.16.0", @@ -146,6 +152,7 @@ "vscode-test": "^1.5.2" }, "dependencies": { - "glob": "^7.1.3" + "glob": "^7.1.3", + "parse-gitignore": "^2.0.0" } } diff --git a/src/config.ts b/src/config.ts index 350075d..7e15faf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,6 +4,7 @@ import { IVsCodeConfig } from './lib/interface'; export function getConfig(): IVsCodeConfig { const config: IVsCodeConfig = { directoryIgnore: vscode.workspace.getConfiguration().get('asciiTreeGenerator.directoryIgnore'), + enableCommentInFile: vscode.workspace.getConfiguration().get('asciiTreeGenerator.enableCommentInFile'), directoryMaxDepth: vscode.workspace.getConfiguration().get('asciiTreeGenerator.directoryMaxDepth'), rootCharCode: vscode.workspace .getConfiguration() diff --git a/src/extension.ts b/src/extension.ts index 9ae074b..440b7dd 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import { formatFileTreeItemsFromDirectory } from './lib/directory'; import { generate } from './lib/generator'; -import { getUserEOL, createWebview, revertTreeString, getCharCodesFromConfig, getDirectoryIgnoreFromConfig, getDirectoryMaxDepthFromConfig } from './utils'; +import { getUserEOL, createWebview, revertTreeString, getCharCodesFromConfig, getDirectoryIgnoreFromConfig, getDirectoryMaxDepthFromConfig, getEnableCommentInFileFromConfig } from './utils'; import { formatFileTreeItemsFromText } from './lib/text'; export function activate(context: vscode.ExtensionContext) { @@ -28,10 +28,11 @@ export function activate(context: vscode.ExtensionContext) { // if no selected resource found, then try to get workspace root path const target: vscode.Uri = resource || rootWorkspace.uri; const root = path.relative(rootWorkspace.uri.fsPath, target.fsPath) || '.'; - + const enableCommentInFile = getEnableCommentInFileFromConfig(); // Todo: read plugin configuration const items = await formatFileTreeItemsFromDirectory(target!.fsPath, { maxDepth: Number.MAX_VALUE, + enableCommentInFile, sort: true, ignore: [], }); @@ -61,10 +62,10 @@ export function activate(context: vscode.ExtensionContext) { const target: vscode.Uri = resource || rootWorkspace.uri; const root = path.relative(rootWorkspace.uri.fsPath, target.fsPath) || '.'; - const items = await formatFileTreeItemsFromDirectory(target!.fsPath, { ignore: getDirectoryIgnoreFromConfig(), maxDepth: getDirectoryMaxDepthFromConfig(), + enableCommentInFile: getEnableCommentInFileFromConfig(), sort: true, }); const text = generate(items, { diff --git a/src/lib/directory.ts b/src/lib/directory.ts index 4b455d4..205644a 100644 --- a/src/lib/directory.ts +++ b/src/lib/directory.ts @@ -2,6 +2,7 @@ import * as rawGlob from 'glob'; import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; +import { getCommentInFile } from '../utils'; import { IFileStat, IFileTreeItem, IListDirectoryConfig } from './interface'; const glob = promisify(rawGlob); @@ -14,19 +15,25 @@ const readStat = promisify(fs.stat); */ async function getFileStat( name: string, - dir?: string + dir?: string, + enableCommentInFile?: boolean ): Promise { const absolutePath = path.join(dir || process.cwd(), name); let stat: fs.Stats; + let comment: string | undefined; try { stat = await readStat(absolutePath); } catch (e) { return null; } + if (enableCommentInFile) { + comment = getCommentInFile(name, dir); + } return { name, absolutePath, stat, + comment, isDirectory: stat.isDirectory(), children: [], }; @@ -63,7 +70,7 @@ export async function listDirectory( ignore, }); let files: IFileStat[] = (await Promise.all( - fileNames.map((item) => getFileStat(item, dir)) + fileNames.map((item) => getFileStat(item, dir, config?.enableCommentInFile)) )) as IFileStat[]; files = files.filter((item) => item !== null); // sort @@ -101,6 +108,7 @@ export async function formatFileTreeItemsFromDirectory( const item = { name: f.name, isLast: index === list.length - 1, + comment: f.comment, depth: parent ? parent.depth + 1 : 0, parent, }; diff --git a/src/lib/generator.ts b/src/lib/generator.ts index 1a724dc..dd44f4f 100644 --- a/src/lib/generator.ts +++ b/src/lib/generator.ts @@ -1,7 +1,8 @@ import { EOL } from 'os'; import { IFileTreeItem, IFormatOptions } from './interface'; -import { defaultCharset } from '../utils'; - +import { defaultCharset, getTreeStringItemCharacterLength } from '../utils'; +const DEFAULT_COMMENT_PRE_SPACE = ' '; +const DEFAULT_COMMENT_PRE_TEXT = '// '; function createTreeString(start: string, fill: string, size = 3) { let result = ''; for (let i = 0; i < size; i++) { @@ -14,6 +15,13 @@ function createTreeString(start: string, fill: string, size = 3) { return result + ' '; } +function getItemCommentText(maxRight: number, item: IFileTreeItem) { + if (!item.comment) return ''; + const itemRight = getTreeStringItemCharacterLength(item.depth, item.name); + const itemRightSpace = maxRight - itemRight; + return Array(itemRightSpace).fill(' ').join('') + DEFAULT_COMMENT_PRE_SPACE + DEFAULT_COMMENT_PRE_TEXT + item.comment; +} + /** generate tree string */ export function generate(items: IFileTreeItem[], options: IFormatOptions = {}) { const { @@ -28,6 +36,9 @@ export function generate(items: IFileTreeItem[], options: IFormatOptions = {}) { : 0; leftSpace = Array(minLeft).fill(' ').join(''); } + const maxRight = items.length + ? Math.max(...items.map((item) => getTreeStringItemCharacterLength(item.depth, item.name) || 0)) + : 0; const lines = items.map((item) => { const texts: string[] = []; texts.push( @@ -43,7 +54,7 @@ export function generate(items: IFileTreeItem[], options: IFormatOptions = {}) { ); parent = parent.parent; } - return leftSpace + texts.join('') + item.name; + return leftSpace + texts.join('') + item.name + getItemCommentText(maxRight, item); }); if (charset.root !== '') { lines.unshift(leftSpace + charset.root); diff --git a/src/lib/interface.ts b/src/lib/interface.ts index f4aac82..ef1cbbf 100644 --- a/src/lib/interface.ts +++ b/src/lib/interface.ts @@ -4,6 +4,7 @@ export interface IListDirectoryConfig { ignore?: string[]; sort?: boolean; maxDepth?: number; + enableCommentInFile?: boolean; } export interface IFileStat { @@ -11,6 +12,7 @@ export interface IFileStat { absolutePath: string; stat: Stats; isDirectory: boolean; + comment?: string, children: IFileStat[]; parent?: IFileStat; } @@ -21,6 +23,7 @@ export interface IFileTreeItem { depth: number; // left space width left?: number; + comment?: string; parent?: IFileTreeItem; } @@ -44,6 +47,7 @@ export interface IVsCodeConfig { parentCharCode: number | undefined; dashCharCode: number | undefined; blankCharCode: number | undefined; + enableCommentInFile: boolean | undefined; } // format tree string options diff --git a/src/utils.ts b/src/utils.ts index e664fb3..92c5f1e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,7 +3,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { ICharset, IVsCodeConfig } from './lib/interface'; import { getConfig } from './config'; - +import * as parseGitignore from 'parse-gitignore'; +const ONE_DEEP_CHARACTER_LENGTH = 4; let isInTestMode = false; export const defaultCharset: ICharset = { @@ -34,6 +35,88 @@ export function getUserEOL() { } return eol; } +/** + * get comment in file + * @param name file name + * @param dir file dir path + * @returns common string + */ +export function getCommentInFile( + name: string, + dir?: string +): string | undefined { + const absolutePath = path.join(dir || process.cwd(), name); + try { + const fileString = getStringFromFile(absolutePath); + const commonString = getCommentInFileString(fileString); + return commonString; + } + catch (e) { + return undefined; + } +} +/** + * get tree string item character length + * @param deep tree string item deep + * @param name file name + * @returns character length + */ +export function getTreeStringItemCharacterLength(deep: number, name: string): number { + return deep * ONE_DEEP_CHARACTER_LENGTH + calculateStringLength(name); +} + +export function checkCharacterLength(char: string) { + const regexChinese = /[\u4e00-\u9fa5]/; + if (regexChinese.test(char)) { + return 2; + } + else { + return 1; + } +} + +export function calculateStringLength(str: string) { + let length = 0; + for (let i = 0; i < str.length; i++) { + length += checkCharacterLength(str[i]); + } + return length; +} + +/** + * get comment in file string + * @param fileString file string + * @returns comment + */ +export function getCommentInFileString(fileString: string): string | undefined { + // get comment from tags @fileoverview @file @overview + const fileRegex = /@(file|fileoverview|overview)\s+(.+)/; + const match = fileString.match(fileRegex); + if (match) { + return match[2].trim(); + } else { + return undefined; + } +} + +export function isPathExists(p: string): boolean { + try { + fs.accessSync(p); + } catch (err) { + return false; + } + return true; +} + +export function getStringFromFile(filePath: string): string { + if (!isPathExists(filePath)) return ''; + try { + const fileString = fs.readFileSync(filePath, "utf8"); + return fileString; + } catch (e) { + return ''; + } +} /** * create webview, with the ability to copy to clipboard, ect @@ -127,11 +210,45 @@ export function getCharCodesFromConfig(): ICharset { return charset; } +export function getEnableCommentInFileFromConfig(): boolean { + const { enableCommentInFile } = getConfig(); + return enableCommentInFile || false; +} + +export function getDirectoryIgnoreFromGitignore(): string[] | undefined { + const workspaces = vscode.workspace.workspaceFolders; + const rootWorkspace: vscode.WorkspaceFolder | undefined = workspaces + ? workspaces[0] + : undefined; + if (!rootWorkspace) { + return undefined; + } + const gitignorePath = path.join( + rootWorkspace.uri.fsPath, + '.gitignore' + ); + if (!isPathExists(gitignorePath)) { + return undefined; + } + try { + const gitignoreContent = getStringFromFile(gitignorePath); + const gitignore = parseGitignore.parse(gitignoreContent).patterns; + return gitignore; + } + catch (e) { + return undefined; + } +} + export function getDirectoryIgnoreFromConfig(): string[] { const { directoryIgnore }: IVsCodeConfig = getConfig(); - if (Array.isArray(directoryIgnore)) { + const directoryIgnoreInGitignore = getDirectoryIgnoreFromGitignore(); + if (Array.isArray(directoryIgnore) && directoryIgnore.length > 0) { return directoryIgnore.filter(item => typeof item === 'string'); } + if (Array.isArray(directoryIgnoreInGitignore) && directoryIgnoreInGitignore.length > 0) { + return directoryIgnoreInGitignore.filter(item => typeof item === 'string'); + } return defaultDirectoryIgnore; } diff --git a/yarn.lock b/yarn.lock index d19f594..cd192bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -103,6 +103,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.15.tgz#10ee6a6a3f971966fddfa3f6e89ef7a73ec622df" integrity sha512-F6S4Chv4JicJmyrwlDkxUdGNSplsQdGwp1A0AJloEVDirWdZOAiRHhovDlsFkKUrquUXhz1imJhXHsf59auyAg== +"@types/parse-gitignore@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-gitignore/-/parse-gitignore-1.0.2.tgz#ee82f98465166090d44fb1ff12b642f3c361b32c" + integrity sha512-AQwj+lNTWI7y1kkMe8qLByiToXoXs/du70qGFIHJZaJUVrF5jB8QzvWmLyR1VWYqRagpY8ABrqAjs7uHsJnVBQ== + dependencies: + "@types/node" "*" + "@types/vscode@^1.50.0": version "1.57.0" resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.57.0.tgz#cc648e0573b92f725cd1baf2621f8da9f8bc689f" @@ -1125,6 +1132,11 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-gitignore@^2.0.0: + version "2.0.0" + resolved "https://registry.npm.qianxin-inc.cn/parse-gitignore/download/parse-gitignore-2.0.0.tgz#81156b265115c507129f3faea067b8476da3b642" + integrity sha1-gRVrJlEVxQcSnz+uoGe4R22jtkI= + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"