Giter VIP home page Giter VIP logo

vue-command's Introduction

vue-command

A fully working, most feature-rich Vue.js terminal emulator. See the demo and check the demo source code. Now with Vue.js 3 support!

Features

  • Simple, yet extensible API
  • Supports asynchronous commands
  • Supports fullscreen mode
  • Customize the terminal with slots
  • Provide your own parser (falls back to simple one)
  • Multiline support (with \)
  • Autocompletion resolver (with )
  • Browse history (with /)
  • Search history (with Ctrl + r)
  • Provide your own event resolver to support additional keyboard events

Installation

$ npm install vue-command --save

Usage

Let's start with a dead simple example. We want to send "Hello world" to Stdout when entering hello-world.

<template>
  <vue-command :commands="commands" />
</template>

<script>
import VueCommand, { createStdout } from "vue-command";
import "vue-command/dist/vue-command.css";

export default {
  components: {
    VueCommand,
  },

  data: () => ({
    commands: {
      "hello-world": () => createStdout("Hello world"),
    },
  }),
};
</script>

Now a more complex one. Let's assume we want to build the nano editor available in many shells.

We inject terminal to make sure the editor is only visible when the terminal is in fullscreen mode and also a function called exit to tell the terminal that the command has been finished when the user enters Ctrl + x. Furthermore, we use setFullscreen to switch the terminal into fullscreen mode.

<template>
  <div v-show="terminal.isFullscreen">
    <textarea ref="nano" @keyup.ctrl.x.exact="exit">
This is a nano text editor emulator! Press Ctrl + x to leave.</textarea
    >
  </div>
</template>

<script>
export default {
  inject: ["exit", "setFullscreen", "terminal"],

  created() {
    this.setFullscreen(true);
  },

  mounted() {
    this.$refs.nano.focus();
  },
};
</script>

<style scoped>
div,
textarea {
  height: 100%;
}
</style>

Now the command has to return the component.

<template>
  <vue-command :commands="commands" />
</template>

<script>
import VueCommand from "vue-command";
import "vue-command/dist/vue-command.css";
import NanoEditor from "@/components/NanoEditor.vue";

export default {
  components: {
    VueCommand,
  },

  data: () => ({
    commands: {
      nano: () => NanoEditor,
    },
  }),
};
</script>

Properties

Some properties can be mutated by the terminal. Therefore, adding the v-model directive is required.

Property Description Type Default value Required Two-way binding
commands See Commands Object {} No No
cursor-position Cursor position Number 0 No Yes
dispatched-queries Non-empty dispatched queries Set new Set() No Yes
event-resolver See Event resolver Function newDefaultEventResolver No No
font Terminal font String '' No No
help-text Command help String '' No Yes
help-timeout Command help timeout Number 3000 No No
hide-bar Hides the bar Boolean false No No
hide-buttons Hides the buttons Boolean false No No
hide-prompt Hides the prompt Boolean false No No
hide-title Hides the title Boolean false No No
history Terminal history Array [] No Yes
history-position Points to the latest dispatched query entry Number 0 No Yes
interpreter See Interpreter Function null No No
invert Inverts the terminals colors Boolean false No No
is-fullscreen Terminal fullscreen mode Boolean false No Yes
options-resolver See Options resolver Function null No No
parser Query parser Function defaultParser No No
prompt Terminal prompt String ~$ No No
show-help Show query help Boolean false No No
title Terminal title String ~$ No No
query Terminal query String '' No Yes

Commands

commands must be an object containing key-value pairs where key is the command and the value is a function that will be called with the parsed arguments. The function can return a Promise and must return or resolve a Vue.js component. To return strings or a new query, use one of the convenient helper methods.

Any component that is not the query component can inject the context. The context includes the parsed and raw query as fields.

Event resolver

It's possible to provide an array property eventResolver which is called when the terminal is mounted. Each event resolver will be called with the terminals references and exposed values.

The libraries defaultHistoryEventResolver makes usage of that and allows to cycle through commands with /.

Options resolver

The terminal provides a built-in autocompletion for the given commands. As soon as the query has been autocompleted by the terminal, it's calling the options resolver provided as property. The resolver is called with the program, parsed query and a setter to update the query.

Interpreter

An interpreter allows to execute arbitrary code after the query has been dispatched and to not rely on missing functionality which includes pipes, streams or running multiple commands in parallel.

