├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── Add-New-Language-Support-For-Developers.md ├── CHANGELOG.md ├── Create-Custom-Common-Alias-Once-and-for-All.md ├── Extra-Path-Settings.md ├── Manually-Download-Tools.md ├── README.md ├── images ├── browse-all-setting-names.png ├── change-settings-example.png ├── cook-command-menu.png ├── cooked-cmd-alias-doskeys-can-be-used-in-many-IDEs.png ├── editor-context-menu.png ├── find-def-ref.gif ├── vscode-msr-icon.png ├── vscode-msr-small.png └── vscode-msr-small.svg ├── license.md ├── package-lock.json ├── package.json ├── src ├── AliasNameBody.ts ├── ScoreTypeResult.ts ├── ToolChecker.ts ├── commands.ts ├── commonAlias.ts ├── configUtils.ts ├── constants.ts ├── cookCommandAlias.ts ├── dynamicConfig.ts ├── enums.ts ├── extension.ts ├── fileUtils.ts ├── filter │ ├── ClassResultFilter.ts │ ├── ClassResultFilterForCSharp.ts │ └── filterClassResults.ts ├── forceSettings.ts ├── gitUtils.ts ├── junkPathEnvArgs.ts ├── outputUtils.ts ├── ranker.ts ├── regexUtils.ts ├── runCommandUtils.ts ├── searchChecker.ts ├── searchConfig.ts ├── searcher.ts ├── terminalUtils.ts ├── test │ ├── runTest.ts │ └── suite │ │ ├── configAndDocTest.ts │ │ ├── cookCmdAliasTest.ts │ │ ├── extension.test.ts │ │ ├── gitIgnoreTest.ts │ │ ├── index.ts │ │ └── utilsTest.ts ├── toolSource.ts ├── utils.ts └── wordReferenceUtils.ts ├── tsconfig.json ├── tslint.json └── vsc-extension-quickstart.md /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | debug.log -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "npm: watch" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "npm: watch" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.tabSize": 2, 4 | "editor.formatOnSave": true, 5 | "editor.formatOnType": true, 6 | "editor.codeActionsOnSave": { 7 | "source.fixAll.tslint": "explicit" 8 | }, 9 | // xxx-language-features: https://github.com/microsoft/vscode/tree/master/extensions 10 | "[json]": { 11 | "editor.defaultFormatter": "vscode.json-language-features" 12 | }, 13 | "[javascript]": { 14 | "editor.defaultFormatter": "vscode.typescript-language-features" 15 | }, 16 | "[javascriptreact]": { 17 | "editor.defaultFormatter": "vscode.typescript-language-features" 18 | }, 19 | "[typescript]": { 20 | "editor.defaultFormatter": "vscode.typescript-language-features" 21 | }, 22 | "[typescriptreact]": { 23 | "editor.defaultFormatter": "vscode.typescript-language-features" 24 | }, 25 | "typescript.tsdk": "node_modules\\typescript\\lib", 26 | "tslint.jsEnable": true, 27 | "stylelint.enable": true, 28 | "files.associations": { 29 | "*.js.mustache": "javascript", 30 | "*.json.mustache": "json" 31 | }, 32 | "files.exclude": { 33 | "out": false // set this to true to hide the "out" folder with the compiled JS files 34 | }, 35 | "search.exclude": { 36 | "out": true // set this to false to include "out" folder in search results 37 | }, 38 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 39 | "typescript.tsc.autoDetect": "off", 40 | "cSpell.words": [ 41 | "aarch", 42 | "aliasdoskey", 43 | "ALLUSERSPROFILE", 44 | "APPDATA", 45 | "args", 46 | "automount", 47 | "autorun", 48 | "bmsr", 49 | "chmod", 50 | "choco", 51 | "CLASSPATH", 52 | "COMPUTERNAME", 53 | "cpptools", 54 | "current", 55 | "cygdrive", 56 | "cygwin", 57 | "deinit", 58 | "difftool", 59 | "doskey", 60 | "doskeys", 61 | "DTEST", 62 | "egrep", 63 | "env", 64 | "ERRORLEVEL", 65 | "findstr", 66 | "gcc", 67 | "gfind", 68 | "github", 69 | "gitignore", 70 | "gitmodules", 71 | "GOPATH", 72 | "GOROOT", 73 | "HKCU", 74 | "hkey", 75 | "HKLM", 76 | "HOMEDRIVE", 77 | "HOMEPATH", 78 | "icacls", 79 | "imatch", 80 | "ipynb", 81 | "kzct", 82 | "LOCALAPPDATA", 83 | "LOGONSERVER", 84 | "macrofile", 85 | "malias", 86 | "MINGW", 87 | "msr", 88 | "MSYSTEM", 89 | "notmatch", 90 | "nterface", 91 | "objd", 92 | "pacigmo", 93 | "pacwgmo", 94 | "PATHEXT", 95 | "popd", 96 | "printenv", 97 | "proto", 98 | "pushd", 99 | "pwsh", 100 | "pycache", 101 | "qualiu", 102 | "readonly", 103 | "reflog", 104 | "reinit", 105 | "resx", 106 | "rgfind", 107 | "ROAMINGPROFILE", 108 | "RUSTUP", 109 | "SETX", 110 | "stdout", 111 | "struct", 112 | "taskkill", 113 | "UATDATA", 114 | "uname", 115 | "ungroup", 116 | "unmock", 117 | "user", 118 | "USERDNSDOMAIN", 119 | "USERDOMAIN", 120 | "USERPROFILE", 121 | "viasf", 122 | "vscode", 123 | "wcopy", 124 | "whereis", 125 | "windir", 126 | "wpaste", 127 | "wwwroot", 128 | "xargs", 129 | "xffd", 130 | "zc" 131 | ] 132 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts 11 | -------------------------------------------------------------------------------- /Add-New-Language-Support-For-Developers.md: -------------------------------------------------------------------------------- 1 | # Add Supports to New Languages by Developers and Contributors 2 | 3 | **Two methods** to support a new language. (Normal user see [here](https://github.com/qualiu/vscode-msr/blob/master/README.md#easy-to-support-new-languages)) 4 | 5 | Suggest: Change the user settings: Un-check `msr.quiet` (and check `msr.debug` if need) when you're debugging this extension. 6 | 7 | ## File to Add New Language Settings 8 | 9 | For developer/contributors: [package.json](https://github.com/qualiu/vscode-msr/blob/master/package.json) 10 | 11 | Take **finding definition** for **batch** files (`*.bat` and `*.cmd`) as an example. 12 | 13 | ## Method-1: Only Add One Extension of the New Language You Want to Support 14 | 15 | If you only want to support finding definition for `*.bat` files other than all `batch` script (`*.bat` + `*.cmd`): 16 | 17 | Add **lower case** `extension name`: "**msr.{extension}.definition**" (here `{extension}` = **bat** ) into the file. 18 | 19 | ```json 20 | "msr.bat.definition": { 21 | "description": "Batch file extension of *.bat file only (Not support *.cmd file).", 22 | "default": "^\\s*:\\s*(%1)\\b|(^|\\s)set\\s+(/a\\s+)?\\\"?(%1)=" 23 | } 24 | ``` 25 | 26 | ## Method-2: Support All Extensions of the New Language by Adding 2 Mandatory Settings 27 | 28 | - Add **lower case** `language name` (as you want): "**msr.fileExtensionMap**.`{Name}`" (here `{Name}` = **batch** ) into the file: 29 | 30 | ```json 31 | "msr.fileExtensionMap.batch": { 32 | "description": "Batch file extensions (*.bat + *.cmd files)", 33 | "default": "bat cmd" 34 | } 35 | ``` 36 | 37 | - Add Regex match pattern to find definition (lower case name `msr.batch.definition`): 38 | 39 | ```json 40 | "msr.batch.definition": { 41 | "description": "Regex pattern to search batch file definitions of a function or variable.", 42 | "default": "^\\s*:\\s*(%1)\\b|(^|\\s)set\\s+(/a\\s+)?\\\"?(%1)=" 43 | } 44 | ``` 45 | 46 | ### Additional Explanation for the Regex Pattern Used above when Support Batch Scripts 47 | 48 | - Batch script's function definition match pattern (Functions are often written at the head of a line): 49 | - `^\s*:MyFunction` to match functions like `:{MyFunction}` at head of lines. 50 | - Batch script's variable definition match pattern: 51 | - String variable: `set Folder=` to match variables like `set Folder=D:\TEMP` 52 | - Quoted strings: `set \"PATH=` to match variables like `set "PATH=Value"` 53 | - Numeric variable: `set /a Count=` to match variables like `set /a Count=3` 54 | - So the merged Regex pattern is: 55 | - `^\s*:\w+|set \w+=|set \"\w+=|set /a \w+=` 56 | - We need to capture/replace the name of `functions` or `variables` under mouse/cursor, use `%1` as a `place holder`: 57 | - `^\s*:%1|set %1=|set \"%1=|set /a %1=` 58 | - More robust Regex to be: 59 | - `^\s*:\s*(%1)\b|(^|\s)set\s+(/a\\s+)?\\"?(%1)=` 60 | - Finally, escape the slash `\` to `\\` for JSON file content: 61 | - `"^\\s*:\\s*(%1)\\b|(^|\\s)set\\s+(/a\\s+)?\\\"?(%1)="` 62 | 63 | ## Optional: Add Other Settings if Necessary 64 | 65 | For example, if you want to overwrite `default.skip.definition` for **batch** files, add "**msr.{name}.skip.definition**" into the file: 66 | 67 | ```json 68 | "msr.batch.skip.definition": { 69 | "default": "" 70 | } 71 | ``` 72 | 73 | ### Many Other Settings if You Want to Override or Add or Update 74 | 75 | - Specific type of definition searching Regex like: 76 | - C# class: `msr.cs.class.definition` 77 | - C# method: `msr.cs.method.definition` 78 | - C# enumerate: `msr.cs.enum.definition` 79 | - Python class: `msr.py.class.definition` 80 | - Skip definition Regex (exclude some search results from search patterns like `msr.py.class.definition`): 81 | - Java: `msr.java.skip.definition` for `Java`+`Scala` (see `msr.fileExtensionMap.java`) 82 | - C#: `msr.cs.skip.definition` for `C#` (`*.cs`+`*.cshtml`) (see `msr.fileExtensionMap.cs`) 83 | - UI: `msr.ui.skip.definition` for `JavaScript`+`TypeScript`+`Vue` (see `msr.fileExtensionMap.ui`) 84 | - Specific type of checking Regex before searching (to determine which Regex patterns to use: `class`, `method` and `enum` etc.): 85 | - Python class check: `msr.py.isFindClass` 86 | - Python member check: `ms.py.isFindMember` (for a class `members`, like `property`/`field` in C#) 87 | 88 | ### Note: Override Rule for the Language Settings in the File 89 | 90 | Explicit/Specific settings will overwrite general settings in the file. 91 | 92 | For `bat`/`batch` file example as above: `bat` = `*.bat file`, `batch` = `*.bat + *.cmd files`, so the override results as following: 93 | 94 | - `msr.bat.definition` overrides `msr.batch.definition` overrides `msr.default.definition` 95 | - `msr.bat.skip.definition` overrides `msr.batch.skip.definition` overrides `msr.default.skip.definition` 96 | 97 | ### Full Priority Order of Config Override Rule 98 | 99 | Take `skipFolders` of finding `definition` as an example, **priority order** as below: (`{subKey}` = `definition`, `{tailKey}` = `skipFolders`) 100 | 101 | - `msr.{rootFolderName}.{extension}.{subKey}.{tailKey}` like **`msr.MyRepoName.bat.definition.skipFolders`** 102 | - `msr.{rootFolderName}.{mappedExt}.{subKey}.{tailKey}` like **`msr.MyRepoName.batch.definition.skipFolders`** 103 | - `msr.{rootFolderName}.{extension}.{tailKey}` like **`msr.MyRepoName.bat.skipFolders`** 104 | - `msr.{rootFolderName}.{mappedExt}.{tailKey}` like **`msr.MyRepoName.batch.skipFolders`** 105 | - `msr.{rootFolderName}.{tailKey}` like **`msr.MyRepoName.skipFolders`** 106 | - `msr.{extension}.{subKey}.{tailKey}` like `msr.bat.definition.skipFolders` 107 | - `msr.{mappedExt}.{subKey}.{tailKey}` like `msr.batch.definition.skipFolders` 108 | - `msr.{extension}.{tailKey}` like `msr.bat.skipFolders` 109 | - `msr.{mappedExt}.{tailKey}` like `msr.batch.skipFolders` 110 | - `msr.default.{subKey}.{tailKey}` like `msr.default.definition.skipFolders` 111 | - `msr.default.{tailKey}` like `msr.default.skipFolders` 112 | 113 | Note: 114 | 115 | - The `{rootFolderName}` is the local `save folder name` of a `git repository`, it's better to **no white spaces**. 116 | - `mappedExt` is a general name for a language like `cpp` or `java`, you can name it to anything as you want: `msr.fileExtensionMap.{mappedExt}`. 117 | - `extension` is one file extension of `mappedExt` like `cpp` / `h` / `hpp`. 118 | - Some config value maybe no `{subKey}`, follow the same priority order above, just ignore/remove `{subKey}`. 119 | 120 | -------------------------------------------------------------------------------- /Create-Custom-Common-Alias-Once-and-for-All.md: -------------------------------------------------------------------------------- 1 | # Create Custom Alias Once and for All for Current and Future vscode 2 | 3 | Custom common alias([example](#example-of-custom-common-alias-and-transformation) and [difference](#difference-between-custom-alias-and-normal-alias)) to auto sync across **local** and **remote** SSH hosts plus **docker** containers like: 4 | 5 | - Local = Windows or Linux/MacOS/FreeBSD: 6 | - Remote = Linux/MacOS/FreeBSD via SSH connection. 7 | - Remote = Docker Containers in Linux/MacOS/FreeBSD. 8 | 9 | ## How to Create Custom Alias 10 | 11 | ### Step-1 Open User Settings 12 | 13 | - First time: 14 | - Open [user settings](./README.md#extension-settings-if-you-want-to-change) (hotkey = `F1` or `"Ctrl + Shift + P"`). 15 | - Choose `"Open User Settings"` to open vscode settings. 16 | - Choose common alias type: 17 | - `msr`.**commonAliasNameBodyList** for **all platforms** (Windows + MinGW/Cygwin + Linux/MacOS/FreeBSD). 18 | - `msr`.**cmd**.`commonAliasNameBodyList` for Windows only. 19 | - `msr`.**bash**.`commonAliasNameBodyList` for MinGW/Cygwin + Linux/MacOS/FreeBSD. 20 | - Click `"Edit in settings.json"`. 21 | - After first time: 22 | - Choose `"Open User Settings (JSON)"` to directly open `settings.json` to add `aliasName` + `aliasBody`. 23 | 24 | ### Step-2 Add or Update Custom Common Alias with Tool to-alias-body 25 | 26 | #### Required and Optional Fields 27 | 28 | - `aliasName` is required: the name of the alias/doskey, see example below. 29 | - Recommend using **long** `aliasName` on Linux/MacOS/FreeBSD since it's easy to auto complete by `Tab` key. 30 | - `aliasBody` is required: the command lines of batch or bash script. 31 | - Should not use single quote(`'`) in `aliasBody` for Linux to avoid breaking `alias function`. 32 | - `description` is optional: you can write any words for the alias. 33 | 34 | #### Use to-alias-body to Transform Multi-line Alias Body to One-line JSON 35 | 36 | - Write script body (maybe multi-lines) in a temp file, or edit in alias file (e.g. `~/msr-cmd-alias.bashrc`). 37 | - Copy the pure content body to clipboard. 38 | - Run **to-alias-body** to read clipboard and transform to **one-line** `aliasBody` **JSON** for `settings.json`. 39 | - Linux/MacOS/FreeBSD: install PowerShell if not exists (like `sudo apt install -y powershell`). 40 | - Paste the JSON body to `aliasBody` in `settings.json`. 41 | 42 | ## Example + Explanations 43 | #### Example of Custom Common Alias and Transformation 44 | 45 | vscode-msr will auto transform(create) normal alias/doskey based on `aliasName` + `aliasBody`. 46 | 47 | - Auto convert `aliasBody` for `Tab`/`spaces`(see [below](#related-config-items)) + `return`/`exit`(Linux alias functions + [script files](./README.md#make-command-shortcuts-to-search-or-replace-in-or-out-of-vscode)). 48 | - When editing/updating `aliasBody`: Free to copy from either alias function body or script file (then use `to-alias-body`). 49 | - Immediately effective for all `MSR-RUN-CMD` + new terminals in all opened vscode (Windows + remote SSH/docker). 50 | - Immediately effective for new system terminals(Bash/CMD/etc.) on Windows + Linux/MacOS/FreeBSD of opened vscode. 51 | - For old/opened terminals(system or vscode), run `use-this-alias` or `update-alias` to effect immediately. 52 | 53 | ```json 54 | "msr.commonAliasNameBodyList": [ 55 | { 56 | "aliasName": "gsf", 57 | "aliasBody": "git --no-pager diff --name-only $1~1 $1", 58 | "description": "Show file list of a git commit(change). The arg is the commit id. Example: gsf HEAD or gsf {commit-hash-id}." 59 | } 60 | ] 61 | ``` 62 | 63 | vscode-msr will transform it to alias/doskey on Windows + Linux/MacOS/FreeBSD like below: 64 | 65 | - Linux/MacOS/FreeBSD: Stored in alias file - default = `~/msr-cmd-alias.bashrc` with below content: 66 | 67 | ```bash 68 | alias gsf='function _gsf() { 69 | git --no-pager diff --name-only $1~1 $1 70 | }; _gsf' 71 | ``` 72 | 73 | - Windows: Stored in alias file - default = `%USERPROFILE%\msr-cmd-alias.doskeys` with below content: 74 | 75 | ```batch 76 | gsf=git --no-pager diff --name-only $1~1 $1 77 | ``` 78 | 79 | #### Related Config Items 80 | 81 | - `msr.replaceMultiLineAliasBodyTabTo`: 82 | - Replace `Tab`(`\t`) to `4 spaces` or `2 spaces` for multi-line alias body in `settings.json`. 83 | - Default = `4 spaces`. 84 | - `msr.replaceHeadSpacesToTabForToAliasBody` 85 | - Replace `4 spaces` or `2 spaces` back to `Tab`(`\t`) when calling **to-alias-body**. 86 | - Not only reduce `aliasBody` size in `settings.json` but also make Tab-conversion **reversible**. 87 | - Default = `true`. 88 | 89 | ## Difference between Custom Alias and Normal Alias 90 | 91 | - Synchronization: 92 | - Normal alias are **only** for **current** vscode on Windows or Linux/MacOS/FreeBSD. 93 | - Custom common alias are **auto synced** across all vscode on Windows + Linux/MacOS/FreeBSD. 94 | - Creating `aliasName` + `aliasBody` is an **once-for-all** effort for current + **future** vscode/system-console. 95 | - Storage location: 96 | - Custom common alias stored in `settings.json`. 97 | - Normal alias stored in [alias files](./README.md#make-command-shortcuts-to-search-or-replace-in-or-out-of-vscode): 98 | - Windows: `%USERPROFILE%\msr-cmd-alias.doskeys`. 99 | - Linux/MacOS/FreeBSD: `~/msr-cmd-alias.bashrc` plus `~/.bashrc`. 100 | - Readability: 101 | - Normal alias are readable and easy to edit. 102 | - Custom common alias `aliasBody` which is **one-line** JSON need escape some chars (use **to-alias-body** to help transform). 103 | -------------------------------------------------------------------------------- /Extra-Path-Settings.md: -------------------------------------------------------------------------------- 1 | # Specific Extra Search Paths Settings 2 | 3 | If you want to set extra search paths for **a specific project**, use below format to set extra `paths` or `path-list-files`: 4 | 5 | - Value format: `[Global-Paths]`; `[Project1-Folder-Name = Path1, Path2, Path3]`; `[Project2-Folder-Name=Path5,Path6]`; 6 | - Use **semicolon** '**;**' to separate `groups`. A `[group]` is either `global-paths` or a `name=paths` pair. 7 | - Use **comma** '**,**' to separate paths in a `[group]`. 8 | - You can omit `global-paths` or `name=paths` pairs. Just set what you want, like one or more paths (global). 9 | 10 | **For example**, if you have 2 projects: `d:\git\`**project1** + `d:\git\`**project2** + a common/global path = `D:\myLibs\boost` 11 | 12 | You can set values for the projects like below, and their `extra search paths` will be below: 13 | 14 | - `msr.default.extraSearchPaths` 15 | - Set value like: `D:\myLibs\boost; project1 = D:\git\baseLib,D:\git\teamLib; project2=d:\git\project1;` 16 | - Then paths will be: 17 | - **project1** extra search paths = `D:\myLibs\boost,D:\git\baseLib,D:\git\teamLib` 18 | - **project2** extra search paths = `D:\myLibs\boost,d:\git\project1` 19 | - `msr.default.extraSearchPathListFiles` 20 | - Set value like: `project1=d:\paths1.txt,D:\paths2.txt; project2 = d:\paths3.txt` 21 | - Then paths will be: 22 | - **project1** extra search path list files = `d:\paths1.txt,D:\paths2.txt` 23 | - **project2** extra search path list files = `d:\paths3.txt` 24 | 25 | **Since 1.0.7** : Much easier to set in [your personal settings file](https://code.visualstudio.com/docs/getstarted/settings#_settings-file-locations) like `%APPDATA%\Code\User\settings.json` on Windows: 26 | 27 | - `msr.project1.extraSearchPaths` : `"D:\myLibs\boost,D:\git\baseLib,D:\git\teamLib"` 28 | - `msr.project2.extraSearchPaths` : `"D:\myLibs\boost,d:\git\project1"` 29 | 30 | - Same to `msr.xxx.extraSearchPathListFiles` settings. 31 | 32 | - You can also use `msr.default.extraSearchPathGroups` + `msr.default.extraSearchPathListFileGroups` which should use **array** values like: 33 | 34 | ```json 35 | "msr.default.extraSearchPathGroups": [ 36 | "D:\\myLibs\\boost, d:\\myLibs\\common", 37 | "Project1 = D:\\git\\baseLib, D:\\git\\teamLib", 38 | "Project2 = D:\\git\\Project1 , D:\\git\\baseLib , D:\\git\\teamLib" 39 | ] 40 | ``` 41 | 42 | You can also set extra search paths for each type of coding language. 43 | -------------------------------------------------------------------------------- /Manually-Download-Tools.md: -------------------------------------------------------------------------------- 1 | # Manually Download Tools and Put into PATH 2 | You can also manually **download** the tiny [msr.EXE](https://github.com/qualiu/msr#liberate--digitize-daily-works-by-2-exe-file-processing-data-mining-map-reduce) (of your system type) , then **add** the folder to `%PATH%` or `$PATH`. 3 | 4 | - **Use** an existing folder or **create** a new folder like `~/tools` or `D:\tools` instead of **`system folder`**, then add it to `$PATH` or `%PATH%`. 5 | 6 | - Or simply **copy 1 command** below to download + copy to **`system folder`** which already in `$PATH` or `%PATH%`: 7 | 8 | - **Windows** 9 | - `x86_64` / `Arm64` 64-bit `Windows` + `MinGW` 10 | 11 | - **If has `curl.exe` or `wget.exe`**: (check by command like `"where curl.exe"`, you can get it by [choco](https://chocolatey.org/packages/Wget) or [cygwin](https://github.com/qualiu/msrTools/blob/master/system/install-cygwin.bat)) 12 | 13 | - **curl** -o `msr.tmp` && `move /y msr.tmp msr.exe` && `icacls msr.exe /grant %USERNAME%:RX` && **move** [msr.exe](https://raw.githubusercontent.com/qualiu/msr/master/tools/msr.exe) `%SystemRoot%\` 14 | - **wget** -O `msr.tmp` && `move /y msr.tmp msr.exe` && `icacls msr.exe /grant %USERNAME%:RX` && **move** [msr.exe](https://raw.githubusercontent.com/qualiu/msr/master/tools/msr.exe) `%SystemRoot%\` 15 | 16 | - Otherwise use `PowerShell`: 17 | 18 | **PowerShell** `-Command "$ProgressPreference = 'SilentlyContinue'; [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/qualiu/msr/master/tools/msr.exe' -OutFile msr.tmp"` && `move /y msr.tmp msr.exe` && `icacls msr.exe /grant %USERNAME%:RX` && **move** [msr.exe](https://raw.githubusercontent.com/qualiu/msr/master/tools/msr.exe) `%SystemRoot%\` 19 | 20 | - **Cygwin** [bash terminal](#supported-4-terminal-types-on-windows) on Windows 21 | 22 | **curl** -o `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `mv msr /usr/bin/msr` 23 | 24 | - `x86` 32-bit `Windows` + `MinGW` 25 | 26 | **curl** -o `msr.tmp` && `move /y msr.tmp msr.exe` && `icacls msr.exe /grant %USERNAME%:RX` && **move** [msr.exe](https://raw.githubusercontent.com/qualiu/msr/master/tools/msr.exe) `%SystemRoot%\` 27 | 28 | 29 | - **Linux** 30 | - `x86_64`: 64-bit `Ubuntu` / `CentOS` / `Fedora` / `WSL-on-Windows` 31 | 32 | **curl** -o `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `sudo mv msr /usr/bin/msr` 33 | 34 | - `Arm64` (like `Ubuntu Arm64`): 35 | 36 | **curl** -o `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `sudo mv msr /usr/bin/msr` 37 | 38 | - `x86` 32-bit `Ubuntu` / `CentOS` / `Fedora` / `WSL-on-Windows` 39 | 40 | **curl** -o `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `sudo mv msr /usr/bin/msr` 41 | 42 | 43 | - **MacOS** `Darwin-Arm64`: 44 | 45 | **curl** -o `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `sudo mv msr /usr/local/bin/msr` 46 | 47 | - **FreeBSD** `amd64`: 48 | 49 | **curl** -o `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `sudo mv msr /usr/local/bin/msr` 50 | 51 | # Alternative Sources if Unable to Download msr/nin from GitHub 52 | If you're unable to download msr/nin tools from [GitHub](https://github.com/qualiu/msr)(validation: [md5.txt](https://github.com/qualiu/msr/blob/master/tools/md5.txt)) by command lines above, try sources + command lines below: 53 | - https://sourceforge.net/projects/avasattva/files/ to download msr/nin by commands or click URLs like: 54 | - **curl** "" -o `msr.tmp` && `move /y msr.tmp msr.exe` && `icacls msr.exe /grant %USERNAME%:RX` && **move** [msr.exe](https://raw.githubusercontent.com/qualiu/msr/master/tools/msr.exe) `%SystemRoot%\` 55 | - **wget** "" -O `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `sudo mv msr /usr/bin/msr` 56 | - **curl** "" -o `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `sudo mv msr /usr/local/bin/msr` 57 | - File validation: [md5.txt](https://master.dl.sourceforge.net/project/avasattva/md5.txt?viasf=1) 58 | - https://gitlab.com/lqm678/msr to download msr/nin by commands or click URLs like: 59 | - **curl** "" -o `msr.tmp` && `move /y msr.tmp msr.exe` && `icacls msr.exe /grant %USERNAME%:RX` && **move** [msr.exe](https://raw.githubusercontent.com/qualiu/msr/master/tools/msr.exe) `%SystemRoot%\` 60 | - **wget** "" -O `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `sudo mv msr /usr/bin/msr` 61 | - **curl** "" -o `msr.tmp` && `mv -f msr.tmp msr` && `chmod +x msr` && `sudo mv msr /usr/local/bin/msr` 62 | - File validation: [md5.txt](https://gitlab.com/lqm678/msr/-/blob/master/tools/md5.txt) 63 | - https://gitee.com/qualiu/msr to manually download msr/nin. 64 | - File validation: [md5.txt](https://gitee.com/qualiu/msr/blob/master/tools/md5.txt) 65 | 66 | Same with [GitHub downloading](#or-manually-download--set-path-once-and-forever) above: You can **use/create a folder** (in `%PATH%`/`$PATH`) to replace **`%SystemRoot%`** or **`/usr/bin/`** or **`/usr/local/bin/`**. 67 | 68 | After done, you can directly run **msr --help** (or **msr -h** or just **msr**) should display [colorful usages and examples on Windows](https://qualiu.github.io/msr/usage-by-running/msr-Windows.html) or Linux like: [Fedora](https://qualiu.github.io/msr/usage-by-running/msr-Fedora-25.html) and [CentOS](https://qualiu.github.io/msr/usage-by-running/msr-CentOS-7.html). 69 | -------------------------------------------------------------------------------- /images/browse-all-setting-names.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qualiu/vscode-msr/d48c9f3d5333fb325cc2866732e65179221c9999/images/browse-all-setting-names.png -------------------------------------------------------------------------------- /images/change-settings-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qualiu/vscode-msr/d48c9f3d5333fb325cc2866732e65179221c9999/images/change-settings-example.png -------------------------------------------------------------------------------- /images/cook-command-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qualiu/vscode-msr/d48c9f3d5333fb325cc2866732e65179221c9999/images/cook-command-menu.png -------------------------------------------------------------------------------- /images/cooked-cmd-alias-doskeys-can-be-used-in-many-IDEs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qualiu/vscode-msr/d48c9f3d5333fb325cc2866732e65179221c9999/images/cooked-cmd-alias-doskeys-can-be-used-in-many-IDEs.png -------------------------------------------------------------------------------- /images/editor-context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qualiu/vscode-msr/d48c9f3d5333fb325cc2866732e65179221c9999/images/editor-context-menu.png -------------------------------------------------------------------------------- /images/find-def-ref.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qualiu/vscode-msr/d48c9f3d5333fb325cc2866732e65179221c9999/images/find-def-ref.gif -------------------------------------------------------------------------------- /images/vscode-msr-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qualiu/vscode-msr/d48c9f3d5333fb325cc2866732e65179221c9999/images/vscode-msr-icon.png -------------------------------------------------------------------------------- /images/vscode-msr-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qualiu/vscode-msr/d48c9f3d5333fb325cc2866732e65179221c9999/images/vscode-msr-small.png -------------------------------------------------------------------------------- /images/vscode-msr-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48 | 49 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | # Free to use and share, completely Free. 2 | 3 | # Hope help you daily work and save your life time. 4 | 5 | -------------------------------------------------------------------------------- /src/AliasNameBody.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface AliasNameBody { 3 | aliasName: string; 4 | aliasBody: string; 5 | description: string | undefined; 6 | }; 7 | -------------------------------------------------------------------------------- /src/ScoreTypeResult.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export enum ResultType { 4 | None = 0, 5 | LocalVariable, 6 | ConstantValue, 7 | Member, 8 | Method, 9 | Interface, 10 | Enum, 11 | Class 12 | } 13 | 14 | export const InvalidLocation = new vscode.Location(vscode.Uri.file('Invalid-Location-Cost'), new vscode.Position(0, 0)); 15 | 16 | export class ScoreTypeResult { 17 | public Score: number = 0; 18 | public Type: ResultType; 19 | public ResultText: string = ''; 20 | public Location: vscode.Location; 21 | 22 | constructor(score: number, type: ResultType, resultText: string, location: vscode.Location = InvalidLocation) { 23 | this.Score = score; 24 | this.Type = type; 25 | this.ResultText = resultText; 26 | this.Location = location; 27 | } 28 | } -------------------------------------------------------------------------------- /src/configUtils.ts: -------------------------------------------------------------------------------- 1 | import { ExecSyncOptions } from 'child_process'; 2 | import * as vscode from 'vscode'; 3 | import { DefaultWorkspaceFolder, getDefaultRepoFolderByActiveFile, getProjectFolderKey } from './constants'; 4 | import { TerminalType } from './enums'; 5 | import { outputInfoQuietByTime } from './outputUtils'; 6 | import path = require('path'); 7 | import ChildProcess = require('child_process'); 8 | 9 | function isGitRecurseSubModuleSupported(): boolean { 10 | const execOption: ExecSyncOptions = { cwd: DefaultWorkspaceFolder }; 11 | try { 12 | ChildProcess.execSync('git ls-files --recurse-submodules .git', execOption); 13 | return true; 14 | } catch (err) { 15 | if (err) { 16 | const errorText = err.toString(); // error: unknown option `recurse-submodules' 17 | const shortError = errorText.replace(/[\r\n]+\s*usage\s*:.*/is, ''); 18 | if (errorText.match(/unknown option \W*recurse-submodules/i)) { 19 | outputInfoQuietByTime(`Detected '--recurse-submodules' not supported in 'git ls-files': ${shortError}`); 20 | return false; 21 | } 22 | } 23 | return false; 24 | } 25 | } 26 | 27 | const IsGitRecurseSubModuleSupported = isGitRecurseSubModuleSupported(); 28 | 29 | function shouldSearchGitSubModules(repoFolderName: string): boolean { 30 | return IsGitRecurseSubModuleSupported 31 | && getConfigValueOfProject(repoFolderName, 'searchGitSubModuleFolders') === 'true'; 32 | } 33 | 34 | export function GitListFileRecursiveArg(repoFolderName: string): string { 35 | return shouldSearchGitSubModules(repoFolderName) ? '--recurse-submodules' : ' '; 36 | } 37 | 38 | export function GitListFileHead(repoFolderName: string): string { 39 | return `git ls-files ${GitListFileRecursiveArg(repoFolderName)}`.trimRight() 40 | } 41 | 42 | export function getConfigValueOfActiveProject(configTailKey: string, allowEmpty = false, addDefault: boolean = true): string { 43 | const repoFolder = getDefaultRepoFolderByActiveFile(); 44 | const repoFolderName = path.basename(repoFolder); 45 | return getConfigValueOfProject(repoFolderName, configTailKey, allowEmpty, addDefault); 46 | } 47 | 48 | export function getConfigValueOfProject(repoFolderName: string, configTailKey: string, allowEmpty = false, addDefault: boolean = true): string { 49 | const prefixSet = GetConfigPriorityPrefixes(repoFolderName, 'default', '', addDefault); 50 | return getConfigValueByPriorityList(prefixSet, configTailKey, allowEmpty); 51 | } 52 | 53 | export function getConfigValueByProjectAndExtension(repoFolderName: string, extension: string, mappedExt: string, configTailKey: string, allowEmpty = false, addDefault: boolean = true): string { 54 | const prefixSet = GetConfigPriorityPrefixes(repoFolderName, extension, mappedExt, addDefault); 55 | return getConfigValueByPriorityList(prefixSet, configTailKey, allowEmpty); 56 | } 57 | 58 | export function GetConfigPriorityPrefixes(repoFolderName: string, extension: string, mappedExt: string, addDefault: boolean = true): string[] { 59 | repoFolderName = getProjectFolderKey(repoFolderName); 60 | let prefixSet = new Set([ 61 | (repoFolderName + '.' + extension).replace(/\.$/, ''), 62 | (repoFolderName + '.' + mappedExt).replace(/\.$/, ''), 63 | repoFolderName, 64 | extension, 65 | mappedExt, 66 | '', 67 | 'default', 68 | ]); 69 | 70 | if (!repoFolderName || repoFolderName === '') { 71 | // prefixSet.delete(''); 72 | } 73 | 74 | if (!addDefault) { 75 | prefixSet.delete('default'); 76 | prefixSet.delete(''); 77 | } 78 | 79 | return Array.from(prefixSet).filter(a => !a.startsWith('.')); 80 | } 81 | 82 | export function getConfigValueByAllParts(repoFolderName: string, extension: string, mappedExt: string, subKeyName: string, configTailKey: string, allowEmpty = false): string { 83 | repoFolderName = getProjectFolderKey(repoFolderName); 84 | let prefixSet = new Set([ 85 | repoFolderName + '.' + extension + '.' + subKeyName, 86 | repoFolderName + '.' + mappedExt + '.' + subKeyName, 87 | repoFolderName + '.' + extension, 88 | repoFolderName + '.' + mappedExt, 89 | repoFolderName + '.' + subKeyName, 90 | repoFolderName, 91 | extension + '.' + subKeyName, 92 | mappedExt + '.' + subKeyName, 93 | extension, 94 | mappedExt, 95 | subKeyName, 96 | '', 97 | 'default', 98 | ]); 99 | 100 | if (!repoFolderName || repoFolderName === '') { 101 | // prefixSet.delete(''); 102 | } 103 | const prefixList = Array.from(prefixSet).filter(a => !a.startsWith('.')); 104 | return getConfigValueByPriorityList(prefixList, configTailKey, allowEmpty); 105 | } 106 | 107 | export function getConfigValueByPriorityList(priorityPrefixList: string[], configNameTail: string, allowEmpty: boolean = true): string { 108 | const config = vscode.workspace.getConfiguration('msr'); 109 | for (let k = 0; k < priorityPrefixList.length; k++) { 110 | const name = (priorityPrefixList[k].length > 0 ? priorityPrefixList[k] + '.' : priorityPrefixList[k]) + configNameTail; 111 | let valueObject = config.get(name); 112 | if (valueObject === undefined || valueObject === null || typeof (valueObject) === 'object') { 113 | continue; 114 | } 115 | 116 | const valueText = String(valueObject); 117 | if (valueText.length > 0 || allowEmpty) { 118 | return valueText; 119 | } 120 | } 121 | 122 | return ''; 123 | } 124 | 125 | export function getPostInitCommands(terminalType: TerminalType, repoFolderName: string) { 126 | const terminalTypeName = TerminalType[terminalType].toString(); 127 | const typeName = (terminalTypeName[0].toLowerCase() + terminalTypeName.substring(1)) 128 | .replace(/CMD/i, 'cmd') 129 | .replace(/MinGW/i, 'mingw') 130 | .replace(/^(Linux|WSL)Bash/i, 'bash'); 131 | const configTailKey = typeName + '.postInitTerminalCommandLine'; 132 | return getConfigValueOfProject(repoFolderName, configTailKey, true); 133 | } 134 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import ChildProcess = require('child_process'); 3 | import os = require('os'); 4 | import * as vscode from 'vscode'; 5 | 6 | export function GetCommandOutput(command: string): string { 7 | try { 8 | const output = ChildProcess.execSync(command); 9 | return !output ? '' : output.toString().trim(); 10 | } catch (error) { 11 | return ''; 12 | } 13 | } 14 | 15 | export const RunCmdTerminalName = 'MSR-RUN-CMD'; 16 | export const OutputChannelName = 'MSR-Def-Ref'; 17 | 18 | export const IsDebugMode = process.execArgv && process.execArgv.length > 0 && process.execArgv.some((arg) => /^--debug=?/.test(arg) || /^--(debug|inspect)-brk=?/.test(arg)); 19 | export const WorkspaceCount = !vscode.workspace.workspaceFolders ? 0 : vscode.workspace.workspaceFolders.length; 20 | export const DefaultWorkspaceFolder: string = getDefaultRepoFolderByActiveFile(true); 21 | export const DefaultRepoFolderName: string = path.basename(DefaultWorkspaceFolder); 22 | 23 | export const Is64BitOS = process.arch.includes('64'); 24 | export const IsWindows = /Win32|Windows/i.test(process.platform); 25 | const SystemInfo = IsWindows ? '' : GetCommandOutput('uname -smr'); 26 | export const IsWSL = !IsWindows && /Microsoft/i.test(SystemInfo) && /WSL/i.test(SystemInfo); 27 | 28 | export const IsDarwinArm64 = !IsWindows && /^Darwin/i.test(process.platform) && process.arch === 'arm64'; 29 | export const IsMacOS = IsDarwinArm64 || (!IsWindows && /Darwin|Mac|\biOS\b|macOS|Apple/.test(SystemInfo)); 30 | 31 | export const IsLinuxArm64 = !IsWindows && !IsMacOS && !IsWSL && /aarch64/i.test(SystemInfo) && /Linux/i.test(SystemInfo); 32 | export const IsLinux86x64 = !IsWindows && !IsMacOS && !IsWSL && !IsLinuxArm64 && /x86_64/.test(SystemInfo) && /Linux/i.test(SystemInfo); 33 | export const IsLinux = IsLinuxArm64 || IsLinux86x64; 34 | 35 | export const IsSupportedSystem = /Win32|Windows|Linux/i.test(process.platform) || IsDarwinArm64; 36 | 37 | export const SearchTextHolder = '%1'; 38 | export const SkipJumpOutForHeadResultsRegex = /\s+(-J\s+-H|-J?H)\s*\d+(\s+-J)?(\s+|$)/; 39 | export const RemoveJumpRegex = /\s+-J(\s+|$)/; 40 | export const TrimSearchTextRegex = /^[^\w\.-]+|[^\w\.-]+$/g; 41 | export const TrimProjectNameRegex: RegExp = /[^\w\.-]/g; 42 | 43 | export const ShouldQuotePathRegex = IsWindows ? /[^\w,\.\\/:~-]/ : /[^\w,\.\\/~-]/; 44 | export const HomeFolder = IsWindows ? path.join(process.env['USERPROFILE'] || '.') : process.env['HOME'] || '.'; 45 | export const SystemBinFolder = IsWindows ? (process.env['SystemRoot'] || String.raw`C:\WINDOWS\system32`) : (IsMacOS ? '/usr/local/bin/' : '/usr/bin/'); 46 | export const TempStorageFolder = IsWindows ? os.tmpdir() : '/tmp/'; 47 | export const InitLinuxTerminalFileName = 'init-linux-terminal.sh'; 48 | export const CheckReCookAliasFileSeconds = 3600; // mitigate alias file inconsistency especially for Linux terminals on Windows. 49 | 50 | // Environment variable names 51 | export const SkipJunkPathEnvArgName: string = "Skip_Junk_Name"; 52 | export const SkipJunkPathEnvArgValue: string = "Skip_Junk_Paths"; 53 | export const SearchGitSubModuleEnvName: string = "Git_List_Args"; 54 | export const GitFileListExpirationTimeEnvName: string = "Git_List_Expire"; 55 | export const GitRepoEnvName: string = "GitRepoTmpName"; 56 | export const TmpGitFileListExpiration = "+1day"; // avoid missing update to different repo. 57 | 58 | export const GitTmpListFilePrefix = "git-paths-"; 59 | 60 | export const ReplaceJunkPattern = new RegExp(String.raw`msr -rp \S+\s+\W+${SkipJunkPathEnvArgName}\W*\s+\W+${SkipJunkPathEnvArgValue}\S*`, 'g'); 61 | 62 | const GitInfoTemplate = "Skip_Junk_Paths length = $L. Parsed $P of $T patterns, omitted $E errors, ignored $X exemptions: see MSR-Def-Ref in OUTPUT tab."; 63 | const FinalTipTemplate = `echo Auto disable self finding $M definition = $D. Uniform slash = $U. Faster gfind-xxx = $F. Auto update search tool = $A.` 64 | + String.raw` | msr -t "%[A-Z]% |\$[A-Z]\b " -o "" -aPAC` // Trim case like %M% 65 | + ` | msr -aPA -i -e true -t "false|Auto.*?(disable).*?definition"`; 66 | 67 | export const WslCheckingCommand = String.raw`grep -E '^root\s*=\s*/\s*$' /etc/wsl.conf >/dev/null || echo 'Please check: https://github.com/qualiu/vscode-msr/blob/master/README.md#use-short-mount-paths-for-wsl-bash-terminal-on-windows' | GREP_COLOR='01;31' grep -E .+ --color=always`; 68 | 69 | export function getEnvNameRef(envName: string, isWindowsTerminal: boolean): string { 70 | return isWindowsTerminal ? `%${envName}%` : `$${envName}`; 71 | } 72 | 73 | export function getEnvNameRefRegex(envName: string, isWindowsTerminal: boolean): RegExp { 74 | return isWindowsTerminal ? new RegExp(String.raw`%${envName}%`, 'g') : new RegExp(String.raw`\$${envName}\b`, 'g'); 75 | } 76 | 77 | export function getSkipJunkPathArgs(isWindowsTerminal: boolean): string { 78 | return `${getEnvNameRef(SkipJunkPathEnvArgName, isWindowsTerminal)} "${getEnvNameRef(SkipJunkPathEnvArgValue, isWindowsTerminal)}"`; 79 | } 80 | 81 | export function getProjectFolderKey(repoFolderName: string): string { 82 | return !repoFolderName ? '' : repoFolderName.replace(TrimProjectNameRegex, '-'); 83 | } 84 | 85 | export function getTipInfoTemplate(isCmdTerminal: boolean, isFinalTip: boolean): string { 86 | const tip = isFinalTip ? FinalTipTemplate : GitInfoTemplate; 87 | return isCmdTerminal ? tip.replace(/\$([A-Z])\b/g, '%$1%') : tip; 88 | } 89 | 90 | export function getCommandToSetGitInfoVar(isCmdTerminal: boolean, skipGitRegexLength: number, totalPatterns: number, parsedPatterns: number, errors: number, exemptions: number): string { 91 | return isCmdTerminal 92 | ? `set L=${skipGitRegexLength} & set T=${totalPatterns} & set P=${parsedPatterns} & set E=${errors} & set X=${exemptions} &`.replace(/ &/g, '&') 93 | : `export L=${skipGitRegexLength}; export T=${totalPatterns}; export P=${parsedPatterns}; export E=${errors}; export X=${exemptions};`; 94 | } 95 | 96 | export function getCommandToSetFinalTipVar(isCmdTerminal: boolean, mappedExt: string, hasDisabledFindDefinition: boolean, isUniversalSlash: boolean, isFastGitFind: boolean, isAutoUpdate: boolean): string { 97 | return isCmdTerminal 98 | ? `set M=${mappedExt} & set D=${hasDisabledFindDefinition} & set U=${isUniversalSlash} & set F=${isFastGitFind} & set A=${isAutoUpdate} &`.replace(/ &/g, '&') 99 | : `export M=${mappedExt}; export D=${hasDisabledFindDefinition}; export U=${isUniversalSlash}; export F=${isFastGitFind}; export A=${isAutoUpdate};`; 100 | } 101 | 102 | export function getRunTipFileCommand(tipFileDisplayPath: string, otherArgs: string): string { 103 | return `msr -p ${tipFileDisplayPath} ${otherArgs.trim()} -XA`; 104 | } 105 | 106 | export function getBashFileHeader(isWindowsTerminal: boolean, addNewLine = "\n"): string { 107 | return isWindowsTerminal ? '' : "#!/bin/bash" + addNewLine; 108 | } 109 | 110 | export function getTipGuideFileName(isWindowsTerminal: boolean): string { 111 | return 'tip-guide' + (isWindowsTerminal ? '.cmd' : '.sh'); 112 | } 113 | 114 | export function getAliasFileName(isWindowsTerminal: boolean, isForProjectCmdAlias = false): string { 115 | return 'msr-cmd-alias' + (isWindowsTerminal ? (isForProjectCmdAlias ? ".cmd" : '.doskeys') : '.bashrc'); 116 | } 117 | 118 | export function isNullOrEmpty(obj: string | undefined): boolean { 119 | return obj === null || obj === undefined || obj.length === 0; 120 | } 121 | 122 | export function getDefaultRepoFolderByActiveFile(useDefaultProjectIfEmpty = false) { 123 | const activePath = getActiveFilePath(); 124 | return getRepoFolder(activePath, useDefaultProjectIfEmpty); 125 | } 126 | 127 | export function getRepoFolder(filePath: string, useFirstFolderIfNotFound = false): string { 128 | const folderUri = isNullOrEmpty(filePath) ? '' : vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath)); 129 | if (!folderUri || !folderUri.uri || !folderUri.uri.fsPath) { 130 | if (useFirstFolderIfNotFound && WorkspaceCount > 0) { 131 | return vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : ''; 132 | } 133 | return ''; 134 | } 135 | 136 | return folderUri.uri.fsPath; 137 | } 138 | 139 | export function getActiveFilePath() { 140 | if (vscode.window.activeTextEditor 141 | && vscode.window.activeTextEditor.document 142 | && !isNullOrEmpty(vscode.window.activeTextEditor.document.fileName)) { 143 | return vscode.window.activeTextEditor.document.fileName; 144 | } else { 145 | return ''; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/enums.ts: -------------------------------------------------------------------------------- 1 | export enum FindType { 2 | None = 0, 3 | Definition = 1, 4 | Reference = 2, 5 | } 6 | 7 | export enum TerminalType { 8 | None = 0, 9 | CMD = 1, 10 | PowerShell = 2, 11 | LinuxBash = 3, 12 | CygwinBash = 4, 13 | MinGWBash = 5, 14 | WslBash = 6, 15 | Pwsh = 7, // PowerShell on Linux/MacOS 16 | } 17 | 18 | export enum ForceFindType { 19 | None = 0, 20 | FindClass = 1 << 1, 21 | FindMethod = 1 << 2, 22 | FindMember = 1 << 3, 23 | FindLocalVariable = 1 << 4 24 | } 25 | 26 | export enum FindCommandType { 27 | None = 0, 28 | RegexFindAsClassOrMethodDefinitionInCodeFiles, 29 | RegexFindDefinitionInCurrentFile, 30 | RegexFindReferencesInCurrentFile, 31 | RegexFindReferencesInCodeFiles, 32 | RegexFindPureReferencesInCodeFiles, 33 | RegexFindPureReferencesInAllSourceFiles, 34 | RegexFindReferencesInConfigFiles, 35 | RegexFindReferencesInDocs, 36 | RegexFindReferencesInAllSourceFiles, 37 | RegexFindReferencesInSameTypeFiles, 38 | RegexFindReferencesInAllSmallFiles, 39 | RegexFindReferencesInCodeAndConfig, 40 | FindPlainTextInCodeFiles, 41 | FindPlainTextInConfigFiles, 42 | FindPlainTextInDocFiles, 43 | FindPlainTextInConfigAndConfigFiles, 44 | FindPlainTextInAllSourceFiles, 45 | FindPlainTextInAllSmallFiles, 46 | SortSourceBySize, 47 | SortSourceByTime, 48 | SortBySize, 49 | SortByTime, 50 | SortCodeBySize, 51 | SortCodeByTime, 52 | FindTopFolder, 53 | FindTopType, 54 | FindTopSourceFolder, 55 | FindTopSourceType, 56 | FindTopCodeFolder, 57 | FindTopCodeType, 58 | MyFindOrReplaceSelectedText, 59 | } 60 | -------------------------------------------------------------------------------- /src/fileUtils.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import path = require('path'); 3 | 4 | import { outputErrorByTime, outputInfoByDebugModeByTime, outputInfoByTime } from "./outputUtils"; 5 | import { getErrorMessage } from './utils'; 6 | 7 | export function saveTextToFile(filePath: string, text: string, info: string = 'file', checkSkipSameContent: boolean = true, tryTimes: number = 3): boolean { 8 | for (let k = 1; k <= tryTimes; k++) { 9 | try { 10 | if (checkSkipSameContent) { 11 | const existingContent = readTextFile(filePath); 12 | if (existingContent === text) { 13 | return true; 14 | } 15 | } 16 | fs.writeFileSync(filePath, text); 17 | if (k > 1) { 18 | outputInfoByTime('Times-' + k + ': Successfully saved ' + info + ': ' + filePath); 19 | } 20 | return true; 21 | } catch (err) { 22 | outputErrorByTime('Times-' + k + ': Failed to save ' + info + ': ' + filePath + ' Error: ' + err); 23 | if (k >= tryTimes) { 24 | return false; 25 | } 26 | } 27 | } 28 | 29 | return false; 30 | } 31 | 32 | export function readTextFile(filePath: string): string { 33 | try { 34 | if (!fs.existsSync(filePath)) { 35 | outputInfoByDebugModeByTime(`Not found file: ${filePath}`); 36 | return ''; 37 | } 38 | const text = fs.readFileSync(filePath); 39 | return !text ? '' : text.toString(); 40 | } catch (err) { 41 | outputErrorByTime('Failed to read file: ' + filePath + ', error: ' + err); 42 | return ''; 43 | } 44 | } 45 | 46 | export function createDirectory(folder: string): boolean { 47 | if (fs.existsSync(folder)) { 48 | return true; 49 | } 50 | 51 | try { 52 | const parentDir = path.dirname(folder); 53 | createDirectory(parentDir); 54 | fs.mkdirSync(folder); 55 | return true; 56 | } catch (err) { 57 | outputErrorByTime('Failed to make single script folder: ' + folder + ' Error: ' + err); 58 | return false; 59 | } 60 | } 61 | 62 | export function getFileModifyTime(filePath: string, invalidYearSubtract: number = 10): Date { 63 | try { 64 | const stat = fs.statSync(filePath); 65 | return stat.mtime; 66 | } catch (err) { 67 | let invalidTime = new Date(); 68 | invalidTime.setFullYear(invalidTime.getFullYear() - invalidYearSubtract); 69 | return invalidTime; 70 | } 71 | } 72 | 73 | export function isSymbolicLink(path: string): boolean { 74 | try { 75 | const stat = fs.lstatSync(path); 76 | return stat.isSymbolicLink(); 77 | } catch (err) { 78 | outputInfoByDebugModeByTime('Failed to check link of file: ' + path + ', error: ' + getErrorMessage(err)); 79 | return false; 80 | } 81 | } 82 | 83 | export function isDirectory(path: string): boolean { 84 | try { 85 | const stat = fs.lstatSync(path); 86 | return stat.isDirectory(); 87 | } catch (err) { 88 | outputInfoByDebugModeByTime('Failed to check link of file: ' + path + ', error: ' + getErrorMessage(err)); 89 | return false; 90 | } 91 | } 92 | 93 | export function isFileExists(path: string): boolean { 94 | try { 95 | const stat = fs.lstatSync(path); 96 | return stat.isFile(); 97 | } catch (err) { 98 | outputInfoByDebugModeByTime('Failed to check link of file: ' + path + ', error: ' + getErrorMessage(err)); 99 | return false; 100 | } 101 | } -------------------------------------------------------------------------------- /src/filter/ClassResultFilter.ts: -------------------------------------------------------------------------------- 1 | import { SearchChecker } from '../searchChecker'; 2 | 3 | export abstract class ClassResultFilter { 4 | constructor( 5 | protected readonly SearchInfo: SearchChecker) { 6 | } 7 | 8 | protected abstract getUsedNamespaces(): void; 9 | public abstract hasDefinedNamespace(resultFilePath: string): boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/filter/ClassResultFilterForCSharp.ts: -------------------------------------------------------------------------------- 1 | import { isNullOrEmpty } from '../constants'; 2 | import { SearchChecker } from '../searchChecker'; 3 | import { ClassResultFilter } from './ClassResultFilter'; 4 | import fs = require('fs'); 5 | 6 | export class ClassResultFilterForCSharp extends ClassResultFilter { 7 | private readonly GetUsedNamespacesRegex: RegExp = /^\s*using\s+((?\w+\S+)\s*=\s*)?(?\w+\S+)\s*;/; 8 | private readonly GetNamespaceDefinitionRegex: RegExp = /^\s*namespace\s+(?[\w\.]+)/mg; 9 | 10 | protected UsedNamespaces: Set = new Set(); 11 | constructor( 12 | protected readonly SearchInfo: SearchChecker 13 | ) { 14 | super(SearchInfo); 15 | this.getUsedNamespaces(); 16 | } 17 | 18 | protected getUsedNamespaces(): void { 19 | this.UsedNamespaces.clear(); 20 | const explicitUsedNamespaceRegex: RegExp = new RegExp(String.raw`\b(\w+[\.\w]+)\.` + this.SearchInfo.currentWord + String.raw`\b`); 21 | const aliasToNamespaceMap = new Map(); 22 | 23 | // vscode.workspace.openTextDocument(sourceFilePath).then((document) => { 24 | var allText: string = fs.readFileSync(this.SearchInfo.currentFilePath).toString(); 25 | var lines = allText.split(/\r?\n/); 26 | for (let k = this.SearchInfo.Position.line - 1; k >= 0; k--) { 27 | const match = this.GetUsedNamespacesRegex.exec(lines[k]); 28 | if (match && match.groups) { 29 | const alias = match.groups['Alias']; 30 | const namespace = match.groups['Namespace']; 31 | this.UsedNamespaces.add(namespace); 32 | if (!isNullOrEmpty(alias)) { 33 | aliasToNamespaceMap.set(alias, namespace); 34 | } 35 | } 36 | } 37 | 38 | const explicitMatch = explicitUsedNamespaceRegex.exec(this.SearchInfo.currentText); 39 | if (explicitMatch && explicitMatch.length > 0) { 40 | const namespace = aliasToNamespaceMap.get(explicitMatch[1]) || explicitMatch[1]; 41 | this.UsedNamespaces.clear(); 42 | this.UsedNamespaces.add(namespace); 43 | return; 44 | } 45 | } 46 | 47 | public hasDefinedNamespace(resultFilePath: string): boolean { 48 | const allText = fs.readFileSync(resultFilePath).toString(); 49 | let match: RegExpExecArray | null = null; 50 | while ((match = this.GetNamespaceDefinitionRegex.exec(allText)) != null) { 51 | if (match.groups && this.UsedNamespaces.has(match.groups['Namespace'])) { 52 | return true; 53 | } 54 | } 55 | 56 | return false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/filter/filterClassResults.ts: -------------------------------------------------------------------------------- 1 | import { ResultType, ScoreTypeResult } from '../ScoreTypeResult'; 2 | import { SearchChecker } from '../searchChecker'; 3 | import { ClassResultFilter } from './ClassResultFilter'; 4 | import { ClassResultFilterForCSharp } from './ClassResultFilterForCSharp'; 5 | 6 | export function filterClassResults(highValueResults: ScoreTypeResult[], searchInfo: SearchChecker): ScoreTypeResult[] { 7 | if (!highValueResults || highValueResults.length < 1 || highValueResults[0].Type !== ResultType.Class) { 8 | return highValueResults; 9 | } 10 | 11 | let results: ScoreTypeResult[] = []; 12 | const classResultFilter = createFilter(searchInfo); 13 | if (!classResultFilter) { 14 | return highValueResults; 15 | } 16 | 17 | highValueResults.forEach((a) => { 18 | if (a.Type === ResultType.Class) { 19 | if (classResultFilter && classResultFilter.hasDefinedNamespace(a.Location.uri.fsPath)) { 20 | results.push(a); 21 | } 22 | } 23 | }); 24 | 25 | return results.length > 0 ? results : highValueResults; 26 | } 27 | 28 | function createFilter(searchInfo: SearchChecker): ClassResultFilter | null { 29 | let classResultFilter: ClassResultFilter | null = null; 30 | const extension = searchInfo.extension || ''; 31 | if (/^(cs|cshtml)$/i.exec(extension)) { 32 | classResultFilter = new ClassResultFilterForCSharp(searchInfo); 33 | } 34 | 35 | return classResultFilter; 36 | } -------------------------------------------------------------------------------- /src/forceSettings.ts: -------------------------------------------------------------------------------- 1 | import { ForceFindType } from "./enums"; 2 | 3 | export class ForceSetting { 4 | public FindClass: boolean = false; 5 | public FindMethod: boolean = false; 6 | public FindMember: boolean = false; 7 | public FindLocalVariableDefinition = false; 8 | public ForceFind: ForceFindType; 9 | 10 | constructor(forceFindType: ForceFindType = ForceFindType.None) { 11 | this.ForceFind = forceFindType; 12 | this.FindClass = ForceFindType.FindClass === (ForceFindType.FindClass & forceFindType); 13 | this.FindMethod = ForceFindType.FindMethod === (ForceFindType.FindMethod & forceFindType); 14 | this.FindMember = ForceFindType.FindMember === (ForceFindType.FindMember & forceFindType); 15 | this.FindLocalVariableDefinition = ForceFindType.FindLocalVariable === (ForceFindType.FindLocalVariable & forceFindType); 16 | } 17 | 18 | public isFindClassOrMethod(): boolean { 19 | return this.FindClass || this.FindMethod; 20 | } 21 | 22 | public hasFlag(forceFindType: ForceFindType, passIfNone: boolean = true): boolean { 23 | return (this.ForceFind === ForceFindType.None && passIfNone) || (forceFindType & this.ForceFind) !== 0; 24 | } 25 | 26 | public hasAnyFlag(forceFindTypes: ForceFindType[]): boolean { 27 | for (let k = 0; k < forceFindTypes.length; k++) { 28 | if (this.hasFlag(forceFindTypes[k])) { 29 | return true; 30 | } 31 | } 32 | return false; 33 | } 34 | 35 | public getAllFlagsExcept(excludeFlags: ForceFindType[] = []): ForceFindType { 36 | if (this.ForceFind === ForceFindType.None) { 37 | return ForceFindType.None; 38 | } 39 | 40 | let flag = ForceFindType.None; 41 | 42 | if (this.hasFlag(ForceFindType.FindClass, false) && !isExcludedFlag(ForceFindType.FindClass)) { 43 | flag |= ForceFindType.FindClass; 44 | } 45 | 46 | if (this.hasFlag(ForceFindType.FindMethod, false) && !isExcludedFlag(ForceFindType.FindMethod)) { 47 | flag |= ForceFindType.FindMethod; 48 | } 49 | 50 | if (this.hasFlag(ForceFindType.FindMember, false) && !isExcludedFlag(ForceFindType.FindMember)) { 51 | flag |= ForceFindType.FindMember; 52 | } 53 | 54 | if (this.hasFlag(ForceFindType.FindLocalVariable, false) && !isExcludedFlag(ForceFindType.FindLocalVariable)) { 55 | flag |= ForceFindType.FindLocalVariable; 56 | } 57 | 58 | return flag; 59 | 60 | function isExcludedFlag(forceFindType: ForceFindType): boolean { 61 | return excludeFlags && excludeFlags.includes(forceFindType); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/gitUtils.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import fs = require('fs'); 3 | import ChildProcess = require('child_process'); 4 | import { GitListFileHead } from './configUtils'; 5 | import { DefaultWorkspaceFolder, getCommandToSetGitInfoVar, getRunTipFileCommand, isNullOrEmpty, OutputChannelName } from './constants'; 6 | import { TerminalType } from './enums'; 7 | import { outputError, outputErrorByTime, outputInfoByDebugMode, outputInfoByTime, outputInfoQuiet, outputInfoQuietByTime, outputWarnByTime } from './outputUtils'; 8 | import { runRawCommandInTerminal } from './runCommandUtils'; 9 | import { DefaultTerminalType, getTipFileDisplayPath, IsLinuxTerminalOnWindows, isWindowsTerminalOnWindows } from './terminalUtils'; 10 | import { IsForwardingSlashSupportedOnWindows, RunCommandChecker } from './ToolChecker'; 11 | import { changeToForwardSlash, getElapsedSecondsToNow, getRepoFolderName } from './utils'; 12 | 13 | export const SkipPathVariableName: string = 'Skip_Junk_Paths'; 14 | const RunCmdFolderWithForwardSlash: string = changeToForwardSlash(DefaultWorkspaceFolder); 15 | 16 | let ProjectFolderToShortSkipGitPathEnvValueMap = new Map(); 17 | 18 | export class GitIgnore { 19 | public Valid: boolean = false; 20 | public Completed: boolean = false; 21 | public ExemptionCount: number = 0; 22 | private Terminal: TerminalType; 23 | private IgnoreFilePath: string = ''; 24 | private UseGitIgnoreFile: boolean; 25 | private OmitGitIgnoreExemptions: boolean; 26 | private SkipDotFolders: boolean = true; 27 | private SkipPathPattern: string = ''; 28 | private RepoFolder: string = ''; 29 | private RepoFolderName: string = ''; 30 | private CheckUseForwardingSlashForCmd = true; 31 | private IsCmdTerminal: boolean; 32 | private MaxCommandLength: number; 33 | 34 | constructor(ignoreFilePath: string, useGitIgnoreFile: boolean = false, omitGitIgnoreExemptions: boolean = false, 35 | ignorableDotFolderNamePattern: string = '', terminalType = DefaultTerminalType, checkUseForwardingSlashForCmd = true) { 36 | this.IgnoreFilePath = ignoreFilePath; 37 | this.UseGitIgnoreFile = useGitIgnoreFile; 38 | this.OmitGitIgnoreExemptions = omitGitIgnoreExemptions; 39 | this.Terminal = terminalType; 40 | this.CheckUseForwardingSlashForCmd = checkUseForwardingSlashForCmd; 41 | this.IsCmdTerminal = isWindowsTerminalOnWindows(this.Terminal); 42 | this.MaxCommandLength = this.IsCmdTerminal ? 8163 : 131072; 43 | 44 | if (isNullOrEmpty(ignoreFilePath)) { 45 | return; 46 | } 47 | 48 | this.RepoFolder = changeToForwardSlash(path.dirname(ignoreFilePath)); 49 | this.RepoFolderName = getRepoFolderName(ignoreFilePath); 50 | const options: ChildProcess.ExecSyncOptionsWithStringEncoding = { 51 | encoding: 'utf8', 52 | cwd: path.dirname(ignoreFilePath), 53 | }; 54 | 55 | if (isNullOrEmpty(ignorableDotFolderNamePattern)) { 56 | return; 57 | } 58 | 59 | try { 60 | const ignorableDotFolderNameRegex = new RegExp(ignorableDotFolderNamePattern, 'i'); 61 | const folderNames = ChildProcess.execSync(String.raw`git ls-tree -d --name-only HEAD`, options).toString().split(/[\r?\n]+/); 62 | for (let i = 0; i < folderNames.length; i++) { 63 | if (folderNames[i].startsWith(".") && !folderNames[i].match(ignorableDotFolderNameRegex)) { 64 | this.SkipDotFolders = false; 65 | outputInfoQuietByTime(`Not skip all dot folders: Found repo-child-folder = ${folderNames[i]} , ignorableDotFolderNamePattern = "${ignorableDotFolderNamePattern}"`); 66 | break; 67 | } 68 | } 69 | } catch (error) { 70 | outputInfoQuietByTime("Cannot use git ls-tree to check git folder: " + error); 71 | } 72 | } 73 | 74 | public getSkipPathRegexPattern(toRunInTerminal: boolean, canUseVariable = true): string { 75 | const pattern = this.SkipPathPattern; 76 | if (isNullOrEmpty(pattern)) { 77 | return ''; 78 | } 79 | 80 | return toRunInTerminal && canUseVariable 81 | ? this.getSkipPathsVariable() 82 | : pattern; 83 | } 84 | 85 | private getSkipPathsVariable() { 86 | return this.IsCmdTerminal ? '"%' + SkipPathVariableName + '%"' : '"$' + SkipPathVariableName + '"'; 87 | } 88 | 89 | public replaceToSkipPathVariable(command: string): string { 90 | if (!isNullOrEmpty(this.SkipPathPattern) && RunCmdFolderWithForwardSlash === this.RepoFolder) { 91 | command = command.replace('"' + this.SkipPathPattern + '"', this.getSkipPathsVariable()); 92 | } 93 | return command; 94 | } 95 | 96 | public compareFileList() { 97 | if (!this.Valid) { 98 | return; 99 | } 100 | 101 | const commands = this.IsCmdTerminal 102 | ? [ 103 | String.raw`${GitListFileHead(this.RepoFolderName)} > %tmp%\git-file-list.txt`, 104 | String.raw`msr -rp . --np "%Skip_Junk_Paths%" -l -PIC | msr -x \ -o / -aPAC > %tmp%\ext-file-list.txt`, 105 | String.raw`nin %tmp%\git-file-list.txt %tmp%\ext-file-list.txt --nt "^\.|/\." -H 5 -T 5`, 106 | String.raw`nin %tmp%\git-file-list.txt %tmp%\ext-file-list.txt --nt "^\.|/\." -S -H 5 -T 5`, 107 | String.raw`nin %tmp%\git-file-list.txt %tmp%\ext-file-list.txt --nt "^\.|/\." -PAC | msr -t "^(\S+.+)" -o "./\1" -PIC > %tmp%\files-only-in-git.txt`, 108 | String.raw`nin %tmp%\git-file-list.txt %tmp%\ext-file-list.txt --nt "^\.|/\." -S -PAC | msr -t "^(\S+.+)" -o "./\1" -PIC > %tmp%\files-only-in-ext.txt`, 109 | String.raw`for /f "tokens=*" %a in ('msr -z "%Skip_Junk_Paths%" -t "\|" -o "\n" -PIC ^| msr -PIC') do @msr -p %tmp%\files-only-in-git.txt -it "%a" -H 3 -T 3 -O -c "Skip_Paths_Regex = %a"`, 110 | String.raw`for /f "tokens=*" %a in ('msr -z "%Skip_Junk_Paths%" -t "\|" -o "\n" -PIC ^| msr -PIC') do @msr -p %tmp%\files-only-in-ext.txt -it "%a" -H 3 -T 3 -O -c "Skip_Paths_Regex = %a"`, 111 | ] 112 | : [ 113 | String.raw`${GitListFileHead(this.RepoFolderName)} > /tmp/git-file-list.txt`, 114 | String.raw`msr -rp . --np "$Skip_Junk_Paths" -l -PIC > /tmp/ext-file-list.txt`, 115 | String.raw`nin /tmp/git-file-list.txt /tmp/ext-file-list.txt --nt "^\.|/\." -H 5 -T 5`, 116 | String.raw`nin /tmp/git-file-list.txt /tmp/ext-file-list.txt --nt "^\.|/\." -S -H 5 -T 5`, 117 | String.raw`nin /tmp/git-file-list.txt /tmp/ext-file-list.txt --nt "^\.|/\." -PAC | msr -t "^(\S+.+)" -o "./\1" -PIC > /tmp/files-only-in-git.txt`, 118 | String.raw`nin /tmp/git-file-list.txt /tmp/ext-file-list.txt --nt "^\.|/\." -S -PAC | msr -t "^(\S+.+)" -o "./\1" -PIC > /tmp/files-only-in-ext.txt`, 119 | String.raw`msr -z "$Skip_Junk_Paths" -t "\|" -o "\n" -PIC | msr -PIC | while IFS= read -r p; do msr -p /tmp/files-only-in-git.txt -it "$p" -H 3 -T 3 -O -c "Skip_Paths_Regex = $p"; done`, 120 | String.raw`msr -z "$Skip_Junk_Paths" -t "\|" -o "\n" -PIC | msr -PIC | while IFS= read -r p; do msr -p /tmp/files-only-in-ext.txt -it "$p" -H 3 -T 3 -O -c "Skip_Paths_Regex = $p"; done` 121 | ]; 122 | 123 | commands.forEach((cmd, _idx, _commands) => { 124 | if (!isNullOrEmpty(cmd)) { 125 | runRawCommandInTerminal(cmd); 126 | } 127 | }); 128 | } 129 | 130 | public parse(callbackWhenSucceeded: (...args: any[]) => void, callbackWhenFailed: (...args: any[]) => void) { 131 | this.Valid = false; 132 | this.Completed = false; 133 | this.ExemptionCount = 0; 134 | if (!this.UseGitIgnoreFile || isNullOrEmpty(this.IgnoreFilePath)) { 135 | this.Completed = true; 136 | callbackWhenFailed(); 137 | return; 138 | } 139 | 140 | if (!fs.existsSync(this.IgnoreFilePath)) { 141 | this.Completed = true; 142 | outputWarnByTime('Not exist git ignore file: ' + this.IgnoreFilePath); 143 | callbackWhenFailed(); 144 | return; 145 | } 146 | 147 | const beginTime = new Date(); 148 | fs.readFile(this.IgnoreFilePath, 'utf8', (err, text) => { 149 | if (err) { 150 | const message = 'Failed to read file: ' + this.IgnoreFilePath + ' , error: ' + err; 151 | outputErrorByTime(message); 152 | this.showErrorInRunCmdTerminal(message); 153 | this.Completed = true; 154 | callbackWhenFailed(); 155 | return; 156 | } 157 | 158 | if (isNullOrEmpty(text)) { 159 | this.Completed = true; 160 | const message = 'Read empty content from file: ' + this.IgnoreFilePath; 161 | outputErrorByTime(message); 162 | this.showErrorInRunCmdTerminal(message); 163 | callbackWhenFailed(); 164 | return; 165 | } 166 | 167 | const lines = text.split(/\r?\n/); 168 | const ignoreCommentSpecialRegex = new RegExp(String.raw`^\s*#` + '|' + String.raw`^/\**/?$`); // Skip cases: /*/ 169 | const exemptionRegex = /^\s*\!/; 170 | const ignoreDotFolderRegex = new RegExp(String.raw`^\.\*$` + '|' + String.raw`^/?\.[\w\./\?-]+$`); // Skip cases: .*.swp 171 | 172 | const useBackSlash = this.IsCmdTerminal && !IsForwardingSlashSupportedOnWindows; 173 | const headSlash = useBackSlash ? '\\\\' : '/'; 174 | const dotFolderPattern = headSlash + (this.SkipDotFolders ? '[\\$\\.]' : '\\$'); 175 | 176 | let skipPatterns = new Set().add(dotFolderPattern); 177 | 178 | if (!this.SkipDotFolders) { 179 | skipPatterns.add(this.getPattern(headSlash + '.git/')); 180 | } 181 | 182 | let readPatternCount = 0; 183 | let errorList = new Array(); 184 | for (let row = 0; row < lines.length; row++) { 185 | const line = lines[row].trim(); 186 | if (isNullOrEmpty(line) || ignoreCommentSpecialRegex.test(line)) { 187 | continue; 188 | } 189 | 190 | readPatternCount++; 191 | if (exemptionRegex.test(line)) { 192 | if (this.OmitGitIgnoreExemptions) { 193 | this.ExemptionCount++; 194 | outputWarnByTime('Ignore exemption: "' + line + '" at ' + this.IgnoreFilePath + ':' + (row + 1) + ' while msr.omitGitIgnoreExemptions = true.'); 195 | continue; 196 | } else { 197 | this.Completed = true; 198 | const message = 'Skip using git-ignore due to found exemption: "' + line + '" at ' + this.IgnoreFilePath + ':' + (row + 1) + ' while msr.omitGitIgnoreExemptions = false.'; 199 | outputErrorByTime(message); 200 | this.showErrorInRunCmdTerminal(message); 201 | callbackWhenFailed(); 202 | return; 203 | } 204 | } 205 | 206 | if (line.startsWith('$') || (this.SkipDotFolders && ignoreDotFolderRegex.test(line))) { 207 | outputInfoQuiet('Ignore redundant dot ignore: "' + line + '" at ' + this.IgnoreFilePath + ':' + (row + 1) + ' while msr.skipDotFoldersIfUseGitIgnoreFile = true.'); 208 | continue; 209 | } 210 | 211 | const pattern = this.getPattern(line); 212 | if (pattern.length < 2) { 213 | outputWarnByTime('Skip too short pattern: "' + line + '" at ' + this.IgnoreFilePath + ':' + (row + 1)); 214 | continue; 215 | } 216 | 217 | try { 218 | // tslint:disable-next-line: no-unused-expression 219 | new RegExp(pattern); 220 | skipPatterns.add(pattern); 221 | } catch (err) { 222 | const message = 'Error[' + (errorList.length + 1) + ']:' + ' at ' + this.IgnoreFilePath + ':' + row + ' : Input_Git_Ignore = ' + line 223 | + ' , Skip_Paths_Regex = ' + pattern + ' , error = ' + err; 224 | errorList.push(message); 225 | outputErrorByTime(message + '\n'); 226 | } 227 | } 228 | 229 | this.SkipPathPattern = this.mergeToTerminalSkipPattern(skipPatterns); 230 | const setVarCmdLength = this.SkipPathPattern.length + (this.IsCmdTerminal ? '@set "="'.length : 'export =""'.length) + SkipPathVariableName.length; 231 | const isInMaxLength = setVarCmdLength < this.MaxCommandLength; 232 | this.Valid = this.SkipPathPattern.length > 0 && isInMaxLength; 233 | if (this.Valid) { 234 | ProjectFolderToShortSkipGitPathEnvValueMap.set(this.RepoFolder, this.SkipPathPattern); 235 | } 236 | this.Completed = true; 237 | if (errorList.length > 0) { 238 | outputError(errorList.join('\n')); 239 | } 240 | 241 | let parsedInfo = `Parsed ${skipPatterns.size} of ${readPatternCount} patterns, omitted ${errorList.length} errors, ignored ${this.ExemptionCount} exemptions in ${this.IgnoreFilePath}`; 242 | if (this.ExemptionCount > 0) { 243 | parsedInfo += ` - see ${OutputChannelName} in OUTPUT. Use gfind-xxx instead of find-xxx for git-exemptions`; 244 | } 245 | 246 | parsedInfo += ' ; ' + SkipPathVariableName + ' length = ' + this.SkipPathPattern.length + '.'; 247 | const message = 'Cost ' + getElapsedSecondsToNow(beginTime).toFixed(3) + ' s: ' + parsedInfo; 248 | outputInfoByTime(message); 249 | 250 | callbackWhenSucceeded(); 251 | 252 | const shouldDisplayTip = this.RepoFolder === RunCmdFolderWithForwardSlash; 253 | if (!shouldDisplayTip || !RunCommandChecker.IsToolExists) { 254 | return; 255 | } 256 | 257 | const tipFileDisplayPath = getTipFileDisplayPath(this.Terminal); 258 | const setVarCmd = getCommandToSetGitInfoVar(this.IsCmdTerminal, this.SkipPathPattern.length, readPatternCount, skipPatterns.size, errorList.length, this.ExemptionCount); 259 | const referencePattern = !isInMaxLength ? "too.long" : (this.ExemptionCount > 0 ? "instead.of.find" : "Free.to.use"); 260 | const replaceCmd = (this.IsCmdTerminal ? `-x ::` : `-x '#'`) + ` -o echo`; 261 | const tipCommand = getRunTipFileCommand(tipFileDisplayPath, `${replaceCmd} -t ${referencePattern}`); 262 | // Use msr to discard tmp variables after execution 263 | const command = `msr -XA -z "${setVarCmd} ${tipCommand}"` + (this.IsCmdTerminal ? ' 2>nul & use-this-alias' : ' 2>/dev/null'); 264 | runRawCommandInTerminal(command); 265 | }); 266 | } 267 | 268 | private showErrorInRunCmdTerminal(message: string) { 269 | if (IsLinuxTerminalOnWindows) { 270 | message = message.replace(/"/g, "'"); 271 | } 272 | runRawCommandInTerminal(`echo ${message.replace(this.IgnoreFilePath, this.IgnoreFilePath.replace(/\\/g, '/'))} | msr -aPA -ix exemption -t "\\S+:\\d+" -e "\\w+ = \\w+"`); 273 | } 274 | 275 | private mergeToTerminalSkipPattern(patterns: Set): string { 276 | patterns.delete(''); 277 | if (patterns.size < 1) { 278 | return ''; 279 | } 280 | 281 | let skipPattern = Array.from(patterns).join('|'); 282 | const useBackSlash = this.IsCmdTerminal && !IsForwardingSlashSupportedOnWindows; 283 | if (useBackSlash && skipPattern.endsWith('\\')) { 284 | // Avoid truncating tail slash on Windows with double-quotes: 285 | skipPattern += '\\\\'; 286 | } 287 | 288 | return skipPattern; 289 | } 290 | 291 | private replaceSlashForSkipPattern(pattern: string): string { 292 | if (this.CheckUseForwardingSlashForCmd && IsForwardingSlashSupportedOnWindows) { 293 | return pattern; 294 | } 295 | 296 | if (this.IsCmdTerminal) { 297 | pattern = pattern.replace(/\//g, '\\\\'); 298 | } else if (TerminalType.MinGWBash === this.Terminal) { 299 | pattern = pattern.replace(/\//g, '\\\\\\\\'); 300 | } 301 | 302 | return pattern; 303 | } 304 | 305 | public getPattern(line: string): string { 306 | // https://git-scm.com/docs/gitignore#_pattern_format, 307 | // 1. Repo paths skip: folder name with or without begin slash '/' like: folder1/fileOrFolder or /folder1/fileOrFolder 308 | // 2. Skip folder not file if slash '/' at tail, like: folder1/folder2/ 309 | // 3.1 An asterisk "*" matches anything except a slash. 310 | // 3.2 The character "?" matches any one character except "/". 311 | // 3.3 Range notation: [a-zA-Z] 312 | // 4.1 A leading "**" followed by a slash means match in all directories. Like: **/folder/fileOrFolder 313 | // 4.2 A trailing "/**" matches everything inside. Like: folder/** skip all files/folders under it. 314 | 315 | if (isNullOrEmpty(line)) { 316 | return ''; 317 | } 318 | 319 | outputInfoByDebugMode('Input_Git_Ignore = ' + line); 320 | 321 | if (line.match(/(?= 0 && k + 3 < pattern.length && pattern[k + 3] === ']') { 416 | const a = pattern[k + 1].toLowerCase(); 417 | const b = pattern[k + 2].toLowerCase(); 418 | if (a >= 'a' && a <= 'z' && a === b) { 419 | pattern = pattern.substring(0, k) + pattern[k + 1] + pattern.substring(k + 4); 420 | k = pattern.indexOf("[", Math.max(0, k - 4)); 421 | } else { 422 | k = pattern.indexOf("[", k + 4); 423 | } 424 | } 425 | return pattern; 426 | } 427 | -------------------------------------------------------------------------------- /src/junkPathEnvArgs.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import ChildProcess = require('child_process'); 3 | import { GitListFileRecursiveArg, getConfigValueOfProject } from './configUtils'; 4 | import { GitFileListExpirationTimeEnvName, GitRepoEnvName, SearchGitSubModuleEnvName, SkipJunkPathEnvArgName, SkipJunkPathEnvArgValue, TmpGitFileListExpiration, WorkspaceCount, getEnvNameRef, getProjectFolderKey, isNullOrEmpty } from './constants'; 5 | import { getGitIgnore, mergeSkipFolderPattern } from './dynamicConfig'; 6 | import { TerminalType } from './enums'; 7 | import { isWindowsTerminalOnWindows } from './terminalUtils'; 8 | import { getSetToolEnvCommand } from './toolSource'; 9 | import { getRepoFolderName } from './utils'; 10 | 11 | export function getSearchGitSubModuleEnvName(isWindowsTerminal: boolean): string { 12 | return getEnvNameRef(SearchGitSubModuleEnvName, isWindowsTerminal); 13 | } 14 | 15 | export function getTrimmedGitRepoEnvName(isWindowsTerminal: boolean): string { 16 | return getEnvNameRef(GitRepoEnvName, isWindowsTerminal); 17 | } 18 | 19 | function getJunkPathEnvValue(repoFolder: string, isForProjectCmdAlias: boolean): [string, string] { 20 | if (isForProjectCmdAlias && !isNullOrEmpty(repoFolder)) { 21 | const gitIgnoreInfo = getGitIgnore(repoFolder); 22 | if (gitIgnoreInfo.Valid) { 23 | const skipPathRegexPattern = gitIgnoreInfo.getSkipPathRegexPattern(false, true); 24 | if (!isNullOrEmpty(skipPathRegexPattern)) { 25 | return ['--np', skipPathRegexPattern]; 26 | } 27 | } 28 | } 29 | 30 | const skipFolderValue = getJunkFolderValue('default', false); 31 | return ['--nd', skipFolderValue]; 32 | } 33 | 34 | function getJunkFolderValue(projectKey: string, isForProjectCmdAlias: boolean): string { 35 | let skipFoldersPattern = getConfigValueOfProject(projectKey, 'skipFolders'); 36 | if (isForProjectCmdAlias) { 37 | skipFoldersPattern = mergeSkipFolderPattern(skipFoldersPattern); 38 | } 39 | return skipFoldersPattern; 40 | } 41 | 42 | function getSetEnvCommand(isWindowsTerminal: boolean, name: string, value: string, permanent: boolean, addCheck: boolean = true): string { 43 | const setEnvCmd = !isWindowsTerminal 44 | ? `export ${name}='${value}'` 45 | : (permanent 46 | ? `SETX ${name} "${value}"` 47 | : `SET "${name}=${value}"` 48 | ); 49 | 50 | if (!addCheck) { 51 | return setEnvCmd; 52 | } 53 | 54 | const checkCmd = isWindowsTerminal 55 | ? `if not defined ${name} ` 56 | : `[ -z "$${name}" ] && `; 57 | 58 | return checkCmd + setEnvCmd; 59 | } 60 | 61 | export function getJunkEnvCommandForTipFile(isWindowsTerminal: boolean, asyncRunning = false) { 62 | if (!isWindowsTerminal) { 63 | return ''; 64 | } 65 | 66 | const permanent = asyncRunning; 67 | const addCheck = !asyncRunning; 68 | const newLine = isWindowsTerminal ? "\r\n" : "\n"; 69 | const [name, value] = getJunkPathEnvValue('', false); 70 | const command = getSetEnvCommand(true, SkipJunkPathEnvArgName, name, permanent, addCheck) + newLine 71 | + getSetEnvCommand(true, SkipJunkPathEnvArgValue, value, permanent, addCheck) + newLine 72 | + getSetEnvCommand(true, GitRepoEnvName, 'tmp-list', permanent, addCheck) + newLine 73 | + getSetEnvCommand(true, SearchGitSubModuleEnvName, '--recurse-submodules', permanent, addCheck) + newLine 74 | + getSetEnvCommand(true, GitFileListExpirationTimeEnvName, TmpGitFileListExpiration, permanent, addCheck); 75 | return command; 76 | } 77 | 78 | export function asyncSetJunkEnvForWindows() { 79 | const commands = getJunkEnvCommandForTipFile(true, true).split("\r\n"); 80 | commands.forEach(cmd => { 81 | ChildProcess.exec(cmd); 82 | }); 83 | } 84 | 85 | export function getSkipJunkPathEnvCommand(terminalType: TerminalType, repoFolder: string, isForProjectCmdAlias: boolean, generalScriptFilesFolder: string): string { 86 | const isWindowsTerminal = isWindowsTerminalOnWindows(terminalType); 87 | if (isWindowsTerminal && !isForProjectCmdAlias) { 88 | // Async setting env for common values on Windows at other place 89 | return ''; 90 | } 91 | 92 | const [name, value] = getJunkPathEnvValue(repoFolder, isForProjectCmdAlias); 93 | const trimmedRepoName = !isForProjectCmdAlias || isNullOrEmpty(repoFolder) ? 'tmp-list' : getProjectFolderKey(path.basename(repoFolder)); 94 | const newLine = isWindowsTerminal ? "\r\n" : "\n"; 95 | const searchSubModuleArg = GitListFileRecursiveArg(isForProjectCmdAlias ? getRepoFolderName(repoFolder) : ''); 96 | const repoFolderName = path.basename(repoFolder); 97 | const expirationTime = isForProjectCmdAlias ? getConfigValueOfProject(repoFolderName, "refreshTmpGitFileListDuration") : TmpGitFileListExpiration; 98 | 99 | let setEnvCommandLines = getSetEnvCommand(isWindowsTerminal, SkipJunkPathEnvArgName, name, false, !isForProjectCmdAlias) 100 | + newLine + getSetEnvCommand(isWindowsTerminal, SkipJunkPathEnvArgValue, value, false, !isForProjectCmdAlias) 101 | + newLine + getSetEnvCommand(isWindowsTerminal, GitRepoEnvName, trimmedRepoName, false, !isForProjectCmdAlias) 102 | + newLine + getSetEnvCommand(isWindowsTerminal, SearchGitSubModuleEnvName, searchSubModuleArg, false, !isForProjectCmdAlias) 103 | + newLine + getSetEnvCommand(isWindowsTerminal, GitFileListExpirationTimeEnvName, expirationTime, false, !isForProjectCmdAlias) 104 | + newLine; 105 | 106 | if (isWindowsTerminal && isForProjectCmdAlias) { 107 | const setToolAliasEnvCmd = getSetToolEnvCommand(terminalType, [generalScriptFilesFolder]); 108 | setEnvCommandLines = "@echo off" + newLine + setToolAliasEnvCmd + newLine + setEnvCommandLines 109 | } 110 | 111 | if (WorkspaceCount > 1 && isForProjectCmdAlias) { 112 | const changeDirCmd = isWindowsTerminal ? `cd /d "${repoFolder}"` : `cd "${repoFolder}"`; 113 | setEnvCommandLines += changeDirCmd + newLine; 114 | } 115 | 116 | return setEnvCommandLines; 117 | } 118 | 119 | export function getResetJunkPathEnvCommand(isWindowsTerminal: boolean): string { 120 | const [name, value] = getJunkPathEnvValue('', false); 121 | const command = isWindowsTerminal 122 | ? 123 | `set "${SkipJunkPathEnvArgName}=${name}" 124 | && set "${SkipJunkPathEnvArgValue}=${value}" 125 | && set "${GitRepoEnvName}=tmp-list" 126 | && set "${SearchGitSubModuleEnvName}=--recurse-submodules" 127 | && set "${GitFileListExpirationTimeEnvName}=${TmpGitFileListExpiration}"` 128 | : `export ${SkipJunkPathEnvArgName}="${name}" 129 | && export ${SkipJunkPathEnvArgValue}="${value}" 130 | && export ${GitRepoEnvName}=tmp-list 131 | && export ${SearchGitSubModuleEnvName}='--recurse-submodules' 132 | && export ${GitFileListExpirationTimeEnvName}=${TmpGitFileListExpiration}` 133 | ; 134 | return command.replace(/\s*[\r\n]+\s*/g, ' '); 135 | } 136 | 137 | -------------------------------------------------------------------------------- /src/outputUtils.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { IsDebugMode, IsSupportedSystem, IsWindows, OutputChannelName } from './constants'; 3 | import { nowText, replaceTextByRegex } from './utils'; 4 | 5 | // When searching plain text, powershell requires extra escaping (like '$'). 6 | export const UsePowershell = false; 7 | const WindowsShell = UsePowershell ? 'powershell' : 'cmd.exe'; 8 | export const ShellPath = IsWindows ? WindowsShell : 'bash'; 9 | const ShowColorHideCmdRegex: RegExp = /\s+-[Cc](\s+|$)/g; 10 | const SearchRegexList: RegExp[] = [ 11 | /\s+(-t|--text-match)\s+(\w+\S*|'(.+?)'|"(.+?)")/, 12 | /\s+(-x|--has-text)\s+(\w+\S*|'(.+?)'|"(.+?)")/ 13 | ]; 14 | 15 | // MSR-Def-Ref output channel 16 | let MessageChannel: vscode.OutputChannel; 17 | 18 | let OutputTimes: number = 0; 19 | 20 | export enum MessageLevel { 21 | None = 0, 22 | DEBUG = 1, 23 | INFO = 2, 24 | WARN = 3, 25 | ERROR = 4, 26 | FATAL = 5 27 | } 28 | 29 | export const DefaultMessageLevel = IsDebugMode ? MessageLevel.DEBUG : MessageLevel.INFO; 30 | let LogLevel = MessageLevel.INFO; 31 | let IsQuiet = true; 32 | 33 | export function updateOutputChannel(messageLevel: MessageLevel = DefaultMessageLevel, isQuiet: boolean = true) { 34 | LogLevel = messageLevel; 35 | IsQuiet = isQuiet; 36 | } 37 | 38 | export function outputMessage(level: MessageLevel, message: string, showWindow: boolean = true) { 39 | switch (level) { 40 | case MessageLevel.DEBUG: 41 | outputDebug(message, showWindow); 42 | break; 43 | case MessageLevel.INFO: 44 | outputInfo(message, showWindow); 45 | break; 46 | case MessageLevel.WARN: 47 | outputWarn(message, showWindow); 48 | break; 49 | case MessageLevel.ERROR: 50 | case MessageLevel.FATAL: 51 | default: 52 | outputError(message, showWindow); 53 | break; 54 | } 55 | } 56 | 57 | export function outputWarn(message: string, showWindow: boolean = true) { 58 | if (MessageLevel.WARN >= LogLevel) { 59 | showOutputChannel(showWindow); 60 | getOutputChannel().appendLine(message); 61 | } 62 | } 63 | 64 | export function outputWarnByTime(message: string, showWindow: boolean = true) { 65 | outputWarn(nowText() + message, showWindow); 66 | } 67 | 68 | export function outputError(message: string, showWindow: boolean = true) { 69 | if (MessageLevel.ERROR >= LogLevel) { 70 | showOutputChannel(showWindow); 71 | getOutputChannel().appendLine(message); 72 | } 73 | } 74 | 75 | export function outputErrorByTime(message: string, showWindow: boolean = true) { 76 | outputError(nowText() + message, showWindow); 77 | } 78 | 79 | export function outputResult(text: string, showWindow: boolean = true) { 80 | getOutputChannel().appendLine(text); 81 | showOutputChannel(showWindow); 82 | } 83 | 84 | export function outputKeyInfo(text: string) { 85 | showOutputChannel(true, true); 86 | getOutputChannel().appendLine(text); 87 | } 88 | 89 | export function outputKeyInfoByTime(text: string) { 90 | outputKeyInfo(nowText() + text); 91 | } 92 | 93 | export function outputInfo(message: string, showWindow: boolean = true) { 94 | if (MessageLevel.INFO >= LogLevel) { 95 | getOutputChannel().appendLine(message); 96 | showOutputChannel(showWindow); 97 | } 98 | } 99 | 100 | export function outputInfoByTime(message: string, showWindow: boolean = true) { 101 | outputInfo(nowText() + message, showWindow); 102 | } 103 | 104 | export function outputInfoClearByTime(message: string, showWindow: boolean = true) { 105 | if (MessageLevel.INFO >= LogLevel) { 106 | clearOutputChannelByTimes(); 107 | getOutputChannel().appendLine(nowText() + message); 108 | showOutputChannel(showWindow); 109 | } 110 | } 111 | 112 | export function outputInfoQuiet(message: string, showWindow: boolean = false) { 113 | if (MessageLevel.INFO >= LogLevel) { 114 | getOutputChannel().appendLine(message); 115 | showOutputChannel(showWindow, false); 116 | } 117 | } 118 | 119 | export function outputInfoQuietByTime(message: string, showWindow: boolean = false) { 120 | outputInfoQuiet(nowText() + message, showWindow); 121 | } 122 | 123 | export function outputDebugOrInfo(isDebug: boolean, message: string, showWindow: boolean = true) { 124 | if (isDebug) { 125 | outputDebug(message, showWindow); 126 | } else { 127 | outputInfo(message, showWindow); 128 | } 129 | } 130 | 131 | export function outputInfoByDebugMode(message: string, showWindow: boolean = true) { 132 | outputDebugOrInfo(!IsDebugMode, message, showWindow); 133 | } 134 | 135 | export function outputInfoByDebugModeByTime(message: string, showWindow: boolean = true) { 136 | outputDebugOrInfo(!IsDebugMode, nowText() + message, showWindow); 137 | } 138 | 139 | export function outputDebug(message: string, showWindow: boolean = false) { 140 | if (MessageLevel.DEBUG >= LogLevel) { 141 | getOutputChannel().appendLine(message); 142 | showOutputChannel(showWindow); 143 | } 144 | } 145 | 146 | export function outputDebugByTime(message: string, showWindow: boolean = false) { 147 | outputDebug('\n' + nowText(message), showWindow); 148 | } 149 | 150 | function clearOutputChannel() { 151 | getOutputChannel().clear(); 152 | } 153 | 154 | export function clearOutputChannelByTimes(circle: number = 1000) { 155 | if (OutputTimes % circle == 0) { 156 | clearOutputChannel(); 157 | } 158 | OutputTimes++; 159 | } 160 | 161 | export function enableColorAndHideCommandLine(cmd: string, removeSearchWordHint: boolean = true): string { 162 | let hasFound = false; 163 | for (let k = 0; k < SearchRegexList.length; k++) { 164 | let match = cmd.match(SearchRegexList[k]); 165 | if (match && match.index !== undefined) { 166 | hasFound = true; 167 | const text1 = cmd.substring(0, match.index); 168 | const text2 = cmd.substring(match.index, match.index + match[0].length); 169 | const text3 = cmd.substring(match.index + match[0].length); 170 | cmd = replaceTextByRegex(text1, ShowColorHideCmdRegex, '$1') + text2 + replaceTextByRegex(text3, ShowColorHideCmdRegex, '$1'); 171 | } 172 | } 173 | 174 | if (!hasFound) { 175 | cmd = replaceTextByRegex(cmd, ShowColorHideCmdRegex, '$1'); 176 | } 177 | 178 | if (removeSearchWordHint) { 179 | cmd = cmd.replace(/\s+Search\s+%~?1[\s\w]*/, ' '); 180 | } 181 | 182 | return cmd.replace(/\s+Search\s*$/, ''); 183 | } 184 | 185 | export function checkIfSupported(): boolean { 186 | if (IsSupportedSystem) { 187 | return true; 188 | } 189 | 190 | outputErrorByTime('Sorry, "' + process.platform + ' ' + process.arch + ' " is not supported yet.'); 191 | outputErrorByTime('https://github.com/qualiu/vscode-msr/blob/master/README.md'); 192 | return false; 193 | } 194 | 195 | function showOutputChannel(showWindow: boolean = true, ignoreQuiet: boolean = false) { 196 | if (showWindow && (ignoreQuiet || !IsQuiet)) { 197 | getOutputChannel().show(true); 198 | } 199 | } 200 | 201 | function getOutputChannel(): vscode.OutputChannel { 202 | if (!MessageChannel) { 203 | MessageChannel = vscode.window.createOutputChannel(OutputChannelName); 204 | } 205 | 206 | return MessageChannel; 207 | } 208 | -------------------------------------------------------------------------------- /src/regexUtils.ts: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | export const NormalTextRegex = /^[\w\.:\\/-]+$/; 5 | export const EmptyRegex: RegExp = new RegExp('^@#x$'); // Workaround for empty RegExp. 6 | 7 | const AlphaNumber = "[a-z0-9A-Z]"; 8 | const HeaderBoundary = "(? { 29 | let s = new Set(); 30 | let m; 31 | do { 32 | m = SingleWordMatchingRegex.exec(text); 33 | if (m && m[0].length >= minLength) { 34 | s.add(ignoreCase ? m[0].toLowerCase() : m[0]); 35 | } 36 | } while (m); 37 | 38 | return s; 39 | } 40 | 41 | export function escapeRegExp(text: string): string { 42 | return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/"/g, '\\"'); 43 | } 44 | 45 | export function createRegex(pattern: string, flags: string | undefined = undefined, setFullMatch: boolean = false): RegExp { 46 | if (!pattern || pattern.length < 1) { 47 | return EmptyRegex; 48 | } 49 | 50 | return new RegExp(setFullMatch ? '^(' + pattern + ')$' : pattern, flags); 51 | } 52 | -------------------------------------------------------------------------------- /src/runCommandUtils.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import * as vscode from 'vscode'; 3 | import { getPostInitCommands } from './configUtils'; 4 | import { DefaultRepoFolderName, IsMacOS, IsWindows, RunCmdTerminalName, getDefaultRepoFolderByActiveFile, isNullOrEmpty } from './constants'; 5 | import { TerminalType } from './enums'; 6 | import { ShellPath, UsePowershell, enableColorAndHideCommandLine, outputDebugByTime } from "./outputUtils"; 7 | import { DefaultTerminalType, IsLinuxTerminalOnWindows, IsWindowsTerminalOnWindows, getTipFileDisplayPath, getTipFileStoragePath, isLinuxTerminalOnWindows, isWindowsTerminalOnWindows } from './terminalUtils'; 8 | import { quotePaths } from './utils'; 9 | import os = require('os'); 10 | import fs = require('fs'); 11 | 12 | const ClearCmd = IsWindows && !UsePowershell ? 'cls' : "clear"; 13 | 14 | // MSR-RUN-CMD terminal 15 | let RunCmdTerminal: vscode.Terminal | undefined; 16 | 17 | export function getRunCmdTerminal(): vscode.Terminal { 18 | const [terminal] = getRunCmdTerminalWithInfo(); 19 | return terminal; 20 | } 21 | 22 | export function getRunCmdTerminalWithInfo(): [vscode.Terminal, boolean] { 23 | if (RunCmdTerminal) { 24 | return [RunCmdTerminal, false]; 25 | } 26 | 27 | if (vscode.window.terminals && vscode.window.terminals.length > 0) { 28 | for (let k = 0; k < vscode.window.terminals.length; k++) { 29 | if (vscode.window.terminals[k].name === RunCmdTerminalName) { 30 | RunCmdTerminal = vscode.window.terminals[k]; 31 | return [RunCmdTerminal, false]; 32 | } 33 | } 34 | } 35 | 36 | const currentProjectFolder = getDefaultRepoFolderByActiveFile(true); 37 | const option: vscode.TerminalOptions = { 38 | shellPath: ShellPath, 39 | name: RunCmdTerminalName, 40 | cwd: currentProjectFolder 41 | } 42 | 43 | RunCmdTerminal = vscode.window.createTerminal(option); 44 | return [RunCmdTerminal, true]; 45 | } 46 | 47 | export function disposeTerminal() { 48 | RunCmdTerminal = undefined; 49 | } 50 | 51 | export function runPostInitCommands(terminal: vscode.Terminal | null | undefined, terminalType: TerminalType, repoFolderName: string) { 52 | if (!terminal) { 53 | return; 54 | } 55 | const postInitCommand = getPostInitCommands(terminalType, repoFolderName); 56 | if (isNullOrEmpty(postInitCommand)) { 57 | return; 58 | } 59 | sendCommandToTerminal(postInitCommand, terminal, true, false, isLinuxTerminalOnWindows(terminalType)); 60 | } 61 | 62 | function checkInitRunCommandTerminal(): vscode.Terminal { 63 | const [terminal, isNewTerminal] = getRunCmdTerminalWithInfo(); 64 | if (isNewTerminal) { 65 | // User closed MSR-RUN-CMD terminal + use menu search which triggers a new MSR-RUN-CMD terminal 66 | const defaultRepoFolder = getDefaultRepoFolderByActiveFile(true); 67 | if (isNullOrEmpty(defaultRepoFolder)) { 68 | if (terminal.name === RunCmdTerminalName) { 69 | const tipFilePath = getTipFileStoragePath(DefaultTerminalType); 70 | if (fs.existsSync(tipFilePath)) { 71 | const commandHead = isWindowsTerminalOnWindows(DefaultTerminalType) ? "call " : "bash "; 72 | const tipCmd = commandHead + quotePaths(getTipFileDisplayPath(DefaultTerminalType)); 73 | sendCommandToTerminal(tipCmd, terminal); 74 | } 75 | } 76 | } else { 77 | sendCommandToTerminal(`use-this-alias`, terminal); 78 | } 79 | // const postInitCommand = getPostInitCommands(, DefaultRepoFolderName); 80 | runPostInitCommands(terminal, IsWindowsTerminalOnWindows ? TerminalType.CMD : TerminalType.LinuxBash, DefaultRepoFolderName) 81 | } 82 | return terminal; 83 | } 84 | 85 | export function runCommandInTerminal(command: string, showTerminal = false, clearAtFirst = false, isLinuxOnWindows = IsLinuxTerminalOnWindows) { 86 | command = enableColorAndHideCommandLine(command); 87 | sendCommandToTerminal(command, checkInitRunCommandTerminal(), showTerminal, clearAtFirst, isLinuxOnWindows); 88 | } 89 | 90 | export function runRawCommandInTerminal(command: string, showTerminal = true, clearAtFirst = false, isLinuxOnWindows = IsLinuxTerminalOnWindows) { 91 | sendCommandToTerminal(command, checkInitRunCommandTerminal(), showTerminal, clearAtFirst, isLinuxOnWindows); 92 | } 93 | 94 | export function sendCommandToTerminal(command: string, terminal: vscode.Terminal, showTerminal = false, clearAtFirst = false, isLinuxOnWindows = IsLinuxTerminalOnWindows) { 95 | if (isNullOrEmpty(command)) { 96 | return; 97 | } 98 | 99 | const searchAndListPattern = /\s+(-i?[tx]|-l)\s+/; 100 | if (command.startsWith("msr") && !command.match(searchAndListPattern)) { 101 | outputDebugByTime("Skip running command due to not found none of matching names of -x or -t, command = " + command); 102 | return; 103 | } 104 | 105 | if (showTerminal) { 106 | terminal.show(); 107 | } 108 | if (clearAtFirst) { 109 | // vscode.commands.executeCommand('workbench.action.terminal.clear'); 110 | terminal.sendText((isLinuxOnWindows || IsMacOS ? 'clear' : ClearCmd) + os.EOL, true); 111 | } 112 | 113 | terminal.sendText(command.trim() + os.EOL, true); 114 | if (IsMacOS) { // MacOS terminal will break if sending command lines to fast. 115 | try { 116 | const sleepMilliseconds = command.trim().length / 1000; 117 | execSync('sleep ' + sleepMilliseconds); 118 | } catch (error) { 119 | console.log(error); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/searchChecker.ts: -------------------------------------------------------------------------------- 1 | import { ParsedPath } from 'path'; 2 | import * as vscode from 'vscode'; 3 | import { GetConfigPriorityPrefixes, getConfigValueByPriorityList, getConfigValueByProjectAndExtension } from './configUtils'; 4 | import { SearchTextHolder, isNullOrEmpty } from './constants'; 5 | import { MyConfig, getConfig } from './dynamicConfig'; 6 | import { FindType, ForceFindType } from './enums'; 7 | import { ForceSetting } from './forceSettings'; 8 | import { outputDebug, outputDebugByTime, outputErrorByTime } from './outputUtils'; 9 | import { EmptyRegex, createRegex, getAllSingleWords } from './regexUtils'; 10 | import { getExtensionNoHeadDot, getRepoFolderName, replaceSearchTextHolder, toPath } from './utils'; 11 | 12 | export class SearchChecker { 13 | public currentFile: ParsedPath; 14 | public currentFilePath: string; 15 | public extension: string; 16 | public mappedExt: string; 17 | public repoFolderName: string; 18 | 19 | public isCodeFile: boolean; 20 | public isScriptFile: boolean; 21 | 22 | public Document: vscode.TextDocument; 23 | public currentWord: string; 24 | public currentWordRegex: RegExp; 25 | 26 | public findType: FindType; 27 | 28 | public currentText: string; 29 | public currentWordRange: vscode.Range; 30 | public currentTextMaskCurrentWord: string; 31 | 32 | public isCapitalizedWord: boolean; 33 | public isUpperCaseWord: boolean; 34 | 35 | public isClassResultRegex: RegExp; 36 | public isEnumResultRegex: RegExp; 37 | public isMethodResultRegex: RegExp; 38 | public isInterfaceResultRegex: RegExp; 39 | public isConstantResultRegex: RegExp; 40 | public isMemberResultRegex: RegExp; 41 | public isLocalVariableResultRegex: RegExp; 42 | 43 | public isFindClassRegex: RegExp; 44 | public isFindMethodRegex: RegExp; 45 | public isFindMemberRegex: RegExp; 46 | public isFindConstantRegex: RegExp; 47 | public isFindEnumRegex: RegExp; 48 | public isFindClassOrEnumRegex: RegExp; 49 | public isFindClassOrMethodRegex: RegExp; 50 | public isFindClassWithWordCheckRegex: RegExp; 51 | public isFindMemberOrLocalVariableRegex: RegExp; 52 | 53 | public isOnlyFindClass: boolean; 54 | public isProbablyFindClass: boolean; 55 | public isOnlyFindMember: boolean; 56 | public isFindClass: boolean; 57 | public isFindMethod: boolean; 58 | public isFindMember: boolean; 59 | public isFindConstant: boolean = false; 60 | public isFindEnum: boolean; 61 | public isFindClassOrEnum: boolean; 62 | public isFindClassOrMethod: boolean; 63 | public isFindMemberOrLocalVariable: boolean; 64 | public maybeFindLocalVariable: boolean; 65 | public isProbablyFindLocalVariable: boolean; 66 | public canAcceptMemberResult: boolean; 67 | 68 | public maybeEnum: boolean; 69 | public maybeEnumResultRegex: RegExp; 70 | public isInTestPath: boolean; 71 | public isInTestFolder: boolean; 72 | public isTestFileName: boolean; 73 | 74 | public classFileNamePattern: string = ''; 75 | public fileNameHighScoreWord: string = ''; 76 | public classFileNameScoreRegex: RegExp; 77 | 78 | public enumOrConstantValueRegex: RegExp; 79 | 80 | public classDefinitionRegex: RegExp; 81 | public memberDefinitionRegex: RegExp; 82 | public enumDefinitionRegex: RegExp; 83 | public methodDefinitionRegex: RegExp; 84 | 85 | public Position: vscode.Position; 86 | public currentWordSet: Set; 87 | public currentFileNameWordSet: Set; 88 | public currentFilePathWordSet: Set; 89 | public highScoreWordSet: Set = new Set(); 90 | 91 | public promoteFolderRegex: RegExp; 92 | public promoteFolderScore: number; 93 | public promotePathRegex: RegExp; 94 | public promotePathScore: number; 95 | 96 | public demoteFolderRegex: RegExp; 97 | public demoteFolderScore: number; 98 | public demotePathRegex: RegExp; 99 | public demotePathScore: number; 100 | public ForceUseDefaultFindingDefinition: boolean = true; 101 | public methodQuoteRegex: RegExp; 102 | 103 | constructor(document: vscode.TextDocument, findType: FindType, currentPosition: vscode.Position, currentWord: string, currentWordRange: vscode.Range, 104 | currentText: string, currentFile: ParsedPath, mappedExt: string) { 105 | const MyConfig = getConfig(); 106 | this.Document = document; 107 | this.findType = findType; 108 | this.Position = currentPosition; 109 | this.currentWord = currentWord; 110 | this.currentText = currentText; 111 | this.currentWordRange = currentWordRange; 112 | 113 | this.isCapitalizedWord = /^[A-Z]\w+$/.test(this.currentWord); 114 | this.isUpperCaseWord = /^[A-Z_0-9]+$/.test(this.currentWord); 115 | 116 | // Avoid mis-checking due to multiple occurrences of current word. 117 | const maskWorkRegex = new RegExp('\\b' + this.currentWord + '\\b', 'g'); 118 | this.currentTextMaskCurrentWord = currentText.substring(0, currentWordRange.start.character).replace(maskWorkRegex, currentWord.substring(0, currentWord.length - 1)) 119 | + currentWord + currentText.substring(currentWordRange.end.character).replace(maskWorkRegex, currentWord.substring(0, currentWord.length - 1)); 120 | 121 | this.currentFile = currentFile; 122 | this.currentFilePath = toPath(currentFile); 123 | this.mappedExt = mappedExt; 124 | this.extension = getExtensionNoHeadDot(currentFile.ext, ''); 125 | this.repoFolderName = getRepoFolderName(this.currentFilePath); 126 | this.isCodeFile = MyConfig.isCodeFiles(this.extension); 127 | this.isScriptFile = MyConfig.isScriptFile(this.extension); 128 | 129 | // for doc + config 130 | this.ForceUseDefaultFindingDefinition = FindType.Definition === this.findType && MyConfig.UseDefaultFindingClassCheckExtensionRegex.test(this.currentFile.ext); 131 | 132 | this.isTestFileName = /test/i.test(currentFile.name); 133 | this.isInTestFolder = /test/i.test(currentFile.dir); 134 | this.isInTestPath = this.isTestFileName || this.isInTestFolder; 135 | 136 | this.isClassResultRegex = this.getCheckingRegex('isClassResult', true, false, 'definition'); 137 | this.isEnumResultRegex = this.getCheckingRegex('isEnumResult', true); 138 | this.isMethodResultRegex = this.getCheckingRegex('isMethodResult', true); 139 | this.isInterfaceResultRegex = this.getCheckingRegex('isInterfaceResult', true); 140 | this.isMemberResultRegex = this.getCheckingRegex('isMemberResult', false); 141 | this.isLocalVariableResultRegex = this.getCheckingRegex('isLocalVariableResult', false); 142 | 143 | this.isFindClassWithWordCheckRegex = this.getCheckingRegex('isFindClassByWordCheck', false, true); 144 | this.isFindClassRegex = this.getCheckingRegex('isFindClass', false); 145 | this.isFindMethodRegex = this.getCheckingRegex('isFindMethod', false); 146 | this.isFindMemberRegex = this.getCheckingRegex('isFindMember', false); 147 | this.isFindEnumRegex = this.getCheckingRegex('isFindEnum', false); 148 | this.isFindClassOrEnumRegex = this.getCheckingRegex('isFindClassOrEnum', false); 149 | this.isFindClassOrMethodRegex = this.getCheckingRegex('isFindClassOrMethod', false); 150 | this.isFindMemberOrLocalVariableRegex = this.getCheckingRegex('isFindMemberOrLocalVariable', false); 151 | this.methodQuoteRegex = new RegExp('\\b' + currentWord + '\\b\\s*\\('); 152 | const isTypeAfterObject = this.extension.match(/^(go|scala)$/); 153 | const isGenericMethodOrConstructor = new RegExp('\\b' + currentWord + '\\s*<[\\s\\w\\.:]+>\\s*\\(').test(this.currentTextMaskCurrentWord); 154 | const onlyFindClassRegex = new RegExp( 155 | '\\bclass\\s+\\w+' 156 | + '|' + '((new|is|as)\\s+|typeof\\W*)[\\w\\.:]*?\\b' + currentWord + "\\b" // new Class 157 | + '|' + '\\b' + currentWord + '\\s*(<|[&\\*]+|\\?)\\s*\\w+' // Class*& var 158 | + '|' + '\\b' + currentWord + '\\.class\\b' // Like Java/Scala 159 | + '|' + '\\b' + currentWord + '\\s*\\[\\]' // Array type like C# 160 | + '|' + '\\w+\\s+\\[\\]' + currentWord + '\\b' // Array type like Golang 161 | + '|' + '\\(\\S*?\\b' + currentWord + '\\)\\s*\\w+' // (Class)var -- type cast 162 | + '|' + '<\\S*?\\b' + currentWord + '\\b' + '[\\w,:\\s]*?>' // generic 163 | + '|' + '<[\\s\\w,]*?\\b' + currentWord + '\\b' + '[\\w,:\\s]*?>' // generic 164 | + '|' + '<[\\w,:\\s]*?\\b' + currentWord + '\\s*>' // generic 165 | + '|' + '<[\\w,:\\s]*?\\b' + currentWord + '\\b[\\s\\w,]*?>' // generic 166 | + '|' + '^\\s*((public|private|protected|internal|static|readonly|const|final|val|virtual|volatile)\\s+)+\\s*' + currentWord + '[^\\w,;=]*\\s+[\\*\\&\\?]?\\w+' 167 | ); 168 | 169 | this.isOnlyFindClass = !isGenericMethodOrConstructor && ( 170 | onlyFindClassRegex.test(this.currentTextMaskCurrentWord) 171 | || (this.isCapitalizedWord && 172 | ( 173 | // left style like C++/C#/Java: Class var 174 | !isTypeAfterObject && new RegExp('(^\\s*|\\(\\s*|,\\s*|\\b[A-Z]\\w+(\\.|::))\\b' + currentWord + '[\\s&\\*\\?]+[a-z_A-Z]\\w+').test(this.currentTextMaskCurrentWord) 175 | 176 | // right style like Golang: var Class 177 | || isTypeAfterObject && new RegExp('(^\\s*|\\(|,)\\s*\\w+\\s*:\\s+\\*?(\\w+\\S+\\.)?' + currentWord + '\\s*(,|\\)|\\s*`)').test(this.currentTextMaskCurrentWord) 178 | 179 | // Scala function return type 180 | || new RegExp('\\b(def\\s+\\w+).*?\\s+' + currentWord + '\\s+').test(this.currentTextMaskCurrentWord) 181 | 182 | // Python params comment 183 | || this.extension === 'py' && new RegExp('^\\s*:\\s*type\\s+\\w+.*?\\b' + this.currentWord + '\\W*$').test(this.currentTextMaskCurrentWord) 184 | ) 185 | ) 186 | ); 187 | 188 | const onlyFindMemberRegex = new RegExp('(this|self)[->\\.]{1,2}' + currentWord + '\\b'); 189 | this.isOnlyFindMember = !this.isOnlyFindClass && !this.methodQuoteRegex.test(this.currentTextMaskCurrentWord) && ( 190 | onlyFindMemberRegex.test(this.currentTextMaskCurrentWord) || ( 191 | !(new RegExp('\\b(def\\s+\\w+).*?\\s+').test(this.currentTextMaskCurrentWord)) && new RegExp('\\b' + currentWord + '\\s*=').test(this.currentTextMaskCurrentWord) 192 | ) 193 | ); 194 | 195 | this.isFindClass = this.isOnlyFindClass 196 | || (isGenericMethodOrConstructor && onlyFindClassRegex.test(this.currentTextMaskCurrentWord)) 197 | || this.isFindClassRegex.test(this.currentTextMaskCurrentWord) && this.isFindClassWithWordCheckRegex.test(currentWord); 198 | 199 | this.isFindMethod = isGenericMethodOrConstructor || 200 | (!this.isOnlyFindClass && !this.isOnlyFindMember) && this.isFindMethodRegex.test(this.currentTextMaskCurrentWord); 201 | 202 | this.isFindMember = this.isOnlyFindMember || !this.isOnlyFindClass && this.isFindMemberRegex.test(this.currentTextMaskCurrentWord) && !this.methodQuoteRegex.test(this.currentTextMaskCurrentWord); 203 | this.isFindEnum = !this.isOnlyFindClass && this.isFindEnumRegex.test(this.currentTextMaskCurrentWord); 204 | this.isFindClassOrEnum = !this.isOnlyFindMember && this.isCapitalizedWord && this.isFindClassOrEnumRegex.test(this.currentTextMaskCurrentWord); 205 | this.isFindClassOrMethod = !this.isOnlyFindMember && !this.isOnlyFindClass && this.isFindClassOrMethodRegex.test(this.currentTextMaskCurrentWord); 206 | this.isFindMemberOrLocalVariable = this.isOnlyFindMember || !this.isOnlyFindClass && this.isFindMemberOrLocalVariableRegex.test(this.currentTextMaskCurrentWord); 207 | 208 | this.isProbablyFindClass = this.isCapitalizedWord && ( 209 | // class.method() 210 | new RegExp('(^|[^\\w\\.:>])' + currentWord + '(\\.|->|::)\\w+\\(').test(this.currentTextMaskCurrentWord) 211 | // class.Constant 212 | || new RegExp('(^|[^\\w\\.:>])' + currentWord + '(\\.|->|::)[A-Z]\\w+').test(this.currentTextMaskCurrentWord) 213 | ); 214 | 215 | if (!this.isFindClass && !this.isOnlyFindMember && !this.isFindClassOrEnum && !this.isFindEnum 216 | && this.isCapitalizedWord 217 | && new RegExp("\\b" + this.currentWord + "\\s+\\w+").test(this.currentTextMaskCurrentWord)) { 218 | this.isFindClass = true; 219 | } 220 | 221 | this.canAcceptMemberResult = /^_?[a-z_]\w+$/.test(this.currentWord) 222 | && !this.methodQuoteRegex.test(this.currentTextMaskCurrentWord); 223 | 224 | this.maybeFindLocalVariable = this.canAcceptMemberResult 225 | && new RegExp('\\b' + this.currentWord + '\\b\\S*\\s*=').test(this.currentTextMaskCurrentWord); 226 | 227 | this.isProbablyFindLocalVariable = !this.isCapitalizedWord 228 | && new RegExp('([^\\w\\.:>])' + this.currentWord + '(\\s*$|[^\\w:-]|\\.\\w+\\()').test(this.currentTextMaskCurrentWord); 229 | 230 | this.isFindConstantRegex = this.getCheckingRegex('isFindConstant', false); 231 | if (this.isCapitalizedWord) { 232 | this.isFindConstant = (this.isFindConstantRegex.source === EmptyRegex.source 233 | ? MyConfig.DefaultConstantsRegex.test(this.currentWord) 234 | : this.isFindConstantRegex.test(this.currentWord) 235 | ) && !this.methodQuoteRegex.test(this.currentTextMaskCurrentWord); 236 | } 237 | 238 | this.isConstantResultRegex = this.getCheckingRegex('isConstantResult', true); 239 | if (this.isConstantResultRegex.source === EmptyRegex.source) { 240 | this.isConstantResultRegex = new RegExp('\\b' + this.currentWord + '\\s*='); 241 | } 242 | 243 | this.currentWordRegex = new RegExp((/^\W/.exec(this.currentWord) ? '' : '\\b') + currentWord + '\\b'); 244 | this.enumOrConstantValueRegex = new RegExp('^\\s*' + this.currentWord + '\\s*=\\s*(-?\\d+|["\']\\w+)'); 245 | 246 | if (!this.isFindClass && !this.isFindMember && !this.isFindMethod && !this.isFindEnum) { 247 | if (this.isCapitalizedWord && new RegExp('^\\s*' + this.currentWord + '\\s*=').test(this.currentTextMaskCurrentWord)) { 248 | this.isFindMember = true; 249 | } 250 | 251 | if (this.isCapitalizedWord && new RegExp('[^\.\w]' + this.currentWord + '(\\??\\.|::|->)\\w+').test(this.currentTextMaskCurrentWord)) { 252 | this.isFindClass = true; 253 | } 254 | } else if (!this.isFindClass && !this.isOnlyFindMember && this.isCapitalizedWord && /^(py|cpp)$/.test(mappedExt) && /^[A-Z]\w+/.test(this.currentWord) && this.methodQuoteRegex.test(currentText)) { 255 | this.isFindClass = true; 256 | } 257 | 258 | this.maybeEnum = this.isCapitalizedWord && new RegExp('\\w+(::|\\.|->)\\s*' + currentWord + '([^\\w\\.:-]|$)').test(this.currentTextMaskCurrentWord); 259 | if (!this.isOnlyFindClass && !this.isOnlyFindMember && this.isCapitalizedWord && !this.isFindClass && !this.isFindMember 260 | && !this.isFindClassOrEnum && !this.isFindEnum && !this.isFindMethod && !this.isFindConstant) { 261 | this.maybeEnum = true; 262 | this.isFindClassOrMethod = true; 263 | } 264 | 265 | if (!this.isOnlyFindClass && !this.isOnlyFindMember && !this.maybeEnum) { 266 | this.maybeEnum = this.isCapitalizedWord && new RegExp('(=|return|case|,)\\s*\\w+\\S*(\\.|->|::)' + currentWord + '\\s*(\\)|[,;:]?\\s*$)').test(this.currentTextMaskCurrentWord); 267 | } 268 | 269 | this.maybeEnumResultRegex = new RegExp('^\\s*' + this.currentWord + '\\b\\s*(' + ',?\\s*$' + '|' + '=\\s*(-?\\d|[\'"])' + ')'); 270 | let classNameWords = []; 271 | if ((this.isFindMember || this.isFindMethod)) { 272 | const leftText = this.currentText.substring(0, this.currentWordRange.start.character); 273 | const rightText = this.currentText.substring(this.currentWordRange.end.character); 274 | const classNameMatch = leftText.match(new RegExp('\\b(\\w+)(\\??\\.|::|->)\\s*$')); 275 | if (classNameMatch) { 276 | classNameWords.push(classNameMatch[1]); 277 | 278 | // for case like xxx.ClassName 279 | if (this.isFindMember && this.isCapitalizedWord 280 | && leftText.match(new RegExp('\\b([A-Z]\\w+)(\\??\\.|::|->)\\s*$')) && rightText.match(new RegExp('[\\W\\s]*$'))) { 281 | if (classNameWords[0].toLowerCase() !== currentWord.toLowerCase()) { 282 | classNameWords.push(currentWord); 283 | } 284 | } 285 | } 286 | } else if (this.isOnlyFindClass || this.isProbablyFindClass || this.isFindClassOrMethod || this.isFindClassOrEnum) { 287 | classNameWords.push(currentWord); 288 | } 289 | 290 | classNameWords = classNameWords.map(a => this.getClassFileNamePattern(a)); 291 | this.classFileNamePattern = classNameWords.join('|'); 292 | if (classNameWords.length > 1) { 293 | this.classFileNamePattern = '(' + this.classFileNamePattern + ')'; 294 | } 295 | 296 | const fileNameHighScoreWordMatch = this.currentTextMaskCurrentWord.match(new RegExp('(^|[^\\w:\\.>])m?_?([a-zA-Z]\\w{2,})(\\??\\.|::|->)' + currentWord + '\\b')); 297 | this.fileNameHighScoreWord = fileNameHighScoreWordMatch ? fileNameHighScoreWordMatch[2] : ''; 298 | this.classFileNameScoreRegex = createRegex((this.classFileNamePattern || this.fileNameHighScoreWord).replace(/^m?_+|_+$/g, ''), 'i'); 299 | 300 | this.currentWordSet = getAllSingleWords(this.currentWord); 301 | this.currentFileNameWordSet = getAllSingleWords(this.currentFile.name); 302 | this.currentFilePathWordSet = getAllSingleWords(this.currentFilePath); 303 | const highScoreRegex = new RegExp('(\\w+)(?:\\??\\.|::|->)' + this.currentWord + '\\b' + '|' + '\\b(' + this.currentWord + ')(?:\\??\\.|::|->)\\w+'); 304 | const highScoreMatch = highScoreRegex.exec(this.currentText); 305 | if (highScoreMatch) { 306 | if (highScoreMatch[1]) { 307 | getAllSingleWords(highScoreMatch[1]).forEach(a => this.highScoreWordSet.add(a)); 308 | } 309 | 310 | if (highScoreMatch[2]) { 311 | getAllSingleWords(highScoreMatch[2]).forEach(a => this.highScoreWordSet.add(a)); 312 | } 313 | } 314 | 315 | const classPattern = replaceSearchTextHolder(this.getSpecificConfigValue('class.definition', false), currentWord); 316 | this.classDefinitionRegex = classPattern.length < 1 ? EmptyRegex : new RegExp(classPattern); 317 | 318 | const methodPattern = replaceSearchTextHolder(this.getSpecificConfigValue('method.definition', false), currentWord); 319 | this.methodDefinitionRegex = methodPattern.length < 1 ? EmptyRegex : new RegExp(methodPattern); 320 | 321 | const memberPattern = replaceSearchTextHolder(this.getSpecificConfigValue('member.definition', false), currentWord); 322 | this.memberDefinitionRegex = memberPattern.length < 1 ? EmptyRegex : new RegExp(memberPattern); 323 | 324 | const enumPattern = replaceSearchTextHolder(this.getSpecificConfigValue('enum.definition', false), currentWord); 325 | this.enumDefinitionRegex = enumPattern.length < 1 ? EmptyRegex : new RegExp(enumPattern); 326 | 327 | const promoteFolderPattern = getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, mappedExt, 'promoteFolderPattern'); 328 | const promotePathPattern = getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, mappedExt, 'promotePathPattern'); 329 | this.promoteFolderRegex = createRegex(promoteFolderPattern, 'i'); 330 | this.promotePathRegex = createRegex(promotePathPattern, 'i'); 331 | this.promoteFolderScore = parseInt(getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, mappedExt, 'promoteFolderScore') || '200'); 332 | this.promotePathScore = parseInt(getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, mappedExt, 'promotePathScore') || '200'); 333 | 334 | const demoteFolderPattern = getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, mappedExt, 'demoteFolderPattern'); 335 | const demotePathPattern = getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, mappedExt, 'demotePathPattern'); 336 | this.demoteFolderRegex = createRegex(demoteFolderPattern, 'i'); 337 | this.demotePathRegex = createRegex(demotePathPattern, 'i'); 338 | this.demoteFolderScore = parseInt(getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, mappedExt, 'demoteFolderScore') || '200'); 339 | this.demotePathScore = parseInt(getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, mappedExt, 'demotePathScore') || '200'); 340 | } 341 | 342 | public getDefaultForceSettings(): ForceSetting { 343 | let flag = ForceFindType.None; 344 | if (this.isFindClass) { 345 | flag |= ForceFindType.FindClass; 346 | } 347 | 348 | if (this.isFindMethod) { 349 | flag |= ForceFindType.FindMethod; 350 | } 351 | 352 | if (this.isFindMember) { 353 | flag |= ForceFindType.FindMember; 354 | } 355 | 356 | if (this.maybeFindLocalVariable) { 357 | flag |= ForceFindType.FindLocalVariable; 358 | } 359 | 360 | return new ForceSetting(flag); 361 | } 362 | 363 | public outputSearchInfo() { 364 | outputDebug('ForceUseDefaultFindingDefinition = ' + this.ForceUseDefaultFindingDefinition); 365 | outputDebug('promoteFolderScore = ' + this.promoteFolderScore + ' , promoteFolderPattern = "' + this.promoteFolderRegex.source + '"'); 366 | outputDebug('promotePathScore = ' + this.promotePathScore + ' , promotePathPattern = "' + this.promotePathRegex.source + '"'); 367 | outputDebug('demoteFolderScore = ' + this.demoteFolderScore + ' , demoteFolderPattern = "' + this.demoteFolderRegex.source + '"'); 368 | outputDebug('demotePathScore = ' + this.demotePathScore + ' , demotePathPattern = "' + this.demotePathRegex.source + '"'); 369 | 370 | outputDebug('isFindConstant = ' + this.isFindConstant + ' , isConstantPattern = "' + MyConfig.DefaultConstantsRegex.source + '" , nonConstRegex = "' + this.methodQuoteRegex.source + '"'); 371 | outputDebug('isFindClass = ' + this.isFindClass + ' , isClassPattern = "' + this.isFindClassRegex.source + '"'); 372 | outputDebug('word = "' + this.currentWord + '" , isFindClassWithWordCheckRegex = "' + this.isFindClassWithWordCheckRegex.source + '"'); 373 | outputDebug('isFindEnum = ' + this.isFindEnum + ' , isEnumPattern = "' + this.isFindEnumRegex.source + '"'); 374 | outputDebug('isFindMethod = ' + this.isFindMethod + ' , isMethodPattern = "' + this.isFindMethodRegex.source + '"'); 375 | outputDebug('isFindMember = ' + this.isFindMember + ' , isMemberPattern = "' + this.isFindMemberRegex.source + '"'); 376 | outputDebug('isFindClassOrEnum = ' + this.isFindClassOrEnum + ' , isClassOrEnumPattern = "' + this.isFindClassOrEnumRegex.source + '"'); 377 | outputDebug('isFindClassOrMethod = ' + this.isFindClassOrMethod + ' , isFindClassOrMethodPattern = "' + this.isFindClassOrMethodRegex.source + '"'); 378 | outputDebug('isFindMemberOrLocalVariable = ' + this.isFindMemberOrLocalVariable + ' , isFindMemberOrLocalVariablePattern = "' + this.isFindMemberOrLocalVariableRegex.source + '"'); 379 | 380 | outputDebug('isClassResultRegex = "' + this.isClassResultRegex.source + '"'); 381 | outputDebug('isEnumResultRegex = "' + this.isEnumResultRegex.source + '"'); 382 | outputDebug('isMethodResultRegex = "' + this.isMethodResultRegex.source + '"'); 383 | 384 | outputDebug('classDefinitionRegex = "' + this.classDefinitionRegex.source + '"'); 385 | outputDebug('methodDefinitionRegex = "' + this.methodDefinitionRegex.source + '"'); 386 | outputDebug('memberDefinitionRegex = "' + this.memberDefinitionRegex.source + '"'); 387 | outputDebug('enumDefinitionRegex = "' + this.enumDefinitionRegex.source + '"'); 388 | outputDebugByTime('Final-Check: isFindMember = ' + this.isFindMember + ', isFindClass = ' + this.isFindClass + ' , isFindMethod = ' + this.isFindMethod + ' , isFindEnum = ' + this.isFindEnum); 389 | } 390 | 391 | public getCheckingRegex(configKeyTail: string, allowEmpty: boolean, matchAnyIfEmpty: boolean = false, fallBackToConfigKeyTail: string = ''): RegExp { 392 | const checkConfigTails = [configKeyTail, fallBackToConfigKeyTail]; 393 | const addDefault = isNullOrEmpty(fallBackToConfigKeyTail) && configKeyTail !== fallBackToConfigKeyTail; 394 | let rawPattern = ''; 395 | for (let k = 0; k < checkConfigTails.length; k++) { 396 | if (isNullOrEmpty(checkConfigTails[k])) { 397 | continue; 398 | } 399 | rawPattern = getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, this.mappedExt, checkConfigTails[k], allowEmpty, addDefault); 400 | if (!isNullOrEmpty(rawPattern)) { 401 | break; 402 | } 403 | } 404 | 405 | if (isNullOrEmpty(rawPattern) && !addDefault) { 406 | rawPattern = getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, this.mappedExt, configKeyTail, allowEmpty, true); 407 | if (isNullOrEmpty(rawPattern) && !isNullOrEmpty(fallBackToConfigKeyTail)) { 408 | rawPattern = getConfigValueByProjectAndExtension(this.repoFolderName, this.extension, this.mappedExt, fallBackToConfigKeyTail, allowEmpty, true); 409 | } 410 | } 411 | 412 | const pattern = replaceSearchTextHolder(rawPattern, this.currentWord); 413 | return matchAnyIfEmpty && isNullOrEmpty(pattern) ? new RegExp(".?") : createRegex(pattern); 414 | } 415 | 416 | public getSpecificConfigValue(configKeyTail: string, addDefault: boolean = true, allowEmpty: boolean = true): string { 417 | let prefixes = this.ForceUseDefaultFindingDefinition 418 | ? GetConfigPriorityPrefixes(this.repoFolderName, '', '', true) 419 | : GetConfigPriorityPrefixes(this.repoFolderName, this.extension, this.mappedExt, addDefault); 420 | const pattern = getConfigValueByPriorityList(prefixes, configKeyTail, allowEmpty) as string || ''; 421 | if (!isNullOrEmpty(pattern) && configKeyTail.includes('definition') && !configKeyTail.includes('skip') && pattern.indexOf(SearchTextHolder) < 0) { 422 | const keys = prefixes.join('.' + configKeyTail + ' or '); 423 | outputErrorByTime('Not found word-holder: "' + SearchTextHolder + '" in search option, please check configuration of ' + keys + ', searchPattern = ' + pattern); 424 | return ''; 425 | } 426 | 427 | return pattern; 428 | } 429 | 430 | private getClassFileNamePattern(className: string): string { 431 | let classNamePattern = (className || '').replace(/^m?_+|([0-9]+|i?e?s|[oe]r)_*$/g, '').replace('_', '.?'); 432 | classNamePattern = classNamePattern.replace(/^I([A-Z]\w+)/, '$1'); 433 | 434 | if (this.extension === 'py' || this.mappedExt === 'py') { 435 | classNamePattern = classNamePattern.replace(/([A-Z][a-z]+)/g, '.?$1').replace(/^\.\?|\.\?$/g, ''); 436 | } 437 | return classNamePattern; 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /src/searchConfig.ts: -------------------------------------------------------------------------------- 1 | import { getConfigValueOfActiveProject } from "./configUtils"; 2 | import { IsLinuxTerminalOnWindows } from "./terminalUtils"; 3 | 4 | export class SearchConfiguration { 5 | public SearchRelativePathForLinuxTerminalsOnWindows: boolean = true; 6 | public SearchRelativePathForNativeTerminals: boolean = true; 7 | 8 | constructor() { 9 | this.reload(); 10 | } 11 | 12 | public reload() { 13 | this.SearchRelativePathForLinuxTerminalsOnWindows = getConfigValueOfActiveProject('searchRelativePathForLinuxTerminalsOnWindows') === 'true'; 14 | this.SearchRelativePathForNativeTerminals = getConfigValueOfActiveProject('searchRelativePathForNativeTerminals') === 'true'; 15 | } 16 | 17 | public shouldUseRelativeSearchPath(toRunInTerminal: boolean) { 18 | if (!toRunInTerminal 19 | || (IsLinuxTerminalOnWindows && !this.SearchRelativePathForLinuxTerminalsOnWindows) 20 | || (!IsLinuxTerminalOnWindows && !this.SearchRelativePathForNativeTerminals) 21 | ) { 22 | return false; 23 | } 24 | 25 | return true; 26 | } 27 | } 28 | 29 | export let SearchConfig: SearchConfiguration = new SearchConfiguration(); 30 | -------------------------------------------------------------------------------- /src/terminalUtils.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Terminal } from 'vscode'; 3 | import { getTipGuideFileName, HomeFolder, InitLinuxTerminalFileName, IsLinux, isNullOrEmpty, IsWindows, IsWSL, TempStorageFolder } from './constants'; 4 | import { TerminalType } from './enums'; 5 | import { outputDebugByTime, outputErrorByTime } from './outputUtils'; 6 | import { getErrorMessage, MatchWindowsDiskRegex, quotePaths, replaceToForwardSlash, runCommandGetOutput } from './utils'; 7 | import fs = require('fs'); 8 | import path = require('path'); 9 | import os = require('os'); 10 | import ChildProcess = require('child_process'); 11 | 12 | export function getTerminalExeFromVsCodeSettings(): string { 13 | const shellConfig = vscode.workspace.getConfiguration('terminal.integrated.shell'); 14 | const exePath = shellConfig.get(IsWindows ? 'windows' : 'linux') as string || ''; 15 | return exePath; 16 | } 17 | 18 | export function getCmdAliasScriptFolder(): string { 19 | const folder = vscode.workspace.getConfiguration('msr').get('cmdAlias.saveFolder') as string; 20 | return isNullOrEmpty(folder) ? HomeFolder : folder.trim(); 21 | } 22 | 23 | // return ~/cmdAlias/ or ~/cmdAlias/cygwin/ or /tmp/ 24 | export function getCmdAliasSaveFolder(isMultipleScripts: boolean, isForProjectCmdAlias: boolean, terminalType: TerminalType): string { 25 | // avoid random folder in Darwin like: '/var/folders/7m/f0z72nfn3nn6_mnb_0000gn/T' 26 | const terminalTypeText = TerminalType[terminalType].toLowerCase() 27 | .replace(/bash$/i, '') 28 | .replace(/PowerShell$/i, 'cmd'); 29 | 30 | const generalFolder = toStoragePath(getCmdAliasScriptFolder()); 31 | const isNativeTerminal = isWindowsTerminalOnWindows(terminalType) || !IsWindows; 32 | if (isNativeTerminal && !isMultipleScripts && !isForProjectCmdAlias) { 33 | return generalFolder; 34 | } 35 | 36 | const parentFolder = isForProjectCmdAlias && !isMultipleScripts ? TempStorageFolder : path.join(generalFolder, 'cmdAlias'); 37 | const shouldSeparate = isLinuxTerminalOnWindows(terminalType) || (isMultipleScripts && (IsWSL || IsWindows)); 38 | 39 | return shouldSeparate 40 | ? path.join(parentFolder, terminalTypeText) 41 | : parentFolder; 42 | } 43 | 44 | export function getTipFileStoragePath(terminalType: TerminalType): string { 45 | const isWindowsTerminal = isWindowsTerminalOnWindows(terminalType); 46 | const tmpAliasStorageFolder = getCmdAliasSaveFolder(false, true, terminalType); 47 | return toStoragePath(path.join(tmpAliasStorageFolder, getTipGuideFileName(isWindowsTerminal))); 48 | } 49 | 50 | export function getTipFileDisplayPath(terminalType: TerminalType): string { 51 | const displayPath = toTerminalPath(getTipFileStoragePath(terminalType)); 52 | return isWindowsTerminalOnWindows(terminalType) 53 | ? displayPath.replace(TempStorageFolder, '%TMP%') 54 | : displayPath; 55 | } 56 | 57 | export function getInitLinuxScriptStoragePath(terminalType: TerminalType,): string { 58 | const folder = path.dirname(getTipFileStoragePath(terminalType)); 59 | return path.join(folder, InitLinuxTerminalFileName); 60 | } 61 | 62 | export function getInitLinuxScriptDisplayPath(terminalType: TerminalType): string { 63 | const storagePath = getInitLinuxScriptStoragePath(terminalType); 64 | return toTerminalPath(storagePath, terminalType); 65 | } 66 | 67 | const TerminalExePath = getTerminalExeFromVsCodeSettings(); 68 | function getTerminalTypeFromExePath(terminalExePath: string = TerminalExePath): TerminalType { 69 | if (IsLinux) { 70 | return TerminalType.LinuxBash; 71 | } else if (IsWSL) { 72 | return TerminalType.WslBash; 73 | } else if (/cmd.exe$/i.test(terminalExePath)) { 74 | return TerminalType.CMD; 75 | } else if (/PowerShell.exe$/i.test(terminalExePath)) { 76 | return TerminalType.PowerShell; 77 | } else if (/Cygwin.*?bash.exe$/i.test(terminalExePath)) { 78 | return TerminalType.CygwinBash; 79 | } else if (/System(32)?.bash.exe$/i.test(terminalExePath)) { 80 | return TerminalType.WslBash; 81 | } else if (/MinGW.*?bash.exe$/i.test(terminalExePath) || /Git.*?bin.*?bash.exe$/i.test(terminalExePath)) { 82 | return TerminalType.MinGWBash; 83 | } else if (/bash.exe$/.test(terminalExePath)) { 84 | return TerminalType.WslBash; 85 | } else if (IsWindows) { 86 | return TerminalType.PowerShell; // TerminalType.CMD; 87 | } else { 88 | return TerminalType.LinuxBash; 89 | } 90 | } 91 | 92 | // Must copy/update extension + Restart vscode if using WSL terminal on Windows: 93 | export const DefaultTerminalType = getTerminalTypeFromExePath(); 94 | const GetInputPathsRegex: RegExp = /^(msr\s+-[r\s]*-?p)\s+("[^\"]+"|\S+)/; 95 | let HasMountPrefixForWSL: boolean | undefined = undefined; 96 | 97 | export function isWindowsTerminalOnWindows(terminalType = DefaultTerminalType): boolean { 98 | return (TerminalType.CMD === terminalType || TerminalType.PowerShell === terminalType); 99 | } 100 | 101 | export function isPowerShellTerminal(terminalType: TerminalType): boolean { 102 | return TerminalType.PowerShell === terminalType || TerminalType.Pwsh === terminalType; 103 | } 104 | 105 | export function isLinuxTerminalOnWindows(terminalType: TerminalType = DefaultTerminalType): boolean { 106 | return IsWindows && !isWindowsTerminalOnWindows(terminalType); 107 | } 108 | 109 | export function isTerminalUsingWindowsUtils(terminalType: TerminalType = DefaultTerminalType): boolean { 110 | return IsWindows && (isWindowsTerminalOnWindows(terminalType) || TerminalType.MinGWBash === terminalType || TerminalType.CygwinBash === terminalType); 111 | } 112 | 113 | export const IsWindowsTerminalOnWindows: boolean = isWindowsTerminalOnWindows(DefaultTerminalType); 114 | 115 | // Must copy/update extension + Restart vscode if using WSL terminal on Windows: 116 | export const IsLinuxTerminalOnWindows: boolean = isLinuxTerminalOnWindows(DefaultTerminalType); 117 | 118 | export function isBashTerminalType(terminalType: TerminalType) { 119 | return TerminalType.CygwinBash === terminalType || TerminalType.LinuxBash === terminalType || TerminalType.WslBash === terminalType || TerminalType.MinGWBash === terminalType; 120 | } 121 | 122 | let BashExePath = ''; 123 | let PowershellExePath = ''; 124 | export function getTerminalInitialPath(terminal: vscode.Terminal | null | undefined): string { 125 | if (!terminal) { 126 | return ''; 127 | } 128 | 129 | const creationOptions = Reflect.get(terminal, 'creationOptions'); 130 | const terminalCwd = Reflect.get(creationOptions, 'cwd'); 131 | let fsPath = ''; 132 | let shellPath = ''; 133 | try { 134 | fsPath = !terminalCwd ? '' : Reflect.get(terminalCwd, 'fsPath') as string || ''; 135 | } catch { } 136 | 137 | try { 138 | shellPath = !creationOptions ? '' : Reflect.get(creationOptions, 'shellPath') as string || ''; 139 | } catch { } 140 | 141 | const terminalPath = fsPath && fsPath.match(/bash$|\w+\.exe$/i) ? fsPath : (shellPath ? shellPath : fsPath); 142 | return terminalPath; 143 | } 144 | 145 | export function getRepoFolderFromTerminalCreation(terminal: Terminal): string { 146 | try { 147 | const creationOptions = Reflect.get(terminal, 'creationOptions'); 148 | const terminalCwd = Reflect.get(creationOptions, 'cwd'); 149 | if (!isNullOrEmpty(terminalCwd) && (terminalCwd instanceof String || typeof terminalCwd === typeof 'a')) { 150 | return terminalCwd.toString(); 151 | } 152 | const fsPath = !terminalCwd ? '' : Reflect.get(terminalCwd, 'fsPath') as string || ''; 153 | return fsPath; 154 | } catch (err) { 155 | console.error('Cannot get creationOptions.cwd.fsPath from terminal: ' + terminal.name + ' in getRepoFolderFromTerminalCreation.'); 156 | return ''; 157 | } 158 | } 159 | 160 | export function getTerminalShellExePath(): string { 161 | // https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration 162 | const suffix = IsWindows ? 'windows' : 'linux'; 163 | const oldShellConfig = vscode.workspace.getConfiguration('terminal.integrated.shell').get(suffix); 164 | const oldShellExePath = !oldShellConfig ? '' : oldShellConfig as string || ''; 165 | 166 | const newDefaultConfig = vscode.workspace.getConfiguration('terminal.integrated.defaultProfile'); 167 | const newDefaultValue = !newDefaultConfig ? '' : newDefaultConfig.get(suffix) as string || ''; 168 | const newConfig = vscode.workspace.getConfiguration('terminal.integrated.profiles'); 169 | let newShellExePath = ''; 170 | if (!isNullOrEmpty(newDefaultValue) && newConfig) { 171 | const newShellExePathObj = newConfig.get(suffix + '.' + newDefaultValue); 172 | if (newShellExePathObj) { 173 | try { 174 | const pathValueObj = Reflect.get(newShellExePathObj as any, 'path'); 175 | const valueType = typeof pathValueObj; 176 | const text = valueType === 'string' ? '' : JSON.stringify(pathValueObj); 177 | newShellExePath = text.startsWith('[') || valueType !== 'string' && valueType.length > 0 ? pathValueObj[0] : pathValueObj; 178 | } catch (err) { 179 | console.log(err); 180 | outputErrorByTime('Failed to get path value from terminal.integrated.profiles.' + suffix + '.path , error: ' + err); 181 | } 182 | } 183 | } 184 | 185 | if (isNullOrEmpty(newShellExePath)) { 186 | newShellExePath = newDefaultValue; 187 | } 188 | 189 | const pathRegex = IsWindows ? /\\\w+.*?\\\w+.*?\.exe$/i : /[/]\w+$/; 190 | const shellExePath = oldShellExePath.match(pathRegex) ? oldShellExePath : newShellExePath; 191 | 192 | if (isNullOrEmpty(shellExePath)) { 193 | if (IsWSL || IsLinux) { 194 | if (isNullOrEmpty(BashExePath)) { 195 | const [, bashPath] = isToolExistsInPath('bash'); 196 | BashExePath = bashPath || '/bin/bash'; 197 | } 198 | return BashExePath; 199 | } 200 | 201 | if (IsWindowsTerminalOnWindows) { 202 | if (isNullOrEmpty(PowershellExePath)) { 203 | const [isExist, psPath] = isToolExistsInPath('powershell.exe'); 204 | PowershellExePath = isExist ? psPath : runCommandGetOutput('msr -rp C:\\Windows\\System32\\WindowsPowerShell -f "^powershell.exe$" -l -PAC -H 1 -J | findstr /I /C:exe').trim(); 205 | if (isNullOrEmpty(PowershellExePath)) { 206 | PowershellExePath = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; 207 | } 208 | } 209 | 210 | return PowershellExePath; 211 | } 212 | } 213 | 214 | return shellExePath; 215 | } 216 | 217 | export function getTerminalNameOrShellExeName(terminal: vscode.Terminal | null | undefined): string { 218 | if (!terminal) { 219 | return ''; 220 | } 221 | 222 | if (!isNullOrEmpty(terminal.name)) { 223 | return terminal.name; 224 | } 225 | 226 | const initialPath = getTerminalInitialPath(terminal) || ''; 227 | const shellExePath = !isNullOrEmpty(initialPath) ? initialPath : getTerminalShellExePath(); 228 | return path.basename(shellExePath); 229 | } 230 | 231 | export function getHomeFolderForLinuxTerminalOnWindows(): string { 232 | const shellExePath = getTerminalShellExePath(); 233 | const folder = path.dirname(shellExePath); 234 | const home = path.join(path.dirname(folder), 'home', os.userInfo().username); 235 | return home; 236 | } 237 | 238 | export function isToolExistsInPath(exeToolName: string, terminalType: TerminalType = DefaultTerminalType): [boolean, string] { 239 | const whereCmd = (IsWindows ? 'where' : 'which') + ' ' + exeToolName; 240 | try { 241 | let output = ChildProcess.execSync(whereCmd).toString(); 242 | if (IsWindows) { 243 | if (TerminalType.CygwinBash === terminalType) { 244 | const exeTitle = exeToolName.replace(/^(msr|nin).*/, '$1'); 245 | const folder = path.dirname(getTerminalShellExePath()); 246 | const binExe = path.join(folder, exeTitle); 247 | if (fs.existsSync(binExe)) { 248 | return [true, binExe]; 249 | } 250 | const homeExe = path.join(path.dirname(folder), 'home', os.userInfo().username, exeTitle); 251 | if (fs.existsSync(homeExe)) { 252 | return [true, homeExe]; 253 | } 254 | outputErrorByTime('Not found any of: ' + binExe + ' + ' + homeExe + ' for ' + TerminalType[terminalType] + ' terminal.'); 255 | } else { 256 | const exePaths = /\.exe$/i.test(exeToolName) 257 | ? output.split(/[\r\n]+/) 258 | : output.split(/[\r\n]+/).filter(a => !/cygwin/i.test(a) && new RegExp('\\b' + exeToolName + '\\.\\w+$', 'i').test(a)); 259 | 260 | if (exePaths.length > 0) { 261 | return [true, exePaths[0]]; 262 | } 263 | } 264 | } else { 265 | const exeMatch = new RegExp('(\\S+/' + exeToolName + ')(\\s+|$)').exec(output); 266 | if (exeMatch) { 267 | return [true, exeMatch[1]]; 268 | } 269 | } 270 | } catch (err) { 271 | outputDebugByTime(getErrorMessage(err)); 272 | } 273 | 274 | return [false, '']; 275 | } 276 | 277 | export function changeFindingCommandForLinuxTerminalOnWindows(command: string): string { 278 | if (!IsLinuxTerminalOnWindows) { 279 | return command; 280 | } 281 | 282 | const match = GetInputPathsRegex.exec(command); 283 | if (!match) { 284 | return command; 285 | } 286 | 287 | const paths = match[1].startsWith('"') ? match[2].substring(1, match[2].length - 2) : match[2]; 288 | const newPaths = paths.split(/\s*[,;]/) 289 | .map((p, _index, _a) => toTerminalPath(p) 290 | ); 291 | 292 | return match[1] + ' ' + quotePaths(newPaths.join(',')) + command.substring(match[0].length); 293 | } 294 | 295 | function getPathEnvSeparator(terminalType: TerminalType) { 296 | return isWindowsTerminalOnWindows(terminalType) ? ";" : ":"; 297 | } 298 | 299 | export function checkAddFolderToPath(exeFolder: string, terminalType: TerminalType, prepend = true) { 300 | const oldPathValue = process.env['PATH'] || (IsWindows ? '%PATH%' : '$PATH'); 301 | const paths = oldPathValue.split(IsWindows ? ';' : ':'); 302 | const trimTailRegex = IsWindows ? new RegExp('[\\s\\\\]+$') : new RegExp('/$'); 303 | const foundFolders = IsWindows 304 | ? paths.filter(a => a.trim().replace(trimTailRegex, '').toLowerCase() === exeFolder.toLowerCase()) 305 | : paths.filter(a => a.replace(trimTailRegex, '') === exeFolder); 306 | 307 | if (foundFolders.length > 0) { 308 | return false; 309 | } 310 | 311 | const separator = getPathEnvSeparator(terminalType); 312 | const newValue = prepend 313 | ? exeFolder + separator + oldPathValue 314 | : oldPathValue + separator + exeFolder; 315 | 316 | process.env['PATH'] = newValue; 317 | 318 | return true; 319 | } 320 | 321 | export function toMinGWPath(path: string): string { 322 | const match = MatchWindowsDiskRegex.exec(path); 323 | if (!match) { 324 | return replaceToForwardSlash(path); 325 | } 326 | path = '/' + match[1].toLowerCase() + replaceToForwardSlash(path.substring(match.length)); 327 | return path.replace(' ', '\\ '); 328 | } 329 | 330 | export function toCygwinPath(path: string): string { 331 | const match = MatchWindowsDiskRegex.exec(path); 332 | if (!match) { 333 | return replaceToForwardSlash(path); 334 | } 335 | path = '/cygdrive/' + match[1].toLowerCase() + replaceToForwardSlash(path.substring(match.length)); 336 | return path.replace(' ', '\\ '); 337 | } 338 | 339 | export function toTerminalPath(path: string, terminalType: TerminalType = DefaultTerminalType): string { 340 | if (IsWSL || TerminalType.WslBash === terminalType) { 341 | return toWSLPath(path, TerminalType.WslBash === terminalType); 342 | } else if (TerminalType.CygwinBash === terminalType) { 343 | return toCygwinPath(path); 344 | } else if (TerminalType.MinGWBash === terminalType) { 345 | return toMinGWPath(path); 346 | } else { 347 | return path; 348 | } 349 | } 350 | 351 | export function toTerminalPathsText(windowsPaths: string, terminalType: TerminalType): string { 352 | const paths = windowsPaths.split(/\s*[,;]/).map((p, _index, _a) => toTerminalPath(p, terminalType)); 353 | return paths.join(","); 354 | } 355 | 356 | export function toTerminalPaths(windowsPaths: Set, terminalType: TerminalType): Set { 357 | if (!IsWSL && TerminalType.WslBash !== terminalType && TerminalType.CygwinBash !== terminalType && TerminalType.MinGWBash !== terminalType) { 358 | return windowsPaths; 359 | } 360 | 361 | let pathSet = new Set(); 362 | windowsPaths.forEach(a => { 363 | const path = toTerminalPath(a, terminalType); 364 | pathSet.add(path); 365 | }); 366 | 367 | return pathSet; 368 | } 369 | 370 | export function toStoragePaths(winPaths: Set, isWslTerminal: boolean = IsWSL): Set { 371 | if (!IsWSL && !isWslTerminal) { 372 | return winPaths; 373 | } 374 | 375 | let pathSet = new Set(); 376 | winPaths.forEach(p => { 377 | pathSet.add(toWSLPath(p, isWslTerminal)); 378 | }); 379 | return pathSet; 380 | } 381 | 382 | export function toStoragePath(path: string, isWslTerminal: boolean = IsWSL): string { 383 | return toWSLPath(path, isWslTerminal); 384 | } 385 | 386 | export function toWSLPath(path: string, isWslTerminal: boolean = IsWSL): string { 387 | if (!IsWSL && !isWslTerminal) { 388 | return path; 389 | } 390 | 391 | const match = MatchWindowsDiskRegex.exec(path); 392 | if (!match) { 393 | return path; 394 | } 395 | 396 | const disk = match[1].toLowerCase(); 397 | const tail = replaceToForwardSlash(path.substring(match.length)); 398 | 399 | // https://docs.microsoft.com/en-us/windows/wsl/wsl-config#configure-per-distro-launch-settings-with-wslconf 400 | const shortPath = '/' + disk + tail; 401 | if (HasMountPrefixForWSL === false) { 402 | return shortPath; 403 | } else if (HasMountPrefixForWSL === undefined) { 404 | if (fs.existsSync(shortPath)) { 405 | HasMountPrefixForWSL = false; 406 | return shortPath; 407 | } 408 | } 409 | 410 | const longPath = '/mnt/' + disk + tail; 411 | if (fs.existsSync(longPath)) { 412 | HasMountPrefixForWSL = true; 413 | return longPath; 414 | } else { 415 | HasMountPrefixForWSL = false; 416 | return shortPath; 417 | } 418 | } 419 | 420 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/configAndDocTest.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fs from 'fs'; 3 | import * as os from 'os'; 4 | import * as path from 'path'; 5 | import { isNullOrEmpty } from '../../constants'; 6 | import { getConfig } from '../../dynamicConfig'; 7 | import { nowText } from '../../utils'; 8 | 9 | const GitRepoPath = path.resolve(__dirname, '../../../'); 10 | const ConfigFilePath = path.join(GitRepoPath, 'package.json'); 11 | const DocFilePath = path.join(GitRepoPath, 'README.md'); 12 | const KeyRegex = /["`](msr\.\w+[\.\w]*)/g; 13 | const SkipKeysRegex = /^(msr|nin)\.(exe|cygwin|gcc48|xxx|tmp)|\.project\d+|default.extra\w*Groups|\.My|^msr.py.extra\w*|^msr.\w+(\.\w+)?.definition|msr.\w+.codeFiles|fileExtensionMap|\.default\.$|bat\w*\.skipFolders|preferSearchingSpeedOverPrecision/i; 14 | const ExemptDuplicateKeyRegex = /^(msr\.)?\w*(find|sort|make)\w+$|^msr.cookCmdAlias\w*|^msr.\w*GitIgnore\w*$/i; 15 | const ExemptNoValueKeyRegex = /extra|skip.definition|extensionPattern|projectRepoFolderNamePattern|cmdAlias\w*|^\w*(find|sort)\w+$|^msr.fileExtensionMap.xxx/i; 16 | const NonStringValueRegex = /^(\d+|bool\w*$)/; 17 | 18 | const [AllConfigKeys, AllKeyToNameMap] = readAllKeysAndRegexPatterns(); 19 | 20 | function readAllKeysAndRegexPatterns(): [Set, Map] { 21 | assert.ok(fs.existsSync(ConfigFilePath), 'Should exist config file: ' + ConfigFilePath); 22 | const lines = fs.readFileSync(ConfigFilePath).toString(); 23 | const rootConfig = getConfig().RepoConfig; 24 | 25 | // Roughly get all possible Regex blocks + Compare and check 26 | // msr -p package.json -b "^\s*.msr.\w+" -Q "^^\s*\}" -t "^\s*.default.:.*?\\\\[sbwS]|^\s*.description.:.*?Regex" -aPI > %tmp%\all-possible-Regex-blocks.txt 27 | // npm run test > %tmp%\npm-test-block-name-Regex.txt 28 | // nin %tmp%\all-possible-Regex-blocks.txt %tmp%\npm-test-block-name-Regex.txt "(msr\.\w+[\.\w]+)" "Validated Regex of (\S+)" 29 | // nin %tmp%\all-possible-Regex-blocks.txt %tmp%\npm-test-block-name-Regex.txt "(msr\.\w+[\.\w]+)" "Validated Regex of (\S+)" -S -w 30 | 31 | // const matchRegexKeyRegex = /\.(definition|isFind\w+|is\w+Result|\w*reference|codeAndConfig\w*|skipFolders|allFiles)$/i; 32 | const matchRegexValueRegex = new RegExp(String.raw`\\[sbwS\$]` + String.raw`|^\^` + String.raw`|\)[\$\|]` + String.raw`|[\w\*\+]\?\|` + String.raw`|\[\\` + String.raw`|\S+\|\S+\|/`); 33 | 34 | let keyToRegexMap = new Map(); 35 | let allKeys: string[] = []; 36 | let m; 37 | do { 38 | m = KeyRegex.exec(lines); 39 | if (m) { 40 | const fullKey = m[1]; 41 | allKeys.push(fullKey); 42 | const key = fullKey.replace(/^msr\./, ''); 43 | const value = rootConfig.get(key); 44 | const valueText = !value || NonStringValueRegex.test(value as string || '') ? value : '"' + value + '"'; 45 | console.info('Found config key = ' + fullKey + ' , value = ' + valueText); 46 | 47 | const textValue = String(value); 48 | if (ExemptNoValueKeyRegex.test(key) === false) { 49 | assert.notStrictEqual(0, textValue.length, 'Value should not be empty for key = ' + fullKey); 50 | if (textValue.length > 0) { 51 | const paths = textValue.split(/\s*[,;]\s*/).map(a => a.trim()); 52 | paths.forEach(a => { 53 | if (a.length > 1 && fs.existsSync(a)) { 54 | assert.fail('Should not store personal or test settings: Key = ' + fullKey + ' , local path value = ' + a + ' , fullValue = ' + textValue); 55 | } 56 | }); 57 | } 58 | } 59 | 60 | if (!isNullOrEmpty(textValue) && matchRegexValueRegex.test(textValue)) { 61 | keyToRegexMap.set(key, textValue); 62 | } 63 | } 64 | } while (m); 65 | 66 | let keySet = new Set(); 67 | allKeys.forEach(a => { 68 | if (keySet.has(a)) { 69 | if (!ExemptDuplicateKeyRegex.test(a)) { 70 | assert.fail('Duplicate key: ' + a + ' in ' + ConfigFilePath); 71 | } 72 | } else { 73 | keySet.add(a); 74 | } 75 | }); 76 | 77 | console.log('Total key count = ' + allKeys.length + ' in ' + ConfigFilePath); 78 | assert.ok(allKeys.length > 1, 'Error key count = ' + allKeys.length + ' in ' + ConfigFilePath); 79 | console.info(os.EOL); 80 | return [keySet, keyToRegexMap]; 81 | } 82 | 83 | export function validateRegexPatterns() { 84 | const getErrorRegex = /(?!<\\)\|\|/; 85 | let validatedRegexCount = 0; 86 | AllKeyToNameMap.forEach((pattern, key, _map) => { 87 | const keyName = 'msr.' + key; 88 | try { 89 | // tslint:disable-next-line: no-unused-expression 90 | new RegExp(pattern); 91 | validatedRegexCount++; 92 | console.info('Validated Regex of ' + keyName + ' = "' + pattern + '"'); 93 | } catch (err) { 94 | assert.fail('Failed to validate Regex of ' + keyName + ' = "' + pattern + '" , error: ' + err); 95 | } 96 | 97 | const matched = pattern.match(getErrorRegex); 98 | if (matched && matched.index) { 99 | assert.fail('Probably wrong Regex of ' + key + ' = "' + pattern + '" at "' + pattern.substring(matched.index) + '"'); 100 | } 101 | }); 102 | 103 | console.info(nowText() + 'Validated ' + String(validatedRegexCount) + ' Regex patterns in ' + ConfigFilePath); 104 | console.info(os.EOL); 105 | } 106 | 107 | export function checkConfigKeysInDoc() { 108 | assert.ok(fs.existsSync(DocFilePath), 'Should exist doc file: ' + DocFilePath); 109 | const lines = fs.readFileSync(DocFilePath).toString(); 110 | let errorMessages = []; 111 | const rootConfig = getConfig().RepoConfig; 112 | 113 | let keyCount = 0; 114 | let m; 115 | do { 116 | m = KeyRegex.exec(lines); 117 | if (m) { 118 | keyCount++; 119 | const fullKey = m[1]; 120 | console.log('Found doc key = ' + fullKey + ' in ' + DocFilePath); 121 | if (!AllConfigKeys.has(fullKey) && !SkipKeysRegex.test(fullKey)) { 122 | const shortKey = fullKey.replace(/^msr\./, ''); 123 | const configValue = rootConfig.get(shortKey); 124 | if (configValue === undefined) { 125 | errorMessages.push('Not found in configuration file: Key = ' + fullKey + ' in ' + DocFilePath); 126 | } 127 | } 128 | } 129 | } while (m); 130 | 131 | console.log('Found ' + keyCount + ' in ' + DocFilePath); 132 | assert.ok(keyCount > 0, 'Just found ' + keyCount + ' keys in ' + DocFilePath); 133 | assert.ok(errorMessages.length < 1, 'Caught ' + errorMessages.length + ' errors as below:\n' + errorMessages.join('\n')); 134 | } 135 | 136 | export function checkDuplicateDescription() { 137 | const allText = fs.readFileSync(ConfigFilePath).toString(); 138 | const jsonObj = JSON.parse(allText); 139 | const properties = jsonObj.contributes.configuration.properties; 140 | delete properties["msr.commonAliasNameBodyList"]; 141 | delete properties["msr.cmd.commonAliasNameBodyList"]; 142 | delete properties["msr.bash.commonAliasNameBodyList"]; 143 | const newText = JSON.stringify(jsonObj, null, 2); 144 | const lines = newText.split('\n'); 145 | const descriptionRegex = /^\s*"description"\s*:\s*(.+?)\s*,?\s*$/; 146 | let descriptionCountMap = new Map(); 147 | let total = 0; 148 | let row = 0; 149 | let duplicateLines: string[] = []; 150 | lines.forEach(a => { 151 | row += 1; 152 | const match = descriptionRegex.exec(a.trim()); 153 | if (match) { 154 | const description = match[1]; 155 | total += 1; 156 | let count = descriptionCountMap.get(description) || 0; 157 | descriptionCountMap.set(description, count + 1); 158 | if (count > 0) { 159 | const message = `Found duplicate description at ${ConfigFilePath}:${row} : ${description}`; 160 | console.error(`${message}${os.EOL}`); 161 | duplicateLines.push(message); 162 | } 163 | } 164 | }); 165 | 166 | let duplicates: string[] = []; 167 | descriptionCountMap.forEach((count, description, _) => { 168 | if (count > 1) { 169 | const message = `Found ${count} times of duplicate description: ${description}`; 170 | duplicates.push(message); 171 | console.error(message); 172 | } 173 | }); 174 | 175 | // nin package.json nul "(description.:.+)" -pdw -k2 176 | const errorHead = `Please solve ${duplicates} duplicate descriptions (total = ${total}) in file: ${ConfigFilePath}`; 177 | assert.strictEqual(duplicates.length, 0, `${errorHead}${os.EOL}${duplicates.join(os.EOL)}${os.EOL}${duplicateLines.join(os.EOL)}`); 178 | } 179 | -------------------------------------------------------------------------------- /src/test/suite/cookCmdAliasTest.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { replaceForLoopVariableForWindowsScript } from '../../commonAlias'; 3 | import { getCommandAliasMap } from '../../cookCommandAlias'; 4 | import { TerminalType } from '../../enums'; 5 | 6 | // Tail args: $* or $@ or ${@} or "${@}" 7 | const TailArgsRegex: RegExp = /\$([\*@]|\{@\})\W*$/; // Linux + Windows-doskey-file 8 | const LinuxFunctionTailArgsRegex: RegExp = /\$([\*@]|\{@\})\W*[\r\n]+/; // Linux-bash-file 9 | const WindowsBatchScriptArg1Regex: RegExp = /%~?1\b/; // Windows-batch-file (.cmd or .bat) 10 | const WindowsBatchScriptTailArgsRegex: RegExp = /%\*\W*$/; // Windows-batch-file (.cmd or .bat) 11 | const IsForLoopExists: RegExp = /\bfor\s+\/[fl]\b(\s+".+?")?\s+%+[a-z]\s+in\s+\(/i; 12 | const WindowsForLoopScriptArgRegex: RegExp = /%%[a-zA-Z]\b/; // Windows-batch-file (.cmd or .bat) 13 | const WindowsAliasForLoopScriptArgRegex: RegExp = /[^\w%]%[a-zA-Z]\b/; // Windows-doskey-file 14 | 15 | function checkWindowsForLoop(command: string, isScriptFile: boolean) { 16 | if (IsForLoopExists.test(command)) { 17 | if (isScriptFile) { 18 | assert.ok(WindowsForLoopScriptArgRegex.test(command)); 19 | } else { 20 | assert.ok(WindowsAliasForLoopScriptArgRegex.test(command)); 21 | } 22 | } 23 | } 24 | 25 | export function testWindowsGeneralCmdAlias() { 26 | const [map] = getCommandAliasMap(TerminalType.CMD, '', false, false); 27 | let alias = map.get('git-add-safe-dir') || ''; 28 | assert.ok(alias.startsWith('git-add-safe-dir=')); 29 | assert.ok(!TailArgsRegex.test(alias)); 30 | checkWindowsForLoop(alias, false); 31 | 32 | alias = map.get('wcopy') || ''; 33 | assert.ok(alias.startsWith('wcopy=')); 34 | assert.ok(!TailArgsRegex.test(alias)); 35 | assert.ok(alias.includes('$1')); 36 | checkWindowsForLoop(alias, false); 37 | 38 | alias = map.get('find-ref') || ''; 39 | assert.ok(alias.startsWith('find-ref=')); 40 | assert.ok(TailArgsRegex.test(alias)); 41 | assert.ok(alias.includes('$1')); 42 | checkWindowsForLoop(alias, false); 43 | 44 | alias = map.get('sfs') || ''; 45 | assert.ok(alias.startsWith('sfs=')); 46 | assert.ok(TailArgsRegex.test(alias)); 47 | assert.ok(!alias.includes('$1')); 48 | checkWindowsForLoop(alias, false); 49 | 50 | alias = map.get('add-user-path') || '';; 51 | assert.ok(alias.startsWith('add-user-path=')); 52 | assert.ok(!alias.endsWith('$*')); 53 | assert.ok(!alias.includes('$1')); 54 | checkWindowsForLoop(alias, false); 55 | 56 | alias = map.get('reset-env') || ''; 57 | assert.ok(alias.startsWith('reset-env=')); 58 | assert.ok(alias.includes(String.raw`+ '^=\"'`)); 59 | checkWindowsForLoop(alias, false); 60 | 61 | alias = map.get('reload-env') || ''; 62 | assert.ok(alias.startsWith('reload-env=')); 63 | assert.ok(alias.includes(String.raw`+ '^='`)); 64 | checkWindowsForLoop(alias, false); 65 | 66 | alias = map.get('find-spring-ref') || ''; 67 | assert.ok(alias.startsWith('find-spring-ref=')); 68 | assert.ok(alias.includes('$1')); 69 | assert.ok(alias.includes('$2 $3 $4 $5 $6 $7 $8 $9')); 70 | checkWindowsForLoop(alias, false); 71 | } 72 | 73 | export function testWindowsGeneralCmdAliasScript() { 74 | const [map] = getCommandAliasMap(TerminalType.CMD, '', false, true); 75 | let alias = map.get('git-add-safe-dir') || ''; 76 | assert.ok(!alias.startsWith('git-add-safe-dir=')); 77 | assert.ok(!WindowsBatchScriptTailArgsRegex.test(alias)); 78 | checkWindowsForLoop(alias, true); 79 | 80 | alias = map.get('wcopy') || ''; 81 | assert.ok(!alias.startsWith('wcopy=')); 82 | assert.ok(!WindowsBatchScriptTailArgsRegex.test(alias)); 83 | assert.ok(WindowsBatchScriptArg1Regex.test(alias)); 84 | checkWindowsForLoop(alias, true); 85 | 86 | alias = map.get('find-ref') || ''; 87 | assert.ok(!alias.startsWith('find-ref=')); 88 | assert.ok(WindowsBatchScriptTailArgsRegex.test(alias)); 89 | assert.ok(WindowsBatchScriptArg1Regex.test(alias)); 90 | checkWindowsForLoop(alias, true); 91 | 92 | alias = map.get('sfs') || ''; 93 | assert.ok(!alias.startsWith('sfs=')); 94 | assert.ok(WindowsBatchScriptTailArgsRegex.test(alias)); 95 | assert.ok(!WindowsBatchScriptArg1Regex.test(alias)); 96 | checkWindowsForLoop(alias, true); 97 | 98 | alias = map.get('add-user-path') || '';; 99 | assert.ok(!alias.startsWith('add-user-path=')); 100 | assert.ok(!WindowsBatchScriptTailArgsRegex.test(alias)); 101 | assert.ok(!WindowsBatchScriptArg1Regex.test(alias)); 102 | assert.ok(alias.includes("'%*'.Trim")); 103 | checkWindowsForLoop(alias, true); 104 | 105 | alias = map.get('reset-env') || ''; 106 | assert.ok(!alias.startsWith('reset-env=')); 107 | assert.ok(alias.includes(String.raw`+ '^=\"'`)); 108 | checkWindowsForLoop(alias, true); 109 | 110 | alias = map.get('reload-env') || ''; 111 | assert.ok(!alias.startsWith('reload-env=')); 112 | assert.ok(alias.includes(String.raw`+ '^='`)); 113 | checkWindowsForLoop(alias, true); 114 | 115 | alias = map.get('find-spring-ref') || ''; 116 | assert.ok(!alias.startsWith('find-spring-ref=')); 117 | assert.ok(WindowsBatchScriptArg1Regex.test(alias)); 118 | checkWindowsForLoop(alias, true); 119 | } 120 | 121 | export function testLinuxGeneralCmdAlias() { 122 | const [map] = getCommandAliasMap(TerminalType.LinuxBash, '', false, false); 123 | let alias = map.get('git-add-safe-dir') || ''; 124 | assert.ok(alias.startsWith('alias git-add-safe-dir=')); 125 | assert.ok(!TailArgsRegex.test(alias) && !LinuxFunctionTailArgsRegex.test(alias)); 126 | 127 | alias = map.get('find-ref') || ''; 128 | assert.ok(alias.startsWith('alias find-ref=')); 129 | assert.ok(!TailArgsRegex.test(alias) && !LinuxFunctionTailArgsRegex.test(alias)); 130 | assert.ok(alias.includes('$1')); 131 | 132 | alias = map.get('sfs') || ''; 133 | assert.ok(alias.startsWith('alias sfs=')); 134 | assert.ok(LinuxFunctionTailArgsRegex.test(alias)); 135 | assert.ok(!alias.includes('$1')); 136 | 137 | alias = map.get('find-spring-ref') || ''; 138 | assert.ok(alias.startsWith('alias find-spring-ref=')); 139 | assert.ok(alias.includes('$1')); 140 | assert.ok(alias.includes('${@:2}')); 141 | } 142 | 143 | export function testLinuxGeneralCmdAliasScript() { 144 | const [map] = getCommandAliasMap(TerminalType.LinuxBash, '', false, true); 145 | let alias = map.get('git-add-safe-dir') || ''; 146 | assert.ok(!alias.startsWith('alias ')); 147 | assert.ok(!TailArgsRegex.test(alias)); 148 | 149 | alias = map.get('find-ref') || ''; 150 | assert.ok(!alias.startsWith('alias ')); 151 | assert.ok(alias.includes('$1')); 152 | assert.ok(alias.includes('${@:2}')); 153 | 154 | alias = map.get('sfs') || ''; 155 | assert.ok(!alias.startsWith('alias ')); 156 | assert.ok(TailArgsRegex.test(alias)); 157 | assert.ok(!alias.includes('$1')); 158 | 159 | alias = map.get('find-spring-ref') || ''; 160 | assert.ok(!alias.startsWith('alias ')); 161 | assert.ok(alias.includes('$1')); 162 | assert.ok(alias.includes('${@:2}')); 163 | } 164 | 165 | 166 | export function testForLoopCmdAlias() { 167 | const doskeyBodyToExpectedMap = new Map() 168 | .set( 169 | `for /L %k in (1,1,3) do echo %k`, 170 | `for /L %%k in (1,1,3) do echo %%k` 171 | ) 172 | .set( 173 | `for /f %a in ('xxx') do echo %a %A %B %b`, 174 | `for /f %%a in ('xxx') do echo %%a %A %B %b` 175 | ) 176 | .set( 177 | `for /f "tokens=*" %a in ('xxx') do echo %a %A %B %b`, 178 | `for /f "tokens=*" %%a in ('xxx') do echo %%a %A %B %b`, 179 | ) 180 | .set( 181 | `for /f "tokens=1,2,3" %a in ('xxx') do echo %a %b %c %A %B %C %d`, 182 | `for /f "tokens=1,2,3" %%a in ('xxx') do echo %%a %%b %%c %A %B %C %d`, 183 | ) 184 | .set( 185 | `for /f "tokens=1,3" %a in ('xxx') do echo %a %b %c %d %A %B %C %a`, 186 | `for /f "tokens=1,3" %%a in ('xxx') do echo %%a %b %%c %d %A %B %C %%a`, 187 | ) 188 | .set( 189 | String.raw`for /f "tokens=1,2,3" %a in ('xxx') do echo %a%b%c\%c/%b\%%a %d %A %B %C`, 190 | String.raw`for /f "tokens=1,2,3" %%a in ('xxx') do echo %%a%%b%%c\%%c/%%b\%%%a %d %A %B %C`, 191 | ) 192 | .set( 193 | `for /f "tokens=1,3 delime=;" %a in ('xxx') do ( for /f "tokens=*" %d in ('loop2') do Loop1 %a %b %c Loop2 %d %D %e Mix %a-%b-%c-%d )`, 194 | `for /f "tokens=1,3 delime=;" %%a in ('xxx') do ( for /f "tokens=*" %%d in ('loop2') do Loop1 %%a %b %%c Loop2 %%d %D %e Mix %%a-%b-%%c-%%d )`, 195 | ) 196 | .set( 197 | `for /f "tokens=1,3 delime=; " %a in ('xxx') do ( for /f %d in ('loop2') do Loop1 %a %b %c Loop2 %d %D %e Mix %a-%b-%c-%d )`, 198 | `for /f "tokens=1,3 delime=; " %%a in ('xxx') do ( for /f %%d in ('loop2') do Loop1 %%a %b %%c Loop2 %%d %D %e Mix %%a-%b-%%c-%%d )`, 199 | ) 200 | .set( 201 | `for /f %a in ('dir /b *.txt') do ( for /f %b in ('dir /a:d /b %~dpa') do echo %~dpa%~nxb )`, 202 | `for /f %%a in ('dir /b *.txt') do ( for /f %%b in ('dir /a:d /b %%~dpa') do echo %%~dpa%%~nxb )` 203 | ) 204 | .set( 205 | `for /L %k in (1,1,3) do ( echo %k && for /f %a in ('dir /b *.txt') do ( do echo %k: %~dpa ) )`, 206 | `for /L %%k in (1,1,3) do ( echo %%k && for /f %%a in ('dir /b *.txt') do ( do echo %%k: %%~dpa ) )` 207 | ) 208 | ; 209 | 210 | doskeyBodyToExpectedMap.forEach((expected, doskey, _) => { 211 | const result = replaceForLoopVariableForWindowsScript(doskey); 212 | console.info('doskey = ' + doskey); 213 | console.info('Result = ' + result); 214 | console.info('Expected = ' + expected); 215 | assert.strictEqual(result, expected || ''); 216 | console.info(''); 217 | }); 218 | } 219 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import { before } from 'mocha'; 2 | // You can import and use all API from the 'vscode' module 3 | // as well as import your extension to test it 4 | import * as vscode from 'vscode'; 5 | import { checkConfigKeysInDoc, checkDuplicateDescription, validateRegexPatterns } from './configAndDocTest'; 6 | import { testForLoopCmdAlias, testLinuxGeneralCmdAlias, testLinuxGeneralCmdAliasScript, testWindowsGeneralCmdAlias, testWindowsGeneralCmdAliasScript } from './cookCmdAliasTest'; 7 | import { testCmdTerminalWithBackSlash, testCmdTerminalWithForwardSlash, testLinuxTerminal, testNotSkipDotPaths, testOmitExemptions } from './gitIgnoreTest'; 8 | import { testEscapeRegex, testEscapeRegexForFindingCommands, testSpecialCaseReplacing } from './utilsTest'; 9 | 10 | suite('Test-1: Basic utils test', () => { 11 | before(() => { 12 | vscode.window.showInformationMessage('Begin of testing basic utils.'); 13 | }); 14 | test('Test escaping Regex.', testEscapeRegex); 15 | test('Test escaping Regex for finding commands in terminal.', testEscapeRegexForFindingCommands); 16 | test('Test special cases replacing.', testSpecialCaseReplacing); 17 | }); 18 | 19 | suite('Test-2: Configuration and doc test suite', () => { 20 | before(() => { 21 | vscode.window.showInformationMessage('Start testing configuration keys + keys in readme doc.'); 22 | }); 23 | 24 | test('Configuration keys can be read and Regex patterns are correct.', validateRegexPatterns); 25 | 26 | test('Keys referenced in readme doc must be defined in configuration.', checkConfigKeysInDoc); 27 | 28 | test('Check duplicate descriptions in config file.', checkDuplicateDescription); 29 | }); 30 | 31 | suite('Test-3: Parsing .gitignore test', () => { 32 | before(() => { 33 | vscode.window.showInformationMessage('Will start parsing .gitignore test for terminals: CMD + MinGW + Cygwin + WSL + Bash.'); 34 | }); 35 | 36 | test('Parsing .gitignore with relative path for Linux terminal or Windows WSL/Cygwin/MinGW terminal.', () => { 37 | testLinuxTerminal(); 38 | }); 39 | 40 | test('Parsing .gitignore with relative path for Windows CMD terminal.', () => { 41 | testCmdTerminalWithBackSlash(); 42 | }); 43 | 44 | test('Parsing .gitignore omit exemptions.', () => { 45 | testOmitExemptions(); 46 | }); 47 | 48 | test('Parsing .gitignore not skip dot/dollar paths.', () => { 49 | testNotSkipDotPaths(); 50 | }); 51 | 52 | test('Parsing .gitignore with relative path + forwarding slash for Windows CMD terminal.', () => { 53 | testCmdTerminalWithForwardSlash(); 54 | }); 55 | }); 56 | 57 | suite('Test-4: Cook each doskey/alias to a batch script file for Windows doskey', () => { 58 | test('Test Windows doskey/alias of one file.', testWindowsGeneralCmdAlias); 59 | test('Test Windows doskey/alias of multiple scripts.', testWindowsGeneralCmdAliasScript); 60 | test('Test Linux alias of multiple scripts.', testLinuxGeneralCmdAlias); 61 | test('Test Linux alias of multiple scripts.', testLinuxGeneralCmdAliasScript); 62 | test('Test use "%%x" for looping variable "%x" when cooking doskey/alias to files on Windows.', testForLoopCmdAlias); 63 | }); 64 | -------------------------------------------------------------------------------- /src/test/suite/gitIgnoreTest.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { TerminalType } from '../../enums'; 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import { GitIgnore } from '../../gitUtils'; 6 | 7 | export function comparePattern(parser: GitIgnore, rawPattern: string, expected: string | null = null) { 8 | const result = parser.getPattern(rawPattern); 9 | if (expected == null) { 10 | console.info("comparePattern(parser, '" + rawPattern + "', String.raw`" + result + "`);"); 11 | } else { 12 | console.info('RawIgnore = ' + rawPattern); 13 | console.info('Result = ' + result); 14 | console.info('Expected = ' + expected); 15 | assert.strictEqual(result, expected || ''); 16 | console.info(''); 17 | } 18 | } 19 | 20 | export function testNotSkipDotPaths() { 21 | const parser = new GitIgnore('', true, true, '^\.(git|vscode)$', TerminalType.LinuxBash); 22 | comparePattern(parser, '.sub-modules', String.raw``); 23 | } 24 | 25 | export function testOmitExemptions() { 26 | const parser = new GitIgnore('', true, false, '^\.', TerminalType.LinuxBash); 27 | comparePattern(parser, '!out/my.txt', ''); 28 | } 29 | 30 | export function testLinuxTerminal() { 31 | // Copy output from vscode: msr -p MSR-Def-Ref-output.txt -b "^Input_Git_Ignore =" -Q "^Skip_Paths_Regex|^\s*$" -S -t "^Input_Git_Ignore = (.+?)[\r\n]+Skip_Paths_Regex = (.+?)[\r\n]" -o "comparePattern(parser, '\1', String.raw`\2`);" -P 32 | const parser = new GitIgnore('', true, true, '^\.', TerminalType.LinuxBash); 33 | // Generate test below: msr -p src\test\suite\gitIgnoreTest.ts -b "function testLinuxTerminal" -q "^\s*\}" -t "^(\s*comparePattern\(\w+, [^,]+).*" -o "\1);" -P 34 | comparePattern(parser, '[Bb]in', String.raw`/Bin/`); 35 | comparePattern(parser, '[Bb]in/', String.raw`/Bin/`); 36 | comparePattern(parser, '/[Bb]in/', String.raw`/Bin/`); 37 | comparePattern(parser, '/.[Bb]in/', String.raw`/\.Bin/`); 38 | comparePattern(parser, 'build', String.raw`/build/`); 39 | comparePattern(parser, '/build/', String.raw`/build/`); 40 | comparePattern(parser, 'build/', String.raw`/build/`); 41 | comparePattern(parser, '/.history/*', String.raw`/\.history/`); 42 | comparePattern(parser, '/.code', String.raw`/\.code`); 43 | comparePattern(parser, '*.txt', String.raw`\.txt$`); 44 | comparePattern(parser, '*.mid.ext', String.raw`\.mid\.ext$`); 45 | comparePattern(parser, '*.mid.ext/', String.raw`\.mid\.ext/`); 46 | comparePattern(parser, '*.mid.*', String.raw`\.mid\.[^/]*$`); 47 | comparePattern(parser, '*.mid.*/', String.raw`\.mid\.[^/]*/`); 48 | comparePattern(parser, '/src/**/*.doc', String.raw`/src/.*/[^/]*\.doc$`); 49 | comparePattern(parser, '/src/test/**/*.mid', String.raw`/src/test/.*/[^/]*\.mid$`); 50 | comparePattern(parser, '/src/**/build.info', String.raw`/src/.*/build\.info`); 51 | comparePattern(parser, 'deploy/my/.config/*', String.raw`/deploy/my/\.config/`); 52 | comparePattern(parser, '__pycache__/', String.raw`/__pycache__/`); 53 | comparePattern(parser, 'Settings.Cache', String.raw`/Settings\.Cache`); 54 | comparePattern(parser, '/web/.vscode', String.raw`/web/\.vscode`); 55 | comparePattern(parser, '/tools/build/', String.raw`/tools/build/`); 56 | comparePattern(parser, '/tools/bin/*.xml', String.raw`/tools/bin/[^/]*\.xml$`); 57 | comparePattern(parser, '/build/obj', String.raw`/build/obj/`); 58 | comparePattern(parser, 'src/**/obj', String.raw`/src/.*/obj/`); 59 | comparePattern(parser, '*~', String.raw`~$`); 60 | comparePattern(parser, '*~$', String.raw`~$`); 61 | comparePattern(parser, '~$*', String.raw`~\$`); 62 | comparePattern(parser, '~$*.doc', String.raw`~\$[^/]*\.doc$`); 63 | comparePattern(parser, '~$*.doc*', String.raw`~\$[^/]*\.doc[^/]*$`); 64 | comparePattern(parser, '_config_*.json', String.raw`/_config_[^/]*\.json$`); 65 | comparePattern(parser, '**/*.tgz', String.raw`/[^/]*\.tgz$`); 66 | comparePattern(parser, 'test-*-bin-*.tgz', String.raw`/test-[^/]*-bin-[^/]*\.tgz$`); 67 | comparePattern(parser, 'bin*.tgz', String.raw`/bin[^/]*\.tgz$`); 68 | comparePattern(parser, '*_test_*.txt', String.raw`_test_[^/]*\.txt$`); 69 | comparePattern(parser, 'local-*.', String.raw`/local-[^/]*\.`); 70 | comparePattern(parser, '[._]*.a[b-c][0-3]', String.raw`[\._]*\.a[b-c][0-3]`); 71 | comparePattern(parser, 'data.[0-9]*.[0-9]*.json', String.raw`/data\.[0-9]*\.[0-9]*\.json$`); 72 | comparePattern(parser, '[._]*.sw[a-p]', String.raw`[\._]*\.sw[a-p]$`); 73 | comparePattern(parser, '*.[Kk][Ee][Yy]', String.raw`\.KEY$`); 74 | comparePattern(parser, 'bin/**', String.raw`/bin/`); 75 | comparePattern(parser, '**/bin/', String.raw`/bin/`); 76 | comparePattern(parser, '**/bin', String.raw`/bin/`); 77 | comparePattern(parser, '**/.env*/', String.raw`/\.env[^/]*/`); 78 | comparePattern(parser, '[._]s[a-w][a-z]', String.raw`[\._]s[a-w][a-z]$`); 79 | comparePattern(parser, '[._]*.s[a-w][a-z]', String.raw`[\._]*\.s[a-w][a-z]$`); 80 | comparePattern(parser, '*.s[a-w][a-z]', String.raw`\.s[a-w][a-z]$`); 81 | comparePattern(parser, '*.[123ab]', String.raw`\.[123ab]$`); 82 | comparePattern(parser, '*.[1-9a-b]', String.raw`\.[1-9a-b]$`); 83 | comparePattern(parser, '*.[123ab][4-6cd]', String.raw`\.[123ab][4-6cd]$`); 84 | comparePattern(parser, '*.[1-3a-b][4-6cd][7-9ef]', String.raw`\.[1-3a-b][4-6cd][7-9ef]$`); 85 | comparePattern(parser, '.*', String.raw``); 86 | comparePattern(parser, '.Trash-*', String.raw``);// String.raw`\.Trash-[^/]*$`); 87 | comparePattern(parser, '._*', String.raw``); // String.raw`\._[^/]*$`); 88 | comparePattern(parser, '*.~doc*', String.raw`\.~doc`); // String.raw`\.~doc[^/]*$`); 89 | } 90 | 91 | export function testCmdTerminalWithBackSlash() { 92 | const parser = new GitIgnore('', true, true, '^\.', TerminalType.CMD, false); 93 | // Generate test below: msr -p src\test\suite\gitIgnoreTest.ts -b "function testLinuxTerminal" -q "^\s*\}" -t "^(\s*comparePattern\(\w+, [^,]+).*" -o "\1);" -P 94 | comparePattern(parser, '[Bb]in', String.raw`\\Bin\\`); 95 | comparePattern(parser, '[Bb]in/', String.raw`\\Bin\\`); 96 | comparePattern(parser, '/[Bb]in/', String.raw`\\Bin\\`); 97 | comparePattern(parser, '/.[Bb]in/', String.raw`\\\.Bin\\`); 98 | comparePattern(parser, 'build', String.raw`\\build\\`); 99 | comparePattern(parser, '/build/', String.raw`\\build\\`); 100 | comparePattern(parser, 'build/', String.raw`\\build\\`); 101 | comparePattern(parser, '/.history/*', String.raw`\\\.history\\`); 102 | comparePattern(parser, '/.code', String.raw`\\\.code`); 103 | comparePattern(parser, '*.txt', String.raw`\.txt$`); 104 | comparePattern(parser, '*.mid.ext', String.raw`\.mid\.ext$`); 105 | comparePattern(parser, '*.mid.ext/', String.raw`\.mid\.ext\\`); 106 | comparePattern(parser, '*.mid.*', String.raw`\.mid\.[^\\]*$`); 107 | comparePattern(parser, '*.mid.*/', String.raw`\.mid\.[^\\]*\\`); 108 | comparePattern(parser, '/src/**/*.doc', String.raw`\\src\\.*\\[^\\]*\.doc$`); 109 | comparePattern(parser, '/src/test/**/*.mid', String.raw`\\src\\test\\.*\\[^\\]*\.mid$`); 110 | comparePattern(parser, '/src/**/build.info', String.raw`\\src\\.*\\build\.info`); 111 | comparePattern(parser, 'deploy/my/.config/*', String.raw`\\deploy\\my\\\.config\\`); 112 | comparePattern(parser, '__pycache__/', String.raw`\\__pycache__\\`); 113 | comparePattern(parser, 'Settings.Cache', String.raw`\\Settings\.Cache`); 114 | comparePattern(parser, '/web/.vscode', String.raw`\\web\\\.vscode`); 115 | comparePattern(parser, '/tools/build/', String.raw`\\tools\\build\\`); 116 | comparePattern(parser, '/tools/bin/*.xml', String.raw`\\tools\\bin\\[^\\]*\.xml$`); 117 | comparePattern(parser, '/build/obj', String.raw`\\build\\obj\\`); 118 | comparePattern(parser, 'src/**/obj', String.raw`\\src\\.*\\obj\\`); 119 | comparePattern(parser, '*~', String.raw`~$`); 120 | comparePattern(parser, '*~$', String.raw`~$`); 121 | comparePattern(parser, '~$*', String.raw`~\$`); 122 | comparePattern(parser, '~$*.doc', String.raw`~\$[^\\]*\.doc$`); 123 | comparePattern(parser, '~$*.doc*', String.raw`~\$[^\\]*\.doc[^\\]*$`); 124 | comparePattern(parser, '_config_*.json', String.raw`\\_config_[^\\]*\.json$`); 125 | comparePattern(parser, '**/*.tgz', String.raw`\\[^\\]*\.tgz$`); 126 | comparePattern(parser, 'test-*-bin-*.tgz', String.raw`\\test-[^\\]*-bin-[^\\]*\.tgz$`); 127 | comparePattern(parser, 'bin*.tgz', String.raw`\\bin[^\\]*\.tgz$`); 128 | comparePattern(parser, '*_test_*.txt', String.raw`_test_[^\\]*\.txt$`); 129 | comparePattern(parser, 'local-*.', String.raw`\\local-[^\\]*\.`); 130 | comparePattern(parser, '[._]*.a[b-c][0-3]', String.raw`[\._]*\.a[b-c][0-3]`); 131 | comparePattern(parser, 'data.[0-9]*.[0-9]*.json', String.raw`\\data\.[0-9]*\.[0-9]*\.json$`); 132 | comparePattern(parser, '[._]*.sw[a-p]', String.raw`[\._]*\.sw[a-p]$`); 133 | comparePattern(parser, '*.[Kk][Ee][Yy]', String.raw`\.KEY$`); 134 | comparePattern(parser, 'bin/**', String.raw`\\bin\\`); 135 | comparePattern(parser, '**/bin/', String.raw`\\bin\\`); 136 | comparePattern(parser, '**/bin', String.raw`\\bin\\`); 137 | comparePattern(parser, '**/.env*/', String.raw`\\\.env[^\\]*\\`); 138 | comparePattern(parser, '[._]s[a-w][a-z]', String.raw`[\._]s[a-w][a-z]$`); 139 | comparePattern(parser, '[._]*.s[a-w][a-z]', String.raw`[\._]*\.s[a-w][a-z]$`); 140 | comparePattern(parser, '*.s[a-w][a-z]', String.raw`\.s[a-w][a-z]$`); 141 | comparePattern(parser, '*.[123ab]', String.raw`\.[123ab]$`); 142 | comparePattern(parser, '*.[1-9a-b]', String.raw`\.[1-9a-b]$`); 143 | comparePattern(parser, '*.[123ab][4-6cd]', String.raw`\.[123ab][4-6cd]$`); 144 | comparePattern(parser, '*.[1-3a-b][4-6cd][7-9ef]', String.raw`\.[1-3a-b][4-6cd][7-9ef]$`); 145 | comparePattern(parser, '.*', String.raw``); 146 | comparePattern(parser, '.Trash-*', String.raw``); 147 | comparePattern(parser, '._*', String.raw``); 148 | comparePattern(parser, '*.~doc*', String.raw`\.~doc`); 149 | } 150 | 151 | export function testCmdTerminalWithForwardSlash() { 152 | const parser = new GitIgnore('', true, true, '^\.', TerminalType.CMD, true); 153 | // Generate test below: msr -p src\test\suite\gitIgnoreTest.ts -b "function testLinuxTerminal" -q "^\s*\}" -t "^(\s*comparePattern\(\w+, [^,]+).*" -o "\1);" -P 154 | comparePattern(parser, '[Bb]in', String.raw`/Bin/`); 155 | comparePattern(parser, '[Bb]in/', String.raw`/Bin/`); 156 | comparePattern(parser, '/[Bb]in/', String.raw`/Bin/`); 157 | comparePattern(parser, '/.[Bb]in/', String.raw`/\.Bin/`); 158 | comparePattern(parser, 'build', String.raw`/build/`); 159 | comparePattern(parser, '/build/', String.raw`/build/`); 160 | comparePattern(parser, 'build/', String.raw`/build/`); 161 | comparePattern(parser, '/.history/*', String.raw`/\.history/`); 162 | comparePattern(parser, '/.code', String.raw`/\.code`); 163 | comparePattern(parser, '*.txt', String.raw`\.txt$`); 164 | comparePattern(parser, '*.mid.ext', String.raw`\.mid\.ext$`); 165 | comparePattern(parser, '*.mid.ext/', String.raw`\.mid\.ext/`); 166 | comparePattern(parser, '*.mid.*', String.raw`\.mid\.[^/]*$`); 167 | comparePattern(parser, '*.mid.*/', String.raw`\.mid\.[^/]*/`); 168 | comparePattern(parser, '/src/**/*.doc', String.raw`/src/.*/[^/]*\.doc$`); 169 | comparePattern(parser, '/src/test/**/*.mid', String.raw`/src/test/.*/[^/]*\.mid$`); 170 | comparePattern(parser, '/src/**/build.info', String.raw`/src/.*/build\.info`); 171 | comparePattern(parser, 'deploy/my/.config/*', String.raw`/deploy/my/\.config/`); 172 | comparePattern(parser, '__pycache__/', String.raw`/__pycache__/`); 173 | comparePattern(parser, 'Settings.Cache', String.raw`/Settings\.Cache`); 174 | comparePattern(parser, '/web/.vscode', String.raw`/web/\.vscode`); 175 | comparePattern(parser, '/tools/build/', String.raw`/tools/build/`); 176 | comparePattern(parser, '/tools/bin/*.xml', String.raw`/tools/bin/[^/]*\.xml$`); 177 | comparePattern(parser, '/build/obj', String.raw`/build/obj/`); 178 | comparePattern(parser, 'src/**/obj', String.raw`/src/.*/obj/`); 179 | comparePattern(parser, '*~', String.raw`~$`); 180 | comparePattern(parser, '*~$', String.raw`~$`); 181 | comparePattern(parser, '~$*', String.raw`~\$`); 182 | comparePattern(parser, '~$*.doc', String.raw`~\$[^/]*\.doc$`); 183 | comparePattern(parser, '~$*.doc*', String.raw`~\$[^/]*\.doc[^/]*$`); 184 | comparePattern(parser, '_config_*.json', String.raw`/_config_[^/]*\.json$`); 185 | comparePattern(parser, '**/*.tgz', String.raw`/[^/]*\.tgz$`); 186 | comparePattern(parser, 'test-*-bin-*.tgz', String.raw`/test-[^/]*-bin-[^/]*\.tgz$`); 187 | comparePattern(parser, 'bin*.tgz', String.raw`/bin[^/]*\.tgz$`); 188 | comparePattern(parser, '*_test_*.txt', String.raw`_test_[^/]*\.txt$`); 189 | comparePattern(parser, 'local-*.', String.raw`/local-[^/]*\.`); 190 | comparePattern(parser, '[._]*.a[b-c][0-3]', String.raw`[\._]*\.a[b-c][0-3]`); 191 | comparePattern(parser, 'data.[0-9]*.[0-9]*.json', String.raw`/data\.[0-9]*\.[0-9]*\.json$`); 192 | comparePattern(parser, '[._]*.sw[a-p]', String.raw`[\._]*\.sw[a-p]$`); 193 | comparePattern(parser, '*.[Kk][Ee][Yy]', String.raw`\.KEY$`); 194 | comparePattern(parser, 'bin/**', String.raw`/bin/`); 195 | comparePattern(parser, '**/bin/', String.raw`/bin/`); 196 | comparePattern(parser, '**/bin', String.raw`/bin/`); 197 | comparePattern(parser, '**/.env*/', String.raw`/\.env[^/]*/`); 198 | comparePattern(parser, '[._]s[a-w][a-z]', String.raw`[\._]s[a-w][a-z]$`); 199 | comparePattern(parser, '[._]*.s[a-w][a-z]', String.raw`[\._]*\.s[a-w][a-z]$`); 200 | comparePattern(parser, '*.s[a-w][a-z]', String.raw`\.s[a-w][a-z]$`); 201 | comparePattern(parser, '*.[123ab]', String.raw`\.[123ab]$`); 202 | comparePattern(parser, '*.[1-9a-b]', String.raw`\.[1-9a-b]$`); 203 | comparePattern(parser, '*.[123ab][4-6cd]', String.raw`\.[123ab][4-6cd]$`); 204 | comparePattern(parser, '*.[1-3a-b][4-6cd][7-9ef]', String.raw`\.[1-3a-b][4-6cd][7-9ef]$`); 205 | comparePattern(parser, '.*', String.raw``); 206 | comparePattern(parser, '.Trash-*', String.raw``); 207 | comparePattern(parser, '._*', String.raw``); 208 | comparePattern(parser, '*.~doc*', String.raw`\.~doc`); 209 | } 210 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | }); 10 | // mocha.useColors(true); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | e(err); 34 | } 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/test/suite/utilsTest.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { escapeRegExpForFindingCommand } from '../../commands'; 3 | import { escapeRegExp } from '../../regexUtils'; 4 | import { IsWindowsTerminalOnWindows } from '../../terminalUtils'; 5 | import { replaceSearchTextHolder } from '../../utils'; 6 | // You can import and use all API from the 'vscode' module 7 | // as well as import your extension to test it 8 | 9 | export function testEscapeRegex() { 10 | const textToExpectedResultMap = new Map() 11 | .set(String.raw`\text`, String.raw`\\text`) 12 | .set('$text$', '\\$text\\$') 13 | .set('^text$', '\\^text\\$') 14 | .set('.test*', '\\.test\\*') 15 | .set('text+', 'text\\+') 16 | .set('text?', 'text\\?') 17 | .set('{text}', '\\{text\\}') 18 | .set('(text)', '\\(text\\)') 19 | .set('[text]', '\\[text\\]') 20 | .set('text|', 'text\\|') 21 | ; 22 | 23 | textToExpectedResultMap.forEach((expected, source, _) => { 24 | const result = escapeRegExp(source); 25 | assert.strictEqual(result, expected, `Source = ${source} , expected = ${expected}, but result = ${result}`); 26 | }); 27 | } 28 | 29 | export function testEscapeRegexForFindingCommands() { 30 | const textToExpectedResultMap = new Map() 31 | .set(String.raw`\text`, IsWindowsTerminalOnWindows ? String.raw`\\text` : String.raw`\\\\text`) 32 | .set('$text$', '\\$text\\$') 33 | .set('^text$', '\\^text\\$') 34 | .set('.test*', '\\.test\\*') 35 | .set('text+', 'text\\+') 36 | .set('text?', 'text\\?') 37 | .set('{text}', '\\{text\\}') 38 | .set('(text)', '\\(text\\)') 39 | .set('[text]', '\\[text\\]') 40 | .set('text|', 'text\\|') 41 | ; 42 | 43 | textToExpectedResultMap.forEach((expected, source, _) => { 44 | const result = escapeRegExpForFindingCommand(source); 45 | assert.strictEqual(result, expected, `Source = ${source} , expected = ${expected}, but result = ${result}`); 46 | }); 47 | } 48 | 49 | export function testSpecialCaseReplacing() { 50 | const source = String.raw`-t "%1" -e "%~1"`; 51 | const patternToExpectedResultMap = new Map() 52 | .set(String.raw`'Macro'`, String.raw`-t "'Macro'" -e "'Macro'"`) 53 | .set(String.raw`"Macro"`, String.raw`-t ""Macro"" -e ""Macro""`) 54 | .set(String.raw`'\$Macro\$'`, String.raw`-t "'\$Macro\$'" -e "'\$Macro\$'"`) 55 | .set(String.raw`"\$Macro\$"`, String.raw`-t ""\$Macro\$"" -e ""\$Macro\$""`) 56 | ; 57 | 58 | patternToExpectedResultMap.forEach((expected, pattern, _) => { 59 | const result = replaceSearchTextHolder(source, pattern); 60 | assert.strictEqual(result, expected, `Pattern = ${pattern} , expected = ${expected}, but result = ${result}`); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /src/toolSource.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import * as vscode from 'vscode'; 3 | import { IsWindows, isNullOrEmpty } from './constants'; 4 | import { TerminalType } from './enums'; 5 | import { isPowerShellTerminal, isWindowsTerminalOnWindows, toTerminalPaths } from './terminalUtils'; 6 | import fs = require('fs'); 7 | import crypto = require('crypto'); 8 | 9 | export const MsrExe = 'msr'; 10 | let TerminalTypeToToolNamePathMap = new Map>(); 11 | 12 | const DefaultCheckingUrlFromConfig: string = vscode.workspace.getConfiguration('msr').get('default.checkingToolUrl') as string || ''; 13 | function getSourceUrls(): string[] { 14 | let urlSet = new Set() 15 | .add(DefaultCheckingUrlFromConfig.replace(/[/]$/, '').trim() + '/') 16 | .add('https://raw.githubusercontent.com/qualiu/msr/master/tools/') 17 | .add('https://gitlab.com/lqm678/msr/-/raw/master/tools/') 18 | .add('https://master.dl.sourceforge.net/project/avasattva/') 19 | ; 20 | 21 | urlSet.delete(''); 22 | urlSet.delete('/'); 23 | return Array.from(urlSet); 24 | } 25 | 26 | export const SourceHomeUrlArray = getSourceUrls(); 27 | 28 | export function getDownloadUrl(sourceExeName: string, useUrlIndex: number = 0): string { 29 | const parentUrl = SourceHomeUrlArray[useUrlIndex % SourceHomeUrlArray.length]; 30 | if (parentUrl.includes('sourceforge')) { 31 | return parentUrl + sourceExeName + '?viasf=1'; 32 | } else if (parentUrl.includes('gitlab')) { 33 | return parentUrl + sourceExeName + '?inline=false'; 34 | } 35 | return parentUrl + sourceExeName; 36 | } 37 | 38 | export function getHomeUrl(sourceHomeUrl: string): string { 39 | if (sourceHomeUrl.includes('gitlab')) { 40 | return 'https://gitlab.com/lqm678/msr/'; 41 | } else if (sourceHomeUrl.includes('sourceforge')) { 42 | return 'https://sourceforge.net/projects/avasattva/files/' 43 | } else { 44 | return 'https://github.com/qualiu/msr'; 45 | } 46 | } 47 | 48 | export function getFileMd5(filePath: string) { 49 | const hash = crypto.createHash('md5'); 50 | const content = fs.readFileSync(filePath, { encoding: '' }); 51 | const md5 = hash.update(content).digest('hex'); 52 | return md5; 53 | } 54 | 55 | export function updateToolNameToPathMap(terminalType: TerminalType, toolName: string, toolPath: string, canReCheck = true) { 56 | let toolNameToPathMap = TerminalTypeToToolNamePathMap.get(terminalType); 57 | if (!toolNameToPathMap) { 58 | toolNameToPathMap = new Map(); 59 | TerminalTypeToToolNamePathMap.set(terminalType, toolNameToPathMap); 60 | if (isWindowsTerminalOnWindows(terminalType)) { 61 | TerminalTypeToToolNamePathMap.set(TerminalType.PowerShell, toolNameToPathMap); 62 | TerminalTypeToToolNamePathMap.set(TerminalType.CMD, toolNameToPathMap); 63 | TerminalTypeToToolNamePathMap.set(TerminalType.MinGWBash, toolNameToPathMap); 64 | } 65 | } 66 | 67 | toolNameToPathMap.set(toolName, toolPath); 68 | if (canReCheck && IsWindows && (terminalType === TerminalType.CMD || TerminalType.PowerShell === terminalType)) { 69 | const tp = terminalType === TerminalType.CMD ? TerminalType.PowerShell : TerminalType.CMD; 70 | updateToolNameToPathMap(tp, toolName, toolPath, false); 71 | } 72 | } 73 | 74 | export function getToolExportFolder(terminalType: TerminalType): string { 75 | const toolNameToPathMap = TerminalTypeToToolNamePathMap.get(terminalType); 76 | if (toolNameToPathMap) { 77 | const toolPath = toolNameToPathMap.get(MsrExe) || ''; 78 | return isNullOrEmpty(toolPath) ? '' : path.dirname(toolPath); 79 | } 80 | return ''; 81 | } 82 | 83 | export function getSetToolEnvCommand(terminalType: TerminalType, foldersToAddPath: string[] = [], directRun = false): string { 84 | let toolFolderSet = new Set(); 85 | const toolNameToPathMap = TerminalTypeToToolNamePathMap.get(terminalType); 86 | const isToolInPath = !toolNameToPathMap; 87 | if (toolNameToPathMap) { 88 | toolNameToPathMap.forEach((value, _key, _m) => { 89 | toolFolderSet.add(path.dirname(value)); 90 | }); 91 | } 92 | 93 | if (foldersToAddPath && foldersToAddPath.length > 0) { 94 | foldersToAddPath.filter(d => !isNullOrEmpty(d)).forEach((folder) => toolFolderSet.add(folder)); 95 | } 96 | 97 | if (toolFolderSet.size === 0) { 98 | return ''; 99 | } 100 | 101 | const toolFolders = Array.from(toTerminalPaths(toolFolderSet, terminalType)); 102 | const isWindowsTerminal = isWindowsTerminalOnWindows(terminalType); 103 | const pathEnv = isWindowsTerminal ? `"%PATH%;"` : `"$PATH"`; 104 | const checkPathsPattern = isWindowsTerminal 105 | ? `-it "^(${toolFolders.join('|').replace(/[\\]/g, '\\\\')})$"` 106 | : `-t "^(${toolFolders.join('|')})$"`; 107 | const splitPattern = isWindowsTerminal || TerminalType.MinGWBash === terminalType 108 | ? String.raw`\\*\s*;\s*` 109 | : String.raw`\s*:\s*`; 110 | const checkCountPattern = "^Matched [" + toolFolders.length + "-9]"; 111 | const checkDuplicate = isToolInPath 112 | ? `msr -z ${pathEnv} -t "${splitPattern}" -o "\\n" -aPAC | msr ${checkPathsPattern} -H 0 -C | msr -t "${checkCountPattern}" -M -H 0 && ` 113 | : ''; 114 | 115 | if (directRun && isPowerShellTerminal(terminalType)) { 116 | return `$env:Path += ";${toolFolders.join(';')};"`; 117 | } 118 | 119 | switch (terminalType) { 120 | case TerminalType.CMD: 121 | case TerminalType.PowerShell: // if merged into cmd file for PowerShell 122 | return checkDuplicate + 'SET "PATH=%PATH%;' + toolFolders.join(';') + ';"'; 123 | case TerminalType.Pwsh: 124 | return `$env:Path += ";${toolFolders.join(';')};"`; 125 | case TerminalType.LinuxBash: 126 | case TerminalType.MinGWBash: 127 | default: 128 | return checkDuplicate + 'export PATH="$PATH:' + toolFolders.join(':').replace(/ /g, '\\ ') + '"'; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { ParsedPath } from 'path'; 2 | import * as vscode from 'vscode'; 3 | import { IsWindows, ShouldQuotePathRegex, TrimSearchTextRegex, getRepoFolder, isNullOrEmpty } from './constants'; 4 | import { TerminalType } from './enums'; 5 | import path = require('path'); 6 | import ChildProcess = require('child_process'); 7 | 8 | export const PathEnvName = IsWindows ? '%PATH%' : '$PATH'; 9 | export const MatchWindowsDiskRegex = /^([A-Z]):/i; 10 | 11 | export function isWeeklyCheckTime(dayInWeek: number = 2, beginHour: number = 7, endHour: number = 12): boolean { 12 | const now = new Date(); 13 | const hour = now.getHours(); 14 | if (now.getDay() !== dayInWeek || hour < beginHour || hour > endHour) { 15 | // outputDebugByTime('Skip checking for now. Only check at every Tuesday 07:00 ~ 12:00.'); 16 | return false; 17 | } 18 | return true; 19 | } 20 | 21 | export function getErrorMessage(error: unknown): string { 22 | if (!error) { 23 | return ''; 24 | } 25 | 26 | if (error instanceof Error) { 27 | return error.message; 28 | } 29 | 30 | const text = String(error); 31 | const nonObject = text.replace(/[Object \[\],]+/g, ''); 32 | if (!isNullOrEmpty(nonObject)) { 33 | return text; 34 | } 35 | try { 36 | return JSON.stringify(error); 37 | } catch (err) { 38 | console.log(error); 39 | console.log('Failed to stringify error message.'); 40 | console.log(err); 41 | return 'Unknown error'; 42 | } 43 | } 44 | 45 | export function runCommandGetOutput(command: string, fetchError = false): string { 46 | try { 47 | return ChildProcess.execSync(command).toString(); 48 | } catch (err) { 49 | if (fetchError && err) { 50 | const keys = Object.keys(err); 51 | const stdoutIndex = !keys ? -1 : keys.indexOf('stdout'); 52 | if (stdoutIndex >= 0) { 53 | const values = Object.values(err); 54 | const stdout = values[stdoutIndex]; 55 | return !stdout ? '' : String(stdout); 56 | } 57 | } 58 | console.log(err); 59 | return ''; 60 | } 61 | } 62 | 63 | export function getSearchPathInCommand(commandLine: string, matchRegex: RegExp = /\s+(-r?p)\s+(".+?"|\S+)/): string { 64 | const match = matchRegex.exec(commandLine); 65 | return match ? match[2] : ''; 66 | } 67 | 68 | export function setSearchPathInCommand(commandLine: string, newSearchPaths: string, matchRegex: RegExp = /\s+(-r?p)\s+(".+?"|\S+)/): string { 69 | const match = matchRegex.exec(commandLine); 70 | if (!match) { 71 | return commandLine; 72 | } 73 | 74 | return commandLine.substring(0, match.index) + ' ' + match[1] + ' ' + quotePaths(newSearchPaths) + commandLine.substring(match.index + match[0].length); 75 | } 76 | 77 | export function removeQuotesForPath(paths: string) { 78 | if (paths.startsWith('"') || paths.startsWith("'")) { 79 | return paths.substring(1, paths.length - 2); 80 | } else { 81 | return paths; 82 | } 83 | } 84 | 85 | export function quotePaths(paths: string, quote = '"') { 86 | paths = removeQuotesForPath(paths); 87 | if (ShouldQuotePathRegex.test(paths)) { 88 | return quote + paths + quote; 89 | } else { 90 | return paths; 91 | } 92 | } 93 | 94 | export function toPath(parsedPath: ParsedPath): string { 95 | return path.join(parsedPath.dir, parsedPath.base); 96 | } 97 | 98 | export function nowText(tailText: string = ' '): string { 99 | return new Date().toISOString() + ' ' + tailText.trimLeft(); 100 | } 101 | 102 | export function getElapsedSeconds(begin: Date, end: Date): number { 103 | return (end.valueOf() - begin.valueOf()) / 1000; 104 | } 105 | 106 | export function getElapsedSecondsToNow(begin: Date): number { 107 | return (Date.now() - begin.valueOf()) / 1000; 108 | } 109 | 110 | export function getCurrentWordAndText(document: vscode.TextDocument, position: vscode.Position, textEditor: vscode.TextEditor | undefined = undefined) 111 | : [string, vscode.Range | undefined, string] { 112 | 113 | if (document.languageId === 'code-runner-output' || document.fileName.startsWith('extension-output-#')) { 114 | return ['', undefined, '']; 115 | } 116 | 117 | const currentText = document.lineAt(position.line).text; 118 | if (!textEditor) { 119 | textEditor = vscode.window.activeTextEditor; 120 | } 121 | 122 | if (textEditor) { 123 | const selectedText = textEditor.document.getText(textEditor.selection); 124 | const isValidSelect = selectedText.length > 2 && /\w+/.test(selectedText); 125 | if (isValidSelect) { 126 | return [selectedText, textEditor.selection, currentText]; 127 | } 128 | } 129 | 130 | const wordRange = document.getWordRangeAtPosition(position); 131 | if (!wordRange) { 132 | return ['', undefined, '']; 133 | } 134 | 135 | const currentWord: string = currentText.slice(wordRange.start.character, wordRange.end.character).replace(TrimSearchTextRegex, ''); 136 | return [currentWord, wordRange, currentText]; 137 | } 138 | 139 | export function getUniqueStringSetNoCase(textSet: Set, deleteEmpty: boolean = true): Set { 140 | let noCaseSet = new Set(); 141 | let newSet = new Set(); 142 | textSet.forEach(a => { 143 | const lowerCase = a.toLowerCase(); 144 | const preSize = noCaseSet.size; 145 | noCaseSet.add(lowerCase); 146 | if (noCaseSet.size > preSize) { 147 | newSet.add(a); 148 | } 149 | }); 150 | 151 | if (deleteEmpty) { 152 | newSet.delete(''); 153 | } 154 | 155 | return newSet; 156 | } 157 | 158 | export function replaceToForwardSlash(sourceText: string): string { 159 | return sourceText.replace(/\\/g, '/'); 160 | } 161 | 162 | export function replaceSearchTextHolder(command: string, searchText: string): string { 163 | const searchTextHolderReplaceRegex = /%~?1/g; 164 | // Regex bug case: 165 | // String.raw`-t "%1" -e "%~1"`.replace(searchTextHolderReplaceRegex, String.raw`'\$Macro\$'`); 166 | // return command.replace(searchTextHolderReplaceRegex, searchText); 167 | 168 | let result = command; 169 | let match: RegExpExecArray | null = null; 170 | const maxReplacingTimes = 99; 171 | const maxIncreasingLength = 20 * command.length; 172 | for (let k = 0; k < maxReplacingTimes && (match = searchTextHolderReplaceRegex.exec(result)) !== null; k++) { 173 | const newText = result.substring(0, match.index) + searchText + result.substring(match.index + match[0].length); 174 | if (newText.length >= maxIncreasingLength || newText === result) { 175 | break; 176 | } 177 | 178 | result = newText; 179 | } 180 | 181 | return result; 182 | } 183 | 184 | export function replaceTextByRegex(sourceText: string, toFindRegex: RegExp, replaceTo: string): string { 185 | let newText = sourceText.replace(toFindRegex, replaceTo); 186 | while (newText !== sourceText) { 187 | sourceText = newText; 188 | newText = newText.replace(toFindRegex, replaceTo); 189 | } 190 | 191 | return newText; 192 | } 193 | 194 | export function getExtensionNoHeadDot(extension: string | undefined, defaultValue: string = 'default'): string { 195 | if (!extension || isNullOrEmpty(extension)) { 196 | return defaultValue; 197 | } 198 | 199 | return extension.replace(/^\./, '').toLowerCase(); 200 | } 201 | 202 | export function changeToForwardSlash(pathString: string, addTailSlash: boolean = true): string { 203 | let newPath = pathString.replace(/\\/g, '/').replace(/\\$/, ''); 204 | if (addTailSlash && !newPath.endsWith('/')) { 205 | newPath += '/'; 206 | } 207 | return newPath; 208 | } 209 | 210 | export function getRepoFolderName(filePath: string, useFirstFolderIfNotFound = false): string { 211 | const folder = getRepoFolder(filePath, useFirstFolderIfNotFound); 212 | return isNullOrEmpty(folder) ? '' : path.parse(folder).base; 213 | } 214 | 215 | export function getRepoFolders(currentFilePath: string): string[] { 216 | if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length < 1) { 217 | return ['']; 218 | } 219 | 220 | let repoFolderSet = new Set().add(getRepoFolder(currentFilePath)); 221 | vscode.workspace.workspaceFolders.forEach(a => repoFolderSet.add(a.uri.fsPath)); 222 | repoFolderSet.delete(''); 223 | return Array.from(repoFolderSet); 224 | } 225 | 226 | export function getPowerShellName(terminalType: TerminalType) { 227 | return !IsWindows || TerminalType.WslBash == terminalType ? "pwsh" : "PowerShell"; 228 | } 229 | 230 | export function isPowerShellCommand(cmd: string, terminalType: TerminalType): boolean { 231 | const powerShellCmd = getPowerShellName(terminalType) + ' -Command'; 232 | return cmd.includes(powerShellCmd); 233 | } 234 | 235 | export function getLoadAliasFileCommand(file: string, isWindowsTerminal: boolean, autoQuote: boolean = true): string { 236 | const head = isWindowsTerminal 237 | ? (file.endsWith('doskeys') ? 'doskey /MACROFILE=' : "") 238 | : 'source '; 239 | return head + (autoQuote ? quotePaths(file) : file); 240 | } 241 | -------------------------------------------------------------------------------- /src/wordReferenceUtils.ts: -------------------------------------------------------------------------------- 1 | import { ParsedPath } from "path"; 2 | import { FileExtensionToMappedExtensionMap, MyConfig } from "./dynamicConfig"; 3 | 4 | export const FindJavaSpringReferenceByPowerShellAlias = ` 5 | $rawWord = '%1'; 6 | if ([string]::IsNullOrWhiteSpace($rawWord)) { 7 | return; 8 | } 9 | $checkWords = New-Object System.Collections.Generic.HashSet[string]; 10 | [void] $checkWords.Add($rawWord); 11 | $memberPattern = '(^m?_+|_+$)'; 12 | if ($rawWord -match $memberPattern) { 13 | [void] $checkWords.Add(($rawWord -replace $memberPattern, '')); 14 | } else { 15 | [void] $checkWords.Add('m_' + $rawWord); 16 | [void] $checkWords.Add('_' + $rawWord); 17 | [void] $checkWords.Add($rawWord + '_'); 18 | } 19 | $wordSet = New-Object System.Collections.Generic.HashSet[string]; 20 | [void] $wordSet.Add($rawWord); 21 | foreach ($word in $checkWords) { 22 | if ($word -match $memberPattern) { 23 | continue; 24 | } 25 | $pure = msr -z $word -t '^(is|get|set)([A-Z])' -o \\2 -aPAC; 26 | $cap = [Char]::ToUpper($pure[0]) + $pure.Substring(1); 27 | $camel = [Char]::ToLower($pure[0]) + $pure.Substring(1); 28 | if ($pure.Length -lt $word.Length) { 29 | if([Char]::IsUpper($pure[0])) { 30 | [void] $wordSet.Add($camel); 31 | } else { 32 | [void] $wordSet.Add($cap); 33 | } 34 | } 35 | [void] $wordSet.Add('is' + $cap); 36 | [void] $wordSet.Add('get' + $cap); 37 | [void] $wordSet.Add('set' + $cap); 38 | } 39 | $pattern = '\\b(' + [String]::Join('|', $wordSet) + ')\\b'; 40 | if ([regex]::IsMatch($rawWord, '^[A-Z_]+$')) { 41 | $pattern = '\\b' + $rawWord + '\\b'; 42 | } 43 | `.trim(); 44 | 45 | export function changeSearchWordToVariationPattern(rawWord: string, parsedFile: ParsedPath): string { 46 | if (!MyConfig.AutoChangeSearchWordForReference) { 47 | return rawWord; 48 | } 49 | 50 | const extension = parsedFile.ext.replace(/^\./, ''); 51 | const mappedExt = FileExtensionToMappedExtensionMap.get(extension) || extension; 52 | 53 | if (!rawWord.startsWith('m_') // cpp member style 54 | && mappedExt !== 'java' // java spring members 55 | && mappedExt !== 'bp' // bond/proto members 56 | ) { 57 | return rawWord; 58 | } 59 | 60 | return getSearchWordVariationPattern(rawWord); 61 | } 62 | 63 | export function getSearchWordVariationPattern(rawWord: string): string { 64 | // skip if contains non-alphabetic char, or whole word is upper case: 65 | if (!rawWord.match(/^\w+$/) || rawWord.match(/^[A-Z_]+$/)) { 66 | return String.raw`\b${rawWord}\b`; 67 | } 68 | 69 | const memberPattern = /^m?_+|_+$/; 70 | let checkWords = new Set() 71 | .add(rawWord); 72 | if (rawWord.match(memberPattern)) { 73 | checkWords.add(rawWord.replace(memberPattern, '')); 74 | } else { 75 | checkWords.add('m_' + rawWord) 76 | .add('_' + rawWord) 77 | .add(rawWord + '_'); 78 | } 79 | 80 | let wordSet = new Set() 81 | .add(rawWord); 82 | 83 | const separator = rawWord.match(/[a-z0-9]+_[a-z0-9]+/) ? '_' : ''; 84 | checkWords.forEach(word => { 85 | if (word.match(memberPattern)) { 86 | return; 87 | } 88 | 89 | const pureWord = word.replace(/^(is|get|set|has)([A-Z])/i, '$2'); 90 | const isCapitalized = word.match(/^[A-Z]/); // word.match(/^(Is|Get|Set|Has)[A-Z]/); 91 | const capitalWord = pureWord[0].toUpperCase() + pureWord.substring(1); 92 | const camelWord = pureWord[0].toLowerCase() + pureWord.substring(1); 93 | if (capitalWord.length < word.length) { 94 | if (!isCapitalized && pureWord[0].toUpperCase() === pureWord[0]) { 95 | wordSet.add(camelWord); 96 | } else { 97 | wordSet.add(capitalWord); 98 | } 99 | } 100 | 101 | const suffixWord = separator === '_' ? pureWord : capitalWord; 102 | wordSet 103 | .add((isCapitalized ? 'Is' : 'is') + separator + suffixWord) 104 | .add((isCapitalized ? 'Get' : 'get') + separator + suffixWord) 105 | .add((isCapitalized ? 'Set' : 'set') + separator + suffixWord) 106 | .add((isCapitalized ? 'Has' : 'has') + separator + suffixWord); 107 | }); 108 | 109 | const text = "\\b" 110 | + (wordSet.size > 1 ? "(" : "") 111 | + Array.from(wordSet).join("|") 112 | + (wordSet.size > 1 ? ")" : "") 113 | + "\\b"; 114 | return text; 115 | } 116 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true, 12 | /*AdditionalChecks*/ 13 | //"noImplicitReturns":true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noUnusedParameters": true, 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "always" 11 | ], 12 | "triple-equals": true 13 | }, 14 | "defaultSeverity": "warning" 15 | } 16 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | ## Explore the API 25 | 26 | * You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`. 27 | 28 | ## Run tests 29 | 30 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 31 | * Press `F5` to run the tests in a new window with your extension loaded. 32 | * See the output of the test result in the debug console. 33 | * Make changes to `test/extension.test.ts` or create new test files inside the `test` folder. 34 | * By convention, the test runner will only consider files matching the name pattern `**.test.ts`. 35 | * You can create folders inside the `test` folder to structure your tests any way you want. 36 | 37 | ## Go further 38 | 39 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/testing-extension). 40 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 41 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 42 | --------------------------------------------------------------------------------