├── LICENSE ├── README.md ├── deno.jsonc ├── denops └── @ddu-sources │ └── file_rec │ ├── deno.json │ └── main.ts └── doc └── ddu-source-file_rec.txt /LICENSE: -------------------------------------------------------------------------------- 1 | MIT license 2 | 3 | Copyright (c) Shougo Matsushita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddu-source-file_rec 2 | 3 | File recursive source for ddu.vim 4 | 5 | This source collects files in the path recursively. 6 | 7 | ## Required 8 | 9 | ### denops.vim 10 | 11 | https://github.com/vim-denops/denops.vim 12 | 13 | ### ddu.vim 14 | 15 | https://github.com/Shougo/ddu.vim 16 | 17 | ### ddu-kind-file 18 | 19 | https://github.com/Shougo/ddu-kind-file 20 | 21 | ## Configuration 22 | 23 | ```vim 24 | call ddu#start(#{ sources: [#{ name: 'file_rec' }] }) 25 | 26 | " Change base path. 27 | " NOTE: "path" must be full path. 28 | call ddu#custom#patch_global('sourceOptions', #{ 29 | \ file_rec: #{ path: expand("~") }, 30 | \ }) 31 | ``` 32 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "lock": false, 3 | "tasks": { 4 | "cache": "deno install --reload", 5 | "check": "deno check denops/**/*.ts", 6 | "lint": "deno lint denops", 7 | "lint-fix": "deno lint --fix denops", 8 | "fmt": "deno fmt denops", 9 | "test": "deno test -A --doc --parallel --shuffle denops/**/*.ts", 10 | "test:publish": "deno publish --dry-run --allow-dirty --set-version 0.0.0", 11 | "update": "deno outdated --recursive", 12 | "upgrade": "deno outdated --recursive --update" 13 | }, 14 | "workspace": [ 15 | "./denops/@ddu-sources/file_rec" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /denops/@ddu-sources/file_rec/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@shougo/ddu-source-file_rec", 3 | "exports": { 4 | ".": "./main.ts" 5 | }, 6 | "publish": { 7 | "include": [ 8 | "**/*.ts" 9 | ] 10 | }, 11 | "imports": { 12 | "@denops/std": "jsr:@denops/std@~8.0.0", 13 | "@shougo/ddu-kind-file": "jsr:@shougo/ddu-kind-file@~1.0.0", 14 | "@shougo/ddu-vim": "jsr:@shougo/ddu-vim@~11.0.0", 15 | "@std/async": "jsr:@std/async@~1.0.4", 16 | "@std/path": "jsr:@std/path@~1.1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /denops/@ddu-sources/file_rec/main.ts: -------------------------------------------------------------------------------- 1 | import type { Context, Item, SourceOptions } from "@shougo/ddu-vim/types"; 2 | import { BaseSource } from "@shougo/ddu-vim/source"; 3 | import { treePath2Filename } from "@shougo/ddu-vim/utils"; 4 | 5 | import { type ActionData } from "@shougo/ddu-kind-file"; 6 | 7 | import type { Denops } from "@denops/std"; 8 | 9 | import { join } from "@std/path/join"; 10 | import { resolve } from "@std/path/resolve"; 11 | import { relative } from "@std/path/relative"; 12 | import { abortable } from "@std/async/abortable"; 13 | 14 | type Params = { 15 | chunkSize: 1000; 16 | ignoredDirectories: string[]; 17 | expandSymbolicLink: boolean; 18 | }; 19 | 20 | type Args = { 21 | denops: Denops; 22 | context: Context; 23 | sourceOptions: SourceOptions; 24 | sourceParams: Params; 25 | }; 26 | 27 | export class Source extends BaseSource { 28 | override kind = "file"; 29 | 30 | override gather( 31 | { sourceOptions, sourceParams, context }: Args, 32 | ): ReadableStream[]> { 33 | const abortController = new AbortController(); 34 | 35 | return new ReadableStream({ 36 | async start(controller) { 37 | const root = treePath2Filename( 38 | sourceOptions.path.length != 0 ? sourceOptions.path : context.path, 39 | ); 40 | const it = walk( 41 | resolve(root, root), 42 | sourceParams.ignoredDirectories, 43 | abortController.signal, 44 | sourceParams.chunkSize, 45 | sourceParams.expandSymbolicLink, 46 | ); 47 | let enqueueSize: number = sourceParams.chunkSize; 48 | let items: Item[] = []; 49 | try { 50 | for await (const chunk of it) { 51 | items = [...items, ...chunk]; 52 | if (items.length >= enqueueSize) { 53 | enqueueSize = 10 * sourceParams.chunkSize; 54 | controller.enqueue(items); 55 | items = []; 56 | } 57 | } 58 | if (items.length) { 59 | controller.enqueue(items); 60 | } 61 | } catch (e: unknown) { 62 | if (e instanceof Error && e.name.includes("AbortReason")) { 63 | // Ignore AbortReason errors 64 | } else { 65 | console.error(e); 66 | } 67 | } finally { 68 | controller.close(); 69 | } 70 | }, 71 | 72 | cancel(reason): void { 73 | abortController.abort(reason); 74 | }, 75 | }); 76 | } 77 | 78 | override params(): Params { 79 | return { 80 | chunkSize: 1000, 81 | ignoredDirectories: [".git"], 82 | expandSymbolicLink: false, 83 | }; 84 | } 85 | } 86 | 87 | async function* walk( 88 | root: string, 89 | ignoredDirectories: string[], 90 | signal: AbortSignal, 91 | chunkSize: number, 92 | expandSymbolicLink: boolean, 93 | ): AsyncGenerator[]> { 94 | const walk = async function* ( 95 | dir: string, 96 | ): AsyncGenerator[]> { 97 | let chunk: Item[] = []; 98 | try { 99 | for await (const entry of abortable(Deno.readDir(dir), signal)) { 100 | const abspath = join(dir, entry.name); 101 | const stat = await readStat(abspath, expandSymbolicLink); 102 | 103 | if (stat === null) { 104 | // Skip invalid files 105 | continue; 106 | } 107 | 108 | if (!stat.isDirectory) { 109 | const n = chunk.push({ 110 | word: relative(root, abspath), 111 | action: { 112 | path: abspath, 113 | isDirectory: false, 114 | }, 115 | }); 116 | if (n >= chunkSize) { 117 | yield chunk; 118 | chunk = []; 119 | } 120 | } else if (ignoredDirectories.includes(entry.name)) { 121 | continue; 122 | } else if ( 123 | stat.isSymlink && stat.isDirectory && 124 | abspath.includes(await Deno.realPath(abspath)) 125 | ) { 126 | // Looped link 127 | continue; 128 | } else { 129 | yield* walk(abspath); 130 | } 131 | } 132 | if (chunk.length) { 133 | yield chunk; 134 | } 135 | } catch (e: unknown) { 136 | if (e instanceof Deno.errors.PermissionDenied) { 137 | // Ignore this error 138 | // See https://github.com/Shougo/ddu-source-file_rec/issues/2 139 | return; 140 | } 141 | throw e; 142 | } 143 | }; 144 | yield* walk(root); 145 | } 146 | 147 | async function readStat( 148 | path: string, 149 | expandSymbolicLink: boolean, 150 | ): Promise { 151 | try { 152 | const stat = await Deno.lstat(path); 153 | if (stat.isSymlink && expandSymbolicLink) { 154 | return { 155 | ...(await Deno.stat(path)), 156 | isSymlink: true, 157 | }; 158 | } 159 | return stat; 160 | } catch (_: unknown) { 161 | // Ignore stat exception 162 | return null; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /doc/ddu-source-file_rec.txt: -------------------------------------------------------------------------------- 1 | *ddu-source-file_rec.txt* File recursive source for ddu.vim 2 | 3 | Author: Shougo 4 | License: MIT license 5 | 6 | CONTENTS *ddu-source-file_rec-contents* 7 | 8 | Introduction |ddu-source-file_rec-introduction| 9 | Install |ddu-source-file_rec-install| 10 | Examples |ddu-source-file_rec-examples| 11 | Params |ddu-source-file_rec-params| 12 | FAQ |ddu-source-file_rec-faq| 13 | 14 | 15 | ============================================================================== 16 | INTRODUCTION *ddu-source-file_rec-introduction* 17 | 18 | This source collects files in the path recursively. 19 | 20 | 21 | ============================================================================== 22 | INSTALL *ddu-source-file_rec-install* 23 | 24 | Please install both "ddu.vim" and "denops.vim" and "ddu-kind-file". 25 | 26 | https://github.com/Shougo/ddu.vim 27 | https://github.com/vim-denops/denops.vim 28 | https://github.com/Shougo/ddu-kind-file 29 | 30 | 31 | ============================================================================== 32 | EXAMPLES *ddu-source-file_rec-examples* 33 | > 34 | call ddu#start(#{ sources: [#{ name: 'file_rec' }] }) 35 | 36 | " Change base path. 37 | " NOTE: "path" must be full path. 38 | call ddu#custom#patch_global('sourceOptions', #{ 39 | \ file_rec: #{ path: expand("~") }, 40 | \ }) 41 | < 42 | 43 | ============================================================================== 44 | PARAMS *ddu-source-file_rec-params* 45 | 46 | *ddu-source-file_rec-param-chunkSize* 47 | chunkSize (number) 48 | The gather files chunkSize. 49 | 50 | Default: 1000 51 | 52 | *ddu-source-file_rec-param-ignoredDirectories* 53 | ignoredDirectories (string[]) 54 | Ignored directories list. 55 | Note: It must be directory name. 56 | 57 | Default: ".git" 58 | 59 | *ddu-source-file_rec-param-expandSymbolicLink* 60 | expandSymbolicLink (bool) 61 | When true, It searches within a directory pointed to by 62 | searched symbolic link. 63 | 64 | Default: v:false 65 | 66 | 67 | ============================================================================== 68 | FREQUENTLY ASKED QUESTIONS (FAQ) *ddu-source-file_rec-faq* 69 | 70 | Q: ".gitignore" is not respected. 71 | 72 | A: It is feature. If you want to use ".gitignore", please use 73 | "ddu-source-file_external" instead. 74 | 75 | https://github.com/matsui54/ddu-source-file_external 76 | 77 | 78 | ============================================================================== 79 | vim:tw=78:ts=8:ft=help:norl:noet:fen:noet: 80 | --------------------------------------------------------------------------------