├── media ├── icon.png ├── find_files.gif ├── find_within_files.gif ├── find_within_files_with_filter.gif └── convert_mov ├── .gitignore ├── .vscode ├── extensions.json ├── settings.json ├── tasks.json └── launch.json ├── workspace.code-workspace ├── release.sh ├── .vscodeignore ├── list_search_locations.sh ├── list_search_locations.ps1 ├── .eslintrc.json ├── tsconfig.json ├── flight_check.sh ├── CONTRIBUTING.md ├── src ├── test │ ├── suite │ │ ├── index.ts │ │ └── extension.test.ts │ ├── runTest.ts │ └── install_deps.sh └── extension.ts ├── flight_check.ps1 ├── LICENSE ├── shared.sh ├── find_files.sh ├── vsc-extension-quickstart.md ├── .github └── workflows │ └── ci.yml ├── find_files.ps1 ├── find_within_files.ps1 ├── find_within_files.sh ├── package.json └── README.md /media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomrijndorp/vscode-finditfaster/HEAD/media/icon.png -------------------------------------------------------------------------------- /media/find_files.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomrijndorp/vscode-finditfaster/HEAD/media/find_files.gif -------------------------------------------------------------------------------- /media/find_within_files.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomrijndorp/vscode-finditfaster/HEAD/media/find_within_files.gif -------------------------------------------------------------------------------- /media/find_within_files_with_filter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomrijndorp/vscode-finditfaster/HEAD/media/find_within_files_with_filter.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | **/*.swp 7 | **/.DS_Store 8 | releases 9 | .snitch 10 | screencaps 11 | workspace2.code-workspace -------------------------------------------------------------------------------- /media/convert_mov: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | IN="$1" 5 | OUT="$2" 6 | 7 | ffmpeg -i "$IN" -pix_fmt rgb8 -r 8 -filter:v scale=1024:-1 -f gif - | gifsicle -O3 > "$OUT" 8 | -------------------------------------------------------------------------------- /.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 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".", 5 | }, 6 | ], 7 | "settings": { 8 | "terminal.integrated.automationProfile.linux": { 9 | "path": "/bin/bash", 10 | "icon": "terminal-bash", 11 | "args": ["-l"] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | echo "Checking out release..." 5 | git fetch 6 | git checkout release 7 | git pull --ff-only 8 | git merge --ff-only main 9 | git push 10 | echo "Done. Switching back to main..." 11 | git checkout main 12 | echo "done" 13 | 14 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | node_modules/** 5 | 6 | src/*.ts 7 | .gitignore 8 | .yarnrc 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/.eslintrc.json 12 | **/*.map 13 | **/*.ts 14 | **/*.svg 15 | **/*.mov 16 | **/*.code-workspace 17 | media/convert_mov 18 | release.sh -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | } -------------------------------------------------------------------------------- /list_search_locations.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | PATHS=("$@") 5 | 6 | clear 7 | echo -e "----------" 8 | echo -e "\033[35;1mThe following files are on your search path:\033[0m" 9 | for P in "${PATHS[@]+"${PATHS[@]}"}"; do 10 | echo "- $P" 11 | done 12 | echo -e "\n$(cat "${EXPLAIN_FILE}")" 13 | echo -e "----------\n" 14 | 15 | echo -e "All these paths are configurable. If you are expecting a path but not seeing it here," 16 | echo -e "it may be turned off in the settings.\n\n" -------------------------------------------------------------------------------- /list_search_locations.ps1: -------------------------------------------------------------------------------- 1 | 2 | $PATHS=$args 3 | 4 | clear 5 | Write-Host "----------" 6 | Write-Host "The following files are on your search path:" -ForegroundColor Cyan 7 | foreach ($P in $PATHS) { 8 | Write-Host "- $P" 9 | } 10 | $fc=Get-Content "$Env:EXPLAIN_FILE" -Raw 11 | Write-Host "" 12 | Write-Host "$fc" 13 | Write-Host "----------" 14 | Write-Host "" 15 | Write-Host "All these paths are configurable. If you are expecting a path but not seeing it here," 16 | Write-Host "it may be turned off in the settings.\n\n" -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.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 | "type": "npm", 21 | "script": "compile", 22 | "problemMatcher": "$tsc-watch", 23 | "isBackground": true, 24 | "presentation": { 25 | "reveal": "never" 26 | }, 27 | "group": { 28 | "kind": "build", 29 | "isDefault": true 30 | }, 31 | }, 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /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, /* enable all strict type-checking options */ 12 | "moduleResolution": "Node" 13 | /* Additional Checks */ 14 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 15 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 16 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 17 | // "noEmitOnError": true, 18 | }, 19 | "exclude": [ 20 | // "node_modules", 21 | // ".vscode-test" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "args": [ 5 | "--extensionDevelopmentPath=${workspaceFolder}" 6 | ], 7 | "name": "Launch Extension", 8 | "outFiles": [ 9 | "${workspaceFolder}/out/**/*.js" 10 | ], 11 | "preLaunchTask": "npm: compile", 12 | "request": "launch", 13 | "type": "extensionHost", 14 | }, 15 | { 16 | "name": "Extension Tests", 17 | "type": "extensionHost", 18 | "request": "launch", 19 | "args": [ 20 | "--disable-extensions", 21 | "--extensionDevelopmentPath=${workspaceFolder}", 22 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 23 | ], 24 | "outFiles": [ 25 | "${workspaceFolder}/out/test/**/*.js" 26 | ], 27 | "preLaunchTask": "npm: compile" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /flight_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | echo "Pre-Flight check:" 5 | echo "-----------------" 6 | 7 | echo "Checking your OS version..." 8 | echo "OS: $(uname) $(uname -r)" 9 | echo "-----------------" 10 | 11 | echo "Checking you have the required command line tools installed..." 12 | 13 | test_installed() { 14 | if which "$1" >/dev/null 2>&1; then 15 | echo "$1: installed" 16 | else 17 | echo "$1: not installed" 18 | fi 19 | } 20 | 21 | test_installed bat 22 | test_installed fzf 23 | test_installed rg 24 | test_installed sed 25 | 26 | echo "-----------------" 27 | 28 | echo "Checking versions of the installed command line tools..." 29 | echo "bat version: $(bat --version)" 30 | echo "fzf version: $(fzf --version)" 31 | echo "rg version : $(rg --version)" 32 | echo "-----------------" 33 | 34 | echo "OK" -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | April 2024 update: 2 | 3 | As of late, I haven't had a lot of time to work on this extension. There are some small issues, but 4 | on most systems, the extension works well and does what it's supposed to do. I therefore consider 5 | this extension feature complete and will likely no longer either implement or merge feature 6 | requests. This extension has remained largely ever since I released it almost three years ago, and 7 | I consider stability of the existing feature set more important than anything else. 8 | 9 | Due to its very nature, this extension relies heavily on bash scripts, which means it's hard to test 10 | and challenging to not regress when adding new functionality, which again can explain my reluctance 11 | to consider additional features at this point. 12 | 13 | Please feel free to report and fix issues, though. It may take time before I can get around to 14 | reviewing or fixing them. 15 | -------------------------------------------------------------------------------- /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 | color: true, 10 | timeout: 0, 11 | }); 12 | 13 | const testsRoot = path.resolve(__dirname, '..'); 14 | 15 | return new Promise((c, e) => { 16 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 17 | if (err) { 18 | return e(err); 19 | } 20 | 21 | // Add files to the test suite 22 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 23 | 24 | try { 25 | // Run the mocha test 26 | mocha.run(failures => { 27 | if (failures > 0) { 28 | e(new Error(`${failures} tests failed.`)); 29 | } else { 30 | c(); 31 | } 32 | }); 33 | } catch (err) { 34 | console.error(err); 35 | e(err); 36 | } 37 | }); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import { SSL_OP_EPHEMERAL_RSA } from 'constants'; 2 | import * as path from 'path'; 3 | import * as process from 'process'; 4 | 5 | import { runTests } from 'vscode-test'; 6 | 7 | async function main() { 8 | try { 9 | // The folder containing the Extension Manifest package.json 10 | // Passed to `--extensionDevelopmentPath` 11 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 12 | 13 | // The path to test runner 14 | // Passed to --extensionTestsPath 15 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 16 | 17 | // Download VS Code, unzip it and run the integration test 18 | await runTests({ 19 | extensionDevelopmentPath, 20 | extensionTestsPath, 21 | launchArgs: ['--disable-extensions'] }); 22 | } catch (err) { 23 | console.error('Failed to run tests'); 24 | process.exit(1); 25 | } 26 | } 27 | 28 | main(); 29 | -------------------------------------------------------------------------------- /flight_check.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "Pre-Flight check:" 2 | Write-Host "-----------------" 3 | 4 | Write-Host "Checking your OS version..." 5 | Write-Host ("OS: " + ([Environment]::OSVersion."VersionString")) 6 | Write-Host "-----------------" 7 | 8 | function InPath($tool, $toolAlias = $false) { 9 | if (!(Get-Command $tool -ErrorAction SilentlyContinue) -and ($toolAlias -ne $false) -and !(Get-Command $toolAlias -ErrorAction SilentlyContinue)) { 10 | Write-Host "$($tool): not installed" -ForegroundColor Red 11 | } else { 12 | Write-Host "$($tool): installed" -ForegroundColor Green 13 | } 14 | } 15 | 16 | Write-Host "Checking you have the required command line tools installed..." -ForegroundColor Cyan 17 | InPath "bat" 18 | InPath "fzf" 19 | InPath "rg" 20 | Write-Host "-----------------" 21 | 22 | Write-Host "Checking versions of the installed command line tools..." 23 | Write-Host "bat version: $(bat --version)" 24 | Write-Host "fzf version: $(fzf --version)" 25 | Write-Host "rg version: $(rg --version)" 26 | Write-Host "-----------------" 27 | 28 | Write-Host "OK" 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tom Rijndorp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | BAT_VER=v0.18.1 5 | BAT_MAC=bat-${BAT_VER}-x86_64-apple-darwin 6 | FZF_VER=0.27.2 7 | FZF_MAC=fzf-${FZF_VER}-darwin_amd64 8 | RG_VER=13.0.0 9 | RG_MAC=ripgrep-${RG_VER}-x86_64-apple-darwin 10 | BIN=/usr/local/bin 11 | 12 | if [[ $(uname) == Darwin ]]; then 13 | cd /tmp 14 | curl -sL "https://github.com/sharkdp/bat/releases/download/$BAT_VER/$BAT_MAC.tar.gz" > bat.tar.gz 15 | curl -sL "https://github.com/junegunn/fzf/releases/download/$FZF_VER/$FZF_MAC.zip" > fzf.zip 16 | curl -sL "https://github.com/BurntSushi/ripgrep/releases/download/$RG_VER/$RG_MAC.tar.gz" > rg.tar.gz 17 | tar -xf bat.tar.gz 18 | tar -xf rg.tar.gz 19 | unzip fzf.zip -d $FZF_MAC 20 | sudo cp $BAT_MAC/bat $BIN 21 | sudo cp $FZF_MAC/fzf $BIN 22 | sudo cp $RG_MAC/rg $BIN 23 | cd - 24 | 25 | else # Linux 26 | sudo apt-get update 27 | sudo apt-get install -y fzf 28 | # The funky install: See https://askubuntu.com/a/491086/582233 29 | sudo apt-get install -y -o Dpkg::Options::="--force-overwrite" bat ripgrep 30 | fi 31 | 32 | echo "Running flight check..." 33 | ./flight_check.sh -------------------------------------------------------------------------------- /shared.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ---------- functions ---------- 4 | 5 | # Join array without inserting a string when the array is empty 6 | array_join() { 7 | if [[ $# -gt 0 ]]; then 8 | for arg in "$@"; do 9 | printf "'%s' " "${arg}" 10 | done 11 | fi 12 | } 13 | 14 | # ---------- execute immediately ---------- 15 | # Code below gets executed as soon as this script is sourced. Think wisely! 16 | 17 | # ---------- Set up whether to use gitignore 18 | USE_GITIGNORE_OPT=() 19 | # Disable requiring export; we're sourcing this file. 20 | # shellcheck disable=SC2034 21 | if [[ "$USE_GITIGNORE" -eq 0 ]]; then USE_GITIGNORE_OPT=('--no-ignore'); fi 22 | 23 | # ---------- Set up an array for type filtering in rg 24 | IFS=: read -r -a TYPE_FILTER <<< "${TYPE_FILTER:-}" 25 | TYPE_FILTER_ARR=() 26 | for ENTRY in ${TYPE_FILTER[@]+"${TYPE_FILTER[@]}"}; do 27 | TYPE_FILTER_ARR+=("--type") 28 | TYPE_FILTER_ARR+=("$ENTRY") 29 | done 30 | 31 | # ---------- Set up glob patterns 32 | IFS=: read -r -a GLOB_PATTERNS <<< "$GLOBS" 33 | GLOBS=() 34 | # Quick note on ${X[@]+"${X[@]}"}: It's complicated. 35 | # https://stackoverflow.com/q/7577052/888916 36 | for ENTRY in ${GLOB_PATTERNS[@]+"${GLOB_PATTERNS[@]}"}; do 37 | GLOBS+=("--glob") 38 | GLOBS+=("$ENTRY") 39 | done 40 | 41 | # Parse fzf version 42 | FZF_VER=$(fzf --version) 43 | FZF_VER_NUM=$(echo "$FZF_VER" | awk '{print $1}') # get rid of "... (brew)", for example 44 | # shellcheck disable=SC2034 45 | FZF_VER_PT1=${FZF_VER:0:3} 46 | # shellcheck disable=SC2034 47 | FZF_VER_PT2=${FZF_VER:3:1} 48 | -------------------------------------------------------------------------------- /find_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -uo pipefail # No -e to support write to canary file after cancel 3 | 4 | . "$EXTENSION_PATH/shared.sh" 5 | 6 | PREVIEW_ENABLED=${FIND_FILES_PREVIEW_ENABLED:-1} 7 | PREVIEW_COMMAND=${FIND_FILES_PREVIEW_COMMAND:-'bat --decorations=always --color=always --plain {}'} 8 | PREVIEW_WINDOW=${FIND_FILES_PREVIEW_WINDOW_CONFIG:-'right:50%:border-left'} 9 | HAS_SELECTION=${HAS_SELECTION:-} 10 | RESUME_SEARCH=${RESUME_SEARCH:-} 11 | CANARY_FILE=${CANARY_FILE:-'/tmp/canaryFile'} 12 | QUERY='' 13 | 14 | # If we only have one directory to search, invoke commands relative to that directory 15 | PATHS=("$@") 16 | SINGLE_DIR_ROOT='' 17 | if [ ${#PATHS[@]} -eq 1 ]; then 18 | SINGLE_DIR_ROOT=${PATHS[0]} 19 | PATHS=() 20 | cd "$SINGLE_DIR_ROOT" || exit 21 | fi 22 | 23 | if [[ "$RESUME_SEARCH" -eq 1 ]]; then 24 | # ... or we resume the last search if that is desired 25 | if [[ -f "$LAST_QUERY_FILE" ]]; then 26 | QUERY="$(tail -n 1 "$LAST_QUERY_FILE")" 27 | fi 28 | elif [[ "$HAS_SELECTION" -eq 1 ]]; then 29 | QUERY="$(cat "$SELECTION_FILE")" 30 | fi 31 | 32 | # Some backwards compatibility stuff 33 | if [[ $FZF_VER_PT1 == "0.2" && $FZF_VER_PT2 -lt 7 ]]; then 34 | PREVIEW_WINDOW='right:50%' 35 | fi 36 | 37 | PREVIEW_STR=() 38 | if [[ "$PREVIEW_ENABLED" -eq 1 ]]; then 39 | PREVIEW_STR=(--preview "$PREVIEW_COMMAND" --preview-window "$PREVIEW_WINDOW") 40 | fi 41 | 42 | callfzf () { 43 | rg \ 44 | --files \ 45 | --hidden \ 46 | $(array_join ${USE_GITIGNORE_OPT+"${USE_GITIGNORE_OPT[@]}"}) \ 47 | --glob '!**/.git/' \ 48 | ${GLOBS[@]+"${GLOBS[@]}"} \ 49 | ${TYPE_FILTER_ARR[@]+"${TYPE_FILTER_ARR[@]}"} \ 50 | ${PATHS[@]+"${PATHS[@]}"} \ 51 | 2> /dev/null \ 52 | | fzf \ 53 | --cycle \ 54 | --multi \ 55 | --history $LAST_QUERY_FILE \ 56 | --query "${QUERY}" \ 57 | ${PREVIEW_STR[@]+"${PREVIEW_STR[@]}"} 58 | } 59 | 60 | VAL=$(callfzf) 61 | 62 | if [[ -z "$VAL" ]]; then 63 | echo canceled 64 | echo "1" > "$CANARY_FILE" 65 | exit 1 66 | else 67 | if [[ -n "$SINGLE_DIR_ROOT" ]]; then 68 | TMP=$(mktemp) 69 | echo "$VAL" > "$TMP" 70 | sed "s|^|$SINGLE_DIR_ROOT/|" "$TMP" > "$CANARY_FILE" 71 | rm "$TMP" 72 | else 73 | echo "$VAL" > "$CANARY_FILE" 74 | fi 75 | fi 76 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as mocha from 'mocha'; 3 | 4 | // You can import and use all API from the 'vscode' module 5 | // as well as import your extension to test it 6 | import * as vscode from 'vscode'; 7 | import * as os from 'os'; 8 | // import * as extension from '../../extension'; 9 | /* 10 | Testing notes: 11 | - There's a difference between running from inside VS Code vs running npm run tests. 12 | - VS Code: debug instance's workspace is undefined 13 | - npm run tests (on mac): workspace has two folders: 14 | 1. / (yes, root) 15 | 2. the repo root 16 | - VS Code: process.cwd() is / 17 | - npm run tests: process.cwd() is repo root. 18 | */ 19 | 20 | function sleep(ms: number) { 21 | return new Promise(resolve => setTimeout(resolve, ms)); 22 | } 23 | 24 | suite('Extension Test Suite', () => { 25 | vscode.window.showInformationMessage('Start all tests.'); 26 | 27 | test('Sample test', () => { 28 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 29 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 30 | }); 31 | 32 | test('Activate', async () => { 33 | console.log('ws folders: ', vscode.workspace.workspaceFolders); 34 | console.log('dirname: ', __dirname); 35 | console.log('cwd: ', process.cwd()); 36 | 37 | doTheThings(); 38 | 39 | await(sleep(2000)); 40 | }); 41 | }); 42 | 43 | async function doTheThings() { 44 | const extension = vscode.extensions.getExtension('TomRijndorp.find-it-faster'); 45 | assert(extension); 46 | const path = extension.extensionPath; 47 | assert(path); 48 | // assert (vscode.workspace.workspaceFolders === undefined); 49 | // vscode.workspace.getconfiguration().update('find-it-faster.general.defaultsearchlocation', __dirname, vscode.configurationtarget.global) 50 | // .then(() => { 51 | vscode.commands.executeCommand('find-it-faster.listSearchLocations'); 52 | // }) 53 | // .then(() => { 54 | // return vscode.window.activeTerminal?.sendText("flight_check.sh\n"); 55 | // }) 56 | // .then(() => { 57 | // assert.ok(vscode.window.activeTextEditor?.document.fileName.indexOf('flight_check.sh') !== undefined); 58 | // console.log(vscode.window.activeTextEditor.document.fileName.indexOf('flight_check.sh')); 59 | // }); 60 | } -------------------------------------------------------------------------------- /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 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press `F5` to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - ci-debug 8 | - release 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | matrix: 14 | os: [macos-latest, ubuntu-latest] # , windows-latest] Failing for unknown reason and we don't really test Windows anyway... 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Install Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: 18.x 25 | 26 | # --- fzf, rg, bat --- 27 | - name: Install FZF, ripgrep, bat 28 | run: src/test/install_deps.sh 29 | if: runner.os == 'Linux' || runner.os == 'macOS' 30 | 31 | # --- deps --- 32 | - name: NPM install 33 | run: npm install 34 | 35 | # --- build / test --- 36 | - name: Test 37 | run: xvfb-run -a npm test 38 | if: runner.os == 'Linux' 39 | 40 | - name: Test 41 | run: npm test 42 | if: runner.os != 'Linux' 43 | 44 | # --- package --- 45 | - name: Package 46 | run: npm run vscode:package 47 | if: runner.os == 'Linux' 48 | 49 | # - uses: actions/upload-artifact@v2 50 | # if: runner.os == 'Linux' 51 | # with: 52 | # name: find-it-faster-vsix 53 | # path: "*.vsix" 54 | 55 | # Unfortunately, this is very redundant. 56 | # Unless I can just push the vsix file that I built earlier, there's 57 | # no way around it, it seems. 58 | publish: 59 | if: github.ref == 'refs/heads/release' 60 | needs: [build] 61 | runs-on: ubuntu-latest 62 | steps: 63 | 64 | - name: Checkout 65 | uses: actions/checkout@v4 66 | 67 | - name: Install Node.js 68 | uses: actions/setup-node@v3 69 | with: 70 | node-version: 18.x 71 | 72 | # --- fzf, rg, bat --- 73 | - name: Install FZF, ripgrep, bat 74 | run: src/test/install_deps.sh 75 | if: runner.os == 'Linux' || runner.os == 'macOS' 76 | 77 | # --- npm deps --- 78 | - name: NPM install 79 | run: npm install 80 | 81 | # --- publish --- 82 | - name: Publish! 83 | env: 84 | VSCE_PAT: ${{ secrets.FIND_IT_FASTER_AZURE_PAT }} 85 | OPENVSX_TOKEN: ${{ secrets.FIND_IT_FASTER_OPENVSX_TOKEN }} 86 | run: | 87 | sudo apt-get install -y jq 88 | FIF_TAG=$(cat package.json | jq -r .version) 89 | npm run vscode:publish 90 | echo "Tagging release with $FIF_TAG..." 91 | git tag "$FIF_TAG" 92 | git push origin "$FIF_TAG" 93 | 94 | # Publish to openvsx 95 | npx ovsx publish -p "$OPENVSX_TOKEN" 96 | -------------------------------------------------------------------------------- /find_files.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | trap 5 | { 6 | # If we except, lets report it visually. Can help with debugging if there IS a problem 7 | # in here. 8 | Write-Host "EXCEPTION: $($PSItem.ToString())" -ForegroundColor Red 9 | Write-Host "$($PSItem.ScriptStackTrace)" 10 | Start-Sleep 10 11 | } 12 | 13 | # Get an environment variable with default value if not present 14 | function VGet($varname, $default) { 15 | if (Test-Path "$varname") { 16 | $val = (Get-Item $varname).Value 17 | if ("$val".Length -gt 0) { 18 | return $val 19 | } 20 | } 21 | return $default 22 | } 23 | 24 | # Get an array as an option separated list of values --glob x --glob y etc... 25 | function VOptGet($varname,$opt) { 26 | $ARR=@() 27 | $DATA=(VGet "$varname" "") 28 | if ("$DATA".Length -gt 0) { 29 | $DATA = $DATA.Split(":") 30 | foreach ($ENTRY in $DATA) { 31 | if ("$ENTRY".Length -gt 0) { 32 | $ARR+=" $opt " 33 | $ARR+="'$ENTRY'" 34 | } 35 | } 36 | } 37 | return $ARR 38 | } 39 | 40 | $USE_GITIGNORE_OPT="" 41 | if ( (VGet "env:USE_GITIGNORE" 0) -eq 0) { 42 | $USE_GITIGNORE_OPT="--no-ignore" 43 | } 44 | 45 | $TYPE_FILTER_ARR=VOptGet "env:TYPE_FILTER" "--type" 46 | $GLOBS=VOptGet "env:GLOBS" "--glob" 47 | 48 | # If we only have one directory to search, invoke commands relative to that directory 49 | $PATHS=$args 50 | $SINGLE_DIR_ROOT="" 51 | if ($PATHS.Count -eq 1) { 52 | $SINGLE_DIR_ROOT=$PATHS[0] 53 | if ( -not (Test-Path "$SINGLE_DIR_ROOT")) { 54 | Write-Host "Failed to push into: $SINGLE_DIR_ROOT" -ForegroundColor Red 55 | exit 1 56 | } 57 | Push-Location "$SINGLE_DIR_ROOT" 58 | $PATHS="" 59 | } 60 | 61 | $PREVIEW_ENABLED=VGet "env:FIND_FILES_PREVIEW_ENABLED" 0 62 | $PREVIEW_COMMAND=VGet "env:FIND_FILES_PREVIEW_COMMAND" 'bat --decorations=always --color=always --plain {}' 63 | $PREVIEW_WINDOW=VGet "env:FIND_FILES_PREVIEW_WINDOW_CONFIG" 'right:50%:border-left' 64 | $HAS_SELECTION=VGet "env:HAS_SELECTION" 0 65 | $SELECTION_FILE=VGet "env:SELECTION_FILE" "" 66 | $QUERY="" 67 | if ($HAS_SELECTION -eq 1 -and "$SELECTION_FILE".Length -gt 0) { 68 | $QUERY="`"$(Get-Content "$SELECTION_FILE" -Raw)`"" 69 | } 70 | 71 | $fzf_command = "fzf --cycle --multi" 72 | if ("$QUERY".Length -gt 0) { 73 | $fzf_command+=" --query" 74 | $fzf_command+=" " 75 | $fzf_command+="${QUERY}" 76 | } 77 | 78 | if ( $PREVIEW_ENABLED -eq 1){ 79 | $fzf_command+=" --preview '$PREVIEW_COMMAND' --preview-window $PREVIEW_WINDOW" 80 | } 81 | 82 | $expression = "rg --files --hidden $USE_GITIGNORE_OPT --glob '!**/.git/' $GLOBS $TYPE_FILTER_ARR $PATHS" + " | " + $fzf_command 83 | $result = Invoke-Expression( $expression ) 84 | 85 | # Output is filename, line number, character, contents 86 | if ("$result".Length -lt 1) { 87 | Write-Host canceled 88 | "1" | Out-File -FilePath "$Env:CANARY_FILE" -Encoding UTF8 89 | exit 1 90 | } else { 91 | if ("$SINGLE_DIR_ROOT".Length -gt 0) { 92 | Join-Path -Path "$SINGLE_DIR_ROOT" -ChildPath "$result" | Out-File -FilePath "$Env:CANARY_FILE" -Encoding UTF8 93 | } else { 94 | $result | Out-File -FilePath "$Env:CANARY_FILE" -Encoding UTF8 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /find_within_files.ps1: -------------------------------------------------------------------------------- 1 | 2 | trap 3 | { 4 | # If we except, lets report it visually. Can help with debugging if there IS a problem 5 | # in here. 6 | Write-Host "EXCEPTION: $($PSItem.ToString())" -ForegroundColor Red 7 | Write-Host "$($PSItem.ScriptStackTrace)" 8 | Start-Sleep 10 9 | } 10 | 11 | # Get an environment variable with default value if not present 12 | function VGet($varname, $default) { 13 | if (Test-Path "$varname") { 14 | $val = (Get-Item $varname).Value 15 | if ("$val".Length -gt 0) { 16 | return $val 17 | } 18 | } 19 | return $default 20 | } 21 | 22 | # Get an array as an option separated list of values --glob x --glob y etc... 23 | function VOptGet($varname,$opt) { 24 | $ARR=@() 25 | $DATA=(VGet "$varname" "") 26 | if ("$DATA".Length -gt 0) { 27 | $DATA = $DATA.Split(":") 28 | foreach ($ENTRY in $DATA) { 29 | if ("$ENTRY".Length -gt 0) { 30 | $ARR+=" $opt " 31 | $ARR+="`"$ENTRY`"" 32 | } 33 | } 34 | } 35 | return $ARR 36 | } 37 | 38 | $USE_GITIGNORE_OPT="" 39 | if ( (VGet "env:USE_GITIGNORE" 0) -eq 0) { 40 | $USE_GITIGNORE_OPT="--no-ignore" 41 | } 42 | 43 | $TYPE_FILTER_ARR=VOptGet "env:TYPE_FILTER" "--type" 44 | $GLOBS=VOptGet "env:GLOBS" "--glob" 45 | 46 | # If we only have one directory to search, invoke commands relative to that directory 47 | $PATHS=$args 48 | $SINGLE_DIR_ROOT="" 49 | if ($PATHS.Count -eq 1) { 50 | $SINGLE_DIR_ROOT=$PATHS[0] 51 | if ( -not (Test-Path "$SINGLE_DIR_ROOT")) { 52 | Write-Host "Failed to push into: $SINGLE_DIR_ROOT" -ForegroundColor Red 53 | exit 1 54 | } 55 | Push-Location "$SINGLE_DIR_ROOT" 56 | $PATHS="" 57 | } 58 | 59 | # 1. Search for text in files using Ripgrep 60 | # 2. Interactively restart Ripgrep with reload action 61 | # 3. Open the file 62 | $RG_PREFIX="rg "` 63 | + "--column "` 64 | + "--hidden "` 65 | + "$USE_GITIGNORE_OPT "` 66 | + "--line-number "` 67 | + "--no-heading "` 68 | + "--color=always "` 69 | + "--smart-case "` 70 | + "--colors `"match:fg:green`" "` 71 | + "--colors `"path:fg:white`" "` 72 | + "--colors `"path:style:nobold`" "` 73 | + "--glob `"!**/.git/`" "` 74 | + "$GLOBS" 75 | 76 | if ($TYPE_FILTER_ARR.Count -gt 0) { 77 | $RG_PREFIX+="$TYPE_FILTER_ARR" 78 | } 79 | #RG_PREFIX+=(" 2> /dev/null") 80 | $PREVIEW_ENABLED=VGet "env:FIND_WITHIN_FILES_PREVIEW_ENABLED" 0 81 | $PREVIEW_COMMAND=VGet "env:FIND_WITHIN_FILES_PREVIEW_COMMAND" 'bat --decorations=always --color=always {1} --highlight-line {2} --style=header,grid' 82 | $PREVIEW_WINDOW=VGet "env:FIND_WITHIN_FILES_PREVIEW_WINDOW_CONFIG" 'right:border-left:50%:+{2}+3/3:~3' 83 | $HAS_SELECTION=VGet "env:HAS_SELECTION" 0 84 | $SELECTION_FILE=VGet "env:SELECTION_FILE" "" 85 | # We match against the beginning of the line so everything matches but nothing gets highlighted... 86 | $QUERY="`"^`"" 87 | $INITIAL_QUERY="" # Don't show initial "^" regex in fzf 88 | if ($HAS_SELECTION -eq 1 -and "$SELECTION_FILE".Length -gt 0) { 89 | # ... or against the selection if we have one 90 | $QUERY="`"$(Get-Content "$SELECTION_FILE" -Raw)`"" 91 | $INITIAL_QUERY="$QUERY" # Do show the initial query when it's not "^" 92 | } 93 | 94 | $FZF_CMD="$RG_PREFIX $QUERY $PATHS" 95 | Write-Host "$FZF_CMD" 96 | $Env:FZF_DEFAULT_COMMAND="$FZF_CMD" 97 | 98 | $QUERYPARAM="" 99 | if ("$INITIAL_QUERY".Length -gt 0) { 100 | $QUERYPARAM="--query" 101 | } 102 | 103 | if( $QUERYPARAM -ne "" ) 104 | { 105 | if($PREVIEW_ENABLED -eq 1) { 106 | # I can't get it not to report an error || true trick doesn't work in powershell. 107 | # $ErrorActionPreference="SilentlyContinue"; 108 | $result=fzf --delimiter ":" --phony "$QUERYPARAM" "$INITIAL_QUERY" --ansi --cycle --bind "change:reload:powershell -m Start-Sleep .1; $RG_PREFIX {q} $PATHS; ''" --preview "$PREVIEW_COMMAND" --preview-window "$PREVIEW_WINDOW" 109 | } else { 110 | $result=fzf --delimiter ":" --phony "$QUERYPARAM" "$INITIAL_QUERY" --ansi --cycle --bind "change:reload:powershell -m Start-Sleep .1; $RG_PREFIX {q} $PATHS; ''" 111 | } 112 | } else { 113 | if($PREVIEW_ENABLED -eq 1) { 114 | $result=fzf --delimiter ":" --ansi --cycle --bind "change:reload:powershell -m Start-Sleep .1; $RG_PREFIX {q} $PATHS; ''" --preview "$PREVIEW_COMMAND" --preview-window "$PREVIEW_WINDOW" 115 | } else { 116 | $result=fzf --delimiter ":" --ansi --cycle --bind "change:reload:powershell -m Start-Sleep .1; $RG_PREFIX {q} $PATHS; ''" 117 | } 118 | } 119 | 120 | # Output is filename, line number, character, contents 121 | if ("$result".Length -lt 1) { 122 | Write-Host canceled 123 | "1" | Out-File -FilePath "$Env:CANARY_FILE" -Encoding UTF8 124 | exit 1 125 | } else { 126 | if ("$SINGLE_DIR_ROOT".Length -gt 0) { 127 | Join-Path -Path "$SINGLE_DIR_ROOT" -ChildPath "$result" | Out-File -FilePath "$Env:CANARY_FILE" -Encoding UTF8 128 | } else { 129 | $result | Out-File -FilePath "$Env:CANARY_FILE" -Encoding UTF8 130 | } 131 | } -------------------------------------------------------------------------------- /find_within_files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uo pipefail # No -e to support write to canary file after cancel 3 | 4 | . "$EXTENSION_PATH/shared.sh" 5 | 6 | # If we only have one directory to search, invoke commands relative to that directory 7 | PATHS=("$@") 8 | SINGLE_DIR_ROOT='' 9 | if [ ${#PATHS[@]} -eq 1 ]; then 10 | SINGLE_DIR_ROOT=${PATHS[0]} 11 | PATHS=() 12 | cd "$SINGLE_DIR_ROOT" || exit 13 | fi 14 | 15 | # 1. Search for text in files using Ripgrep 16 | # 2. Interactively restart Ripgrep with reload action 17 | # 3. Open the file 18 | # shellcheck disable=SC2207 19 | RG_PREFIX=(rg 20 | --column 21 | --hidden 22 | $(array_join ${USE_GITIGNORE_OPT+"${USE_GITIGNORE_OPT[@]}"}) 23 | --line-number 24 | --no-heading 25 | --color=always 26 | --smart-case 27 | --colors 'match:fg:green' 28 | --colors 'path:fg:white' 29 | --colors 'path:style:nobold' 30 | --glob "'!**/.git/'" 31 | $(array_join "${GLOBS[@]+"${GLOBS[@]}"}") 32 | ) 33 | if [[ ${#TYPE_FILTER_ARR[@]} -gt 0 ]]; then 34 | RG_PREFIX+=("$(printf "%s " "${TYPE_FILTER_ARR[@]}")") 35 | fi 36 | RG_PREFIX+=(" 2> /dev/null") 37 | 38 | PREVIEW_ENABLED=${FIND_WITHIN_FILES_PREVIEW_ENABLED:-1} 39 | PREVIEW_COMMAND=${FIND_WITHIN_FILES_PREVIEW_COMMAND:-'bat --decorations=always --color=always {1} --highlight-line {2} --style=header,grid'} 40 | PREVIEW_WINDOW=${FIND_WITHIN_FILES_PREVIEW_WINDOW_CONFIG:-'right:border-left:50%:+{2}+3/3:~3'} 41 | HAS_SELECTION=${HAS_SELECTION:-} 42 | RESUME_SEARCH=${RESUME_SEARCH:-} 43 | FUZZ_RG_QUERY=${FUZZ_RG_QUERY:-} 44 | # We match against the beginning of the line so everything matches but nothing gets highlighted... 45 | QUERY='^' 46 | INITIAL_QUERY='' # Don't show initial "^" regex in fzf 47 | INITIAL_POS='1' 48 | if [[ "$RESUME_SEARCH" -eq 1 ]]; then 49 | # ... or we resume the last search if that is desired 50 | if [[ -f "$LAST_QUERY_FILE" ]]; then 51 | QUERY="$(tail -n 1 "$LAST_QUERY_FILE")" 52 | INITIAL_QUERY="$QUERY" # Do show the initial query when it's not "^" 53 | if [[ -f "$LAST_POS_FILE" ]]; then 54 | read -r pos < "$LAST_POS_FILE" 55 | ((pos++)) # convert index to position 56 | INITIAL_POS="$pos" 57 | fi 58 | fi 59 | elif [[ "$HAS_SELECTION" -eq 1 ]]; then 60 | # ... or against the selection if we have one 61 | QUERY="$(cat "$SELECTION_FILE")" 62 | INITIAL_QUERY="$QUERY" # Do show the initial query when it's not "^" 63 | fi 64 | 65 | # Some backwards compatibility stuff 66 | if [[ $FZF_VER_PT1 == "0.2" && $FZF_VER_PT2 -lt 7 ]]; then 67 | if [[ "$PREVIEW_COMMAND" != "$FIND_WITHIN_FILES_PREVIEW_COMMAND" ]]; then 68 | PREVIEW_COMMAND='bat {1} --color=always --highlight-line {2} --line-range {2}:' 69 | fi 70 | if [[ "$PREVIEW_WINDOW" != "$FIND_WITHIN_FILES_PREVIEW_WINDOW_CONFIG" ]]; then 71 | PREVIEW_WINDOW='right:50%' 72 | fi 73 | fi 74 | 75 | PREVIEW_STR=() 76 | if [[ "$PREVIEW_ENABLED" -eq 1 ]]; then 77 | PREVIEW_STR=(--preview "$PREVIEW_COMMAND" --preview-window "$PREVIEW_WINDOW") 78 | fi 79 | 80 | # dummy fallback binding because fzf v<0.36 does not support `load` and I did not figure out how to 81 | # conditionally set the entire binding string (i.e., with the "--bind" part) 82 | RESUME_POS_BINDING="backward-eof:ignore" 83 | if [[ "$(printf '%s\n' "$FZF_VER_NUM" "0.36" | sort -V | head -n 1)" == "0.36" ]]; then 84 | # fzf version is greater or equal 0.36, so the `load` trigger is supported 85 | RESUME_POS_BINDING="load:pos($INITIAL_POS)" 86 | fi 87 | 88 | RG_QUERY_PARSING="{q}" 89 | if [[ "$FUZZ_RG_QUERY" -eq 1 ]]; then 90 | RG_QUERY_PARSING="\$(echo {q} | sed 's/ /.*/g')" 91 | QUERY="$(echo $QUERY | sed 's/ /.*/g')" 92 | fi 93 | 94 | RG_PREFIX_STR=$(array_join "${RG_PREFIX+"${RG_PREFIX[@]}"}") 95 | RG_PREFIX_STR="${RG_PREFIX+"${RG_PREFIX[@]}"}" 96 | FZF_CMD="${RG_PREFIX+"${RG_PREFIX[@]}"} '$QUERY' $(array_join "${PATHS[@]+"${PATHS[@]}"}")" 97 | 98 | # echo $FZF_CMD 99 | echo "$RG_PREFIX_STR" 100 | # exit 1 101 | # IFS sets the delimiter 102 | # -r: raw 103 | # -a: array 104 | # Quick note on ${PREVIEW_STR[@]+"${PREVIEW_STR[@]}"}: Don't ask. 105 | # https://stackoverflow.com/q/7577052/888916 106 | IFS=: read -ra VAL < <( 107 | FZF_DEFAULT_COMMAND="$FZF_CMD" \ 108 | fzf --ansi \ 109 | --cycle \ 110 | --bind "change:reload:sleep 0.1; $RG_PREFIX_STR $RG_QUERY_PARSING $(array_join "${PATHS[@]+"${PATHS[@]}"}") || true" \ 111 | --delimiter : \ 112 | --history $LAST_QUERY_FILE \ 113 | --bind "enter:execute(echo {n} > $LAST_POS_FILE)+accept" \ 114 | --bind "$RESUME_POS_BINDING" \ 115 | --phony --query "$INITIAL_QUERY" \ 116 | ${PREVIEW_STR[@]+"${PREVIEW_STR[@]}"} \ 117 | ) 118 | # Output is filename, line number, character, contents 119 | 120 | if [[ ${#VAL[@]} -eq 0 ]]; then 121 | echo canceled 122 | echo "1" > "$CANARY_FILE" 123 | exit 1 124 | else 125 | FILENAME=${VAL[0]}:${VAL[1]}:${VAL[2]} 126 | if [[ -n "$SINGLE_DIR_ROOT" ]]; then 127 | echo "$SINGLE_DIR_ROOT/$FILENAME" > "$CANARY_FILE" 128 | else 129 | echo "$FILENAME" > "$CANARY_FILE" 130 | fi 131 | fi 132 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "find-it-faster", 3 | "displayName": "FindItFaster", 4 | "publisher": "TomRijndorp", 5 | "icon": "media/icon.png", 6 | "repository": { 7 | "url": "https://github.com/tomrijndorp/vscode-finditfaster" 8 | }, 9 | "description": "Find it, but faster! Leveraging fzf and rg.", 10 | "keywords": [ 11 | "ag", 12 | "bat", 13 | "batcat", 14 | "find it faster", 15 | "files", 16 | "find", 17 | "finder", 18 | "fuzzy", 19 | "fzf", 20 | "grep", 21 | "open", 22 | "rg", 23 | "ripgrep", 24 | "search", 25 | "within" 26 | ], 27 | "version": "0.0.39", 28 | "engines": { 29 | "vscode": "^1.90.0" 30 | }, 31 | "categories": [ 32 | "Other" 33 | ], 34 | "activationEvents": [ 35 | "onCommand:find-it-faster.findFiles", 36 | "onCommand:find-it-faster.findFilesWithType", 37 | "onCommand:find-it-faster.findWithinFiles", 38 | "onCommand:find-it-faster.findWithinFilesWithType", 39 | "onCommand:find-it-faster.listSearchLocations" 40 | ], 41 | "main": "./out/extension.js", 42 | "contributes": { 43 | "commands": [ 44 | { 45 | "command": "find-it-faster.findFiles", 46 | "title": "Find It Faster: search file" 47 | }, 48 | { 49 | "command": "find-it-faster.findFilesWithType", 50 | "title": "Find It Faster: search file (with type filter)" 51 | }, 52 | { 53 | "command": "find-it-faster.findWithinFiles", 54 | "title": "Find It Faster: search within files" 55 | }, 56 | { 57 | "command": "find-it-faster.findWithinFilesWithType", 58 | "title": "Find It Faster: search within files (with type filter)" 59 | }, 60 | { 61 | "command": "find-it-faster.listSearchLocations", 62 | "title": "Find It Faster: list search locations (modify in extension settings)" 63 | }, 64 | { 65 | "command": "find-it-faster.resumeSearch", 66 | "title": "Find It Faster: resume last search" 67 | } 68 | ], 69 | "keybindings": [ 70 | { 71 | "command": "find-it-faster.findFiles", 72 | "mac": "cmd+shift+j", 73 | "linux": "ctrl+shift+j" 74 | }, 75 | { 76 | "command": "find-it-faster.findWithinFiles", 77 | "mac": "cmd+shift+u", 78 | "linux": "ctrl+shift+u" 79 | }, 80 | { 81 | "command": "find-it-faster.findWithinFilesWithType", 82 | "mac": "ctrl+cmd+shift+u", 83 | "linux": "ctrl+shift+alt+u" 84 | } 85 | ], 86 | "configuration": { 87 | "title": "FindItFaster", 88 | "properties": { 89 | "find-it-faster.general.hideTerminalAfterSuccess": { 90 | "markdownDescription": "Extensions have limited control over the terminal panel. Ideally, it would be possible to automatically switch back to the previous thing you were doing (e.g. hide the terminal, show the terminal you're working on, show the tasks panel if that's what you were on), but we can't. Select here whether you want the terminal to be hidden or remain shown after invoking a command that fails / is canceled.", 91 | "type": "boolean", 92 | "default": true 93 | }, 94 | "find-it-faster.general.hideTerminalAfterFail": { 95 | "markdownDescription": "Extensions have limited control over the terminal panel. Ideally, it would be possible to automatically switch back to the previous thing you were doing (e.g. hide the terminal, show the terminal you're working on, show the tasks panel if that's what you were on), but we can't. Select here whether you want the terminal to be hidden or remain shown after invoking a command that succeeds.", 96 | "type": "boolean", 97 | "default": true 98 | }, 99 | "find-it-faster.general.clearTerminalAfterUse": { 100 | "markdownDescription": "Sends a 'clear' command after invoking a terminal command for a cleaner look", 101 | "type": "boolean", 102 | "default": true 103 | }, 104 | "find-it-faster.general.killTerminalAfterUse": { 105 | "markdownDescription": "Kills this extension's terminal such that your previous terminal gets back into focus. Comes with a performance penalty for creating a new terminal on every invocation.", 106 | "type": "boolean", 107 | "default": false 108 | }, 109 | "find-it-faster.general.showMaximizedTerminal": { 110 | "markdownDescription": "Show the terminal in full screen. Works especially nicely if you set the \"hide after success / fail\" options in these preferences.", 111 | "type": "boolean", 112 | "default": false 113 | }, 114 | "find-it-faster.general.useGitIgnoreExcludes": { 115 | "markdownDescription": "Use `.gitignore` entries (if present) to limit search paths", 116 | "type": "boolean", 117 | "default": true 118 | }, 119 | "find-it-faster.general.useWorkspaceSearchExcludes": { 120 | "markdownDescription": "In order to speed up VS Code's native search functionality, you can use the `search.exclude` setting. If checked, this extension will honor the same exclude patterns (as long as they follow the standard \"gitignore glob style\" that `rg` uses.", 121 | "type": "boolean", 122 | "default": true 123 | }, 124 | "find-it-faster.general.additionalSearchLocations": { 125 | "markdownDescription": "Search files in these locations", 126 | "type": "array", 127 | "default": [] 128 | }, 129 | "find-it-faster.general.additionalSearchLocationsWhen": { 130 | "markdownDescription": "When to search the files listed in `general.searchLocations`", 131 | "type": "string", 132 | "enum": [ 133 | "always", 134 | "never", 135 | "noWorkspaceOnly" 136 | ], 137 | "enumDescriptions": [ 138 | "Always search these locations", 139 | "Never search these locations (setting useful for individual workspace overrides)", 140 | "Only when no workspace is open" 141 | ], 142 | "default": "always" 143 | }, 144 | "find-it-faster.general.searchWorkspaceFolders": { 145 | "markdownDescription": "Search the folders defined in the workspace", 146 | "type": "boolean", 147 | "default": true 148 | }, 149 | "find-it-faster.general.searchCurrentWorkingDirectory": { 150 | "markdownDescription": "Search the current working directory (`process.cwd()`)", 151 | "type": "string", 152 | "enum": [ 153 | "always", 154 | "never", 155 | "noWorkspaceOnly" 156 | ], 157 | "enumDescriptions": [ 158 | "Always search the working directory", 159 | "Never search the working directory", 160 | "Only search the working directory when no workspace is open" 161 | ], 162 | "default": "noWorkspaceOnly" 163 | }, 164 | "find-it-faster.general.batTheme": { 165 | "markdownDescription": "The color theme to use for `bat` (see `bat --list-themes`)", 166 | "type": "string", 167 | "default": "1337" 168 | }, 169 | "find-it-faster.general.openFileInPreviewEditor": { 170 | "markdownDescription": "When set to `true` files open in a Preview Editor. Use in conjunction with the `workbench.editor.enablePreivew` setting.", 171 | "type": "boolean", 172 | "default": false 173 | }, 174 | "find-it-faster.findFiles.showPreview": { 175 | "markdownDescription": "Show a preview window when searching files", 176 | "type": "boolean", 177 | "default": true 178 | }, 179 | "find-it-faster.findFiles.previewCommand": { 180 | "markdownDescription": "When populated: Used by `fzf` to produce the preview. Use `{}` to indicate the filename. Example: `bat {}`.", 181 | "type": "string", 182 | "default": "" 183 | }, 184 | "find-it-faster.findFiles.previewWindowConfig": { 185 | "markdownDescription": "When populated: Used by `fzf` to determine position and look of the preview window. See the `fzf` documentation. Example for a horizontal split: `top,50%`.", 186 | "type": "string", 187 | "default": "" 188 | }, 189 | "find-it-faster.findWithinFiles.showPreview": { 190 | "markdownDescription": "Show a preview window when searching within files", 191 | "type": "boolean", 192 | "default": true 193 | }, 194 | "find-it-faster.findWithinFiles.previewCommand": { 195 | "markdownDescription": "When populated: Used by `fzf` to produce the preview when searching within files. Use `{1}` to indicate the filename, `{2}` for the line number. Example using `cat` and `awk` instead of `bat`: `cat {1} | awk '{if (NR>={2}) print}'`.", 196 | "type": "string", 197 | "default": "" 198 | }, 199 | "find-it-faster.findWithinFiles.previewWindowConfig": { 200 | "markdownDescription": "When populated: Used by `fzf` to determine position and look of the preview window. See the `fzf` documentation. Example for a horizontal split: `top,50%,border-bottom,+{2}+3/3,~3`.", 201 | "type": "string", 202 | "default": "" 203 | }, 204 | "find-it-faster.findWithinFiles.fuzzRipgrepQuery": { 205 | "markdownDescription": "By default, matching in \"Find Within Files\" is exact. Enabling this option relaxes the matching by slightly fuzzing the query that is sent to `rg`: it treats whitespaces as wildcards (`.*`). For example, when enabled, the query `function bar` will match `function foobar`. Otherwise, it won't. This setting does not work on Windows at the moment.", 206 | "type": "boolean", 207 | "default": false 208 | }, 209 | "find-it-faster.advanced.disableStartupChecks": { 210 | "markdownDescription": "By default, we check that you have `fzf`, `rg`, and `bat` on your path. If you'd like to disable these checks because you're getting creative (e.g. use `cat` instead of `bat`), check this box. Obviously you may see errors on your terminal if we do call one of these programs when they are not present!", 211 | "type": "boolean", 212 | "default": false 213 | }, 214 | "find-it-faster.advanced.useEditorSelectionAsQuery": { 215 | "markdownDescription": "By default, if you have an active editor with a text selection, we'll use that to populate the prompt in `fzf` such that it will start filtering text directly. Uncheck to disable.", 216 | "type": "boolean", 217 | "default": true 218 | }, 219 | "find-it-faster.general.restoreFocusTerminal": { 220 | "markdownDescription": "When enabled, the extension will restore focus to the previously active terminal after executing a command. This is useful if you frequently switch between the terminal and other parts of VS Code. Note: due to limitations in VS Code, will not focus other panel tabs such as problems/output/etc.", 221 | "type": "boolean", 222 | "default": false 223 | }, 224 | "find-it-faster.general.useTerminalInEditor": { 225 | "markdownDescription": "When enabled, the extension will create the terminal in the main editor stage.", 226 | "type": "boolean", 227 | "default": false 228 | }, 229 | "find-it-faster.general.shellPathForTerminal": { 230 | "markdownDescription": "Set the path for the shell (terminal type) to be used.", 231 | "type": "string", 232 | "default": "" 233 | }, 234 | "find-it-faster.general.shellArgsForTerminal": { 235 | "markdownDescription": "Set the args for the shell (terminal type) to be used.", 236 | "type": "array | undefined", 237 | "default": "undefined" 238 | } 239 | } 240 | } 241 | }, 242 | "scripts": { 243 | "vscode:prepublish": "npm run compile", 244 | "compile": "tsc --noEmitOnError --strict -p ./", 245 | "watch": "tsc -watch -p ./", 246 | "pretest": "npm run compile && npm run lint", 247 | "lint": "eslint src --ext ts", 248 | "test": "node ./out/test/runTest.js", 249 | "vscode:package": "vsce package --githubBranch main", 250 | "vscode:publish": "vsce publish --githubBranch main", 251 | "minimist": ">=1.2.6", 252 | "qs": ">=6.10.3", 253 | "ansi-regex": ">=5.0.1", 254 | "nanoid": ">=3.1.31" 255 | }, 256 | "devDependencies": { 257 | "@types/glob": "^7.1.3", 258 | "@types/mocha": "^8.2.2", 259 | "@types/node": "14.x", 260 | "@types/vscode": "^1.90.0", 261 | "@typescript-eslint/eslint-plugin": "^4.26.0", 262 | "@typescript-eslint/parser": "^4.26.0", 263 | "eslint": "^7.27.0", 264 | "glob": "^7.1.7", 265 | "mocha": "^10.4.0", 266 | "typescript": "^4.3.2", 267 | "vsce": "^2.15.0", 268 | "vscode-test": "^1.6.1" 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FindItFaster 2 | 3 | [![CI pipeline - release](https://github.com/tomrijndorp/vscode-finditfaster/actions/workflows/ci.yml/badge.svg?branch=release)](https://github.com/tomrijndorp/vscode-finditfaster/actions?query=branch%3Amain) 4 | ![Platform support](https://img.shields.io/badge/platform-macos%20%7C%20linux%20%7C%20windows%20(wsl)%20%7C%20windows%20powershell%20(experimental)-334488) 5 | 6 | Finds files and text within files, but faster than VS Code normally does. 7 | 8 | Make sure to check the [Requirements](#requirements) below (TL;DR: have `fzf`, `rg`, `bat` on your 9 | `PATH`). 10 | 11 |
12 | 13 | Default key bindings: 14 | - `cmd+shift+j` / `ctrl+shift+j` to search files, 15 | - `cmd+shift+u` / `ctrl+shift+u` to search for text within files, 16 | - `cmd+shift+ctrl+u` / `ctrl+shift+alt+u` to search for text within files with type pre-filtering. 17 | 18 | You can change these using VS Code's keyboard shortcuts. 19 | 20 | Recommended settings: 21 | - set `find-it-faster.general.useTerminalInEditor` to true to have the extension window open in the 22 | editor panel rather than in the terminal panel. 23 | 24 |
25 | 26 | Native Windows support is now implemented (experimental)! Also see 27 | [Known Issues](#known_issues). 28 | 29 |
30 | 31 | ## Features 32 | This plugin is useful if you deal with very large projects with lots of files (which makes VS Code's 33 | search functionality quite slow), or when you simply love using `fzf` and `rg` and would like to 34 | bring those tools inside VS Code, similar to how the excellent `fzf.vim` plugin works for Vim. 35 | 36 | This extension exposes four commands: 37 | 1. Search for files and open them. Uses a combination of `fzf`, `rg`, and `bat`. 38 | 2. Search within files for text and open them. Uses a combination of `fzf`, `rg`, and `bat`. 39 | 3. Like 2., but you can limit the file types that will be searched. 40 | 4. Resume search. Repeats the last run command with the previous query prepopulated. 41 | 42 | If your active text editor has a selection, it will be used as the initial query (you can disable 43 | this setting). 44 | 45 | ⬇️  **Find files** 46 | ![Find Files](media/find_files.gif) 47 | 48 | ⬇️  **Find text within files** 49 | ![Find Within Files](media/find_within_files.gif) 50 | 51 | ⬇️  **Find text within files, with file type filter** 52 | ![Find Within Files](media/find_within_files_with_filter.gif) 53 | 54 | This extension has also been tested on remote workspaces (e.g. SSH sessions). 55 | 56 |
57 | 58 | 59 | ## Requirements 60 | 61 | This plugin opens a terminal inside VS Code. Make sure that you can run `fzf`, `rg`, `bat`, and 62 | `sed` by running these commands directly in your terminal. If those work, this plugin will work as 63 | expected. If it doesn't, confirm that you are running recent versions of all three tools. 64 | 65 | If you're not familiar with these command line tools, you might want to check them out. They are 66 | awesome tools that can be individually used and make you more productive. And when combined such as 67 | for instance in this extension, they're very powerful. They're available for many platforms and easy 68 | to install using package managers or by simply installing a binary. 69 | 70 | - [`fzf` ("command-line fuzzy finder")](https://github.com/junegunn/fzf) 71 | - [`rg` ("ripgrep")](https://github.com/BurntSushi/ripgrep) 72 | - [`bat` ("a cat clone with wings")](https://github.com/sharkdp/bat) 73 | 74 | I have no affiliation with any of these tools, but hugely appreciate them, and wanted to bring them 75 | into a VS Code context. 76 | 77 |
78 | 79 | ## Extension Settings 80 | 81 | See the settings for this extension in the GUI. 82 | You might want to play with `fzf`, `rg` and `bat` on the command line and read their manuals in 83 | order to get a better understanding of some of the settings in this extension. It will be worth 84 | your time. 85 | 86 | `fzf` can also be configured through various environment variables. This extension does nothing to 87 | disable that behavior, so feel free to use those. You can also check whether `fzf` is running inside 88 | this extension by checking whether the `FIND_IT_FASTER_ACTIVE` environment variable is set. 89 | 90 |
91 | 92 | 93 | ## Known Issues 94 | 95 | **Windows**. There are two ways of running this extension on Windows: 96 | 1. **Natively using Powershell**. This feature was added as of May 2022 and is experimental at this 97 | stage. Please file an issue on [Github]( https://github.com/tomrijndorp/vscode-finditfaster/issues) 98 | if you find one. 99 | 2. **Through WSL** (Windows Subsystem for Linux). You can run this extension inside a [Remote-WSL 100 | workspace](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl). 101 | 102 | **Not tested on Docker / Github code spaces**. Will likely work without issues as the 103 | functionality is very similiar to other remote sessions (e.g. SSH, WSL). 104 | 105 | **Various small terminal issues**. VS Code gives developers little control over the terminal. We 106 | can't know if you typed text into the terminal we create, so that might interfere with the 107 | extension. There are various subtle ways in which in which things can break, many of which can't be 108 | detected. That said, if you don't touch the FindItFaster terminal, things should work well! 109 | 110 | ### **NixOS**: 111 | 112 | The bash scripts use a shebang that conflicts with NixOs: `#!/bin/bash` , as bash isn't 113 | available in `/bin`, a work around is to this to follow the instructions in [Issue #44](https://github.com/tomrijndorp/vscode-finditfaster/issues/44) 114 | and change the shebangs manually, after this, the extention should work normally. 115 | 116 |
117 | 118 | ## FAQ 119 | 120 | ### 🕹 _How do I control the fuzzy finder view?_ 121 | ➥ Whatever defaults are present on your system (and read by VS Code) are used. For `fzf`, this means 122 | <Ctrl+K> moves the selection up, <Ctrl+J> moves down, and <Enter> selects. You can 123 | also use the up and down arrows if that's your thing. <TAB> for multiple select when 124 | available. Read the excellent `fzf` [documentation](https://github.com/junegunn/fzf#readme) to learn 125 | more about using `fzf`. 126 | 127 | ### ⬆️ _I'm on Linux and I can't use Ctrl+K to navigate upwards in `fzf`._ 128 | ➥ Probably VS Code is waiting for you to complete a multi-step keyboard shortcut (chord). Change the 129 | following setting in your preferences to disable chords: 130 | ``` 131 | "terminal.integrated.allowChords": false 132 | ``` 133 | 134 | ### 🔍 _There's a file that cannot be found / searched through?_ 135 | ➥ This extension enables you to search through multiple directories: the process working directory, 136 | the workspace directories, and any additional directories you specify in the extension settings. 137 | What paths are included is configured through the settings. There's a `listSearchLocations` 138 | command that can show you which paths are currently being indexed. 139 | 140 | ### 🧘 _Can you give focus back to my editor / my problems panel / other?_ 141 | ➥ I don't the VS Code API enables me to do this. Shoot me a message if you think I'm mistaken and 142 | I'll try to make this better. 143 | 2023 Update: Added a setting `find-it-faster.general.killTerminalAfterUse`. When true, it will kill 144 | the extension's terminal which may result in VS Code focusing the previous one. It may help you. 145 | 2024 Update: I recommend using `find-it-faster.general.useTerminalInEditor`. This way, the 146 | extension window won't interfere with your other terminals, and you get more real estate for 147 | previewing files. 148 | 149 | ### 🐞 _I found a bug!_ 150 | ➥ Yeah, that's not unlikely. There are a lot of edge cases with this sort of tooling. Three options: 151 | 1. Shrug :) 152 | 2. File a Github issue. Please give detailed information as the devil is in the details. Please 153 | provide at least: 154 | - OS 155 | - VS Code version 156 | - Does it happen after you reset to default settings (if relevant)? 157 | - Anything special about your configuration / workspace. Did you have spaces in there? Is it 158 | on a network share or some other thing I definitely didn't test? Did you modify the extension 159 | settings? 160 | 3. For the most up to date information on contributing fixes and features, see `CONTRIBUTING.md`. 161 | 162 | ### 💩 _I don't like `fzf` / `rg` / `bat`. Can I just use `find`, `grep`, and `cat` or something else?_ 163 | ➥ You can actually already use other preview tools than `bat`, e.g. `cat`. I've left some hints in 164 | the settings. Outside of what's available, substituting other tools is not supported. 165 | 166 | ### 💲 Can I set FZF env vars that only work within this extension? 167 | ➥ You can, by adding something like this to `~/.bashrc` or whatever configures your enviroment: 168 | ```bash 169 | if [[ $FIND_IT_FASTER_ACTIVE -eq 1 ]]; then 170 | FZF_DEFAULT_OPTS='--height=50%' 171 | fi 172 | ``` 173 | 174 | ### 🪚 _Can I build in a feature myself / contribute in some way?_ 175 | ➥ For the most up to date information on contributing fixes and features, see `CONTRIBUTING.md`. 176 | 177 | ### 🤑 _Do you take donations?_ 178 | ➥ Thanks for asking, but no. The amount of work that went into this extension is tiny compared to 179 | the countless hours that have gone into the command line tools that are leveraged in this 180 | extension. Please support those tools instead. 181 | What I do appreciate is if you'd help others find this extension by spreading the word and/or 182 | leaving a rating! 183 | 184 |
185 | 186 | ## Release Notes 187 | 188 | ### 0.0.39 189 | - Add option to set a shell path to enable running with a non-default shell 190 | - Fixed an issue with wrong env var names in Windows scripts 191 | 192 | ### 0.0.38 193 | - Updated dependencies, addressed a bug that caused the extension to not open 194 | 195 | ### 0.0.37 (broken) 196 | - New setting to have the window appear in the editor panel: 197 | `find-it-faster.general.useTerminalInEditor`. Please try it out! 198 | - This extension is now also available on OpenVSX (and therefore VSCodium) 199 | 200 | ### 0.0.36 201 | - Testing publishing to OpenVSX 202 | 203 | ### 0.0.35 204 | - Testing publishing to OpenVSX 205 | 206 | ### 0.0.34 207 | - Improve initial checks on Windows 208 | - Fix some issues with text selection on Windows 209 | - Better handling of special characters in paths 210 | 211 | ### 0.0.33 212 | - Fixed an issue related to directories with special characters in them 213 | 214 | ### 0.0.32 215 | - Fixed a bug in cursor when opening files that were previously opened 216 | 217 | ### 0.0.31 218 | - Various Windows fixes 219 | 220 | ### 0.0.30 221 | - Remove unnecessary check for `sed` on Windows 222 | 223 | ### 0.0.29 224 | - Fixed an issue with the killTerminalAfterUse feature 225 | - Add option to restore the previously active terminal 226 | - Small improvements to resumeSearch and query fuzzing 227 | 228 | ### 0.0.28 229 | - Fixed a bug that caused the extension to stop working altogether. 230 | 231 | ### 0.0.27 232 | - Add setting to fuzzy match on space bar in `search within files` function 233 | - Add `FIND_IT_FASTER_ACTIVE` environment variable when running inside extension 234 | 235 | ### 0.0.26 236 | - Resume search added as a new command. Resumes the last invoked command with the same query. Thanks 237 | for implementing @alexrenz! 238 | - Add feature to kill terminal after each use such that focus goes back to the previous terminal 239 | 240 | ### 0.0.25 241 | - Fix for UTF-8 paths on Windows. Thanks @ch0pex! 242 | 243 | ### 0.0.24 244 | - No user facing changes 245 | 246 | ### 0.0.023 247 | - Address Dependabot security issues 248 | 249 | ### 0.0.22 250 | - Bug fix on Windows that caused files to not open. Thank you @ObieMunoz for reporting and 251 | @Coombszy! for submitting a fix! 252 | 253 | ### 0.0.21 254 | - Add option to open editors in preview window. Thank you @phrabos! 255 | 256 | ### 0.0.20 257 | - Experimental windows support! Thanks so much @ihdavids! 258 | 259 | ### 0.0.19 260 | - Add a new command: `search file (with type filter)`. Thanks for suggesting @prime31! 261 | - Add option to set the `bat` theme. Thanks for suggesting @mariush2! 262 | - Add `--cycle` to `fzf` call. Thanks for suggesting @shlomocarmeter! 263 | - Fix some bugs that caused `search within files` to behave incorrectly 264 | 265 | ### 0.0.18 266 | - Add an option to use the contents of `.gitignore` files to limit search paths (on by default) 267 | 268 | ### 0.0.17 269 | - Fix a bug that caused `search within files` to not search in all paths 270 | 271 | ### 0.0.16 272 | - Ensure text selection searches immediately 273 | - Small bug fix where the extension wouldn't correctly initialize when a text selection was present 274 | 275 | ### 0.0.15 276 | - Show relative paths when only one directory is on the search path 277 | 278 | ### 0.0.14 279 | - Fix a bug in `search within files` on Linux 280 | - Prevent custom PS1 prompt from leaking into user's history 281 | - Fix a bug regarding fzf version discovery (e.g. Homebrew, Ubuntu 21.04 installs) 282 | 283 | ### 0.0.13 284 | - Lower minimum required VS Code version to 1.48 285 | - Small bug fix in type filtering window 286 | - New screen captures in README 287 | 288 | ### 0.0.12 289 | - Option to "find within files", but pre-filter by file type. 290 | Exposes a new keyboard shortcut (default: `cmd+shift+ctrl+u` / `ctrl+shift+alt+u`). 291 | The selection is stateful; it will be kept across searches such that you can easily re-run a 292 | search with the same file types. Should add new screen captures, but will do that in 0.0.13. 293 | 294 | ### 0.0.11 295 | - Bug fix: bring back preview window in "Find Files" 296 | 297 | ### 0.0.10 298 | - No new features, but this version should be automatically published through Github Actions. 299 | 300 | ### 0.0.9 301 | - Much better search path support (see extension settings) 302 | - Option to add the process working directory under various conditions 303 | - Option to disable searching the workspace folders 304 | - Option to add additional search paths through the settings under various conditions 305 | - Better error message for native Windows use case 306 | - Filtering out stderr output when searching. Prevents messing up `fzf`'s layout when permissions 307 | errors arise during searching. 308 | - Add basic CI sanity checks and badge. 309 | - Small logo update 310 | 311 | ### 0.0.7 312 | - Text selections: if you have text selected, we'll use that to fill `fzf`'s query. There's an 313 | option to disable it. 314 | - Clean up some terminal spam 315 | 316 | ### 0.0.6 317 | - Honor search.exclude setting and add option to disable 318 | - Don't store command history 319 | - Always run bash in terminal, add warning to PS1 320 | 321 | ### 0.0.3 322 | - Support multiple sessions at the same time without interfering with one another. 323 | - Option to disable checks (e.g. `which bat`). Useful if you want to use e.g. `cat` instead. 324 | - Option to disable previews for each of the find commands 325 | - Settings overhaul; they're now empty when default. Enables some more flexibility on the backend. 326 | - Cosmetic improvements if using fzf >= 0.27 327 | 328 | ### 0.0.2 329 | - SSH support 🎉 330 | - Ignore .git directory 331 | - Always show error dialog box when a dependency isn't found 332 | - Default search location preference for when a session has no workspace 333 | - Add screen captures showing functionality 334 | - Add an ugly icon 335 | - Various smaller fixes 336 | 337 | ### 0.0.1 338 | You gotta start somewhere! 339 | 340 | Tested on these configurations: 341 | 342 | **Mac OS**: 343 | ``` 344 | OS : Darwin 20.1.0 (MacOS Big Sur 11.0.1) 345 | bat version: bat 0.18.0 346 | fzf version: 0.27.1 (brew) 347 | rg version : ripgrep 13.0.0 348 | ``` 349 | 350 | **Linux**: 351 | ``` 352 | OS : Linux 5.8.0-55-generic (Ubuntu 20.04) 353 | bat version: bat 0.12.1 354 | fzf version: 0.20.0 355 | rg version : ripgrep 12.1.1 (rev 7cb211378a) 356 | ``` 357 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO: 3 | * [ ] Show relative paths whenever possible 4 | * - This might be tricky. I could figure out the common base path of all dirs we search, I guess? 5 | * 6 | * Feature options: 7 | * [ ] Buffer of open files / show currently open files / always show at bottom => workspace.textDocuments is a bit curious / borked 8 | */ 9 | 10 | import * as assert from 'assert'; 11 | import * as cp from 'child_process'; 12 | import * as fs from 'fs'; 13 | import * as os from 'os'; 14 | import { tmpdir } from 'os'; 15 | import * as path from 'path'; 16 | import * as vscode from 'vscode'; 17 | 18 | // Let's keep it DRY and load the package here so we can reuse some data from it 19 | let PACKAGE: any; 20 | // Reference to the terminal we use 21 | let term: vscode.Terminal; 22 | let previousActiveTerminal: vscode.Terminal | null; 23 | let isExtensionChangedTerminal = false; 24 | 25 | // 26 | // Define the commands we expose. URIs are populated upon extension activation 27 | // because only then we'll know the actual paths. 28 | // 29 | interface Command { 30 | script: string, 31 | uri: vscode.Uri | undefined, 32 | preRunCallback: undefined | (() => boolean | Promise), 33 | postRunCallback: undefined | (() => void), 34 | } 35 | const commands: { [key: string]: Command } = { 36 | findFiles: { 37 | script: 'find_files', // we append a platform-specific extension later 38 | uri: undefined, 39 | preRunCallback: undefined, 40 | postRunCallback: undefined, 41 | }, 42 | findFilesWithType: { 43 | script: 'find_files', 44 | uri: undefined, 45 | preRunCallback: selectTypeFilter, 46 | postRunCallback: () => { CFG.useTypeFilter = false; }, 47 | }, 48 | findWithinFiles: { 49 | script: 'find_within_files', 50 | uri: undefined, 51 | preRunCallback: undefined, 52 | postRunCallback: undefined, 53 | }, 54 | findWithinFilesWithType: { 55 | script: 'find_within_files', 56 | uri: undefined, 57 | preRunCallback: selectTypeFilter, 58 | postRunCallback: () => { CFG.useTypeFilter = false; }, 59 | }, 60 | listSearchLocations: { 61 | script: 'list_search_locations', 62 | uri: undefined, 63 | preRunCallback: writePathOriginsFile, 64 | postRunCallback: undefined, 65 | }, 66 | flightCheck: { 67 | script: 'flight_check', 68 | uri: undefined, 69 | preRunCallback: undefined, 70 | postRunCallback: undefined, 71 | }, 72 | resumeSearch: { 73 | script: 'resume_search', // Dummy. We will set the uri from the last-run script. But we will use this value to check whether we are resuming. 74 | uri: undefined, 75 | preRunCallback: undefined, 76 | postRunCallback: undefined, 77 | }, 78 | }; 79 | 80 | type WhenCondition = 'always' | 'never' | 'noWorkspaceOnly'; 81 | enum PathOrigin { 82 | cwd = 1 << 0, 83 | workspace = 1 << 1, 84 | settings = 1 << 2, 85 | } 86 | 87 | function getTypeOptions() { 88 | const result = cp.execSync('rg --type-list').toString(); 89 | return result.split('\n').map(line => { 90 | const [typeStr, typeInfo] = line.split(':'); 91 | return new FileTypeOption(typeStr, typeInfo, CFG.findWithinFilesFilter.has(typeStr)); 92 | }).filter(x => x.label.trim().length !== 0); 93 | } 94 | 95 | class FileTypeOption implements vscode.QuickPickItem { 96 | label: string; 97 | description: string; 98 | picked: boolean; 99 | 100 | constructor(typeStr: string, types: string, picked: boolean = false) { 101 | this.label = typeStr; 102 | this.description = types; 103 | this.picked = picked; 104 | } 105 | } 106 | 107 | async function selectTypeFilter() { 108 | const opts = getTypeOptions(); 109 | return await new Promise((resolve, _) => { 110 | const qp = vscode.window.createQuickPick(); 111 | let hasResolved = false; // I don't understand why this is necessary... Seems like I can resolve twice? 112 | 113 | qp.items = opts; 114 | qp.title = `Type one or more type identifiers below and press Enter, 115 | OR select the types you want below. Example: typing "py cpp" 116 | (without ticking any boxes will search within python and C++ files. 117 | Typing nothing and selecting those corresponding entries will do the 118 | same. Typing "X" (capital x) clears all selections.`; 119 | qp.placeholder = 'enter one or more types...'; 120 | qp.canSelectMany = true; 121 | // https://github.com/microsoft/vscode/issues/103084 122 | // https://github.com/microsoft/vscode/issues/119834 123 | qp.selectedItems = qp.items.filter(x => CFG.findWithinFilesFilter.has(x.label)); 124 | qp.value = [...CFG.findWithinFilesFilter.keys()].reduce((x, y) => x + ' ' + y, ''); 125 | qp.matchOnDescription = true; 126 | qp.show(); 127 | qp.onDidChangeValue(() => { 128 | if (qp.value.length > 0 && qp.value[qp.value.length - 1] === 'X') { 129 | // This is where we're fighting with VS Code a little bit. 130 | // When you don't reassign the items, the "X" will still be filtering the results, 131 | // which we obviously don't want. Currently (6/2021), this works as expected. 132 | qp.value = ''; 133 | qp.selectedItems = []; 134 | qp.items = qp.items; // keep this 135 | } 136 | }); 137 | qp.onDidAccept(() => { 138 | CFG.useTypeFilter = true; 139 | console.log(qp.activeItems); 140 | CFG.findWithinFilesFilter.clear(); // reset 141 | if (qp.selectedItems.length === 0) { 142 | // If there are no active items, use the string that was entered. 143 | // split on empty string yields an array with empty string, catch that 144 | const types = qp.value === '' ? [] : qp.value.trim().split(/\s+/); 145 | types.forEach(x => CFG.findWithinFilesFilter.add(x)); 146 | } else { 147 | // If there are active items, use those. 148 | qp.selectedItems.forEach(x => CFG.findWithinFilesFilter.add(x.label)); 149 | } 150 | hasResolved = true; 151 | resolve(true); 152 | qp.dispose(); 153 | }); 154 | qp.onDidHide(() => { 155 | qp.dispose(); 156 | if (!hasResolved) { 157 | resolve(false); 158 | } 159 | }); 160 | }); 161 | } 162 | 163 | /** Global variable cesspool erm, I mean, Configuration Data Structure! It does the job for now. */ 164 | interface Config { 165 | extensionName: string | undefined, 166 | searchPaths: string[], 167 | searchPathsOrigins: { [key: string]: PathOrigin }, 168 | disableStartupChecks: boolean, 169 | useEditorSelectionAsQuery: boolean, 170 | useGitIgnoreExcludes: boolean, 171 | useWorkspaceSearchExcludes: boolean, 172 | findFilesPreviewEnabled: boolean, 173 | findFilesPreviewCommand: string, 174 | findFilesPreviewWindowConfig: string, 175 | findWithinFilesPreviewEnabled: boolean, 176 | findWithinFilesPreviewCommand: string, 177 | findWithinFilesPreviewWindowConfig: string, 178 | findWithinFilesFilter: Set, 179 | workspaceSettings: { 180 | folders: string[], 181 | }, 182 | canaryFile: string, 183 | selectionFile: string, 184 | lastQueryFile: string, 185 | lastPosFile: string, 186 | hideTerminalAfterSuccess: boolean, 187 | hideTerminalAfterFail: boolean, 188 | clearTerminalAfterUse: boolean, 189 | showMaximizedTerminal: boolean, 190 | flightCheckPassed: boolean, 191 | additionalSearchLocations: string[], 192 | additionalSearchLocationsWhen: WhenCondition, 193 | searchCurrentWorkingDirectory: WhenCondition, 194 | searchWorkspaceFolders: boolean, 195 | extensionPath: string, 196 | tempDir: string, 197 | useTypeFilter: boolean, 198 | lastCommand: string, 199 | batTheme: string, 200 | openFileInPreviewEditor: boolean, 201 | killTerminalAfterUse: boolean, 202 | fuzzRipgrepQuery: boolean, 203 | restoreFocusTerminal: boolean, 204 | useTerminalInEditor: boolean, 205 | shellPathForTerminal: string, 206 | shellArgsForTerminal: string[] | undefined, 207 | }; 208 | const CFG: Config = { 209 | extensionName: undefined, 210 | searchPaths: [], 211 | searchPathsOrigins: {}, 212 | disableStartupChecks: false, 213 | useEditorSelectionAsQuery: true, 214 | useGitIgnoreExcludes: true, 215 | useWorkspaceSearchExcludes: true, 216 | findFilesPreviewEnabled: true, 217 | findFilesPreviewCommand: '', 218 | findFilesPreviewWindowConfig: '', 219 | findWithinFilesPreviewEnabled: true, 220 | findWithinFilesPreviewCommand: '', 221 | findWithinFilesPreviewWindowConfig: '', 222 | findWithinFilesFilter: new Set(), 223 | workspaceSettings: { 224 | folders: [], 225 | }, 226 | canaryFile: '', 227 | selectionFile: '', 228 | lastQueryFile: '', 229 | lastPosFile: '', 230 | hideTerminalAfterSuccess: false, 231 | hideTerminalAfterFail: false, 232 | clearTerminalAfterUse: false, 233 | showMaximizedTerminal: false, 234 | flightCheckPassed: false, 235 | additionalSearchLocations: [], 236 | additionalSearchLocationsWhen: 'never', 237 | searchCurrentWorkingDirectory: 'never', 238 | searchWorkspaceFolders: true, 239 | extensionPath: '', 240 | tempDir: '', 241 | useTypeFilter: false, 242 | lastCommand: '', 243 | batTheme: '', 244 | openFileInPreviewEditor: false, 245 | killTerminalAfterUse: false, 246 | fuzzRipgrepQuery: false, 247 | restoreFocusTerminal: false, 248 | useTerminalInEditor: false, 249 | shellPathForTerminal: '', 250 | shellArgsForTerminal: undefined, 251 | }; 252 | 253 | /** Ensure that whatever command we expose in package.json actually exists */ 254 | function checkExposedFunctions() { 255 | for (const x of PACKAGE.contributes.commands) { 256 | const fName = x.command.substring(PACKAGE.name.length + '.'.length); 257 | assert(fName in commands); 258 | } 259 | } 260 | 261 | /** We need the extension context to get paths to our scripts. We do that here. */ 262 | function setupConfig(context: vscode.ExtensionContext) { 263 | CFG.extensionName = PACKAGE.name; 264 | assert(CFG.extensionName); 265 | const localScript = (x: string) => vscode.Uri.file(path.join(context.extensionPath, x) + (os.platform() === 'win32' ? '.ps1' : '.sh')); 266 | commands.findFiles.uri = localScript(commands.findFiles.script); 267 | commands.findFilesWithType.uri = localScript(commands.findFiles.script); 268 | commands.findWithinFiles.uri = localScript(commands.findWithinFiles.script); 269 | commands.findWithinFilesWithType.uri = localScript(commands.findWithinFiles.script); 270 | commands.listSearchLocations.uri = localScript(commands.listSearchLocations.script); 271 | commands.flightCheck.uri = localScript(commands.flightCheck.script); 272 | } 273 | 274 | /** Register the commands we defined with VS Code so users have access to them */ 275 | function registerCommands() { 276 | Object.keys(commands).map((k) => { 277 | vscode.commands.registerCommand(`${CFG.extensionName}.${k}`, () => { 278 | executeTerminalCommand(k); 279 | }); 280 | }); 281 | } 282 | 283 | /** Entry point called by VS Code */ 284 | export function activate(context: vscode.ExtensionContext) { 285 | CFG.extensionPath = context.extensionPath; 286 | const local = (x: string) => vscode.Uri.file(path.join(CFG.extensionPath, x)); 287 | 288 | // Load our package.json 289 | PACKAGE = JSON.parse(fs.readFileSync(local('package.json').fsPath, 'utf-8')); 290 | setupConfig(context); 291 | checkExposedFunctions(); 292 | 293 | handleWorkspaceSettingsChanges(); 294 | handleWorkspaceFoldersChanges(); 295 | 296 | registerCommands(); 297 | reinitialize(); 298 | } 299 | 300 | /* Called when extension is deactivated by VS Code */ 301 | export function deactivate() { 302 | term?.dispose(); 303 | fs.rmSync(CFG.canaryFile, { force: true }); 304 | fs.rmSync(CFG.selectionFile, { force: true }); 305 | if (fs.existsSync(CFG.lastQueryFile)) { 306 | fs.rmSync(CFG.lastQueryFile, { force: true }); 307 | } 308 | if (fs.existsSync(CFG.lastPosFile)) { 309 | fs.rmSync(CFG.lastPosFile, { force: true }); 310 | } 311 | } 312 | 313 | /** Map settings from the user-configurable settings to our internal data structure */ 314 | function updateConfigWithUserSettings() { 315 | function getCFG(key: string) { 316 | const userCfg = vscode.workspace.getConfiguration(); 317 | const ret = userCfg.get(`${CFG.extensionName}.${key}`); 318 | assert(ret !== undefined); 319 | return ret; 320 | } 321 | 322 | CFG.disableStartupChecks = getCFG('advanced.disableStartupChecks'); 323 | CFG.useEditorSelectionAsQuery = getCFG('advanced.useEditorSelectionAsQuery'); 324 | CFG.useWorkspaceSearchExcludes = getCFG('general.useWorkspaceSearchExcludes'); 325 | CFG.useGitIgnoreExcludes = getCFG('general.useGitIgnoreExcludes'); 326 | CFG.additionalSearchLocations = getCFG('general.additionalSearchLocations'); 327 | CFG.additionalSearchLocationsWhen = getCFG('general.additionalSearchLocationsWhen'); 328 | CFG.searchCurrentWorkingDirectory = getCFG('general.searchCurrentWorkingDirectory'); 329 | CFG.searchWorkspaceFolders = getCFG('general.searchWorkspaceFolders'); 330 | CFG.hideTerminalAfterSuccess = getCFG('general.hideTerminalAfterSuccess'); 331 | CFG.hideTerminalAfterFail = getCFG('general.hideTerminalAfterFail'); 332 | CFG.clearTerminalAfterUse = getCFG('general.clearTerminalAfterUse'); 333 | CFG.killTerminalAfterUse = getCFG('general.killTerminalAfterUse'); 334 | CFG.showMaximizedTerminal = getCFG('general.showMaximizedTerminal'); 335 | CFG.batTheme = getCFG('general.batTheme'); 336 | CFG.openFileInPreviewEditor = getCFG('general.openFileInPreviewEditor'), 337 | CFG.findFilesPreviewEnabled = getCFG('findFiles.showPreview'); 338 | CFG.findFilesPreviewCommand = getCFG('findFiles.previewCommand'); 339 | CFG.findFilesPreviewWindowConfig = getCFG('findFiles.previewWindowConfig'); 340 | CFG.findWithinFilesPreviewEnabled = getCFG('findWithinFiles.showPreview'); 341 | CFG.findWithinFilesPreviewCommand = getCFG('findWithinFiles.previewCommand'); 342 | CFG.findWithinFilesPreviewWindowConfig = getCFG('findWithinFiles.previewWindowConfig'); 343 | CFG.fuzzRipgrepQuery = getCFG('findWithinFiles.fuzzRipgrepQuery'); 344 | CFG.restoreFocusTerminal = getCFG('general.restoreFocusTerminal'); 345 | CFG.useTerminalInEditor = getCFG('general.useTerminalInEditor'); 346 | CFG.shellPathForTerminal = getCFG('general.shellPathForTerminal'); 347 | CFG.shellArgsForTerminal = getCFG('general.shellArgsForTerminal'); 348 | } 349 | 350 | function collectSearchLocations() { 351 | const locations: string[] = []; 352 | // searchPathsOrigins is for diagnostics only 353 | CFG.searchPathsOrigins = {}; 354 | const setOrUpdateOrigin = (path: string, origin: PathOrigin) => { 355 | if (CFG.searchPathsOrigins[path] === undefined) { 356 | CFG.searchPathsOrigins[path] = origin; 357 | } else { 358 | CFG.searchPathsOrigins[path] |= origin; 359 | } 360 | }; 361 | // cwd 362 | const addCwd = () => { 363 | const cwd = process.cwd(); 364 | locations.push(cwd); 365 | setOrUpdateOrigin(cwd, PathOrigin.cwd); 366 | }; 367 | switch (CFG.searchCurrentWorkingDirectory) { 368 | case 'always': 369 | addCwd(); 370 | break; 371 | case 'never': 372 | break; 373 | case 'noWorkspaceOnly': 374 | if (vscode.workspace.workspaceFolders === undefined) { 375 | addCwd(); 376 | } 377 | break; 378 | default: 379 | assert(false, 'Unhandled case'); 380 | } 381 | 382 | // additional search locations from extension settings 383 | const addSearchLocationsFromSettings = () => { 384 | locations.push(...CFG.additionalSearchLocations); 385 | CFG.additionalSearchLocations.forEach(x => setOrUpdateOrigin(x, PathOrigin.settings)); 386 | }; 387 | switch (CFG.additionalSearchLocationsWhen) { 388 | case 'always': 389 | addSearchLocationsFromSettings(); 390 | break; 391 | case 'never': 392 | break; 393 | case 'noWorkspaceOnly': 394 | if (vscode.workspace.workspaceFolders === undefined) { 395 | addSearchLocationsFromSettings(); 396 | } 397 | break; 398 | default: 399 | assert(false, 'Unhandled case'); 400 | } 401 | 402 | // add the workspace folders 403 | if (CFG.searchWorkspaceFolders && vscode.workspace.workspaceFolders !== undefined) { 404 | const dirs = vscode.workspace.workspaceFolders.map(x => { 405 | const uri = decodeURIComponent(x.uri.toString()); 406 | if (uri.substring(0, 7) === 'file://') { 407 | if (os.platform() === 'win32') { 408 | return uri.substring(8) 409 | .replace(/\//g, "\\") 410 | .replace(/%3A/g, ":"); 411 | } else { 412 | return uri.substring(7); 413 | } 414 | } else { 415 | vscode.window.showErrorMessage('Non-file:// uri\'s not currently supported...'); 416 | return ''; 417 | } 418 | }); 419 | locations.push(...dirs); 420 | dirs.forEach(x => setOrUpdateOrigin(x, PathOrigin.workspace)); 421 | } 422 | 423 | return locations; 424 | } 425 | 426 | /** Produce a human-readable string explaining where the search paths come from */ 427 | function explainSearchLocations(useColor = false) { 428 | const listDirs = (which: PathOrigin) => { 429 | let str = ''; 430 | Object.entries(CFG.searchPathsOrigins).forEach(([k, v]) => { 431 | if ((v & which) !== 0) { 432 | str += `- ${k}\n`; 433 | } 434 | }); 435 | if (str.length === 0) { 436 | str += '- \n'; 437 | } 438 | return str; 439 | }; 440 | 441 | const maybeBlue = (s: string) => { 442 | return useColor ? `\\033[36m${s}\\033[0m` : s; 443 | }; 444 | 445 | let ret = ''; 446 | ret += maybeBlue('Paths added because they\'re the working directory:\n'); 447 | ret += listDirs(PathOrigin.cwd); 448 | ret += maybeBlue('Paths added because they\'re defined in the workspace:\n'); 449 | ret += listDirs(PathOrigin.workspace); 450 | ret += maybeBlue('Paths added because they\'re the specified in the settings:\n'); 451 | ret += listDirs(PathOrigin.settings); 452 | 453 | return ret; 454 | } 455 | 456 | function writePathOriginsFile() { 457 | fs.writeFileSync(path.join(CFG.tempDir, 'paths_explain'), explainSearchLocations(os.platform() !== 'win32')); 458 | return true; 459 | } 460 | 461 | function handleWorkspaceFoldersChanges() { 462 | 463 | CFG.searchPaths = collectSearchLocations(); 464 | 465 | // Also re-update when anything changes 466 | vscode.workspace.onDidChangeWorkspaceFolders(event => { 467 | console.log('workspace folders changed: ', event); 468 | CFG.searchPaths = collectSearchLocations(); 469 | }); 470 | } 471 | 472 | function handleWorkspaceSettingsChanges() { 473 | updateConfigWithUserSettings(); 474 | 475 | // Also re-update when anything changes 476 | vscode.workspace.onDidChangeConfiguration(_ => { 477 | updateConfigWithUserSettings(); 478 | // This may also have affected our search paths 479 | CFG.searchPaths = collectSearchLocations(); 480 | // We need to update the env vars in the terminal 481 | reinitialize(); 482 | }); 483 | } 484 | 485 | /** Check seat belts are on. Also, check terminal commands are on PATH */ 486 | function doFlightCheck(): boolean { 487 | const parseKeyValue = (line: string) => { 488 | return line.split(': ', 2); 489 | }; 490 | 491 | if (!commands.flightCheck || !commands.flightCheck.uri) { 492 | vscode.window.showErrorMessage('Failed to find flight check script. This is a bug. Please report it.'); 493 | return false; 494 | } 495 | 496 | try { 497 | let errStr = ''; 498 | const kvs: any = {}; 499 | let out = ""; 500 | if (os.platform() === 'win32') { 501 | out = cp.execFileSync("powershell.exe", ['-ExecutionPolicy', 'Bypass', '-File', `"${commands.flightCheck.uri.fsPath}"`], { shell: true }).toString('utf-8'); 502 | } else { 503 | out = cp.execFileSync(commands.flightCheck.uri.fsPath, { shell: true }).toString('utf-8'); 504 | } 505 | out.split('\n').map(x => { 506 | const maybeKV = parseKeyValue(x); 507 | if (maybeKV.length === 2) { 508 | kvs[maybeKV[0]] = maybeKV[1]; 509 | } 510 | }); 511 | if (kvs['bat'] === undefined || kvs['bat'] === 'not installed') { 512 | errStr += 'bat not found on your PATH. '; 513 | } 514 | if (kvs['fzf'] === undefined || kvs['fzf'] === 'not installed') { 515 | errStr += 'fzf not found on your PATH. '; 516 | } 517 | if (kvs['rg'] === undefined || kvs['rg'] === 'not installed') { 518 | errStr += 'rg not found on your PATH. '; 519 | } 520 | if (os.platform() !== 'win32' && (kvs['sed'] === undefined || kvs['sed'] === 'not installed')) { 521 | errStr += 'sed not found on your PATH. '; 522 | } 523 | if (errStr !== '') { 524 | vscode.window.showErrorMessage(`Failed to activate plugin! Make sure you have the required command line tools installed as outlined in the README. ${errStr}`); 525 | } 526 | 527 | return errStr === ''; 528 | } catch (error) { 529 | vscode.window.showErrorMessage(`Failed to run checks before starting extension. Maybe this is helpful: ${error}`); 530 | return false; 531 | } 532 | } 533 | 534 | /** 535 | * All the logic that's the same between starting the plugin and re-starting 536 | * after user settings change 537 | */ 538 | function reinitialize() { 539 | term?.dispose(); 540 | updateConfigWithUserSettings(); 541 | // console.log('plugin config:', CFG); 542 | if (!CFG.flightCheckPassed && !CFG.disableStartupChecks) { 543 | CFG.flightCheckPassed = doFlightCheck(); 544 | } 545 | 546 | if (!CFG.flightCheckPassed && !CFG.disableStartupChecks) { 547 | return false; 548 | } 549 | 550 | // 551 | // Set up a file watcher. Its contents tell us what files the user selected. 552 | // It also means the command was completed so we can do stuff like 553 | // optionally hiding the terminal. 554 | // 555 | CFG.tempDir = fs.mkdtempSync(`${tmpdir()}${path.sep}${CFG.extensionName}-`); 556 | CFG.canaryFile = path.join(CFG.tempDir, 'snitch'); 557 | CFG.selectionFile = path.join(CFG.tempDir, 'selection'); 558 | CFG.lastQueryFile = path.join(CFG.tempDir, 'last_query'); 559 | CFG.lastPosFile = path.join(CFG.tempDir, 'last_position'); 560 | fs.writeFileSync(CFG.canaryFile, ''); 561 | fs.watch(CFG.canaryFile, (eventType) => { 562 | if (eventType === 'change') { 563 | handleCanaryFileChange(); 564 | } else if (eventType === 'rename') { 565 | vscode.window.showErrorMessage(`Issue detected with extension ${CFG.extensionName}. You may have to reload it.`); 566 | } 567 | }); 568 | return true; 569 | } 570 | 571 | /** Interpreting the terminal output and turning them into a vscode command */ 572 | function openFiles(data: string) { 573 | const filePaths = data.split('\n').filter(s => s !== ''); 574 | assert(filePaths.length > 0); 575 | filePaths.forEach(p => { 576 | let [file, lineTmp, charTmp] = p.split(':', 3); 577 | // TODO: We might want to just do this the RE way on all platforms? 578 | // On Windows at least the c: makes the split approach problematic. 579 | if (os.platform() === 'win32') { 580 | let re = /^\s*(?([a-zA-Z][:])?[^:]+)([:](?\d+))?\s*([:](?\d+))?.*/; 581 | let v = p.match(re); 582 | if (v && v.groups) { 583 | file = v.groups['file']; 584 | lineTmp = v.groups['lineTmp']; 585 | charTmp = v.groups['charTmp']; 586 | //vscode.window.showWarningMessage('File: ' + file + "\nlineTmp: " + lineTmp + "\ncharTmp: " + charTmp); 587 | } else { 588 | vscode.window.showWarningMessage('Did not match anything in filename: [' + p + "] could not open file!"); 589 | } 590 | } 591 | // On windows we sometimes get extra characters that confound 592 | // the file lookup. 593 | file = file.trim(); 594 | let selection = undefined; 595 | if (lineTmp !== undefined) { 596 | let char = 0; 597 | if (charTmp !== undefined) { 598 | char = parseInt(charTmp) - 1; // 1 based in rg, 0 based in VS Code 599 | } 600 | let line = parseInt(lineTmp) - 1; // 1 based in rg, 0 based in VS Code 601 | assert(line >= 0); 602 | assert(char >= 0); 603 | selection = new vscode.Range(line, char, line, char); 604 | } 605 | vscode.window.showTextDocument( 606 | vscode.Uri.file(file), 607 | { preview: CFG.openFileInPreviewEditor, selection: selection }); 608 | }); 609 | } 610 | 611 | /** Logic of what to do when the user completed a command invocation on the terminal */ 612 | function handleCanaryFileChange() { 613 | if (CFG.clearTerminalAfterUse) { 614 | term.sendText('clear'); 615 | } 616 | 617 | if (CFG.killTerminalAfterUse) { 618 | // Some folks like having a constant terminal open. This will kill ours such that VS Code will 619 | // switch back to theirs. We don't have more control over the terminal so this is the best we 620 | // can do. This is not the default because creating a new terminal is sometimes expensive when 621 | // people use e.g. powerline or other fancy PS1 stuff. 622 | // 623 | // We set a timeout here to address #56. Don't have a good hypothesis as to why this works but 624 | // it seems to fix the issue consistently. 625 | setTimeout(() => term.dispose(), 100); 626 | } 627 | 628 | fs.readFile(CFG.canaryFile, { encoding: 'utf-8' }, (err, data) => { 629 | if (err) { 630 | // We shouldn't really end up here. Maybe leave the terminal around in this case... 631 | vscode.window.showWarningMessage('Something went wrong but we don\'t know what... Did you clean out your /tmp folder?'); 632 | } else { 633 | const commandWasSuccess = data.length > 0 && data[0] !== '1'; 634 | 635 | // open the file(s) 636 | if (commandWasSuccess) { 637 | openFiles(data); 638 | } 639 | 640 | if (CFG.restoreFocusTerminal && previousActiveTerminal) { 641 | handleTerminalFocusRestore(commandWasSuccess); 642 | return; 643 | } 644 | 645 | if (commandWasSuccess && CFG.hideTerminalAfterSuccess) { 646 | term.hide(); 647 | } else if (!commandWasSuccess && CFG.hideTerminalAfterFail) { 648 | term.hide(); 649 | } else { 650 | // Don't hide the terminal and make clippy angry 651 | } 652 | } 653 | }); 654 | } 655 | 656 | function handleTerminalFocusRestore(commandWasSuccess: boolean) { 657 | const shouldHideTerminal = (commandWasSuccess && CFG.hideTerminalAfterSuccess) || (!commandWasSuccess && CFG.hideTerminalAfterFail); 658 | 659 | if (shouldHideTerminal) { 660 | const disposable = vscode.window.onDidChangeActiveTerminal(activeTerminal => { 661 | if (isExtensionChangedTerminal && activeTerminal === previousActiveTerminal) { 662 | previousActiveTerminal?.hide(); 663 | previousActiveTerminal = null; 664 | isExtensionChangedTerminal = false; 665 | disposable.dispose(); 666 | } 667 | }); 668 | } 669 | 670 | isExtensionChangedTerminal = true; 671 | previousActiveTerminal?.show(); 672 | } 673 | 674 | function createTerminal() { 675 | const terminalOptions: vscode.TerminalOptions = { 676 | name: 'F️indItFaster', 677 | location: CFG.useTerminalInEditor ? vscode.TerminalLocation.Editor : vscode.TerminalLocation.Panel, 678 | hideFromUser: !CFG.useTerminalInEditor, // works only for terminal panel, not editor stage 679 | env: { 680 | /* eslint-disable @typescript-eslint/naming-convention */ 681 | FIND_IT_FASTER_ACTIVE: '1', 682 | HISTCONTROL: 'ignoreboth', // bash 683 | // HISTORY_IGNORE: '*', // zsh 684 | EXTENSION_PATH: CFG.extensionPath, 685 | FIND_FILES_PREVIEW_ENABLED: CFG.findFilesPreviewEnabled ? '1' : '0', 686 | FIND_FILES_PREVIEW_COMMAND: CFG.findFilesPreviewCommand, 687 | FIND_FILES_PREVIEW_WINDOW_CONFIG: CFG.findFilesPreviewWindowConfig, 688 | FIND_WITHIN_FILES_PREVIEW_ENABLED: CFG.findWithinFilesPreviewEnabled ? '1' : '0', 689 | FIND_WITHIN_FILES_PREVIEW_COMMAND: CFG.findWithinFilesPreviewCommand, 690 | FIND_WITHIN_FILES_PREVIEW_WINDOW_CONFIG: CFG.findWithinFilesPreviewWindowConfig, 691 | USE_GITIGNORE: CFG.useGitIgnoreExcludes ? '1' : '0', 692 | GLOBS: CFG.useWorkspaceSearchExcludes ? getIgnoreString() : '', 693 | CANARY_FILE: CFG.canaryFile, 694 | SELECTION_FILE: CFG.selectionFile, 695 | LAST_QUERY_FILE: CFG.lastQueryFile, 696 | LAST_POS_FILE: CFG.lastPosFile, 697 | EXPLAIN_FILE: path.join(CFG.tempDir, 'paths_explain'), 698 | BAT_THEME: CFG.batTheme, 699 | FUZZ_RG_QUERY: CFG.fuzzRipgrepQuery ? '1' : '0', 700 | /* eslint-enable @typescript-eslint/naming-convention */ 701 | }, 702 | }; 703 | // Use provided terminal from settings, otherwise use default terminal profile 704 | if (CFG.shellPathForTerminal !== '') { 705 | terminalOptions.shellPath = CFG.shellPathForTerminal; 706 | } 707 | 708 | if (CFG.shellArgsForTerminal !== undefined) { 709 | terminalOptions.shellArgs = CFG.shellArgsForTerminal; 710 | } 711 | 712 | term = vscode.window.createTerminal(terminalOptions); 713 | } 714 | 715 | function getWorkspaceFoldersAsString() { 716 | // For bash invocation. Need to wrap in quotes so spaces within paths don't 717 | // split the path into two strings. 718 | return CFG.searchPaths.reduce((x, y) => x + ` '${y}'`, ''); 719 | } 720 | 721 | function getCommandString(cmd: Command, withArgs: boolean = true, withTextSelection: boolean = true) { 722 | assert(cmd.uri); 723 | let ret = ''; 724 | const cmdPath = cmd.uri.fsPath; 725 | if (CFG.useEditorSelectionAsQuery && withTextSelection) { 726 | const editor = vscode.window.activeTextEditor; 727 | if (editor) { 728 | const selection = editor.selection; 729 | if (!selection.isEmpty) { 730 | // 731 | // Fun story on text selection: 732 | // My first idea was to use an env var to capture the selection. 733 | // My first test was to use a selection that contained shell script... 734 | // This breaks. And fixing it is not easy. See https://unix.stackexchange.com/a/600214/128132. 735 | // So perhaps we should write this to file, and see if we can get bash to interpret this as a 736 | // string. We'll use an env var to indicate there is a selection so we don't need to read a 737 | // file in the general no-selection case, and we don't have to clear the file after having 738 | // used the selection. 739 | // 740 | const selectionText = editor.document.getText(selection); 741 | fs.writeFileSync(CFG.selectionFile, selectionText); 742 | ret += envVarToString('HAS_SELECTION', '1'); 743 | } else { 744 | ret += envVarToString('HAS_SELECTION', '0'); 745 | } 746 | } 747 | } 748 | // useTypeFilter should only be try if we activated the corresponding command 749 | if (CFG.useTypeFilter && CFG.findWithinFilesFilter.size > 0) { 750 | ret += envVarToString('TYPE_FILTER', "'" + [...CFG.findWithinFilesFilter].reduce((x, y) => x + ':' + y) + "'"); 751 | } 752 | if (cmd.script === 'resume_search') { 753 | ret += envVarToString('RESUME_SEARCH', '1'); 754 | } 755 | ret += cmdPath; 756 | if (withArgs) { 757 | let paths = getWorkspaceFoldersAsString(); 758 | ret += ` ${paths}`; 759 | } 760 | return ret; 761 | } 762 | 763 | function getIgnoreGlobs() { 764 | const exclude = vscode.workspace.getConfiguration('search.exclude'); // doesn't work though the docs say it should? 765 | const globs: string[] = []; 766 | Object.entries(exclude).forEach(([k, v]) => { 767 | // Messy proxy object stuff 768 | if (typeof v === 'function') { return; } 769 | if (v) { globs.push(`!${k}`); } 770 | }); 771 | return globs; 772 | } 773 | 774 | function getIgnoreString() { 775 | const globs = getIgnoreGlobs(); 776 | // We separate by colons so we can have spaces in the globs 777 | return globs.reduce((x, y) => x + `${y}:`, ''); 778 | } 779 | 780 | async function executeTerminalCommand(cmd: string) { 781 | getIgnoreGlobs(); 782 | if (!CFG.flightCheckPassed && !CFG.disableStartupChecks) { 783 | if (!reinitialize()) { 784 | return; 785 | } 786 | } 787 | 788 | if (cmd === "resumeSearch") { 789 | // Run the last-run command again 790 | if (os.platform() === 'win32') { 791 | vscode.window.showErrorMessage('Resume search is not implemented on Windows. Sorry! PRs welcome.'); 792 | return; 793 | } 794 | if (CFG.lastCommand === '') { 795 | vscode.window.showErrorMessage('Cannot resume the last search because no search was run yet.'); 796 | return; 797 | } 798 | commands["resumeSearch"].uri = commands[CFG.lastCommand].uri; 799 | commands["resumeSearch"].preRunCallback = commands[CFG.lastCommand].preRunCallback; 800 | commands["resumeSearch"].postRunCallback = commands[CFG.lastCommand].postRunCallback; 801 | } else if (cmd.startsWith("find")) { // Keep track of last-run cmd, but we don't want to resume `listSearchLocations` etc 802 | CFG.lastCommand = cmd; 803 | } 804 | 805 | if (!term || term.exitStatus !== undefined) { 806 | createTerminal(); 807 | if (os.platform() !== 'win32') { 808 | term.sendText('bash'); 809 | term.sendText('export PS1="::: Terminal allocated for FindItFaster. Do not use. ::: "; clear'); 810 | } 811 | } 812 | 813 | assert(cmd in commands); 814 | const cb = commands[cmd].preRunCallback; 815 | let cbResult = true; 816 | if (cb !== undefined) { cbResult = await cb(); } 817 | if (cbResult === true) { 818 | term.sendText(getCommandString(commands[cmd])); 819 | if (CFG.showMaximizedTerminal) { 820 | vscode.commands.executeCommand('workbench.action.toggleMaximizedPanel'); 821 | } 822 | if (CFG.restoreFocusTerminal) { 823 | previousActiveTerminal = vscode.window.activeTerminal ?? null; 824 | } 825 | term.show(); 826 | const postRunCallback = commands[cmd].postRunCallback; 827 | if (postRunCallback !== undefined) { postRunCallback(); } 828 | } 829 | } 830 | 831 | function envVarToString(name: string, value: string) { 832 | // Note we add a space afterwards 833 | return (os.platform() === 'win32') 834 | ? `$Env:${name}=${value}; ` 835 | : `${name}=${value} `; 836 | } 837 | --------------------------------------------------------------------------------