├── denops └── @ddu-sources │ └── file │ ├── deno.json │ └── main.ts ├── deno.jsonc ├── README.md ├── LICENSE └── doc └── ddu-source-file.txt /denops/@ddu-sources/file/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@shougo/ddu-source-file", 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/path": "jsr:@std/path@~1.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddu-source-file 2 | 3 | File source for ddu.vim 4 | 5 | This source collects files in the path. 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' }] }) 25 | 26 | " Change base path. 27 | call ddu#custom#patch_global('sourceOptions', #{ 28 | \ file: #{ path: expand("~") }, 29 | \ }) 30 | ``` 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /doc/ddu-source-file.txt: -------------------------------------------------------------------------------- 1 | *ddu-source-file.txt* File source for ddu.vim 2 | 3 | Author: Shougo 4 | License: MIT license 5 | 6 | CONTENTS *ddu-source-file-contents* 7 | 8 | Introduction |ddu-source-file-introduction| 9 | Install |ddu-source-file-install| 10 | Examples |ddu-source-file-examples| 11 | Params |ddu-source-file-params| 12 | FAQ |ddu-source-file-faq| 13 | 14 | 15 | ============================================================================== 16 | INTRODUCTION *ddu-source-file-introduction* 17 | 18 | This source collects files in the path. 19 | 20 | 21 | ============================================================================== 22 | INSTALL *ddu-source-file-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-examples* 33 | >vim 34 | call ddu#start(#{ sources: [#{ name: 'file' }] }) 35 | 36 | " Change base path. 37 | call ddu#custom#patch_global('sourceOptions', #{ 38 | \ file: #{ path: expand("~") }, 39 | \ }) 40 | < 41 | 42 | ============================================================================== 43 | PARAMS *ddu-source-file-params* 44 | 45 | *ddu-source-file-param-ignoreDirectories* 46 | ignoreDirectories (boolean) 47 | Ignore directories. 48 | 49 | Default: v:false 50 | 51 | *ddu-source-file-param-new* 52 | new (boolean) 53 | Create new file by user input. 54 | 55 | Default: v:false 56 | 57 | ============================================================================== 58 | FREQUENTLY ASKED QUESTIONS (FAQ) *ddu-source-file-faq* 59 | 60 | Q: I want to create file like denite.nvim "file:new" feature. 61 | 62 | A: You can use |ddu-source-file-param-new|. 63 | Note: It use "ddu-commands.vim". 64 | https://github.com/Shougo/ddu-commands.vim 65 | > 66 | Ddu file file -source-param-new -source-option-volatile 67 | < 68 | 69 | ============================================================================== 70 | vim:tw=78:ts=8:ft=help:norl:noet:fen:noet: 71 | -------------------------------------------------------------------------------- /denops/@ddu-sources/file/main.ts: -------------------------------------------------------------------------------- 1 | import type { Context, Item, SourceOptions } from "@shougo/ddu-vim/types"; 2 | import { BaseSource } from "@shougo/ddu-vim/source"; 3 | import { printError, 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 | import * as fn from "@denops/std/function"; 9 | 10 | import { join } from "@std/path/join"; 11 | import { isAbsolute } from "@std/path/is-absolute"; 12 | import { relative } from "@std/path/relative"; 13 | 14 | type Params = { 15 | ignoreDirectories: boolean; 16 | new: boolean; 17 | }; 18 | 19 | export class Source extends BaseSource { 20 | override kind = "file"; 21 | 22 | override gather(args: { 23 | denops: Denops; 24 | context: Context; 25 | sourceOptions: SourceOptions; 26 | sourceParams: Params; 27 | input: string; 28 | }): ReadableStream[]> { 29 | this.prevMtime = new Date(); 30 | 31 | return new ReadableStream({ 32 | async start(controller) { 33 | const maxItems = 20000; 34 | 35 | const basePath = treePath2Filename( 36 | args.sourceOptions.path.length != 0 37 | ? args.sourceOptions.path 38 | : args.context.path, 39 | ); 40 | 41 | const tree = async (root: string) => { 42 | const stat = await safeStat(root); 43 | if (!stat?.isDirectory) { 44 | return []; 45 | } 46 | 47 | let items: Item[] = []; 48 | try { 49 | for await (const entry of Deno.readDir(root)) { 50 | const path = join(root, entry.name); 51 | 52 | const stat = await safeStat(path); 53 | if (!stat) { 54 | continue; 55 | } else if ( 56 | args.sourceParams.ignoreDirectories && stat.isDirectory 57 | ) { 58 | continue; 59 | } 60 | 61 | const word = 62 | (isAbsolute(args.input) ? path : relative(basePath, path)) + 63 | (stat.isDirectory ? "/" : ""); 64 | 65 | items.push({ 66 | word, 67 | action: { 68 | path, 69 | isDirectory: stat.isDirectory, 70 | isLink: stat.isSymlink, 71 | }, 72 | status: { 73 | size: stat.size, 74 | time: stat.mtime?.getTime(), 75 | }, 76 | isTree: stat.isDirectory, 77 | treePath: path, 78 | }); 79 | 80 | if (items.length > maxItems) { 81 | // Update items 82 | controller.enqueue(items); 83 | 84 | // Clear 85 | items = []; 86 | } 87 | } 88 | } catch (e: unknown) { 89 | if (e instanceof Error && e.name.includes("AbortReason")) { 90 | // Ignore AbortReason errors 91 | } else { 92 | console.error(e); 93 | } 94 | } 95 | 96 | return items; 97 | }; 98 | 99 | if (args.sourceParams.new) { 100 | if (args.input != "") { 101 | controller.enqueue( 102 | [{ 103 | word: args.input, 104 | display: `[new] ${args.input}`, 105 | action: { 106 | path: join(basePath, args.input), 107 | }, 108 | }], 109 | ); 110 | } 111 | } else { 112 | const slashPos = args.input.lastIndexOf("/"); 113 | const rootPath = isAbsolute(args.input) 114 | ? args.input.slice(0, slashPos) 115 | : slashPos >= 0 116 | ? join(basePath, args.input.slice(0, slashPos)) 117 | : basePath; 118 | 119 | const stat = await safeStat(basePath); 120 | if (stat?.isDirectory) { 121 | controller.enqueue(await tree(rootPath)); 122 | } else { 123 | // Check the file exists. 124 | const stat = await safeStat(basePath); 125 | if (stat) { 126 | controller.enqueue( 127 | [{ 128 | word: basePath, 129 | action: { 130 | path: basePath, 131 | isDirectory: stat.isDirectory, 132 | isLink: stat.isSymlink, 133 | }, 134 | status: { 135 | size: stat.size, 136 | time: stat.mtime?.getTime(), 137 | }, 138 | isTree: stat.isDirectory, 139 | treePath: basePath, 140 | }], 141 | ); 142 | } else { 143 | await printError( 144 | args.denops, 145 | `${rootPath} is not found.`, 146 | ); 147 | } 148 | } 149 | } 150 | 151 | controller.close(); 152 | }, 153 | }); 154 | } 155 | 156 | override async checkUpdated(args: { 157 | denops: Denops; 158 | sourceOptions: SourceOptions; 159 | }): Promise { 160 | let dir = treePath2Filename(args.sourceOptions.path); 161 | if (dir == "") { 162 | dir = await fn.getcwd(args.denops) as string; 163 | } 164 | 165 | const stat = await safeStat(dir); 166 | if (!stat || !stat.isDirectory || !stat.mtime) { 167 | return false; 168 | } 169 | 170 | const check = stat.mtime > this.prevMtime; 171 | this.prevMtime = stat.mtime; 172 | 173 | return check; 174 | } 175 | 176 | override params(): Params { 177 | return { 178 | ignoreDirectories: false, 179 | new: false, 180 | }; 181 | } 182 | } 183 | 184 | const safeStat = async (path: string): Promise => { 185 | // NOTE: Deno.stat() may be failed 186 | try { 187 | const stat = await Deno.lstat(path); 188 | if (stat.isSymlink) { 189 | try { 190 | const stat = await Deno.stat(path); 191 | stat.isSymlink = true; 192 | return stat; 193 | } catch (_: unknown) { 194 | // Ignore stat exception 195 | } 196 | } 197 | return stat; 198 | } catch (_: unknown) { 199 | // Ignore stat exception 200 | } 201 | return null; 202 | }; 203 | --------------------------------------------------------------------------------