The interpreter is a property function that is called with the unparsed query right after the query component calls dispatch and terminates it at the same time. After the call, you must use the properties and exposed functions to reach the desired behaviour.

Slots

Bar

You can replace the whole terminal bar with the named slot bar. This will replace the whole element, including the action buttons and its assigned CSS classes. Example:

<vue-command>
  <template #bar>
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
  </template>
</vue-command>

Buttons

Inside the bar, you can customize the buttons. If you use this slot, hideButtons property has no effect. Example:

<vue-command>
  <template #buttons>
    &times; &#95; &square;
  </template>
</vue-command>

Title

Inside the bar, you can customize the title. If you use this slot, hideTitle and title property have no effect. Example:

<vue-command>
  <template #title>
    bash - 720x350
  </template>
</vue-command>

Prompt

You can overwrite the prompt with the prompt slot. If you use this slot, hidePrompt and prompt property have no effect. Example:

<vue-command>
  <template #prompt>
    ~$
  </template>
</vue-command>

Library

Library provides helper methods to render terminal related content.

Function Parameters Description
createCommandNotFound command, text = 'command not found', name = 'VueCommandNotFound' Creates a command not found component
createStderr formatterOrText, name = 'VueCommandStderr' Creates a "stderr" component
createStdout formatterOrText, name = 'VueCommandStdout' Creates a "stdout" component
createQuery Creates a query component
defaultHistoryEventResolver refs, eventProvider The default history event resolver
defaultParser query The default parser
defaultSignalEventResolver refs, eventProvider The default signal event resolver
jsonFormatter value See Formatters
listFormatter ...lis See Formatters
newDefaultEventResolver Returns a new default event resolver
newDefaultHistory Returns a new default history
tableFormatter rows See Formatters
textFormatter text, innerHtml = false See Formatters

Helper methods can be imported by name:

import { createStdout, createQuery } from "vue-command";

Formatters

The first argument of createStdout can be either a primitive (Boolean, Number or String) or a formatter. A formatter formats the content as a list or table or something else.

Function Parameters
jsonFormatter value
listFormatter ...lis
tableFormatter rows
textFormatter text, innerHtml = false

Formatters can be imported by name:

import { listFormatter } from "vue-command";

Provided

Identifier Type Parameters
addDispatchedQuery Function dispatchedQuery
appendToHistory Function ...components
dispatch Function query
decrementHistory Function
exit Function
context Object
helpText String
helpTimeout Number
hidePrompt Boolean
incrementHistory Function
optionsResolver Function program, parsedQuery, setQuery
parser Function query
programs Array
sendSignal Function signal
setCursorPosition Function cursorPosition
setFullscreen Function isFullscreen
setHistoryPosition Function historyPosition
setQuery Function query
showHelp Boolean
signals Object
slots Object
terminal Object

Provider can be injected into your component by name:

inject: ["exit", "terminal"],

Exposed

Identifier Type Parameters
addDispatchedQuery Function dispatchedQuery
appendToHistory Function ...components
decrementHistory Function
dispatch Function query
exit Function
incrementHistory Function
programs Array
sendSignal Function signal
setCursorPosition Function cursorPosition
setFullscreen Function isFullscreen
setHistoryPosition Function historyPosition
setQuery Function query
signals Object
terminal Object

Events

Name Description
closeClicked Emitted on button close click
minimizeClicked Emitted on button minimize click
fullscreenClicked Emitted on button fullscreen click

Signals

You can send and receive signals like SIGINT, SIGTERM or SIGKILL. SIGINT is the only implemented signal for now. When the user presses Ctrl + c, you can listen to this event by providing a signal name and a callback:

const signals = inject("signals");
const sigint = () => {
  // Tear down component
};
signals.on("SIGINT", sigint);

To unsubscribe from the signal, pass the same signal name and callback you used to subscribe to the signal.

signals.off("SIGINT", sigint);

The libraries query component makes usage of that and allows to cancel a query with SIGINT and appending ^C to the query.

Nice-to-haves

These features didn't make it into the last release. If you would like to contribute please consult CONTRIBUTING.md.

  • Draggable terminal
  • More events (like query dispatched)
  • More key combinations

Browser support

This library uses the ResizeObserver to track if the terminal needs to scroll to the bottom. You may need a pollyfill to support your target browser.

