Skip to content

Commit 290c00f

Browse files
committed
feat: focus reverse i search
1 parent 8885f7a commit 290c00f

File tree

4 files changed

+100
-57
lines changed

4 files changed

+100
-57
lines changed

Diff for: src/components/VueCommand.vue

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
'vue-command': !invert,
66
'vue-command--invert': invert
77
}">
8+
<!-- Bar -->
89
<slot name="bar">
910
<div
1011
v-if="!hideBar"
@@ -54,6 +55,7 @@
5455
</div>
5556
</slot>
5657

58+
<!-- History -->
5759
<div
5860
ref="vueCommandHistoryRef"
5961
:class="{
@@ -71,7 +73,7 @@
7173
'vue-command__history__entry--fullscreen': shouldBeFullscreen(index),
7274
'vue-command__history__entry--fullscreen--invert': invert && shouldBeFullscreen(index)
7375
}">
74-
<!-- User given components like bash and query -->
76+
<!-- Components -->
7577
<component
7678
:is="component"
7779
ref="vueCommandHistoryEntryComponentRefs"
@@ -315,8 +317,8 @@ const addDispatchedQuery = dispatchedQuery => {
315317
local.dispatchedQueries.add(dispatchedQuery)
316318
emits('update:dispatchedQueries', local.dispatchedQueries)
317319
}
318-
// Focuses to the last query or multiline query input if the last history entry
319-
// is a query input
320+
// Focuses to the last query, multiline query or reverse I search input if the
321+
// last history entry is a query input
320322
const autoFocus = () => {
321323
// Not the query needs to maintain the validation upon focus but rather the
322324
// terminal itself
@@ -398,9 +400,9 @@ const dispatch = async query => {
398400
const exit = () => {
399401
// TODO Does order matter?
400402
appendToHistory(createQuery())
401-
setHistoryPosition(local.dispatchedQueries.size)
402403
setCursorPosition(0)
403404
setFullscreen(false)
405+
setHistoryPosition(local.dispatchedQueries.size)
404406
setQuery('')
405407
}
406408
// Decreases the history position about one and sets the new query

Diff for: src/components/VueCommandQuery.vue

+59-51
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ const showHelp = inject('showHelp')
158158
const signals = inject('signals')
159159
const terminal = inject('terminal')
160160
161-
// Indicates if the query, including multiline queries, is invalid
161+
// Indicates if the query, including multiline queries and reverse I search, is
162+
// invalid
162163
const isOutdated = ref(false)
163164
const isReverseISearch = ref(false)
164165
const multilineQueryRefs = ref(null)
@@ -172,6 +173,7 @@ const local = reactive({
172173
prompt: terminal.value.prompt,
173174
query: ''
174175
})
176+
// Entered with "\"
175177
const multilineQueries = reactive([])
176178
177179
const isBeforeReverseISearch = computed(() => {
@@ -290,8 +292,13 @@ const autocompleteQuery = async () => {
290292
const cycleReverseISearch = () => {
291293
// TODO
292294
}
293-
// Focuses the last query or multiline query
295+
// Focuses the last query, multiline query or reverse I search
294296
const focus = () => {
297+
if (isReverseISearch.value) {
298+
reverseISearchRef.value.focus()
299+
return
300+
}
301+
295302
if (isEmpty(multilineQueries)) {
296303
queryRef.value.focus()
297304
return
@@ -312,6 +319,18 @@ const hideReverseISearch = async () => {
312319
const resizeReverseISearch = () => {
313320
reverseISearchRef.value.style.width = `${parseInt(reverseISearch.value.length)}ch`
314321
}
322+
// Sets the last multiline query
323+
const setLastMultilineQuery = multilineQuery => {
324+
set(multilineQueries, size(multilineQueries) - 1, multilineQuery)
325+
}
326+
// Shows and focuses the reverse I search
327+
const showReverseISearch = async () => {
328+
isReverseISearch.value = true
329+
330+
// Wait for DOM
331+
await nextTick()
332+
reverseISearchRef.value.focus()
333+
}
315334
// Cancels the current query or multiline query and creates a new query
316335
const sigint = () => {
317336
if (isEmpty(multilineQueries)) {
@@ -329,18 +348,6 @@ const sigint = () => {
329348
isOutdated.value = true
330349
appendToHistory(createQuery())
331350
}
332-
// Sets the last multiline query
333-
const setLastMultilineQuery = multilineQuery => {
334-
set(multilineQueries, size(multilineQueries) - 1, multilineQuery)
335-
}
336-
// Shows and focuses the reverse I search
337-
const showReverseISearch = async () => {
338-
isReverseISearch.value = true
339-
340-
// Wait for DOM
341-
await nextTick()
342-
reverseISearchRef.value.focus()
343-
}
344351
// Deactivates this query or spawns eventually new multiline queries and finally
345352
// dispatches the query to execute it
346353
const submit = async () => {
@@ -373,43 +380,38 @@ const submit = async () => {
373380
.replaceAll(/(?<!\\)\\(?!\\)/g, '')
374381
.trim()
375382
376-
// Dispatch the query to the parent
383+
// Dispatch the query to the terminal
377384
dispatch(query)
378385
}
379386
380-
const unwatchMultilineQueries = watch(multilineQueries, async () => {
381-
await nextTick()
387+
const unwatchIsOutdated = watch(isOutdated, () => {
388+
// Free resources if query, multiline query or reverse I search are
389+
// outdated/inactive
382390
383-
const lastMultilineQueryRef = last(multilineQueryRefs.value)
384-
// Apply given cursor position to actual cursor position
385-
setCursorPosition(lastMultilineQueryRef.selectionStart)
391+
signals.off('SIGINT', sigint)
392+
unwatchLocalQuery()
393+
unwatchTerminalQuery()
394+
unwatchTerminalCursorPosition()
395+
placeholder.value = ''
396+
unwatchMultilineQueries()
397+
unwatchIsOutdated()
398+
unwatchReverseISearch()
386399
})
387400
const unwatchLocalQuery = watch(() => local.query, async () => {
388401
await nextTick()
389402
390403
// Apply given cursor position to actual cursor position
391404
setCursorPosition(queryRef.value.selectionStart)
392405
})
393-
const unwatchTerminalCursorPosition = watch(
394-
() => terminal.value.cursorPosition,
395-
async cursorPosition => {
396-
await nextTick()
397-
398-
// Apply given cursor position to actual cursor position
399-
queryRef.value.setSelectionRange(cursorPosition, cursorPosition)
400-
}
401-
)
402-
const unwatchTerminalQuery = watch(
403-
() => terminal.value.query,
404-
async query => {
405-
await nextTick()
406+
const unwatchMultilineQueries = watch(multilineQueries, async () => {
407+
await nextTick()
406408
407-
// This allows to mutate the query from outside the component and not only
408-
// inside a history entry
409-
local.query = query
410-
}
411-
)
409+
const lastMultilineQueryRef = last(multilineQueryRefs.value)
410+
// Apply given cursor position to actual cursor position
411+
setCursorPosition(lastMultilineQueryRef.selectionStart)
412+
})
412413
const unwatchReverseISearch = watch(reverseISearch, () => {
414+
// Search in dispatched queries
413415
for (const dispatchedQuery of terminal.value.dispatchedQueries) {
414416
if (dispatchedQuery.startsWith(reverseISearch.value)) {
415417
if (isEmpty(multilineQueries)) {
@@ -429,25 +431,31 @@ const unwatchReverseISearch = watch(reverseISearch, () => {
429431
430432
reverseISearchStatus.value = 'failed reverse-i-search'
431433
})
432-
const unwatchIsOutdated = watch(isOutdated, () => {
433-
// Free resources if query, multiline query or reverse I search is
434-
// outdated/inactive
434+
const unwatchTerminalCursorPosition = watch(
435+
() => terminal.value.cursorPosition,
436+
async cursorPosition => {
437+
await nextTick()
435438
436-
signals.off('SIGINT', sigint)
437-
unwatchLocalQuery()
438-
unwatchTerminalQuery()
439-
unwatchTerminalCursorPosition()
440-
placeholder.value = ''
441-
unwatchMultilineQueries()
442-
unwatchIsOutdated()
443-
unwatchReverseISearch()
444-
})
439+
// Apply given cursor position to actual cursor position
440+
queryRef.value.setSelectionRange(cursorPosition, cursorPosition)
441+
}
442+
)
443+
const unwatchTerminalQuery = watch(
444+
() => terminal.value.query,
445+
async query => {
446+
await nextTick()
447+
448+
// This allows to mutate the query from outside the component and not only
449+
// inside a history entry
450+
local.query = query
451+
}
452+
)
445453
446454
onMounted(() => {
447455
// Bind signals
448456
signals.on('SIGINT', sigint)
449457
450-
// Set initial query state
458+
// Set initial query states
451459
setQuery('')
452460
setCursorPosition(0)
453461

Diff for: tests/unit/vue-command-query.spec.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { mount } from '@vue/test-utils'
2+
import VueCommand from '@/components/VueCommand.vue'
3+
import VueCommandQuery from '@/components/VueCommandQuery.vue'
4+
import {
5+
noop,
6+
wrap
7+
} from 'lodash'
8+
import {
9+
computed,
10+
ref
11+
} from 'vue'
12+
13+
// Mock
14+
class ResizeObserver {
15+
observe() { }
16+
unobserve() { }
17+
disconnect() { }
18+
}
19+
20+
window.ResizeObserver = ResizeObserver
21+
22+
describe('VueCommandQuery', () => {
23+
it('shows multiline query', async () => {
24+
const vueCommandWrapper = mount(VueCommand)
25+
26+
const vueCommandQueryWrapper = vueCommandWrapper.findComponent(VueCommandQuery)
27+
const queryRef = vueCommandQueryWrapper.find({ ref: 'queryRef' })
28+
29+
queryRef.setValue('TEST_QUERY \\')
30+
await queryRef.trigger('keyup.enter')
31+
32+
const multilineQueriesWrapper = vueCommandQueryWrapper.find({ ref: 'multilineQueryRefs' })
33+
expect(multilineQueriesWrapper.exists()).toBe(true)
34+
})
35+
})

Diff for: tests/unit/vue-command.spec.js

-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import VueCommandQuery from '@/components/VueCommandQuery.vue'
55
// Mock
66
class ResizeObserver {
77
observe () { }
8-
98
unobserve () { }
10-
119
disconnect () { }
1210
}
1311

0 commit comments

Comments
 (0)