Skip to content

Commit

Permalink
feat(list): support extended search mode
Browse files Browse the repository at this point in the history
  • Loading branch information
chemzqm committed Jun 8, 2019
1 parent 7e54f40 commit 9458540
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 33 deletions.
5 changes: 5 additions & 0 deletions data/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,11 @@
"default": "*",
"description": "Sign text for selected lines."
},
"list.extendedSearchMode": {
"type": "boolean",
"default": true,
"description": "Enable extended search mode which allows multiple search patterns delimited by spaces."
},
"list.autoResize": {
"type": "boolean",
"default": true,
Expand Down
4 changes: 4 additions & 0 deletions doc/coc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,10 @@ Builtin configurations:~

Key used for select previous line on insert mode., default: `"<C-k>"`

"list.extendedSearchMode": ~

Enable extended search mode which allows multiple search patterns delimited by spaces.

"list.normalMappings":~

Custom keymappings on normal mode., default: `{}`
Expand Down
16 changes: 14 additions & 2 deletions src/__tests__/modules/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import os from 'os'
import { mkdirp } from '../../util'
import { isGitIgnored, resolveRoot, statAsync, parentDirs, isParentFolder } from '../../util/fs'
import { fuzzyChar, fuzzyMatch, getCharCodes } from '../../util/fuzzy'
import { score, positions } from '../../util/fzy'
import { getHiglights } from '../../util/highlight'
import { score } from '../../util/match'
import { score as matchScore } from '../../util/match'
import { mixin } from '../../util/object'
import { indexOf, resolveVariables } from '../../util/string'
import helper from '../helper'
Expand All @@ -27,9 +28,20 @@ afterAll(async () => {
describe('score test', () => {
test('should match schema', () => {
let uri = URI.file('/foo').toString()
let s = score([{ language: '*', scheme: 'file' }], uri, 'typescript')
let s = matchScore([{ language: '*', scheme: 'file' }], uri, 'typescript')
expect(s).toBe(5)
})

test('fzy#score', async () => {
let a = score("amuser", "app/models/user.rb")
let b = score("amuser", "app/models/customer.rb")
expect(a).toBeGreaterThan(b)
})

test('fzy#positions', async () => {
let arr = positions("amuser", "app/models/user.rb")
expect(arr).toEqual([0, 4, 11, 12, 13, 14])
})
})

describe('mkdirp', () => {
Expand Down
98 changes: 67 additions & 31 deletions src/list/worker.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Neovim } from '@chemzqm/neovim'
import path from 'path'
import { Emitter, Event, CancellationTokenSource } from 'vscode-languageserver-protocol'
import { AnsiHighlight, ListHighlights, ListItem, ListItemsEvent, ListTask } from '../types'
import { ansiparse } from '../util/ansiparse'
import { patchLine } from '../util/diff'
import { fuzzyMatch, getCharCodes } from '../util/fuzzy'
import { hasMatch, positions, score } from '../util/fzy'
import { getMatchResult } from '../util/score'
import { byteIndex, byteLength, upperFirst } from '../util/string'
import { ListManager } from './manager'
import workspace from '../workspace'
import uuidv1 = require('uuid/v1')
import { URI } from 'vscode-uri'
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
const logger = require('../util/logger')('list-worker')
const controlCode = '\x1b'
Expand All @@ -18,11 +19,11 @@ export interface ExtendedItem extends ListItem {
score: number
matches: number[]
filterLabel: string
recentIndex: number
}

// perform loading task
export default class Worker {
private recentFiles: string[] = []
private _loading = false
private taskId: string
private task: ListTask = null
Expand Down Expand Up @@ -54,6 +55,13 @@ export default class Worker {
})
}

private loadMru(): void {
let mru = workspace.createMru('mru')
mru.load().then(files => {
this.recentFiles = files
}, logError)
}

private set loading(loading: boolean) {
if (this._loading == loading) return
this._loading = loading
Expand Down Expand Up @@ -86,6 +94,7 @@ export default class Worker {
public async loadItems(reload = false): Promise<void> {
let { context, list, listOptions } = this.manager
if (!list) return
this.loadMru()
if (this.timer) clearTimeout(this.timer)
let id = this.taskId = uuidv1()
this.loading = true
Expand Down Expand Up @@ -271,57 +280,80 @@ export default class Worker {
highlights
}
}
let extended = this.manager.getConfig<boolean>('extendedSearchMode', true)
let filtered: ListItem[] | ExtendedItem[]
if (input.length > 0) {
let inputs = extended ? input.split(/\s+/) : [input]
if (matcher == 'strict') {
filtered = items.filter(item => {
let text = item.filterText || item.label
if (!ignorecase) return text.indexOf(input) !== -1
return text.toLowerCase().indexOf(input.toLowerCase()) !== -1
})
for (let item of filtered) {
let spans: [number, number][] = []
let filterLabel = getFilterLabel(item)
let idx = ignorecase ? filterLabel.toLocaleLowerCase().indexOf(input.toLowerCase()) : filterLabel.indexOf(input)
if (idx != -1) {
highlights.push({
spans: [[byteIndex(filterLabel, idx), byteIndex(filterLabel, idx + input.length)]]
})
for (let input of inputs) {
let idx = ignorecase ? filterLabel.toLowerCase().indexOf(input.toLowerCase()) : filterLabel.indexOf(input)
if (idx == -1) return false
spans.push([byteIndex(filterLabel, idx), byteIndex(filterLabel, idx + byteLength(input))])
}
}
highlights.push({ spans })
return true
})
} else if (matcher == 'regex') {
let regex = new RegExp(input, ignorecase ? 'i' : '')
filtered = items.filter(item => regex.test(item.filterText || item.label))
for (let item of filtered) {
let flags = ignorecase ? 'iu' : 'u'
let regexes = inputs.reduce((p, c) => {
try {
let regex = new RegExp(c, flags)
p.push(regex)
// tslint:disable-next-line: no-empty
} catch (e) { }
return p
}, [])
filtered = items.filter(item => {
let spans: [number, number][] = []
let filterLabel = getFilterLabel(item)
let ms = filterLabel.match(regex)
if (ms && ms.length) {
highlights.push({
spans: [[byteIndex(filterLabel, ms.index), byteIndex(filterLabel, ms.index + ms[0].length)]]
})
for (let regex of regexes) {
let ms = filterLabel.match(regex)
if (ms == null) return false
spans.push([byteIndex(filterLabel, ms.index), byteIndex(filterLabel, ms.index + byteLength(ms[0]))])
}
}
highlights.push({ spans })
return true
})
} else {
let codes = getCharCodes(input)
filtered = items.filter(item => fuzzyMatch(codes, item.filterText || item.label))
filtered = items.filter(item => {
let filterText = item.filterText || item.label
return inputs.every(s => hasMatch(s, filterText))
})
filtered = filtered.map(item => {
let filename = item.location ? path.basename(getItemUri(item)) : null
let filterLabel = getFilterLabel(item)
let res = getMatchResult(filterLabel, input, filename)
let matchScore = 0
let matches: number[] = []
for (let input of inputs) {
matches.push(...positions(input, filterLabel))
matchScore += score(input, filterLabel)
}
let { recentScore } = item
if (!recentScore && item.location) {
let uri = getItemUri(item)
if (uri.startsWith('file')) {
let fsPath = URI.parse(uri).fsPath
recentScore = - this.recentFiles.indexOf(fsPath)
}
}
return Object.assign({}, item, {
filterLabel,
score: res ? res.score : 0,
matches: res ? res.matches : []
score: matchScore,
recentScore,
matches
})
}) as ExtendedItem[]
if (sort && items.length) {
(filtered as ExtendedItem[]).sort((a, b) => {
if (a.score != b.score) return b.score - a.score
if (input.length && a.recentScore != b.recentScore) {
return (a.recentScore || -Infinity) - (b.recentScore || -Infinity)
}
if (a.location && b.location) {
let au = getItemUri(a)
let bu = getItemUri(b)
if (au.length != bu.length) {
return au.length - bu.length
}
return au > bu ? 1 : -1
}
return a.label > b.label ? 1 : -1
Expand Down Expand Up @@ -407,3 +439,7 @@ function getItemUri(item: ListItem): string {
if (typeof location == 'string') return location
return location.uri
}

function logError(e): void {
logger.error(e)
}
Loading

0 comments on commit 9458540

Please sign in to comment.