Projects using vue-command

Chuck Norris API

The Chuck Norris jokes are comming from this API. This library has no relation to Chuck Norris or the services provided by the API.

Author

Julian Claus and contributors. Special thanks to krmax44 for the amazing work!

I apologize to some contributors that are not in the Git history anymore since I had to delete the repository because of problems with semantic-release.

License

MIT

vue-command's People

Contributors

dependabot-preview[bot] avatar dependabot[bot] avatar ndabap avatar semantic-release-bot avatar tobiasbeck avatar twizzler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

vue-command's Issues

Bug: Spaces in options

Hi,

In earlier versions I could input options like this one:

exec Do-Stuff --stuff "great stuff"

this does not check out currently.
Having yargs in earlier versions did this really well.

For reproduction, use the demos reverse command like this:
reverse "text with space"

autocomplete example problem

I use vue

<template>
  <vue-command
    ref="terminal"
    :commands="commands"
    :title="termTitle"
    :prompt="termPrompt"
    :autocompletion-resolver="autocompletionResolver"
    :history="termHistory"
    :stdin="termStdin"
    :cursor="termCursor"
  />
</template>

<script>
import VueCommand, { createStdout } from 'vue-command'
import 'vue-command/dist/vue-command.css'

export default {
  components: { VueCommand },
  data: () => ({
    termTitle: 'msf: ~',
    termPrompt: 'msf > ',
    commands: {
      'hello-world': () => createStdout('Hello world'),
      'help': () => createStdout('help')
    },
    termStdin: '',
    termHistory: [],
    termCursor: 0
  }),
  methods: {
    autocompletionResolver() {
      // Make sure only programs are autocompleted since there is no support for arguments, yet
      const command = this.termStdin.split(' ')
      if (command.length > 1) {
        return
      }

      const autocompleteableProgram = command[0]
      // Collect all autocompletion candidates
      let candidates = []
      const programs = [...Object.keys(this.commands)].sort()
      programs.forEach(program => {
        if (program.startsWith(autocompleteableProgram)) {
          candidates.push(program)
        }
      })

      // Autocompletion resolved into multiple results
      if (this.termStdin !== '' && candidates.length > 1) {
        this.termHistory.push({
          // Build table programmatically
          render: createElement => {
            const columns = candidates.length < 5 ? candidates.length : 4
            const rows = candidates.length < 5 ? 1 : Math.ceil(candidates.length / columns)

            let index = 0
            let table = []
            for (let i = 0; i < rows; i++) {
              let row = []
              for (let j = 0; j < columns; j++) {
                row.push(createElement('td', candidates[index]))
                index++
              }

              table.push(createElement('tr', [row]))
            }

            return createElement('table', { style: { width: '100%' }}, [table])
          }
        })
      }

      // Autocompletion resolved into one result
      if (candidates.length === 1) {
        this.termStdin = candidates[0]
      }
    }
  }
}
</script>

but the termInput and the component's input isn't bind. I only set the component's input via set termInput, but not vice versa.

So I only implement the autocomplete via this.$refs.terminal.local.input

I wonder if there is a better way

Question: Idiomatic way of receiving user input without requiring 'commands'

I wanted to do something like:

  1. disable using commands so that when user enters some text vue-command does NOT try to parse for commands but just stores input in a buffer
  2. when user presses 'enter' vue-command returns the text that was entered

Trying to create a python interpreter terminal using vue-command:

>> print("hello world")
>> hello world
>> x = 7
>> print(x)
>> 7

Is there an idiomatic way to do this with vue-command?

Thanks!

Test: Add tests

Add more tests:

  • Test bar and prompt slot
  • Test given event listeners: History, Search

Documentation: Create more examples

The main site has only one big example. Create multiple examples how to use the library with code examples. A complete redesign is absolutely possible.

You find the documentation source code here. Start it with npm run serve.

Feature: push multiple lines of output to terminal without triggering prompt

I have a situation where I need to display some instructions to a user on the terminal, for example, after some timeout where user does not enter anything into the prompt.
What I want to do is something like:

  • user has not entered anything after 10 seconds
  • after 10 seconds is up some instructions are displayed 1 second apart
>> 
COUNT DOWN TILL PROGRAM QUITS....
ONE (happens 1 second after prev line)
TWO (happens 1 second after prev line)
THREE (happens 1 second after prev line)

