Skip to content

Commit a04b769

Browse files
authored
Merge pull request #406 from ndabAP/feat/signal
feat: send signals
2 parents 758abb5 + 22a0bfa commit a04b769

14 files changed

+142
-60
lines changed

README.md

Lines changed: 19 additions & 1 deletion
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+
You can send and receive signals like `SIGINT`, `SIGTERM` or `SIGKILL`.
284+
`SIGINT` is the only implemented signal for now. When the user presses
285+
<kbd>Ctrl</kbd> + <kbd>c</kbd>, you can listen to this event by providing a
286+
signal name and a callback:
287+
288+
```js
289+
const signals = inject("signals");
290+
signals.on("SIGINT", () => 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

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="favicon.ico"><![endif]--><title>vue-command</title><link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"><link href="css/app.dc600676.css" rel="preload" as="style"><link href="js/app.ccacc744.js" rel="preload" as="script"><link href="js/chunk-vendors.237296fb.js" rel="preload" as="script"><link href="css/app.dc600676.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="vue-command"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but vue-command doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.237296fb.js"></script><script src="js/app.ccacc744.js"></script></body></html>
1+
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="favicon.ico"><![endif]--><title>vue-command</title><link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"><link href="css/app.dc600676.css" rel="preload" as="style"><link href="js/app.1c48fd61.js" rel="preload" as="script"><link href="js/chunk-vendors.237296fb.js" rel="preload" as="script"><link href="css/app.dc600676.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="vue-command"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but vue-command doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.237296fb.js"></script><script src="js/app.1c48fd61.js"></script></body></html>

docs/js/app.1c48fd61.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/js/app.1c48fd61.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/js/app.ccacc744.js

Lines changed: 0 additions & 2 deletions
This file was deleted.

docs/js/app.ccacc744.js.map

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/precache-manifest.2032eed84a887953ede510352a11da7d.js renamed to docs/precache-manifest.4af45eb0bd164658140cfe45f68b9fbb.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
self.__precacheManifest = (self.__precacheManifest || []).concat([
22
{
3-
"revision": "41ea92d2220469da0ef2",
3+
"revision": "08f3678f038dd723afed",
44
"url": "css/app.dc600676.css"
55
},
66
{
7-
"revision": "b825913d9118f944000bde80bed82a17",
7+
"revision": "d456809b0afff9e778f5d74e4f5c74e0",
88
"url": "index.html"
99
},
1010
{
11-
"revision": "41ea92d2220469da0ef2",
12-
"url": "js/app.ccacc744.js"
11+
"revision": "08f3678f038dd723afed",
12+
"url": "js/app.1c48fd61.js"
1313
},
1414
{
1515
"revision": "37263e520d1b52d48297",

docs/service-worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
1515

1616
importScripts(
17-
"precache-manifest.2032eed84a887953ede510352a11da7d.js"
17+
"precache-manifest.4af45eb0bd164658140cfe45f68b9fbb.js"
1818
);
1919

2020
workbox.core.setCacheNameDetails({prefix: "vue-command"});

src/components/VueCommand.vue

Lines changed: 27 additions & 14 deletions
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 () => {
@@ -342,6 +352,7 @@ const dispatch = async () => {
342352
343353
// Command is user created component. Decorate component
344354
const component = defineComponent({
355+
name: 'VueCommandEntryComponent',
345356
provide () {
346357
return {
347358
// This will be unique for the component and not reactive by design
@@ -357,11 +368,6 @@ const dispatch = async () => {
357368
})
358369
appendToHistory(markRaw(component))
359370
}
360-
361-
const appendToHistory = (...components) => {
362-
local.history.push(...components)
363-
emits('update:history', local.history)
364-
}
365371
// Tear down component and execute final steps
366372
const exit = () => {
367373
// TODO Does order matter?
@@ -371,23 +377,23 @@ const exit = () => {
371377
setFullscreen(false)
372378
setQuery('')
373379
}
374-
const incrementHistory = () => {
375-
// History pointer must be lower query history
376-
if (!lt(local.historyPosition, local.dispatchedQueries.size)) {
380+
const decrementHistory = () => {
381+
// History pointer must be greater zero
382+
if (eq(local.historyPosition, 0)) {
377383
return
378384
}
379385
380-
setHistoryPosition(local.historyPosition + 1)
386+
setHistoryPosition(local.historyPosition - 1)
381387
const query = nth([...local.dispatchedQueries], local.historyPosition)
382388
setQuery(query)
383389
}
384-
const decrementHistory = () => {
385-
// History pointer must be greater zero
386-
if (eq(local.historyPosition, 0)) {
390+
const incrementHistory = () => {
391+
// History pointer must be lower query history
392+
if (!lt(local.historyPosition, local.dispatchedQueries.size)) {
387393
return
388394
}
389395
390-
setHistoryPosition(local.historyPosition - 1)
396+
setHistoryPosition(local.historyPosition + 1)
391397
const query = nth([...local.dispatchedQueries], local.historyPosition)
392398
setQuery(query)
393399
}
@@ -396,6 +402,9 @@ const scrollToBottom = async () => {
396402
await nextTick()
397403
vueCommandHistoryRef.value.scrollTop = vueCommandHistoryRef.value.scrollHeight
398404
}
405+
const sendSignal = 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>

src/components/VueCommandQuery.vue

Lines changed: 8 additions & 1 deletion
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({

0 commit comments

Comments
 (0)