Skip to content

Commit 1e31d32

Browse files
committed
feat: send signals
1 parent 758abb5 commit 1e31d32

File tree

6 files changed

+104
-17
lines changed

6 files changed

+104
-17
lines changed

Diff for: README.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# vue-command
22

3-
A fully working, most feature-rich Vue.js terminal emulator. See the [demo](https://ndabap.github.io/vue-command/) and check the demo [source code](https://github.com/ndabAP/vue-command/blob/master/src/hosted/App.vue).
3+
A fully working, most feature-rich Vue.js terminal emulator. See the
4+
[demo](https://ndabap.github.io/vue-command/) and check the demo
5+
[source code](https://github.com/ndabAP/vue-command/blob/master/src/hosted/App.vue).
46

57
## Features
68

@@ -234,10 +236,12 @@ import { listFormatter } from "vue-command";
234236
| `optionsResolver` |
235237
| `parser` |
236238
| `programs` |
239+
| `sendSignal` |
237240
| `setCursorPosition` |
238241
| `setFullscreen` |
239242
| `setHistoryPosition` |
240243
| `showHelp` |
244+
| `signals` |
241245
| `setQuery` |
242246
| `terminal` |
243247

@@ -258,10 +262,12 @@ inject: ["exit", "terminal"],
258262
| `exit` |
259263
| `incrementHistory` |
260264
| `programs` |
265+
| `sendSignal` |
261266
| `setCursorPosition` |
262267
| `setFullscreen` |
263268
| `setHistoryPosition` |
264269
| `setQuery` |
270+
| `signals` |
265271
| `terminal` |
266272

267273
## Events
@@ -272,6 +278,18 @@ inject: ["exit", "terminal"],
272278
| `minimizeClicked` | Emitted on button minimize click |
273279
| `fullscreenClicked` | Emitted on button fullscreen click |
274280

281+
## Signals
282+
283+
It's possible to send and receive signals like `SIGINT`, `SIGTERM` or `SIGKILL`.
284+
`SIGINT` is the only implemented signal. When the terminal user clicks
285+
<kbd>Ctrl</kbd> + <kbd>c</kbd>, you can listen to the event by providing the
286+
signal name and a callback:
287+
288+
```js
289+
const signals = inject("signals");
290+
signals.on("SIGNINT", () => console.debug("SIGINT"));
291+
```
292+
275293
## Nice-to-haves
276294

277295
These features didn't make it into the last release. If you would like to

Diff for: src/components/VueCommand.vue

+19-6
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ import {
9595
} from '@/library'
9696
import {
9797
and,
98-
or
98+
or,
99+
newEventBus,
100+
PUBLISH_SYMBOL
99101
} from '@/utils'
100102
import head from 'lodash.head'
101103
import isFunction from 'lodash.isfunction'
@@ -233,6 +235,9 @@ const vueCommandHistoryEntryComponentRefs = ref(null)
233235
const vueCommandHistoryRef = ref(null)
234236
const vueCommandRef = ref(null)
235237
238+
// Signals like SIGINT or SIGKILL
239+
const signals = reactive(newEventBus())
240+
236241
// A local copy to allow the absence of properties
237242
const local = reactive({
238243
cursorPosition: props.cursorPosition,
@@ -269,6 +274,7 @@ const shouldShowHistoryEntry = computed(() => {
269274
and(local.isFullscreen, eq(index, size(local.history) - 1))
270275
)
271276
})
277+
// Determinates if the given history entry at index should be fullscreen or not
272278
const shouldBeFullscreen = computed(() => {
273279
return index => and(local.isFullscreen, eq(index, size(local.history) - 1))
274280
})
@@ -303,6 +309,10 @@ const autoFocus = () => {
303309
const autoHistoryPosition = () => {
304310
setHistoryPosition(local.dispatchedQueries.size)
305311
}
312+
const appendToHistory = (...components) => {
313+
local.history.push(...components)
314+
emits('update:history', local.history)
315+
}
306316
// Parses the query, looks for a user given command and appends the resulting
307317
// component to the history
308318
const dispatch = async () => {
@@ -357,11 +367,6 @@ const dispatch = async () => {
357367
})
358368
appendToHistory(markRaw(component))
359369
}
360-
361-
const appendToHistory = (...components) => {
362-
local.history.push(...components)
363-
emits('update:history', local.history)
364-
}
365370
// Tear down component and execute final steps
366371
const exit = () => {
367372
// TODO Does order matter?
@@ -396,6 +401,10 @@ const scrollToBottom = async () => {
396401
await nextTick()
397402
vueCommandHistoryRef.value.scrollTop = vueCommandHistoryRef.value.scrollHeight
398403
}
404+
const sendSignal = signal => {
405+
console.log(signal)
406+
signals[PUBLISH_SYMBOL](signal)
407+
}
399408
const setCursorPosition = cursorPosition => {
400409
local.cursorPosition = cursorPosition
401410
emits('update:cursorPosition', cursorPosition)
@@ -469,10 +478,12 @@ provide('invert', props.invert)
469478
provide('optionsResolver', props.optionsResolver)
470479
provide('parser', props.parser)
471480
provide('programs', programs)
481+
provide('sendSignal', sendSignal)
472482
provide('setCursorPosition', setCursorPosition)
473483
provide('setFullscreen', setFullscreen)
474484
provide('setHistoryPosition', setHistoryPosition)
475485
provide('showHelp', props.showHelp)
486+
provide('signals', signals)
476487
provide('setQuery', setQuery)
477488
provide('terminal', terminal)
478489
@@ -484,10 +495,12 @@ defineExpose({
484495
exit,
485496
incrementHistory,
486497
programs,
498+
sendSignal,
487499
setCursorPosition,
488500
setFullscreen,
489501
setHistoryPosition,
490502
setQuery,
503+
signals,
491504
terminal
492505
})
493506
</script>

Diff for: src/components/VueCommandQuery.vue

+8-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
type="text"
3030
@click="setCursorPosition($refs.queryRef.selectionStart)"
3131
@input="setQuery($event.target.value)"
32-
@keydown.ctrl.c.exact.prevent="sigint"
3332
@keydown.tab.exact.prevent="autocompleteQuery"
3433
@keydown.ctrl.r.exact.prevent="reverseISearch"
3534
@keyup.arrow-left.exact="setCursorPosition($refs.queryRef.selectionStart)"
@@ -78,6 +77,7 @@ const programs = inject('programs')
7877
const setCursorPosition = inject('setCursorPosition')
7978
const setQuery = inject('setQuery')
8079
const showHelp = inject('showHelp')
80+
const signals = inject('signals')
8181
const terminal = inject('terminal')
8282
8383
const isOutdated = ref(false)
@@ -165,6 +165,9 @@ const autocompleteQuery = async () => {
165165
const focus = () => {
166166
queryRef.value.focus()
167167
}
168+
const bindSignals = () => {
169+
signals.on('SIGINT', sigint)
170+
}
168171
const reverseISearch = event => {
169172
// TODO
170173
// console.debug(event)
@@ -216,6 +219,7 @@ const unwatchTerminalQuery = watch(
216219
)
217220
// Free resources if query is outdated/inactive
218221
const unwatchIsOutdated = watch(isOutdated, () => {
222+
signals.off('SIGINT')
219223
unwatchTerminalQuery()
220224
unwatchLocalQuery()
221225
unwatchTerminalCursorPosition()
@@ -231,6 +235,9 @@ onMounted(() => {
231235
232236
// Show eventually help as placeholder
233237
showDelayedHelp()
238+
239+
// Bind signals like "SIGINT"
240+
bindSignals()
234241
})
235242
236243
defineExpose({

Diff for: src/hosted/ChuckNorris.vue

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const API_TIMEOUT = 5000 // 5 seconds
1313
const abortController = new AbortController()
1414
1515
export default {
16-
inject: ['exit'],
16+
inject: ['exit', 'signals'],
1717
1818
data: () => ({
1919
isError: false,
@@ -30,6 +30,12 @@ export default {
3030
}
3131
}, API_TIMEOUT)
3232
33+
this.signals.on('SIGINT', () => {
34+
console.debug('SIGINT')
35+
abortController.abort()
36+
this.exit()
37+
})
38+
3339
try {
3440
const response = await fetch(API_URL, { signal: abortController.signal })
3541
if (!response.ok) {

Diff for: src/library.js

+20-8
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ import isFunction from 'lodash.isfunction'
1616
const ARROW_UP_KEY = 'ArrowUp'
1717
const ARROW_DOWN_KEY = 'ArrowDown'
1818

19-
// Cycles through dispatched queries with arrow keyes
20-
export const defaultHistoryEventResolver = (refs, eventProvider) => {
21-
// TODO Bind on last query?
22-
const vueCommandHistoryRef = refs.vueCommandHistoryRef
19+
// Cycles through dispatched queries with arrow keys
20+
export const defaultHistoryEventResolver = (refs, { decrementHistory, incrementHistory }) => {
21+
const vueCommandRef = refs.vueCommandRef
2322

2423
const eventResolver = event => {
2524
switch (event.key) {
@@ -34,22 +33,35 @@ export const defaultHistoryEventResolver = (refs, eventProvider) => {
3433
switch (event.key) {
3534
// Back in history, index down
3635
case ARROW_UP_KEY:
37-
eventProvider.decrementHistory()
36+
decrementHistory()
3837
break
3938

4039
// Back in history, index up
4140
case ARROW_DOWN_KEY:
42-
eventProvider.incrementHistory()
41+
incrementHistory()
4342
break
4443
}
4544
}
4645
}
4746

48-
vueCommandHistoryRef.addEventListener('keydown', eventResolver)
47+
vueCommandRef.addEventListener('keydown', eventResolver)
48+
}
49+
50+
export const defaultSignalEventResolver = (refs, { sendSignal }) => {
51+
const vueCommandRef = refs.vueCommandRef
52+
53+
const eventResolver = event => {
54+
switch (event.key) {
55+
// Validate event
56+
}
57+
sendSignal('SIGINT')
58+
}
59+
60+
vueCommandRef.addEventListener('keydown', eventResolver)
4961
}
5062

5163
// Returns a list of default event resolver
52-
export const newDefaultEventResolver = () => [defaultHistoryEventResolver]
64+
export const newDefaultEventResolver = () => [defaultHistoryEventResolver, defaultSignalEventResolver]
5365

5466
// Creates a "stdout" with the given formatter or text and name. It exits as
5567
// soon as the component has been mounted

Diff for: src/utils/index.js

+31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
// These are helpers for the package
22

3+
export const PUBLISH_SYMBOL = Symbol('publish')
4+
5+
// Creats a new event bus to publish, subscribe and unsubscribe from events
6+
export const newEventBus = () => {
7+
const events = {}
8+
return {
9+
[PUBLISH_SYMBOL] (event) {
10+
const callbacks = events[event]
11+
if (!callbacks) {
12+
return
13+
}
14+
15+
for (const callback of callbacks) {
16+
callback()
17+
}
18+
},
19+
20+
on (event, callback) {
21+
if (!events[event]) {
22+
events[event] = []
23+
}
24+
25+
events[event].push(callback)
26+
},
27+
28+
off (event) {
29+
delete events[event]
30+
}
31+
}
32+
}
33+
334
export const and = (x, y) => {
435
return x && y
536
}

0 commit comments

Comments
 (0)