|
1 | 1 | <script lang="ts" context="module">
|
2 | 2 | export enum FuzzyFinderTabType {
|
| 3 | + All = 'all', |
3 | 4 | Repos = 'repos',
|
4 | 5 | Symbols = 'symbols',
|
5 | 6 | Files = 'files',
|
|
23 | 24 | import SymbolKindIcon from '$lib/search/SymbolKindIcon.svelte'
|
24 | 25 | import { displayRepoName } from '$lib/shared'
|
25 | 26 | import TabsHeader, { type Tab } from '$lib/TabsHeader.svelte'
|
26 |
| - import { Input } from '$lib/wildcard' |
| 27 | + import { Alert, Input } from '$lib/wildcard' |
27 | 28 | import Button from '$lib/wildcard/Button.svelte'
|
28 | 29 |
|
29 |
| - import { filesHotkey, reposHotkey, symbolsHotkey } from './keys' |
30 |
| - import { |
31 |
| - createRepositorySource, |
32 |
| - type CompletionSource, |
33 |
| - createFileSource, |
34 |
| - type FuzzyFinderResult, |
35 |
| - createSymbolSource, |
36 |
| - } from './sources' |
| 30 | + import { allHotkey, filesHotkey, reposHotkey, symbolsHotkey } from './keys' |
| 31 | + import { type CompletionSource, createFuzzyFinderSource } from './sources' |
37 | 32 |
|
38 | 33 | export let open = false
|
39 | 34 | export let scope = ''
|
|
45 | 40 | }
|
46 | 41 | }
|
47 | 42 |
|
| 43 | + const client = getGraphQLClient() |
| 44 | + const tabs: (Tab & { source: CompletionSource })[] = [ |
| 45 | + { |
| 46 | + id: 'all', |
| 47 | + title: 'All', |
| 48 | + shortcut: allHotkey, |
| 49 | + source: createFuzzyFinderSource({ |
| 50 | + client, |
| 51 | + queryBuilder: value => |
| 52 | + `patterntype:keyword (type:repo OR type:path OR type:symbol) count:50 ${scope} ${value}`, |
| 53 | + }), |
| 54 | + }, |
| 55 | + { |
| 56 | + id: 'repos', |
| 57 | + title: 'Repos', |
| 58 | + shortcut: reposHotkey, |
| 59 | + source: createFuzzyFinderSource({ |
| 60 | + client, |
| 61 | + queryBuilder: value => `patterntype:keyword type:repo count:50 ${value}`, |
| 62 | + }), |
| 63 | + }, |
| 64 | + { |
| 65 | + id: 'symbols', |
| 66 | + title: 'Symbols', |
| 67 | + shortcut: symbolsHotkey, |
| 68 | + source: createFuzzyFinderSource({ |
| 69 | + client, |
| 70 | + queryBuilder: value => `patterntype:keyword type:symbol count:50 ${scope} ${value}`, |
| 71 | + }), |
| 72 | + }, |
| 73 | + { |
| 74 | + id: 'files', |
| 75 | + title: 'Files', |
| 76 | + shortcut: filesHotkey, |
| 77 | + source: createFuzzyFinderSource({ |
| 78 | + client, |
| 79 | + queryBuilder: value => `patterntype:keyword type:path count:50 ${scope} ${value}`, |
| 80 | + }), |
| 81 | + }, |
| 82 | + ] |
| 83 | +
|
48 | 84 | let dialog: HTMLDialogElement | undefined
|
49 | 85 | let listbox: HTMLElement | undefined
|
50 | 86 | let input: HTMLInputElement | undefined
|
51 | 87 | let query = ''
|
| 88 | + let selectedTab = tabs[0] |
| 89 | + let selectedOption: number = 0 |
52 | 90 |
|
53 |
| - const client = getGraphQLClient() |
54 |
| - const tabs: (Tab & { source: CompletionSource<FuzzyFinderResult> })[] = [ |
55 |
| - { id: 'repos', title: 'Repos', shortcut: reposHotkey, source: createRepositorySource(client) }, |
56 |
| - { id: 'symbols', title: 'Symbols', shortcut: symbolsHotkey, source: createSymbolSource(client, () => scope) }, |
57 |
| - { id: 'files', title: 'Files', shortcut: filesHotkey, source: createFileSource(client, () => scope) }, |
58 |
| - ] |
| 91 | + $: useScope = scope && selectedTab.id !== 'repos' |
| 92 | + $: source = selectedTab.source |
| 93 | + $: if (open) { |
| 94 | + source.next(query) |
| 95 | + } |
| 96 | + $: if (open) { |
| 97 | + dialog?.showModal() |
| 98 | + input?.select() |
| 99 | + } else { |
| 100 | + dialog?.close() |
| 101 | + } |
| 102 | + $: placeholder = (function () { |
| 103 | + switch (selectedTab.id) { |
| 104 | + case 'repos': |
| 105 | + return 'Find repositories...' |
| 106 | + case 'symbols': |
| 107 | + return 'Find symbols...' |
| 108 | + case 'files': |
| 109 | + return 'Find files...' |
| 110 | + default: |
| 111 | + return 'Find anything...' |
| 112 | + } |
| 113 | + })() |
59 | 114 |
|
60 | 115 | function selectNext() {
|
61 | 116 | let next: HTMLElement | null = null
|
|
165 | 220 | dialog?.close()
|
166 | 221 | }
|
167 | 222 | }
|
168 |
| -
|
169 |
| - let selectedTab = tabs[0] |
170 |
| - let selectedOption: number = 0 |
171 |
| -
|
172 |
| - $: useScope = scope && selectedTab.id !== 'repos' |
173 |
| - $: source = selectedTab.source |
174 |
| - $: if (open) { |
175 |
| - source.next(query) |
176 |
| - } |
177 |
| - $: if (open) { |
178 |
| - dialog?.showModal() |
179 |
| - input?.select() |
180 |
| - } else { |
181 |
| - dialog?.close() |
182 |
| - } |
183 | 223 | </script>
|
184 | 224 |
|
185 | 225 | <dialog bind:this={dialog} on:close>
|
|
208 | 248 | <Input
|
209 | 249 | type="text"
|
210 | 250 | bind:input
|
211 |
| - placeholder="Enter a fuzzy query" |
| 251 | + {placeholder} |
212 | 252 | autofocus
|
213 | 253 | value={query}
|
214 | 254 | onInput={event => {
|
|
227 | 267 | {/if}
|
228 | 268 | </div>
|
229 | 269 | <ul role="listbox" bind:this={listbox} aria-label="Search results">
|
230 |
| - {#if $source.value} |
231 |
| - {#each $source.value as item, index (item.item)} |
232 |
| - {@const repo = item.item.repository.name} |
| 270 | + {#if $source.pending} |
| 271 | + <li class="message">Waiting for response...</li> |
| 272 | + {:else if $source.error} |
| 273 | + <li class="error"><Alert variant="danger">{$source.error.message}</Alert></li> |
| 274 | + {:else if $source.value?.results} |
| 275 | + {#each $source.value.results as item, index (item)} |
| 276 | + {@const repo = item.repository.name} |
233 | 277 | {@const displayRepo = displayRepoName(repo)}
|
234 | 278 | <li role="option" aria-selected={selectedOption === index} data-index={index}>
|
235 |
| - {#if item.item.type === 'repo'} |
| 279 | + {#if item.type === 'repo'} |
236 | 280 | {@const matchOffset = repo.length - displayRepo.length}
|
237 |
| - <a href="/{item.item.repository.name}" on:click={handleClick}> |
238 |
| - <span class="icon"><CodeHostIcon repository={item.item.repository.name} /></span> |
| 281 | + <a href="/{item.repository.name}" on:click={handleClick}> |
| 282 | + <span class="icon"><CodeHostIcon repository={item.repository.name} /></span> |
239 | 283 | <span class="label"
|
240 |
| - ><EmphasizedLabel |
241 |
| - label={displayRepo} |
242 |
| - matches={item.positions} |
243 |
| - offset={matchOffset} |
244 |
| - /></span |
| 284 | + ><EmphasizedLabel label={displayRepo} offset={matchOffset} /></span |
245 | 285 | >
|
246 | 286 | <span class="info">{repo}</span>
|
247 | 287 | </a>
|
248 |
| - {:else if item.item.type == 'symbol'} |
249 |
| - <a href={item.item.symbol.location.url} on:click={handleClick}> |
250 |
| - <span class="icon"><SymbolKindIcon symbolKind={item.item.symbol.kind} /></span> |
251 |
| - <span class="label" |
252 |
| - ><EmphasizedLabel |
253 |
| - label={item.item.symbol.name} |
254 |
| - matches={item.positions} |
255 |
| - /></span |
256 |
| - > |
| 288 | + {:else if item.type == 'symbol'} |
| 289 | + <a href={item.symbol.location.url} on:click={handleClick}> |
| 290 | + <span class="icon"><SymbolKindIcon symbolKind={item.symbol.kind} /></span> |
| 291 | + <span class="label"><EmphasizedLabel label={item.symbol.name} /></span> |
257 | 292 | <span class="info mono"
|
258 |
| - >{#if !useScope}{displayRepo} · {/if}{item.item.file.path}</span |
| 293 | + >{#if !useScope}{displayRepo} · {/if}{item.file.path}</span |
259 | 294 | >
|
260 | 295 | </a>
|
261 |
| - {:else if item.item.type == 'file'} |
262 |
| - {@const fileName = item.item.file.name} |
263 |
| - {@const folderName = dirname(item.item.file.path)} |
264 |
| - <a href={item.item.file.url} on:click={handleClick}> |
265 |
| - <span class="icon"><FileIcon file={item.item.file} inline /></span> |
| 296 | + {:else if item.type == 'file'} |
| 297 | + {@const fileName = item.file.name} |
| 298 | + {@const folderName = dirname(item.file.path)} |
| 299 | + <a href={item.file.url} on:click={handleClick}> |
| 300 | + <span class="icon"><FileIcon file={item.file} inline /></span> |
266 | 301 | <span class="label"
|
267 |
| - ><EmphasizedLabel |
268 |
| - label={fileName} |
269 |
| - matches={item.positions} |
270 |
| - offset={folderName.length + 1} |
271 |
| - /></span |
| 302 | + ><EmphasizedLabel label={fileName} offset={folderName.length + 1} /></span |
272 | 303 | >
|
273 | 304 | <span class="info mono">
|
274 | 305 | {#if !useScope}{displayRepo} · {/if}
|
275 |
| - <EmphasizedLabel label={folderName} matches={item.positions} /> |
| 306 | + <EmphasizedLabel label={folderName} /> |
276 | 307 | </span>
|
277 | 308 | </a>
|
278 | 309 | {/if}
|
279 | 310 | </li>
|
280 | 311 | {:else}
|
281 |
| - <li class="empty">No matches</li> |
| 312 | + <li class="message">No matches</li> |
282 | 313 | {/each}
|
283 | 314 | {/if}
|
284 | 315 | </ul>
|
|
381 | 412 | }
|
382 | 413 | }
|
383 | 414 |
|
384 |
| - .empty { |
| 415 | + .message, .error { |
385 | 416 | padding: 1rem;
|
| 417 | + } |
| 418 | +
|
| 419 | + .message { |
386 | 420 | text-align: center;
|
387 | 421 | color: var(--text-muted);
|
388 | 422 | }
|
|
0 commit comments