├── denops └── @ddu-filters │ └── matcher_kensaku │ ├── LICENSE │ ├── types.ts │ ├── main.ts │ ├── deno.jsonc │ ├── README.md │ └── matcher.ts ├── deno.jsonc ├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── LICENSE ├── doc └── ddu-filter-kensaku.txt └── README.md /denops/@ddu-filters/matcher_kensaku/LICENSE: -------------------------------------------------------------------------------- 1 | ../../../LICENSE -------------------------------------------------------------------------------- /denops/@ddu-filters/matcher_kensaku/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parameters for the **matcher_kensaku** Ddu filter. 3 | */ 4 | export type MatcherKensakuParams = { 5 | /** 6 | * The highlight group of matched text. 7 | * If empty, this feature will be disabled. 8 | * 9 | * @default "" 10 | */ 11 | highlightMatched?: string; 12 | }; 13 | -------------------------------------------------------------------------------- /denops/@ddu-filters/matcher_kensaku/main.ts: -------------------------------------------------------------------------------- 1 | // Do NOT rename this file to anything other than "main.ts". ddu.vim looks for this file. 2 | /** 3 | * Entry point for the ddu.vim filter plugin. 4 | * 5 | * @module 6 | */ 7 | 8 | // Re-export filter class as `Filter` for ddu.vim to recognize it. 9 | export { MatcherKensakuFilter as Filter } from "./matcher.ts"; 10 | export type * from "./types.ts"; 11 | -------------------------------------------------------------------------------- /denops/@ddu-filters/matcher_kensaku/deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@milly/ddu-filter-matcher-kensaku", 3 | "exports": { 4 | ".": "./main.ts", 5 | "./matcher": "./matcher.ts", 6 | "./types": "./types.ts" 7 | }, 8 | "publish": { 9 | "include": [ 10 | "**/*.ts", 11 | "README.md", 12 | "LICENSE" 13 | ] 14 | }, 15 | "imports": { 16 | "@denops/std": "jsr:@denops/std@^8.0.0", 17 | "@shougo/ddu-vim": "jsr:@shougo/ddu-vim@^11.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /denops/@ddu-filters/matcher_kensaku/README.md: -------------------------------------------------------------------------------- 1 | # ddu-filter-matcher_kensaku 2 | 3 | [![license:MIT](https://img.shields.io/github/license/Milly/ddu-filter-kensaku?style=flat-square)](LICENSE) 4 | 5 | Migemo matcher filter for [ddu.vim] 6 | 7 | This library provides a filter class and configuration types in a reusable form. 8 | 9 | If you want to use it as a Vim plugin, please refer to the [ddu-filter-kensaku] 10 | repository. 11 | 12 | [ddu.vim]: https://github.com/Shougo/ddu.vim 13 | [ddu-filter-kensaku]: https://github.com/Milly/ddu-filter-kensaku 14 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "lock": false, 3 | "tasks": { 4 | "cache": "deno install", 5 | "check": "deno task check:lint && deno task check:fmt && deno task check:type && deno task check:doc && deno task check:publish --allow-dirty", 6 | "check:lint": "deno lint", 7 | "check:fmt": "deno fmt --check", 8 | "check:type": "deno check --no-lock", 9 | "check:doc": "deno check --no-lock --doc-only **/*.ts **/*.md", 10 | "check:publish": "deno publish --dry-run --set-version=0.0.0", 11 | "fix": "deno lint --fix && deno fmt", 12 | "test": "deno test -A --doc --parallel --shuffle" 13 | }, 14 | "workspace": [ 15 | "./denops/@ddu-filters/matcher_kensaku" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | permissions: 9 | contents: read 10 | id-token: write # The OIDC ID token is used for authentication with JSR. 11 | 12 | jobs: 13 | jsr: 14 | strategy: 15 | matrix: 16 | runner: 17 | - ubuntu-latest 18 | deno: 19 | - version: ${{ vars.PUBLISH_DENO_VERSION }} 20 | runs-on: ${{ matrix.runner }} 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | 26 | - uses: denoland/setup-deno@v2 27 | with: 28 | deno-version: ${{ matrix.deno.version }} 29 | 30 | - name: Pre-cache dependencies 31 | run: | 32 | deno task cache 33 | 34 | - name: Publish 35 | run: deno run -A jsr:@david/publish-on-tag@0.2.0 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT license 2 | 3 | Copyright (c) Milly https://github.com/Milly 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 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | paths: 9 | - "**.md" 10 | - "**.ts" 11 | - "deno.jsonc" 12 | - ".github/workflows/test.yml" 13 | 14 | defaults: 15 | run: 16 | shell: bash --noprofile --norc -eo pipefail {0} 17 | 18 | jobs: 19 | check: 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | runner: 24 | - ubuntu-latest 25 | deno: 26 | - version: ${{ vars.MIN_DENO_VERSION }} 27 | - version: ${{ vars.MAX_DENO_VERSION }} 28 | runs-on: ${{ matrix.runner }} 29 | steps: 30 | - uses: actions/checkout@v4 31 | 32 | - uses: denoland/setup-deno@v2 33 | with: 34 | deno-version: "${{ matrix.deno.version }}" 35 | 36 | - name: Pre-cache dependencies 37 | run: deno task cache 38 | 39 | - name: Check lint 40 | if: ${{ !cancelled() }} 41 | run: deno task check:lint 42 | 43 | - name: Check format 44 | if: ${{ !cancelled() }} 45 | run: deno task check:fmt 46 | 47 | - name: Check type 48 | if: ${{ !cancelled() }} 49 | run: deno task check:type 50 | 51 | - name: Check doc 52 | if: ${{ !cancelled() }} 53 | run: deno task check:doc 54 | 55 | check-publish: 56 | needs: check 57 | strategy: 58 | matrix: 59 | runner: 60 | - ubuntu-latest 61 | deno: 62 | - version: ${{ vars.PUBLISH_DENO_VERSION }} 63 | runs-on: ${{ matrix.runner }} 64 | steps: 65 | - uses: actions/checkout@v4 66 | 67 | - uses: denoland/setup-deno@v2 68 | with: 69 | deno-version: ${{ matrix.deno.version }} 70 | 71 | - name: Pre-cache dependencies 72 | run: deno task cache 73 | 74 | - name: Check slow types 75 | run: deno task check:publish 76 | -------------------------------------------------------------------------------- /doc/ddu-filter-kensaku.txt: -------------------------------------------------------------------------------- 1 | *ddu-filter-kensaku.txt* Migemo matcher for ddu.vim 2 | 3 | Author: Milly https://github.com/Milly 4 | License: MIT License (See LICENSE) 5 | 6 | CONTENTS *ddu-filter-kensaku* 7 | 8 | Introduction |ddu-filter-kensaku-introduction| 9 | Install |ddu-filter-kensaku-install| 10 | Examples |ddu-filter-kensaku-examples| 11 | Params |ddu-filter-kensaku-params| 12 | 13 | 14 | ============================================================================== 15 | INTRODUCTION *ddu-filter-kensaku-introduction* 16 | 17 | This matcher filters Migemo matched items. 18 | 19 | It supports narrowing inputs by spaces. 20 | 21 | 22 | ============================================================================== 23 | INSTALL *ddu-filter-kensaku-install* 24 | 25 | Install all plugins below. 26 | 27 | [denops.vim]: https://github.com/vim-denops/denops.vim 28 | [ddu.vim]: https://github.com/Shougo/ddu.vim 29 | [kensaku.vim]: https://github.com/lambdalisue/kensaku.vim 30 | 31 | 32 | ============================================================================== 33 | EXAMPLES *ddu-filter-kensaku-examples* 34 | 35 | Example configuration in Vim script: 36 | >vim 37 | call ddu#custom#patch_global('sourceOptions', #{ 38 | \ _: #{ 39 | \ matchers: ['matcher_kensaku'], 40 | \ }, 41 | \}) 42 | 43 | " Option: Enable highlight matched text 44 | call ddu#custom#patch_global('filterParams', #{ 45 | \ matcher_kensaku: #{ 46 | \ highlightMatched: 'Search', 47 | \ }, 48 | \}) 49 | < 50 | Example configuration in TypeScript (See |ddu#custom#load_config()|): 51 | >typescript 52 | import { BaseConfig, type ConfigArguments } from "jsr:@shougo/ddu-vim/config"; 53 | import type { MatcherKensakuParams } from "jsr:@milly/ddu-filter-matcher-kensaku/types"; 54 | 55 | export class Config extends BaseConfig { 56 | override async config({ contextBuilder }: ConfigArguments) { 57 | contextBuilder.patchGlobal({ 58 | filterParams: { 59 | matcher_kensaku: { 60 | highlightMatched: "Special", 61 | } satisfies MatcherKensakuParams, 62 | }, 63 | }); 64 | } 65 | } 66 | < 67 | 68 | ============================================================================== 69 | PARAMS *ddu-filter-kensaku-params* 70 | 71 | *ddu-filter-kensaku-param-highlightMatched* 72 | highlightMatched (string) 73 | The highlight group of matched text. 74 | If empty, this feature will be disabled. 75 | Note: This feature slows down matching when there are many 76 | search items. 77 | 78 | Default: "" 79 | 80 | ============================================================================== 81 | vim:tw=78:ts=8:ft=help:norl:noet:fen:noet: 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddu-filter-kensaku 2 | 3 | [![license:MIT](https://img.shields.io/github/license/Milly/ddu-filter-kensaku?style=flat-square)](LICENSE) 4 | [![Vim doc](https://img.shields.io/badge/doc-%3Ah_ddu--filter--kensaku-orange.svg?style=flat-square&logo=vim)](doc/ddu-filter-kensaku.txt) 5 | 6 | [![Denops 8.0.0 or above](https://img.shields.io/badge/Denops-Support_8.0.0-yellowgreen.svg)](https://github.com/vim-denops/deno-denops-std/tree/v8.0.0) 7 | [![ddu.vim 11.0.0 or above](https://img.shields.io/badge/ddu.vim-Support_11.0.0-yellowgreen.svg)](https://github.com/Shougo/ddu.vim/tree/v11.0.0) 8 | 9 | Migemo matcher filter for [ddu.vim] 10 | 11 | [Migemo] を利用してローマ字入力により [ddu.vim] 12 | のアイテムから日本語文字列をマッチングします。 13 | 14 | 単語を空白で区切って入力することで AND 検索ができます。 15 | 16 | 例: `roma nihongo` で「... ローマ字 ... 日本語 ...」のような文にマッチします。 17 | 18 | ![ddu-filter-kensaku](https://github.com/user-attachments/assets/e58a0b25-0100-43b5-b759-289465b52a5a) 19 | 20 | ## Required 21 | 22 | 以下のプラグインに依存します。 23 | 24 | - [denops.vim] 25 | - [ddu.vim] 26 | - [kensaku.vim] 27 | 28 | ## Installation 29 | 30 | 1. [Deno] をインストールします。 31 | 2. [vim-plug] などを利用してプラグインをインストールします。 32 | 33 | ```vim:plugins.vim 34 | Plug 'vim-denops/denops.vim' 35 | Plug 'Shougo/ddu.vim' 36 | Plug 'lambdalisue/kensaku.vim' 37 | Plug 'Milly/ddu-filter-kensaku' 38 | ``` 39 | 40 | ## Configuration 41 | 42 | ddu.vim の設定を行います。 43 | 44 | ```vim:config.vim 45 | call ddu#custom#patch_global('sourceOptions', #{ 46 | \ _: #{ 47 | \ matchers: ['matcher_kensaku'], 48 | \ }, 49 | \}) 50 | 51 | " Option: Enable highlight matched text 52 | call ddu#custom#patch_global('filterParams', #{ 53 | \ matcher_kensaku: #{ 54 | \ highlightMatched: 'Search', 55 | \ }, 56 | \}) 57 | ``` 58 | 59 | ### Type-safe Configuration 60 | 61 | [ddu.vim] の TypeScript 設定ファイルで型安全に設定できます。 62 | 63 | ```typescript:config.ts 64 | import { BaseConfig, type ConfigArguments } from "jsr:@shougo/ddu-vim/config"; 65 | import type { MatcherKensakuParams } from "jsr:@milly/ddu-filter-matcher-kensaku/types"; 66 | 67 | export class Config extends BaseConfig { 68 | override async config({ contextBuilder }: ConfigArguments) { 69 | contextBuilder.patchGlobal({ 70 | filterParams: { 71 | matcher_kensaku: { 72 | highlightMatched: "Special", 73 | } satisfies MatcherKensakuParams, 74 | }, 75 | }); 76 | } 77 | } 78 | ``` 79 | 80 | ```vim:config.vim 81 | call ddu#custom#load_config("path/to/your/config.ts") 82 | ``` 83 | 84 | [Deno]: https://deno.land/ 85 | [Migemo]: http://0xcc.net/migemo/ 86 | [ddu.vim]: https://github.com/Shougo/ddu.vim 87 | [denops.vim]: https://github.com/vim-denops/denops.vim 88 | [kensaku.vim]: https://github.com/lambdalisue/kensaku.vim 89 | [vim-plug]: https://github.com/junegunn/vim-plug 90 | -------------------------------------------------------------------------------- /denops/@ddu-filters/matcher_kensaku/matcher.ts: -------------------------------------------------------------------------------- 1 | import type { Denops } from "@denops/std"; 2 | import { BaseFilter, type FilterArguments } from "@shougo/ddu-vim/filter"; 3 | import type { 4 | DduItem, 5 | FilterOptions, 6 | ItemHighlight, 7 | SourceOptions, 8 | } from "@shougo/ddu-vim/types"; 9 | 10 | import type { MatcherKensakuParams } from "./types.ts"; 11 | 12 | const MATCHED_HIGHLIGHT_NAME = "ddu-filter-matcher_kensaku-matched"; 13 | 14 | type Params = Required; 15 | type ItemHighlightPos = Pick; 16 | 17 | export class MatcherKensakuFilter extends BaseFilter { 18 | override params(): Params { 19 | return { 20 | highlightMatched: "", 21 | }; 22 | } 23 | 24 | override async filter( 25 | args: FilterArguments, 26 | ): Promise { 27 | const { 28 | denops, 29 | input, 30 | items, 31 | sourceOptions, 32 | filterOptions, 33 | filterParams, 34 | } = args; 35 | 36 | const matchers = await this.#getMatchers( 37 | denops, 38 | input, 39 | sourceOptions, 40 | filterOptions, 41 | ); 42 | if (matchers.length === 0) return items; 43 | 44 | let filteredItems = this.#extractMatches(items, matchers); 45 | 46 | if (filterParams.highlightMatched !== "") { 47 | filteredItems = this.#updateHighlights( 48 | filteredItems, 49 | matchers, 50 | filterParams, 51 | ); 52 | } 53 | 54 | return filteredItems; 55 | } 56 | 57 | async #getMatchers( 58 | denops: Denops, 59 | input: string, 60 | { ignoreCase, smartCase }: SourceOptions, 61 | { minInputLength }: FilterOptions, 62 | ): Promise { 63 | input = input.trim(); 64 | if (input === "") return []; 65 | const inputParts = input.split(/\s+/).filter((part) => 66 | part.length >= minInputLength 67 | ); 68 | return await Promise.all( 69 | inputParts.map(async (text) => { 70 | let patternFlags = ""; 71 | if (ignoreCase && (!smartCase || !/\p{Lu}/v.test(text))) { 72 | text = text.toLowerCase(); 73 | patternFlags = "i"; 74 | } 75 | const pattern = await kensakuQuery(denops, text); 76 | return new RegExp(pattern, patternFlags); 77 | }), 78 | ); 79 | } 80 | 81 | #extractMatches(items: DduItem[], matchers: RegExp[]): DduItem[] { 82 | return items.filter(({ matcherKey }) => 83 | matchers.every((m) => m.test(matcherKey)) 84 | ); 85 | } 86 | 87 | #updateHighlights( 88 | items: DduItem[], 89 | matchers: RegExp[], 90 | { highlightMatched }: Params, 91 | ): DduItem[] { 92 | // Add global flag, to get all matches in the item 93 | const globalMatchers = matchers.map((matcher) => 94 | new RegExp(matcher, "g" + matcher.flags) 95 | ); 96 | 97 | const getMatchedRanges = (text: string) => 98 | globalMatchers 99 | // Get matches 100 | .flatMap((matcher) => [...text.matchAll(matcher)]) 101 | // Convert to ItemHighlightPos 102 | .map((m): ItemHighlightPos => ({ 103 | col: getByteLength(text.slice(0, m.index!)) + 1, 104 | width: getByteLength(m[0]), 105 | })) 106 | // Sort by ascending `col` 107 | .toSorted((a, b) => a.col - b.col) 108 | // Merge overlaps 109 | .reduce( 110 | (acc, cur) => { 111 | const prev = acc.at(-1); 112 | if (prev && cur.col <= prev.col + prev.width) { 113 | prev.width = cur.col + cur.width - prev.col; 114 | } else { 115 | acc.push(cur); 116 | } 117 | return acc; 118 | }, 119 | [] as ItemHighlightPos[], 120 | ); 121 | 122 | return items.map( 123 | (item) => { 124 | const display = item.display ?? item.word; 125 | const ranges = getMatchedRanges(display); 126 | const matchedHighlights = ranges 127 | .map(({ col, width }): ItemHighlight => ({ 128 | name: MATCHED_HIGHLIGHT_NAME, 129 | "hl_group": highlightMatched, 130 | col, 131 | width, 132 | })); 133 | const highlightsWithoutMatched = 134 | item.highlights?.filter(({ name }) => 135 | name !== MATCHED_HIGHLIGHT_NAME 136 | ) ?? []; 137 | const highlights = [ 138 | ...highlightsWithoutMatched, 139 | ...matchedHighlights, 140 | ]; 141 | return { ...item, highlights }; 142 | }, 143 | ); 144 | } 145 | } 146 | 147 | const textEncoder = new TextEncoder(); 148 | 149 | function getByteLength(s: string): number { 150 | return textEncoder.encode(s).length; 151 | } 152 | 153 | function kensakuQuery(denops: Denops, text: string): Promise { 154 | return denops.dispatch("kensaku", "query", text) as Promise; 155 | } 156 | --------------------------------------------------------------------------------