Skip to content

Commit c0e4226

Browse files
committed
feat(search): Sort search results by levenshtein distance
1 parent 3b05ab5 commit c0e4226

File tree

2 files changed

+118
-12
lines changed

2 files changed

+118
-12
lines changed

src/dialog_launcher.ts

+18-12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as result from 'result';
1111
import * as search from 'dialog_search';
1212
import * as launch from 'launcher_service';
1313
import * as plugins from 'launcher_plugins';
14+
import * as levenshtein from 'levenshtein';
1415

1516
import type { ShellWindow } from 'window';
1617
import type { Ext } from 'extension';
@@ -90,15 +91,13 @@ export class Launcher extends search.Search {
9091
return needles.every((n) => hay.includes(n));
9192
};
9293

93-
let apps: Array<launch.SearchOption> = new Array();
94-
9594
// Filter matching windows
9695
for (const window of ext.tab_list(Meta.TabList.NORMAL, null)) {
9796
const retain = contains_pattern(window.name(ext), needles)
9897
|| contains_pattern(window.meta.get_title(), needles);
9998

10099
if (retain) {
101-
apps.push(window_selection(ext, window, this.icon_size()))
100+
this.options.push(window_selection(ext, window, this.icon_size()))
102101
}
103102
}
104103

@@ -113,7 +112,7 @@ export class Launcher extends search.Search {
113112
if (retain) {
114113
const generic = app.generic_name();
115114

116-
apps.push(new launch.SearchOption(
115+
this.options.push(new launch.SearchOption(
117116
app.name(),
118117
generic ? generic + " — " + where : where,
119118
'application-default-symbolic',
@@ -125,19 +124,26 @@ export class Launcher extends search.Search {
125124
}
126125

127126
// Sort the list of matched selections
128-
apps.sort((a, b) => {
129-
const a_name = a.title.toLowerCase();
130-
const b_name = b.title.toLowerCase();
127+
this.options.sort((a, b) => {
128+
const a_name = a.title.toLowerCase()
129+
const b_name = b.title.toLowerCase()
131130

132131
const pattern_lower = pattern.toLowerCase()
133132

134-
const a_includes = a_name.includes(pattern_lower);
135-
const b_includes = b_name.includes(pattern_lower);
133+
let a_name_weight = levenshtein.compare(pattern_lower, a_name)
136134

137-
return ((a_includes && b_includes) || (!a_includes && !b_includes)) ? (a_name > b_name ? 1 : 0) : a_includes ? -1 : b_includes ? 1 : 0;
138-
});
135+
let b_name_weight = levenshtein.compare(pattern_lower, b_name)
136+
137+
if (a.description) {
138+
a_name_weight = Math.min(a_name_weight, levenshtein.compare(pattern_lower, a.description.toLowerCase()))
139+
}
139140

140-
for (const app of apps) this.options.push(app)
141+
if (b.description) {
142+
b_name_weight = Math.min(b_name_weight, levenshtein.compare(pattern_lower, b.description.toLowerCase()))
143+
}
144+
145+
return a_name_weight > b_name_weight ? 1 : 0
146+
});
141147

142148
// Truncate excess items from the list
143149
this.options.splice(this.list_max());

src/levenshtein.ts

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Based on https://github.com/gustf/js-levenshtein/blob/master/index.js
3+
* MIT License
4+
* Copyright (c) 2017 Gustaf Andersson
5+
*/
6+
7+
function _min(d0: number, d1: number, d2: number, bx: number, ay: number): number {
8+
return d0 < d1 || d2 < d1
9+
? d0 > d2
10+
? d2 + 1
11+
: d0 + 1
12+
: bx === ay
13+
? d1
14+
: d1 + 1
15+
}
16+
17+
export function compare(a: string, b: string): number {
18+
if (a === b) return 0
19+
20+
if (a.length > b.length) {
21+
const tmp = a
22+
a = b
23+
b = tmp
24+
}
25+
26+
let la = a.length
27+
let lb = b.length
28+
29+
while (la > 0 && (a.charCodeAt(la - 1) === b.charCodeAt(lb - 1))) {
30+
la--
31+
lb--
32+
}
33+
34+
let offset = 0
35+
36+
while (offset < la && (a.charCodeAt(offset) === b.charCodeAt(offset))) {
37+
offset++
38+
}
39+
40+
la -= offset
41+
lb -= offset
42+
43+
if (la === 0 || lb < 3) return lb
44+
45+
let x = 0,
46+
y = 0,
47+
d0 = 0,
48+
d1 = 0,
49+
d2 = 0,
50+
d3 = 0,
51+
dd = 0,
52+
dy = 0,
53+
ay = 0,
54+
bx0 = 0,
55+
bx1 = 0,
56+
bx2 = 0,
57+
bx3 = 0
58+
59+
const vector = []
60+
61+
for (y = 0; y < la; y++) {
62+
vector.push(y + 1)
63+
vector.push(a.charCodeAt(offset + y))
64+
}
65+
66+
let len = vector.length - 1
67+
68+
for (; x < lb - 3;) {
69+
bx0 = b.charCodeAt(offset + (d0 = x))
70+
bx1 = b.charCodeAt(offset + (d1 = x + 1))
71+
bx2 = b.charCodeAt(offset + (d2 = x + 2))
72+
bx3 = b.charCodeAt(offset + (d3 = x + 3))
73+
dd = (x += 4)
74+
for (y = 0; y < len; y += 2) {
75+
dy = vector[y]
76+
ay = vector[y + 1]
77+
d0 = _min(dy, d0, d1, bx0, ay)
78+
d1 = _min(d0, d1, d2, bx1, ay)
79+
d2 = _min(d1, d2, d3, bx2, ay)
80+
dd = _min(d2, d3, dd, bx3, ay)
81+
vector[y] = dd
82+
d3 = d2
83+
d2 = d1
84+
d1 = d0
85+
d0 = dy
86+
}
87+
}
88+
89+
for (; x < lb;) {
90+
bx0 = b.charCodeAt(offset + (d0 = x))
91+
dd = ++x
92+
for (y = 0; y < len; y += 2) {
93+
dy = vector[y]
94+
vector[y] = dd = _min(dy, d0, dd, bx0, vector[y + 1])
95+
d0 = dy
96+
}
97+
}
98+
99+
return dd
100+
}

0 commit comments

Comments
 (0)