I can't figure out how to do something like this - how to push multiple lines of output separately.

  • Currently, when I push a line of output using:

    • this.history.push(createStdout('hello'))
    • the terminal inserts a prompt right after (this is what I want to disable)
  • For example if I do:

    • this.history.push(createStdout('hello'))
    • this.history.push(createStdout('world'))
  • then I see

hello
>>
world
>>
  • instead of
hello
world

Add Promise support

It would be great if a command could return a Promise - this would allow for asynchronous commands. For example:

data: () => ({
  commands: {
   'my-async-command': ({ _ }) => {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(`You typed: "${_.join(' ')}"`);
        }, 2000);
      });
    },
  },
})

Feature: Add options per command

Hi,

I wanted to emulate a terminal with some basic commands like ls and cd. I used vue-command to do it however I have a problem with the options :
parser-options only takes 1 JSON and in the handle for getOpts, "this.parserOptions" is used and not "this.parserOptions[program]".
Could it be possible to change it so we can define options per command like:
{
ls: {boolean:["l","i"]},
cd: {boolean:["r","g"]
}

Thank you

PS: the code I saw is in src/mixins/handle.js line 87

Refactor: Better state management

Having a seperate Vue instance as a data bus can get hairy fast and complicates simple things like a clear command. Maybe we can figure out a simpler way to handle state.

Feature (proposal): Return generator function as command

To imitate multiple delayed logs for one command like:

Oct 15 00:15:41 isleofskye kernel: [17214570.744000] usb 4-5: new high speed USB device using ehci_hcd and address 4
Oct 15 00:15:41 isleofskye kernel: [17214570.876000] usb 4-5: configuration #1 chosen from 1 choice
Oct 15 00:15:41 isleofskye kernel: [17214570.876000] scsi4 : SCSI emulation for USB Mass Storage devices
Oct 15 00:15:41 isleofskye kernel: [17214570.880000] usb-storage: device found at 4
Oct 15 00:15:41 isleofskye kernel: [17214570.880000] usb-storage: waiting for device to settle before scanning
Oct 15 00:15:46 isleofskye kernel: [17214575.880000] usb-storage: device scan complete
Oct 15 00:15:46 isleofskye kernel: [17214575.880000] Vendor: USB007 Model: mini-USB2TX Rev: 100
Oct 15 00:15:46 isleofskye kernel: [17214575.880000] Type: Direct-Access

Returning a generator could be an appropriate solution. Problems: Complexity gets very high because its necessary to check what kind of command function is returned. It may also clash with #5.

Chore: Move built docs from Git repository

At the moment the built docs are tracked in the Git repo to be served by Github pages. This isn't ideal as it's cluttering up commits and the repo should only contain the source files. A couple of ways to solve this:

  • Use a CI/CD like Gitlab Pages, Netlify or (at the moment only a very hacky solution is available) Github Actions
  • use npm module gh-pages to build locally and push to the pages branch. Drawback: no deploy previews, not automated, messy.

Bug: Up and down keys are captured by the VueCommand

Hi,

First of all thanks for the project, it saves me a lot of time.

Next:

@keydown.38.exact.prevent="decreaseHistory"
@keydown.40.exact.prevent="increaseHistory"

Here you add listeners for the up and down keys and prevent the default behavior. Because of this, the nano editor cannot use the up and down keys. see https://ndabap.github.io/vue-command/

Do you think its a good idea to make these event listeners optional? And if so, can you point me in the right direction so I can make a pr.

Im thinking of removing the prevent modifiers and add event.preventDefault() in the increaseHistory and decreaseHistory methods. So that you can disable the history when the nano editor is started. What do you think?

Add ability to print "intro text"

I would love the ability to print some intro text on the first line(s) of the terminal when the component is initialized (i.e. Welcome to my-app!).

Off the top of my head, this could be implemented in two ways:

  1. Accept a simple welcome-text binding
  2. Accept a startup-command binding that runs one of the commands automatically on startup

Thanks so much for building this awesome library! 👍

Feature: Make auto focus optional

Every new Stdin creates an auto focus, which may not be the desired behavior. Create a boolean property isAutofocus to let user decide.

Feature: Capture cursor when clicking anywhere

At the moment, one has to explicitly click on the stdin <input> to enter a command. It'd be great if the user could click anywhere in the <vue-command> area to focus the text field.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.