Repository: railsware/upterm Branch: master Commit: 6db8d5b89625 Files: 157 Total size: 582.8 KB Directory structure: gitextract_wntplryt/ ├── .dockerignore ├── .gitignore ├── .node-version ├── .npmrc ├── .nvmrc ├── .travis.yml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── build/ │ └── icon.icns ├── docs/ │ └── vscodedebugging.md ├── package.json ├── src/ │ ├── Autocompletion.ts │ ├── Char.ts │ ├── Decorators.ts │ ├── EmitterWithUniqueID.ts │ ├── Enums.ts │ ├── Interfaces.ts │ ├── Output.ts │ ├── PTY.ts │ ├── PluginManager.ts │ ├── main/ │ │ └── Main.ts │ ├── monaco/ │ │ ├── Loader.ts │ │ ├── PromptTheme.ts │ │ ├── ShellHistoryLanguage.ts │ │ └── ShellLanguage.ts │ ├── plugins/ │ │ ├── AliasSuggestions.ts │ │ ├── DotEnvLoader.ts │ │ ├── GitGrep.tsx │ │ ├── JSON.tsx │ │ ├── JobFinishedNotifications.ts │ │ ├── NVM.ts │ │ ├── PWDOperatingSystemIntegrator.ts │ │ ├── RVM.ts │ │ ├── SaveHistory.ts │ │ ├── SaveWindowBounds.ts │ │ ├── Show.tsx │ │ ├── UpdateLastPresentWorkingDirectory.ts │ │ ├── completion/ │ │ │ ├── Brew.ts │ │ │ ├── Cat.ts │ │ │ ├── Cd.ts │ │ │ ├── Cp.ts │ │ │ ├── Df.ts │ │ │ ├── Executable.ts │ │ │ ├── Find.ts │ │ │ ├── Git.ts │ │ │ ├── Grep.ts │ │ │ ├── History.ts │ │ │ ├── Ln.ts │ │ │ ├── Locate.ts │ │ │ ├── Ls.ts │ │ │ ├── Mkdir.ts │ │ │ ├── Mv.ts │ │ │ ├── NPM.ts │ │ │ ├── Ps.ts │ │ │ ├── Pwd.ts │ │ │ ├── Rails.ts │ │ │ ├── Rake.ts │ │ │ ├── Rm.ts │ │ │ ├── Scp.ts │ │ │ ├── Shutdown.ts │ │ │ ├── Tail.ts │ │ │ ├── Top.ts │ │ │ └── Vagrant.ts │ │ └── completion_utils/ │ │ ├── Button.tsx │ │ ├── Combine.ts │ │ ├── Common.ts │ │ └── Descriptions.ts │ ├── references.d.ts │ ├── services/ │ │ ├── FontService.ts │ │ ├── GitService.ts │ │ ├── HistoryService.ts │ │ ├── JobsService.ts │ │ ├── SessionsService.ts │ │ ├── UpdatesService.ts │ │ ├── WindowService.ts │ │ └── index.ts │ ├── shell/ │ │ ├── Aliases.ts │ │ ├── BuiltInCommands.ts │ │ ├── CommandExecutor.ts │ │ ├── Environment.ts │ │ ├── Job.ts │ │ ├── Parser.ts │ │ ├── Prompt.ts │ │ ├── Scanner.ts │ │ └── Session.ts │ ├── utils/ │ │ ├── Common.ts │ │ ├── Git.ts │ │ ├── HistoryTrie.ts │ │ ├── JSONTree.tsx │ │ ├── Link.tsx │ │ ├── ManPageParsingUtils.ts │ │ ├── ManPages.ts │ │ ├── OrderedSet.ts │ │ ├── Process.ts │ │ └── Shell.ts │ └── views/ │ ├── ApplicationComponent.tsx │ ├── JobComponent.tsx │ ├── JobHeaderComponent.tsx │ ├── Main.tsx │ ├── OutputComponent.tsx │ ├── PrettifyToggleComponent.tsx │ ├── PromptComponent.tsx │ ├── SearchComponent.tsx │ ├── SessionComponent.tsx │ ├── TabComponent.tsx │ ├── TabHeaderComponent.tsx │ ├── ViewUtils.ts │ ├── css/ │ │ ├── FontAwesome.ts │ │ ├── colors.ts │ │ ├── functions.ts │ │ └── styles.ts │ ├── index.html │ ├── keyevents/ │ │ └── Keybindings.ts │ ├── menu/ │ │ └── Menu.ts │ └── mouseevents/ │ └── MouseEvents.ts ├── test/ │ ├── e2e.ts │ ├── environment_spec.ts │ ├── output_spec.ts │ ├── pty_spec.ts │ ├── references.d.ts │ ├── shell/ │ │ └── scanner_spec.ts │ ├── test_files/ │ │ ├── file_names_test/ │ │ │ └── file with brackets() │ │ └── vttest/ │ │ ├── 1-1 │ │ ├── 1-2 │ │ ├── 1-3 │ │ ├── 1-4 │ │ ├── 1-5 │ │ ├── 1-6 │ │ ├── 2-1 │ │ ├── 2-10 │ │ ├── 2-11 │ │ ├── 2-12 │ │ ├── 2-13 │ │ ├── 2-14 │ │ ├── 2-15 │ │ ├── 2-2 │ │ ├── 2-3 │ │ ├── 2-4 │ │ ├── 2-5 │ │ ├── 2-6 │ │ ├── 2-7 │ │ ├── 2-8 │ │ └── 2-9 │ └── utils/ │ ├── ManPages_spec.ts │ ├── common_spec.ts │ ├── history_trie_spec.ts │ └── ordered_set_spec.ts ├── tsconfig.json ├── tslint.json └── typings/ ├── Interfaces.d.ts ├── Overrides.d.ts ├── child-process-promise.d.ts ├── dirStat.d.ts ├── mode-to-permissions.d.ts └── uuid.d.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ node_modules/ dist/ compiled/ test/ ================================================ FILE: .gitignore ================================================ /node_modules/ /compiled/src/* /tmp/* /npm-debug.log /dist/* # Created by https://www.gitignore.io/api/osx ### OSX ### .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # Directory created by Visual Studio Code .vscode # Directory created by IDEA .idea ================================================ FILE: .node-version ================================================ 8.11.0 ================================================ FILE: .npmrc ================================================ save-exact = true registry = https://registry.npmjs.org ================================================ FILE: .nvmrc ================================================ v8.11.0 ================================================ FILE: .travis.yml ================================================ language: node_js before_script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start; fi matrix: include: - os: linux sudo: required dist: trusty # - os: osx # osx_image: xcode7.3 cache: directories: - node_modules deploy: skip_cleanup: true provider: script script: npm run pack on: tags: true env: global: # GH_TOKEN secure: LkI4RAc08x0XsiMlK0cIKFy381qJ8v8WKepjhAGdwbNgPqDcMqLXSznTTaGTcN1hKonHBPJVL9l0G02oRjj/ulh/yeGV1Q/JRPBm2XLoG0EX6hcuDj/skaLO24fuYf/miYejZbJcRBj2F/OP7sR1SfQPl6dfzEnLj96IHCUxzEBbB6PPJdGRL35117HqD0Y/LGLPkHgdh53n/FEtsEbl6DwvkPr8+yDZxUCqTJW53ZoBJzf9znZE5gMZg6btkStWTUuM5n5doe446ipRouGtomOXkgsCtQbsd66cRzdlIlEGKUEDaEL/c4KDMIUUDw2MpxoUm2fDJ98krxRLhvANgN/rqQVBoYY45OT7SzK9TYcvqS36E6a9pdmFpt0M3w532f5E6simgJp1a6gBoSBBYoZL8hRscF2VgAvjJV0QVQzos6Ec01nGjAbpC/i2B6IR6tnI1L5C3YHR4xDvSqW3iDo3hpc+Y4INOMysMt3cK+oWx3bEsbH8G3JRbU6Edz5vUVQ5aeoyVgfr/vxhIohWq8NpGd8zqdyyfKq59DYugkDNpvKr07w+FQZLtexILKw6FzNEjm3gI9prz5a7+WzFLQcdgy6xFM7z3GN5e1kbX39BSMcmkueWk96kCYSpKOflAX5h+WOs3VjwVxoIaB5uYeQD10L+6703eSyXy3Ni3Sg= ================================================ FILE: CONTRIBUTING.md ================================================ I have found a bug! ------------------- Awesome, but before you [report it to us](issues/new), make sure to [check whether this has already been reported](issues?q=is%3Aissue). If not, before reporting the issue you'll need to gather some information by following these instructions: 1. Make sure you are using the latest version of the application: ```bash git pull rm -r "/Applications/Upterm"* npm run pack ``` 2. If the bug is still present, [open an issue](issues/new). 3. Write steps to reproduce the bug. 4. Take some screenshots. 5. Gather debug logs. 5.1. Open developer tools (View -> Toggle Developer Tools). 5.2. Find Console. 5.3. Copy the output and paste it into the issue. Developing ---------- Clone the repo, then `npm start`. This will install dependencies, build, and run Electron. You may need additional packages, such as `libgconf2` ([reference](https://github.com/railsware/upterm/issues/1320)). I have some important changes! ------------------------------ 1. [Clone the repo](https://help.github.com/articles/importing-a-git-repository-using-the-command-line/). 2. [Create a separate branch](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches) (to prevent unrelated updates). 3. Apply your changes. 4. [Create a pull request](https://help.github.com/articles/creating-a-pull-request/). 5. Describe what has been done. Test ---- * Install [selenium-standalone](https://github.com/vvo/selenium-standalone) * `selenium-standalone start` * `npm run test` ================================================ FILE: Dockerfile ================================================ FROM node:8.11.0 RUN mkdir /upterm WORKDIR /upterm COPY package.json . COPY .npmrc . RUN npm install COPY . /upterm RUN npm run pack VOLUME /dist CMD cp /upterm/dist/*.AppImage /dist ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Volodymyr Shatsky Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![Join the chat at https://gitter.im/railsware/upterm](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/upterm/Lobby) [![Build Status](https://travis-ci.org/railsware/upterm.svg?branch=master)](https://travis-ci.org/railsware/upterm) # Deprecated Upterm is deprecated. Project had [lost maintainer](https://github.com/railsware/upterm/issues/1301#issue-327003344) and have zero activity to support it from community (only issues was created in repo). I am no longer accepting pull requests and issues. I recomended to check [Hyper](https://hyper.is/) instead. What Is It? ----------- **[Upterm is looking for maintainers](https://github.com/railsware/upterm/issues/1301)** Upterm (formerly Black Screen) is an IDE in the world of terminals. Strictly speaking, it's both a terminal emulator and an *interactive* shell based on [Electron](http://electron.atom.io/). ![](README/main.png) ###### Autocompletion Upterm shows the autocompletion box as you type and tries to be smart about what to suggest. Often you can find useful additional information on the right side of the autocompletion, e.g. expanded alias value, command descriptions, value of the previous directory (`cd -`), etc. ###### Compatibility All command-line programs (including emacs, ssh and vim) should work as expected. If you experience any glitches, please [create an issue](https://github.com/railsware/upterm/issues/new). Install ------------ ###### MacOS ```bash brew cask install upterm ``` Beware that the version in Homebrew might be outdated. Visit the [releases](https://github.com/railsware/upterm/releases) page to download the latest version. ###### Linux *(Arch Linux)* ```bash yaourt -S upterm ``` As with macOS's `brew` install, the AUR may also be outdated. To install the latest version, refer to the [install guide for Linux (Others)](#linux-others). ###### Linux *(Others)* * Download and open the AppImage file from the [releases](https://github.com/railsware/upterm/releases) page. ###### Windows Windows is not officially supported at the moment. The [Windows Support](https://github.com/railsware/upterm/issues/800) Issue explains potential experimental support. Technologies ------------ * [Electron](http://electron.atom.io/) * [TypeScript](http://www.typescriptlang.org/) * [ReactJS](https://facebook.github.io/react/) More Screenshots ---------------- ![](README/npm_autocompletion.png) ![](README/error.png) ![](README/top_autocompletion.png) ![](README/json_prettyfier.png) ![](README/vim.png) ![](README/emacs.png) ![](README/htop.png) ![](README/cd.png) Development Setup ------------ ```bash git clone https://github.com/railsware/upterm.git && cd upterm npm start ``` Instructions are available for [debugging the application in Visual Studio Code](docs/vscodedebugging.md). To create a standalone application, execute `npm run pack` in the project directory. Contributing ------------ See [Contributing Guide](CONTRIBUTING.md). License ------- [The MIT License](LICENSE). ================================================ FILE: docs/vscodedebugging.md ================================================ # Debugging Upterm in Visual Studio Code Microsoft's open source Visual Studio Code (vscode) provides debugging for Typescript based applications. The recommended steps for debugging Upterm in vscode are described in the steps below. ## Step 1. Install the Debugger for Chrome Extension To debug Electron based applications the vscode "Debugger for Chrome" extension is required. To install this extension choose the Extensions icon on the left hand side. Then search for and select the Debugger for Chrome. The extension should take about a minute to install after which you will be prompted to reload vscode. ![chrome debugger](images/install_chrome_debug_ext.png "chrome debugger") ## Step 2. Setup vscode project build and debug configuration Under the `.vscode` folder create the `launch.json` and `tasks.json` files. ![.vscode folder](images/dot-vscode-folder.png ".vscode folder") The contents of these files should be as follows. a. launch.json ``` { // Use IntelliSense to learn about possible Node.js debug attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Electron Main", // Mac OS & Linux process runtime executable "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", "windows": { // Windows process runtime executable "runtimeExecutable": "${workspaceRoot}\\node_modules\\.bin\\electron.cmd" }, "program": "${workspaceRoot}/compiled/src/main/Main.js", "protocol": "inspector", "stopOnEntry": false, "args": [], "cwd": "${workspaceRoot}/", "runtimeArgs": [ "--enable-logging" ], "env": {}, "sourceMaps": true, "outFiles": [ "${workspaceRoot}/compiled/src" ], "internalConsoleOptions": "openOnSessionStart", "console": "integratedTerminal" }, { "name": "Debug renderer process", "type": "chrome", "request": "launch", // Mac OS & Linux process runtime executable "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", "windows": { // Windows process runtime executable "runtimeExecutable": "${workspaceRoot}\\node_modules\\.bin\\electron.cmd" }, "runtimeArgs": [ "${workspaceRoot}/", "--enable-logging", "--remote-debugging-port=9222" ], "webRoot": "${workspaceRoot}/", "sourceMaps": true, "internalConsoleOptions": "openOnSessionStart" } ] } ``` b. tasks.json ``` { "version": "2.0.0", "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "new" }, "tasks": [ { // Custom prestart task i.e. npm run prestart. "taskName": "prestart", "command": "npm", "args": [ "run", "prestart" ], "type": "shell", "group": "build", // Specify tsc problem matcher. "problemMatcher": ["$tsc-watch"] }, { // Default compile task from package.json i.e. npm run compile. "type": "npm", "script": "compile", "group": "build", // Specify tsc problem matcher. "problemMatcher": ["$tsc-watch"] } ] } ``` ## Step 3. Build the project After first install of Upterm and after each time Upterm is modified the project needs to be re-built before launching a debug session. To build the project in vscode open the activity search box by selecting the `⌘ + p` keys on Mac OS (or `Ctrl + p` on Linux and Windows), and then enter `task prestart`. Or alternatively from the command line run `npn run prestart`. ![prestart task launch](images/launch_task_prestart.png "prestart task launch") If the code has been modified and no updates to dependent node modules were made you can compile the project by entering `task compile` into the search box. Or from the command line run `npn run compile`. Note. The compile task is a npm script defined in `package.json` which vscode detects and displays as `npm: compile` in the search box. ## Step 4. Debug the project Source maps are enabled for the Upterm project which allows the Typescript code to be debugged. Source maps map the Typescript code to the generated (transpiled) Javascript code, with the Javascript code being what's executed by node.js. To enter debugging mode select the Debug icon on the left hand side menu. Or alternatively, select the `Shift + ⌘ + d` (or `Shift + Ctrl + d`) keys. ![debug process](images/debug_renderer_process.png "debug process") To launch a debug session, from the top left hand side of the Debug window select `Debug rendered process` and press the Play button. Note. A breakpoint needs to be placed in the code prior to launching a debug session, to allow stepping through code. These instructions were tested on vscode version 1.16.0. ================================================ FILE: package.json ================================================ { "name": "upterm", "productName": "Upterm", "description": "A terminal emulator for the 21st century.", "version": "0.4.4", "main": "compiled/src/main/Main.js", "author": "Volodymyr Shatskyi ", "repository": { "type": "git", "url": "https://github.com/railsware/upterm.git" }, "bugs": { "url": "https://github.com/railsware/upterm/issues" }, "engines": { "node": ">=8.0.0 || >=10.0.0" }, "keywords": [ "terminal", "emulator", "shell", "console" ], "dependencies": { "child-process-promise": "2.2.1", "chokidar": "2.0.3", "classnames": "2.2.5", "csv-parse": "2.2.0", "csv-stringify": "3.0.0", "dirStat": "0.0.2", "font-awesome": "4.7.0", "fs-extra": "5.0.0", "immutable": "3.8.2", "klaw": "2.1.1", "lodash": "4.17.11", "mode-to-permissions": "0.0.2", "monaco-editor": "vlad-shatskyi/monaco-editor", "node-ansiparser": "2.2.0", "node-pty": "0.7.4", "react": "16.2.0", "react-dom": "16.2.0", "rxjs": "5.5.8", "tinycolor2": "1.4.1" }, "devDependencies": { "@types/chai": "4.1.3", "@types/chokidar": "1.7.5", "@types/classnames": "2.2.3", "@types/csv-parse": "1.1.11", "@types/csv-stringify": "1.4.2", "@types/enzyme": "3.1.10", "@types/fs-extra": "5.0.2", "@types/klaw": "2.1.1", "@types/lodash": "4.14.116", "@types/mocha": "5.2.0", "@types/node": "9.6.1", "@types/react": "16.1.0", "@types/webdriverio": "4.10.1", "chai": "4.1.2", "concurrently": "3.5.1", "cpx": "1.5.0", "cross-env": "5.1.4", "devtron": "1.4.0", "electron": "2.0.10", "electron-builder": "20.28.4", "electron-mocha": "6.0.4", "enzyme": "3.3.0", "mkdirp": "0.5.1", "mocha": "5.2.0", "npm-check-updates": "2.14.2", "react-addons-test-utils": "15.6.2", "rimraf": "2.6.2", "spectron": "3.8.0", "ts-node": "5.0.1", "tslint": "5.9.1", "typescript": "2.8.1" }, "scripts": { "preinstall": "npm prune", "postinstall": "electron-builder install-app-deps", "pack": "build", "electron": "electron .", "prestart": "npm install && npm run compile", "start": "concurrently --kill-others -s first \"tsc --watch\" \"cross-env NODE_ENV=development npm run electron\"", "test": "npm run lint && npm run compile && npm run unit-tests && npm run ui-tests && npm run integration-tests", "unit-tests": "NODE_ENV=test electron-mocha --require ts-node/register $(find test -name '*_spec.ts')", "ui-tests": "NODE_ENV=test electron-mocha --require ts-node/register $(find test -name '*_spec.tsx')", "integration-tests": "NODE_ENV=test electron-mocha --require ts-node/register test/e2e.ts", "update-dependencies": "ncu -u", "lint": "tslint `find src -name '*.ts*'` `find test -name '*.ts*'`", "cleanup": "rimraf compiled/src", "copy-html": "mkdirp compiled/src/views && cpx src/views/index.html compiled/src/views", "compile": "npm run cleanup && npm run tsc && npm run copy-html", "tsc": "tsc" }, "license": "MIT", "build": { "appId": "com.github.railsware.upterm", "linux": { "icon": "icons" } } } ================================================ FILE: src/Autocompletion.ts ================================================ import {leafNodeAt, ASTNode} from "./shell/Parser"; import * as _ from "lodash"; import {Environment} from "./shell/Environment"; import {OrderedSet} from "./utils/OrderedSet"; import {Aliases} from "./shell/Aliases"; type GetSuggestionsOptions = { currentText: string; currentCaretPosition: number; ast: ASTNode; environment: Environment; historicalPresentDirectoriesStack: OrderedSet; aliases: Aliases; }; export async function getSuggestions({ currentCaretPosition, ast, environment, historicalPresentDirectoriesStack, aliases, }: GetSuggestionsOptions): Promise { const node = leafNodeAt(currentCaretPosition, ast); const suggestions = await node.suggestions({ environment: environment, historicalPresentDirectoriesStack: historicalPresentDirectoriesStack, aliases: aliases, }); const uniqueSuggestions = _.uniqBy(suggestions, suggestion => suggestion.label); return { isIncomplete: false, items: uniqueSuggestions.map(suggestion => ({ ...suggestion, kind: monaco.languages.CompletionItemKind.Interface, })), }; } ================================================ FILE: src/Char.ts ================================================ import {Attributes} from "./Interfaces"; import {Brightness, Weight, Color} from "./Enums"; export const defaultAttributes = Object.freeze({ inverse: false, color: Color.White, backgroundColor: Color.Black, brightness: Brightness.Normal, weight: Weight.Normal, underline: false, crossedOut: false, blinking: false, cursor: false, }); export interface Char { value: string; attributes: Attributes; } export function createChar(char: string, attributes: Attributes): Char { if (char.length !== 1) { throw(`Char can be created only from a single character; passed ${char.length}: ${char}`); } return { value: char, attributes: attributes, }; } ================================================ FILE: src/Decorators.ts ================================================ import * as _ from "lodash"; export function memoize(resolver: ((...args: any[]) => any) | undefined = undefined) { if (typeof resolver !== "function") { resolver = (...args: any[]) => JSON.stringify(args); } return (_target: any, _name: string, descriptor: TypedPropertyDescriptor) => { descriptor.value = _.memoize(descriptor.value, resolver); return descriptor; }; } export const memoizeAccessor = (_target: Object, name: string | symbol, descriptor: TypedPropertyDescriptor) => { const memoizedPropertyName = `__memoized_${name}`; const originalGetter = descriptor.get; descriptor.get = function (this: any) { if (!this[memoizedPropertyName]) { this[memoizedPropertyName] = originalGetter!.call(this); } return this[memoizedPropertyName]; }; return descriptor; }; export function debounce(wait: number = 0) { return (_target: any, _name: string, descriptor: PropertyDescriptor) => { descriptor.value = _.debounce(descriptor.value, wait); return descriptor; }; } ================================================ FILE: src/EmitterWithUniqueID.ts ================================================ import * as events from "events"; export class EmitterWithUniqueID extends events.EventEmitter { public id: number; constructor() { super(); this.id = Date.now(); } } ================================================ FILE: src/Enums.ts ================================================ /** * @link https://css-tricks.com/snippets/javascript/javascript-keycodes/ * @link https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html */ export enum KeyCode { Bell = 7, Backspace = 8, Tab = 9, NewLine = 10, VerticalTab = 11, CarriageReturn = 13, ShiftOut = 14, ShiftIn = 15, Shift = 16, Ctrl = 17, Alt = 18, CapsLock = 20, Escape = 27, Space = 32, Left = 37, Up = 38, Right = 39, Down = 40, One = 49, Nine = 57, A = 65, B = 66, C = 67, D = 68, E = 69, F = 70, G = 71, H = 72, I = 73, J = 74, K = 75, L = 76, M = 77, N = 78, O = 79, P = 80, Q = 81, R = 82, S = 83, T = 84, U = 85, V = 86, W = 87, X = 88, Y = 89, Z = 90, Meta = 91, Delete = 127, Underscore = 189, Period = 190, VerticalBar = 220, AltGraph = 225, } export enum Color { Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, } export enum Status { InProgress = "in-progress", Failed = "failed", Success = "success", } export enum ScreenMode { Light = "light", Dark = "dark", } export enum BufferType { Normal = "normal", Alternate = "alternate", } export enum Weight { Normal = "normal", Bold = "bold", Faint = "faint", } export enum Brightness { Normal = "normal", Bright = "bright", } export enum LogLevel { Info = "info", Log = "log", Error = "error", } export enum KeyboardAction { // CLI commands cliClearJobs, cliClearText, cliAppendLastArgumentOfPreviousCommand, cliHistoryPrevious, cliHistoryNext, // autocomplete commands autocompleteInsertCompletion, autocompletePreviousSuggestion, autocompleteNextSuggestion, // tab commands tabNew, tabFocus, tabPrevious, tabNext, // session commands otherSession, sessionClose, // edit/clipboard commands clipboardCopy, clipboardPaste, editFind, editFindClose, increaseFontSize, decreaseFontSize, resetFontSize, // view commands viewToggleFullScreen, // Upterm commands uptermQuit, // developer toggleDeveloperTools, } ================================================ FILE: src/Interfaces.ts ================================================ import {Weight, Brightness} from "./Enums"; import {Stats} from "fs"; import {ReactElement} from "react"; import {Job} from "./shell/Job"; import {Session} from "./shell/Session"; import {Suggestion} from "./plugins/completion_utils/Common"; import {Output} from "./Output"; import {Environment} from "./shell/Environment"; import {OrderedSet} from "./utils/OrderedSet"; import {Argument} from "./shell/Parser"; import {Aliases} from "./shell/Aliases"; export type ColorCode = number | number[]; export interface Attributes { readonly inverse: boolean; readonly color: ColorCode; readonly backgroundColor: ColorCode; readonly brightness: Brightness; readonly weight: Weight; readonly underline: boolean; readonly crossedOut: boolean; readonly blinking: boolean; readonly cursor: boolean; } export interface PreliminaryAutocompletionContext { readonly environment: Environment; readonly historicalPresentDirectoriesStack: OrderedSet; readonly aliases: Aliases; } export interface AutocompletionContext extends PreliminaryAutocompletionContext { readonly argument: Argument; } export type AutocompletionProvider = (context: AutocompletionContext) => Promise; export interface FileInfo { name: string; stat: Stats; } export interface Prettyfier { isApplicable: (job: Job) => boolean; prettify: (job: Job) => ReactElement; } export interface EnvironmentObserverPlugin { presentWorkingDirectoryWillChange: (session: Session, directory: string) => void; presentWorkingDirectoryDidChange: (session: Session, directory: string) => void; } export interface TerminalLikeDevice { output: Output; write: (input: string | KeyboardEvent) => void; } export type UserEvent = KeyboardEvent | ClipboardEvent; export type MouseEvent = DragEvent; ================================================ FILE: src/Output.ts ================================================ import * as events from "events"; import {defaultAttributes, createChar, Char} from "./Char"; import * as i from "./Interfaces"; import * as e from "./Enums"; import {List} from "immutable"; import {Color, Weight, Brightness, KeyCode, LogLevel, BufferType, ScreenMode} from "./Enums"; import {Attributes, TerminalLikeDevice, ColorCode} from "./Interfaces"; import {print, error, info, csi, times} from "./utils/Common"; import * as _ from "lodash"; const ansiParserConstructor: typeof AnsiParser = require("node-ansiparser"); interface HandlerResult { status: string; description: string; longDescription?: string; url: string; } interface SavedState { cursorRowIndex: number; cursorColumnIndex: number; attributes: i.Attributes; designatedCharacterSets: DesignatedCharacterSets; selectedCharacterSet: SelectedCharacterSet; } /** * @link http://vt100.net/docs/vt220-rm/chapter4.html */ enum CharacterSets { ASCIIGraphics, SupplementalGraphics, } interface DesignatedCharacterSets { G0: CharacterSets; G1: CharacterSets; G2: CharacterSets; G3: CharacterSets; } type SelectedCharacterSet = keyof DesignatedCharacterSets; function or1(value: number | undefined) { if (value === undefined) { return 1; } else { return value; } } function logPosition(buffer: Buffer) { const position = {rowIndex: buffer.cursorRowIndex, columnIndex: buffer.cursorColumnIndex}; const char = buffer.at(position); const value = char ? char.value : "NULL"; info(`%crow: ${position.rowIndex + 1}\tcolumn: ${buffer.cursorColumnIndex + 1}\t value: ${value}, rows: ${buffer.size}`, "color: grey"); } /** * Copied from xterm.js * @link https://github.com/sourcelair/xterm.js/blob/master/src/Charsets.ts */ const graphicCharset: Dictionary = { "`": "\u25c6", // "◆" "a": "\u2592", // "▒" "b": "\u0009", // "\t" "c": "\u000c", // "\f" "d": "\u000d", // "\r" "e": "\u000a", // "\n" "f": "\u00b0", // "°" "g": "\u00b1", // "±" "h": "\u2424", // "\u2424" (NL) "i": "\u000b", // "\v" "j": "\u2518", // "┘" "k": "\u2510", // "┐" "l": "\u250c", // "┌" "m": "\u2514", // "└" "n": "\u253c", // "┼" "o": "\u23ba", // "⎺" "p": "\u23bb", // "⎻" "q": "\u2500", // "─" "r": "\u23bc", // "⎼" "s": "\u23bd", // "⎽" "t": "\u251c", // "├" "u": "\u2524", // "┤" "v": "\u2534", // "┴" "w": "\u252c", // "┬" "x": "\u2502", // "│" "y": "\u2264", // "≤" "z": "\u2265", // "≥" "{": "\u03c0", // "π" "|": "\u2260", // "≠" "}": "\u00a3", // "£" "~": "\u00b7", // "·" }; const SGR: { [indexer: string]: (attributes: Attributes) => Attributes } = { 0: (_attributes: Attributes) => defaultAttributes, 1: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright}), 2: (attributes: Attributes) => ({...attributes, weight: Weight.Faint}), 4: (attributes: Attributes) => ({...attributes, underline: true}), 7: (attributes: Attributes) => ({...attributes, inverse: true}), 22: (attributes: Attributes) => ({...attributes, weight: Weight.Normal}), 24: (attributes: Attributes) => ({...attributes, underline: false}), 27: (attributes: Attributes) => ({...attributes, inverse: false}), 30: (attributes: Attributes) => ({...attributes, color: Color.Black}), 31: (attributes: Attributes) => ({...attributes, color: Color.Red}), 32: (attributes: Attributes) => ({...attributes, color: Color.Green}), 33: (attributes: Attributes) => ({...attributes, color: Color.Yellow}), 34: (attributes: Attributes) => ({...attributes, color: Color.Blue}), 35: (attributes: Attributes) => ({...attributes, color: Color.Magenta}), 36: (attributes: Attributes) => ({...attributes, color: Color.Cyan}), 37: (attributes: Attributes) => ({...attributes, color: Color.White}), 39: (attributes: Attributes) => ({...attributes, color: Color.White}), 40: (attributes: Attributes) => ({...attributes, backgroundColor: Color.Black}), 41: (attributes: Attributes) => ({...attributes, backgroundColor: Color.Red}), 42: (attributes: Attributes) => ({...attributes, backgroundColor: Color.Green}), 43: (attributes: Attributes) => ({...attributes, backgroundColor: Color.Yellow}), 44: (attributes: Attributes) => ({...attributes, backgroundColor: Color.Blue}), 45: (attributes: Attributes) => ({...attributes, backgroundColor: Color.Magenta}), 46: (attributes: Attributes) => ({...attributes, backgroundColor: Color.Cyan}), 47: (attributes: Attributes) => ({...attributes, backgroundColor: Color.White}), 49: (attributes: Attributes) => ({...attributes, backgroundColor: Color.Black}), 90: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, color: Color.Black}), 91: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, color: Color.Red}), 92: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, color: Color.Green}), 93: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, color: Color.Yellow}), 94: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, color: Color.Blue}), 95: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, color: Color.Magenta}), 96: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, color: Color.Cyan}), 97: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, color: Color.White}), 100: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, backgroundColor: Color.Black}), 101: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, backgroundColor: Color.Red}), 102: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, backgroundColor: Color.Green}), 103: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, backgroundColor: Color.Yellow}), 104: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, backgroundColor: Color.Blue}), 105: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, backgroundColor: Color.Magenta}), 106: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, backgroundColor: Color.Cyan}), 107: (attributes: Attributes) => ({...attributes, brightness: Brightness.Bright, backgroundColor: Color.White}), }; const CSI = { erase: { toEnd: 0, toBeginning: 1, entire: 2, entireSsh: 3, }, }; const colorFormatCodes = { format8bit: 5, formatTrueColor: 2, }; export class Output extends events.EventEmitter { public activeBufferType = e.BufferType.Normal; public isCursorKeysModeSet = false; public screenMode = ScreenMode.Dark; private normalBuffer: Buffer; private alternateBuffer: Buffer; private parser: AnsiParser; constructor(private terminalDevice: TerminalLikeDevice, public dimensions: Dimensions) { super(); this.normalBuffer = new Buffer(this, 200); this.alternateBuffer = new Buffer(this, 0); this.parser = new ansiParserConstructor({ inst_p: (text: string) => { info("text", text, text.split("").map(letter => letter.charCodeAt(0))); for (let i = 0; i !== text.length; ++i) { this.activeBuffer.writeOne(text.charAt(i)); } logPosition(this.activeBuffer); }, inst_o: function (s: any) { error("osc", s); }, inst_x: (flag: string) => { this.activeBuffer.writeOne(flag); print((KeyCode[flag.charCodeAt(0)] ? LogLevel.Log : LogLevel.Error), ["char", flag.split("").map((_, index) => flag.charCodeAt(index))]); logPosition(this.activeBuffer); }, /** * CSI handler. */ inst_c: (collected: any, params: Array, flag: string) => { let handlerResult: HandlerResult; if (collected === "?") { if (params.length !== 1) { return error(`CSI private mode has ${params.length} parameters: ${params}`); } if (flag !== "h" && flag !== "l") { return error(`CSI private mode has an incorrect flag: ${flag}`); } const mode = params[0]; handlerResult = this.decPrivateModeHandler(mode, flag); if (handlerResult.status === "handled") { info(`%cCSI ? ${mode} ${flag}`, "color: blue", handlerResult.description, handlerResult.url); } else { error(`%cCSI ? ${mode} ${flag}`, "color: blue", handlerResult.description, handlerResult.url); } } else { handlerResult = this.csiHandler(collected, params, flag); if (handlerResult.status === "handled") { info(`%cCSI ${params} ${flag}`, "color: blue", handlerResult.description, handlerResult.url); } else { error(`%cCSI ${params} ${flag}`, "color: blue", handlerResult.description, handlerResult.url); } } logPosition(this.activeBuffer); }, /** * ESC handler. */ inst_e: (collected: any, flag: string) => { const handlerResult = this.escapeHandler(collected, flag); if (handlerResult.status === "handled") { info(`%cESC ${collected} ${flag}`, "color: blue", handlerResult.description, handlerResult.url); } else { error(`%cESC ${collected} ${flag}`, "color: blue", handlerResult.description, handlerResult.url); } logPosition(this.activeBuffer); }, }); } write(ansiString: string) { this.parser.parse(ansiString); this.emit("data"); } toLines() { return this.activeBuffer.toLines(); } toString(): string { return this.toLines().join("\n"); } isEmpty(): boolean { return this.activeBuffer.size === 0; } get activeBuffer() { if (this.activeBufferType === e.BufferType.Normal) { return this.normalBuffer; } else { return this.alternateBuffer; } } private escapeHandler(collected: any, flag: string) { let short = ""; let long = ""; let url = ""; let status = "handled"; if (collected) { if (collected === "#" && flag === "8") { short = "DEC Screen Alignment Test (DECALN)."; url = "http://www.vt100.net/docs/vt510-rm/DECALN"; const dimensions = this.activeBuffer.dimensions; for (let i = 0; i !== dimensions.rows; ++i) { this.activeBuffer.moveCursorAbsolute({rowIndex: i, columnIndex: 0}); this.write(Array(dimensions.columns).join("E")); } this.activeBuffer.moveCursorAbsolute({rowIndex: 0, columnIndex: 0}); } else if (collected === "(" && flag === "0") { short = "Designate Graphic Charset to G0"; this.activeBuffer.designatedCharacterSets.G0 = CharacterSets.SupplementalGraphics; } else if (collected === "(" && flag === "B") { short = "Designate ASCII Charset to G0"; this.activeBuffer.designatedCharacterSets.G0 = CharacterSets.ASCIIGraphics; } else if (collected === ")" && flag === "0") { short = "Designate Graphic Charset to G1"; this.activeBuffer.designatedCharacterSets.G1 = CharacterSets.SupplementalGraphics; } else if (collected === ")" && flag === "B") { short = "Designate ASCII Charset to G1"; this.activeBuffer.designatedCharacterSets.G1 = CharacterSets.ASCIIGraphics; } else { status = "unhandled"; } } else { switch (flag) { case "A": short = "Cursor up."; this.activeBuffer.moveCursorRelative({vertical: -1}); break; case "B": short = "Cursor down."; this.activeBuffer.moveCursorRelative({vertical: 1}); break; case "C": short = "Cursor right."; this.activeBuffer.moveCursorRelative({horizontal: 1}); break; case "D": short = "Index (IND)."; url = "http://www.vt100.net/docs/vt510-rm/IND"; this.activeBuffer.moveCursorRelative({vertical: 1}); break; case "H": short = "Horizontal Tab Set (HTS)."; url = "http://www.vt100.net/docs/vt510-rm/HTS"; this.activeBuffer.setTabStop(); break; case "M": short = "Reverse Index (RI)."; /* tslint:disable:max-line-length */ long = "Move the active position to the same horizontal position on the preceding line if the active position is at the top margin, a scroll down is performed."; if (this.activeBuffer.cursorRowIndex === this.activeBuffer.marginTop) { this.activeBuffer.scrollDown(1); } else { this.activeBuffer.moveCursorRelative({vertical: -1}); } break; case "E": short = "Next Line (NEL)."; /* tslint:disable:max-line-length */ long = "This sequence causes the active position to move to the first position on the next line downward. If the active position is at the bottom margin, a scroll up is performed."; this.activeBuffer.moveCursorRelative({vertical: 1}); this.activeBuffer.moveCursorAbsolute({columnIndex: 0}); break; case "7": long = "Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1)."; this.activeBuffer.saveCurrentState(); break; case "8": long = "Restore state most recently saved by ESC 7."; this.activeBuffer.restoreCurrentState(); break; default: status = "unhandled"; } } return { status: status, description: short, longDescription: long, url: url, }; } private decPrivateModeHandler(ps: number, flag: "h" | "l"): HandlerResult { let description = ""; let url = ""; let status: "handled" | "unhandled" = "handled"; let shouldSet = flag === "h"; // noinspection FallThroughInSwitchStatementJS switch (ps) { case 1: description = "Cursor Keys Mode."; url = "http://www.vt100.net/docs/vt510-rm/DECCKM"; this.isCursorKeysModeSet = shouldSet; break; case 3: url = "http://www.vt100.net/docs/vt510-rm/DECCOLM"; if (shouldSet) { description = "132 Column Mode (DECCOLM)."; this.dimensions = {columns: 132, rows: this.activeBuffer.dimensions.rows}; } else { description = "80 Column Mode (DECCOLM)."; this.dimensions = {columns: 80, rows: this.activeBuffer.dimensions.rows}; } this.activeBuffer.clear(); // TODO // If you change the DECCOLM setting, the terminal: // Sets the left, right, top and bottom scrolling margins to their default positions. // Erases all data in page memory. // DECCOLM resets vertical split screen mode (DECLRMM) to unavailabl // DECCOLM clears data from the status line if the status line is set to host-writabl break; case 5: description = "Reverse Video (DECSCNM)."; url = "http://www.vt100.net/docs/vt510-rm/DECSCNM"; this.screenMode = (shouldSet ? ScreenMode.Light : ScreenMode.Dark); break; case 6: description = "Origin Mode (DECOM)."; url = "http://www.vt100.net/docs/vt510-rm/DECOM"; this.activeBuffer.isOriginModeSet = shouldSet; break; case 7: description = "Wraparound Mode (DECAWM)."; url = "http://www.vt100.net/docs/vt510-rm/DECAWM"; this.activeBuffer.isAutowrapModeSet = shouldSet; break; case 12: if (shouldSet) { description = "Start Blinking Cursor (att610)."; this.activeBuffer.blinkCursor(true); } else { description = "Stop Blinking Cursor (att610)."; this.activeBuffer.blinkCursor(false); } break; case 25: url = "http://www.vt100.net/docs/vt510-rm/DECTCEM"; if (shouldSet) { description = "Show Cursor (DECTCEM)."; this.activeBuffer.showCursor(true); } else { description = "Hide Cursor (DECTCEM)."; this.activeBuffer.showCursor(false); } break; case 1049: if (shouldSet) { /* tslint:disable:max-line-length */ description = "Save cursor as in DECSC and use Alternate Screen Buffer, clearing it first. (This may be disabled by the titeInhibit resource). This combines the effects of the 1047 and 1048 modes. Use this with terminfo-based applications rather than the 47 mod"; this.activeBufferType = BufferType.Alternate; } else { // TODO: Add Implementation status = "unhandled"; } break; case 2004: if (shouldSet) { description = "Set bracketed paste mod"; } else { // TODO: Add Implementation status = "unhandled"; } break; default: status = "unhandled"; } return { status: status, description: description, url: url, }; } private csiHandler(_collected: any, rawParams: number[] | number, flag: string): HandlerResult { let short = ""; let long = ""; let url = ""; let status = "handled"; let params: number[] = Array.isArray(rawParams) ? rawParams : []; const param: number = params[0] || 0; switch (flag) { case "A": short = "Cursor Up Ps Times (default = 1) (CUU)."; this.activeBuffer.moveCursorRelative({vertical: -(param || 1)}); break; case "B": short = "Cursor Down Ps Times (default = 1) (CUD)."; this.activeBuffer.moveCursorRelative({vertical: (param || 1)}); break; case "C": short = "Cursor Forward Ps Times (default = 1) (CUF)."; this.activeBuffer.moveCursorRelative({horizontal: (param || 1)}); break; case "D": short = "Cursor Backward Ps Times (default = 1) (CUB)."; this.activeBuffer.moveCursorRelative({horizontal: -(param || 1)}); break; // CSI Ps E Cursor Next Line Ps Times (default = 1) (CNL). // CSI Ps F Cursor Preceding Line Ps Times (default = 1) (CPL). case "G": short = "Cursor Character Absolute [column] (default = [row,1]) (CHA)"; url = "http://www.vt100.net/docs/vt510-rm/CHA"; this.activeBuffer.moveCursorAbsolute({columnIndex: or1(param || 1) - 1}); break; case "H": short = "Cursor Position [row;column] (default = [1,1]) (CUP)."; url = "http://www.vt100.net/docs/vt510-rm/CUP"; this.activeBuffer.moveCursorAbsolute({rowIndex: or1(params[0]) - 1, columnIndex: or1(params[1]) - 1}); break; case "J": url = "http://www.vt100.net/docs/vt510-rm/ED"; switch (param) { case CSI.erase.entire: case CSI.erase.entireSsh: short = "Erase Entire Display (ED)."; this.activeBuffer.clear(); break; case CSI.erase.toEnd: short = "Erase Display Below (ED)."; this.activeBuffer.clearToEnd(); break; case CSI.erase.toBeginning: short = "Erase Display Above (ED)."; this.activeBuffer.clearToBeginning(); break; default: throw `Unknown CSI erase: "${param}".`; } break; case "K": url = "http://www.vt100.net/docs/vt510-rm/DECSEL"; switch (param) { case CSI.erase.entire: short = "Erase the Line (DECSEL)."; this.activeBuffer.clearRow(); break; case CSI.erase.toEnd: short = "Erase Line to Right (DECSEL)."; this.activeBuffer.clearRowToEnd(); break; case CSI.erase.toBeginning: short = "Erase Line to Left (DECSEL)."; this.activeBuffer.clearRowToBeginning(); break; default: throw `Unknown CSI erase: "${param}".`; } break; case "L": url = "http://www.vt100.net/docs/vt510-rm/IL"; short = "Inserts one or more blank lines, starting at the cursor. (DL)"; this.activeBuffer.scrollDown(param || 1); break; case "M": url = "http://www.vt100.net/docs/vt510-rm/DL"; short = "Deletes one or more lines in the scrolling region, starting with the line that has the cursor. (DL)"; this.activeBuffer.scrollUp(param || 1, this.activeBuffer.cursorRowIndex); break; case "P": url = "http://www.vt100.net/docs/vt510-rm/DCH.html"; short = "Deletes one or more characters from the cursor position to the right."; this.activeBuffer.deleteRight(param); break; case "X": short = "Erase P s Character(s) (default = 1) (ECH)"; url = "http://www.vt100.net/docs/vt510-rm/ECH"; this.activeBuffer.eraseRight(param || 1); break; case "c": short = "Send Device Attributes (Primary DA)"; this.terminalDevice.write("\x1b>1;2;"); break; case "d": short = "Line Position Absolute [row] (default = [1,column]) (VPA)."; url = "http://www.vt100.net/docs/vt510-rm/VPA"; this.activeBuffer.moveCursorAbsolute({rowIndex: or1(param || 1) - 1}); break; case "f": short = "Horizontal and Vertical Position [row;column] (default = [1,1]) (HVP)."; url = "http://www.vt100.net/docs/vt510-rm/HVP"; this.activeBuffer.moveCursorAbsolute({rowIndex: or1(params[0]) - 1, columnIndex: or1(params[1]) - 1}); break; case "g": url = "http://www.vt100.net/docs/vt510-rm/TBC"; switch (param) { case 0: short = "Clear Tab Stop At Current Column (TBC)."; this.activeBuffer.clearTabStop(); break; case 3: short = "Clear All Tab Stops (TBC)."; this.activeBuffer.clearAllTabStops(); break; default: error(`Unknown tab clear parameter "${param}", ignoring.`); } break; case "m": short = `SGR: ${params}`; if (params.length === 0) { short = "Reset SGR"; this.activeBuffer.resetAttributes(); break; } while (params.length !== 0) { const sgr = params.shift()!; if (sgr === 38 || sgr === 48) { const colorFormat = params.shift(); if (colorFormat === colorFormatCodes.format8bit) { const color = params.shift(); if (color) { this.setColor(sgr, color); } else { error("sgr", sgr, colorFormat, params); } } else if (colorFormat === colorFormatCodes.formatTrueColor) { this.setColor(sgr, params); params = []; } else { error("sgr", sgr, colorFormat, params); } } else { const attributesUpdater = SGR[sgr]; if (attributesUpdater) { this.activeBuffer.setAttributes(attributesUpdater(this.activeBuffer.attributes)); } else { error("sgr", sgr, params); } } } break; case "n": if (param === 6) { url = "http://www.vt100.net/docs/vt510-rm/CPR"; short = "Report Cursor Position (CPR) [row;column] as CSI r ; c R"; this.terminalDevice.write(csi(`${this.activeBuffer.cursorRowIndex + 1};${this.activeBuffer.cursorColumnIndex + 1}R`)); } else { status = "unhandled"; } break; case "r": url = "http://www.vt100.net/docs/vt510-rm/DECSTBM"; short = "Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM)."; const top = (params[0] ? params[0] - 1 : undefined); const bottom = (params[1] ? params[1] - 1 : undefined); this.activeBuffer.margins = {top: top, bottom: bottom}; this.activeBuffer.moveCursorAbsolute({rowIndex: 0, columnIndex: 0}); break; case "@": url = "http://www.vt100.net/docs/vt510-rm/ICH.html"; short = "Inserts one or more space (SP) characters starting at the cursor position."; this.activeBuffer.insertSpaceRight(param); break; default: status = "unhandled"; } return { status: status, description: short, longDescription: long, url: url, }; } private setColor(sgr: number, color: ColorCode): void { if (sgr === 38) { this.activeBuffer.setAttributes({...this.activeBuffer.attributes, color: color}); } else { this.activeBuffer.setAttributes({...this.activeBuffer.attributes, backgroundColor: color}); } } } class Buffer { public cursorRowIndex = 0; public cursorColumnIndex = 0; public _showCursor = true; public _blinkCursor = true; public designatedCharacterSets: DesignatedCharacterSets = { G0: CharacterSets.ASCIIGraphics, G1: CharacterSets.ASCIIGraphics, G2: CharacterSets.ASCIIGraphics, G3: CharacterSets.ASCIIGraphics, }; public selectedCharacterSet: SelectedCharacterSet = "G0"; public isOriginModeSet = false; public isAutowrapModeSet = true; private scrollback = List>(); private page = List>(); private _attributes: i.Attributes = {...defaultAttributes, color: e.Color.White, weight: e.Weight.Normal}; private _margins: Margins = {top: 0, left: 0}; private savedState: SavedState | undefined; private tabStopIndices = _.range(8, 300, 8); constructor(private output: Output, private maxScrollbackSize: number) { this.ensureCursorRowExists(); } map(callback: (row: List, index: number) => T): T[] { const result: T[] = []; let index = 0; this.scrollback.forEach(row => { result.push(callback(row!, index)); ++index; }); this.page.forEach(row => { result.push(callback(row!, index)); ++index; }); return result; } writeOne(char: string): void { const charCode = char.charCodeAt(0); /** * Is a special symbol. * TODO: take into account C1 and DELETE. * @link http://www.asciitable.com/index/asciifull.gif */ if (charCode < 32) { switch (charCode) { case e.KeyCode.Bell: break; case e.KeyCode.Backspace: this.moveCursorRelative({horizontal: -1}); break; case e.KeyCode.Tab: this.moveCursorAbsolute({columnIndex: this.nextTabStopIndex}); break; case e.KeyCode.NewLine: case e.KeyCode.VerticalTab: if (this.cursorRowIndex === this._margins.bottom) { this.scrollUp(1); } else { this.moveCursorRelative({vertical: 1}); } break; case e.KeyCode.CarriageReturn: this.moveCursorAbsolute({columnIndex: 0}); break; case e.KeyCode.ShiftIn: this.selectedCharacterSet = "G0"; break; case e.KeyCode.ShiftOut: this.selectedCharacterSet = "G1"; break; default: error(`Couldn't write a special char with code ${charCode}.`); } } else { const charFromCharset = this.charFromCharset(char); const charObject = createChar(charFromCharset, this.attributes); if (this.cursorColumnIndex === this.dimensions.columns) { if (this.isAutowrapModeSet) { this.moveCursorAbsolute({columnIndex: 0}); this.moveCursorRelative({vertical: 1}); } else { this.moveCursorRelative({horizontal: -1}); } } this.set(charObject); this.moveCursorRelative({horizontal: 1}); } } scrollDown(count: number) { times(count, () => this.page = this.page.delete(this.marginBottom)); times(count, () => this.page = this.page.insert(this.cursorRowIndex, this.emptyLine)); } scrollUp(count: number, deletedLine = this._margins.top) { times(count, () => this.page = this.page.splice((this._margins.bottom || 0) + 1, 0, this.emptyLine).toList()); this.page = this.page.splice(deletedLine, count).toList(); } get attributes(): i.Attributes { return this._attributes; } resetAttributes(): void { this._attributes = defaultAttributes; } setAttributes(attributes: i.Attributes): void { this._attributes = {...this._attributes, ...attributes}; } toLines(): string[] { return this.map(row => row.map(char => char!.value).join("")); } showCursor(state: boolean): void { this._showCursor = state; } blinkCursor(state: boolean): void { this._blinkCursor = state; } moveCursorRelative(advancement: Advancement): this { const unboundRowIndex = this.cursorRowIndex + (advancement.vertical || 0); const boundRowIndex = this._margins.bottom ? Math.min(this.marginBottom, unboundRowIndex) : unboundRowIndex; // Cursor might be hanging after the last column. const boundColumnIndex = Math.min(this.lastColumnIndex, this.cursorColumnIndex); this.cursorRowIndex = Math.max(0, boundRowIndex); this.cursorColumnIndex = Math.min(this.dimensions.columns, Math.max(0, boundColumnIndex + (advancement.horizontal || 0))); this.ensureCursorRowExists(); return this; } moveCursorAbsolute(position: Partial): this { if (typeof position.columnIndex === "number") { this.cursorColumnIndex = Math.max(position.columnIndex, 0) + this.homePosition.columnIndex; } if (typeof position.rowIndex === "number") { this.cursorRowIndex = Math.max(position.rowIndex, 0) + this.homePosition.rowIndex; } this.ensureCursorRowExists(); return this; } deleteRight(n: number) { this.page = this.page.update( this.cursorRowIndex, row => row.splice(this.cursorColumnIndex, n).concat(this.spaces(n)).toList(), ); } insertSpaceRight(n: number) { this.page = this.page.update( this.cursorRowIndex, row => row.splice(this.cursorColumnIndex, 0, this.spaces(n)).toList(), ); } eraseRight(n: number) { this.page = this.page.update( this.cursorRowIndex, row => row.take(this.cursorColumnIndex) .concat(this.spaces(n), row.skip(this.cursorColumnIndex + n)) .toList(), ); } clearRow() { this.page = this.page.set(this.cursorRowIndex, this.emptyLine); } clearRowToEnd() { const oldRow = this.page.get(this.cursorRowIndex); const charsToDeleteCount = this.dimensions.columns - this.cursorColumnIndex; const newHead = oldRow.splice(this.cursorColumnIndex, charsToDeleteCount); const newTail = this.spaces(charsToDeleteCount); const newRow = newHead.concat(newTail).toList(); this.page = this.page.set(this.cursorRowIndex, newRow); } clearRowToBeginning() { const count = this.cursorColumnIndex + 1; this.page = this.page.update( this.cursorRowIndex, row => this.spaces(count).concat(row.skip(count)).toList()); } clear() { this.page = List>(); this.moveCursorAbsolute({rowIndex: 0, columnIndex: 0}); } clearToBeginning() { this.clearRowToBeginning(); const replacement = Array(this.cursorRowIndex).fill(this.emptyLine); this.page = this.page.splice(0, this.cursorRowIndex, ...replacement).toList(); } clearToEnd() { this.clearRowToEnd(); this.page = this.page.splice(this.cursorRowIndex + 1, this.size - this.cursorRowIndex).toList(); } get scrollbackSize(): number { return this.scrollback.size; } get size(): number { return this.page.size; } set margins(margins: Partial) { this._margins = {...this._margins, ...margins}; } get marginTop(): number { return this._margins.top; } get marginBottom(): number { if (this._margins.bottom) { return this._margins.bottom; } else { return this.dimensions.rows - 1; } } at(position: RowColumn): Char { return this.page.getIn([position.rowIndex, position.columnIndex]); } saveCurrentState() { this.savedState = { cursorRowIndex: this.cursorRowIndex, cursorColumnIndex: this.cursorColumnIndex, attributes: {...this.attributes}, designatedCharacterSets: {...this.designatedCharacterSets}, selectedCharacterSet: this.selectedCharacterSet, }; } restoreCurrentState() { if (this.savedState) { this.moveCursorAbsolute({rowIndex: this.savedState.cursorRowIndex, columnIndex: this.savedState.cursorColumnIndex}); this.setAttributes(this.savedState.attributes); this.selectedCharacterSet = this.savedState.selectedCharacterSet; this.designatedCharacterSets = this.savedState.designatedCharacterSets; } else { console.error("No state to restore."); } } setTabStop() { this.tabStopIndices = _.sortBy(_.union(this.tabStopIndices, [this.cursorColumnIndex])); } clearTabStop() { this.tabStopIndices = _.without(this.tabStopIndices, this.cursorColumnIndex); } clearAllTabStops() { this.tabStopIndices = []; } get nextTabStopIndex() { const unboundTabStopIndex = this.tabStopIndices.find(index => index > this.cursorColumnIndex) || this.cursorColumnIndex; return Math.min(unboundTabStopIndex, this.lastColumnIndex); } private get homePosition(): RowColumn { if (this.isOriginModeSet) { return {rowIndex: this._margins.top || 0, columnIndex: this._margins.left || 0}; } else { return {rowIndex: 0, columnIndex: 0}; } } private set(char: Char): void { this.ensureCursorRowExists(); this.page = this.page.setIn([this.cursorRowIndex, this.cursorColumnIndex], char); } private ensureCursorRowExists(): void { for (let index = this.cursorRowIndex; index >= 0; --index) { if (!this.page.get(index)) { this.page = this.page.set(index, this.spaces(this.dimensions.columns, defaultAttributes)); } else { break; } } if (this.size > this.dimensions.rows) { const newStorage = this.page.takeLast(this.dimensions.rows).toList(); const rowsToMoveToScrollback = this.page.skipLast(this.dimensions.rows).toList(); this.scrollback = this.scrollback.concat(rowsToMoveToScrollback).takeLast(this.maxScrollbackSize).toList(); this.page = newStorage; this.cursorRowIndex = this.size - 1; } } private charFromCharset(char: string) { if (this.designatedCharacterSets[this.selectedCharacterSet] === CharacterSets.ASCIIGraphics) { return char; } else { return graphicCharset[char] || char; } } private get lastColumnIndex() { return this.dimensions.columns - 1; } private get emptyLine() { return this.spaces(this.dimensions.columns); } private spaces(n: number, attributes = this.attributes) { return List.of(...Array(n).fill(createChar(" ", attributes))); } get dimensions() { return this.output.dimensions; } } ================================================ FILE: src/PTY.ts ================================================ import * as ChildProcess from "child_process"; import * as OS from "os"; import * as _ from "lodash"; import * as pty from "node-pty"; import {loginShell} from "./utils/Shell"; import {homeDirectory, info} from "./utils/Common"; interface ITerminal { write(data: string): void; resize(cols: number, rows: number): void; kill(signal?: string): void; on(type: string, listener: (...args: any[]) => any): void; } export class PTY { private terminal: ITerminal; // TODO: write proper signatures. // TODO: use generators. // TODO: terminate. https://github.com/atom/atom/blob/v1.0.15/src/task.coffee#L151 constructor(words: EscapedShellWord[], env: ProcessEnvironment, dimensions: Dimensions, dataHandler: (d: string) => void, exitHandler: (c: number) => void) { const shellArguments = [...loginShell.noConfigSwitches, ...loginShell.interactiveCommandSwitches, words.join(" ")]; info(`PTY: ${loginShell.executableName} ${JSON.stringify(shellArguments)}`); info(`Dimensions: ${JSON.stringify(dimensions)}}`); this.terminal = pty.fork(loginShell.executableName, shellArguments, { cols: dimensions.columns, rows: dimensions.rows, cwd: env.PWD, env: env, }); this.terminal.on("data", (data: string) => dataHandler(data)); this.terminal.on("exit", (code: number) => exitHandler(code)); } write(data: string): void { this.terminal.write(data); } resize(dimensions: Dimensions) { this.terminal.resize(dimensions.columns, dimensions.rows); } kill(signal: string): void { /** * The if branch is necessary because pty.js doesn't handle SIGINT correctly. * You can test whether it works by executing * ruby -e "loop { puts 'yes'; sleep 1 }" * and trying to kill it with SIGINT. * * {@link https://github.com/chjj/pty.js/issues/58} */ if (signal === "SIGINT") { this.terminal.kill("SIGTERM"); } else { this.terminal.kill(signal); } } } export function executeCommand( command: string, args: string[] = [], directory: string, execOptions?: any, ): Promise { return new Promise((resolve, reject) => { const options = { ...execOptions, env: _.extend({PWD: directory, SHLVL: 1}, process.env), cwd: directory, shell: loginShell.commandExecutorPath, }; ChildProcess.exec(`${command} ${args.join(" ")}`, options, (error, output) => { if (error) { reject(error); } else { resolve(output.toString()); } }); }); } export async function linedOutputOf(command: string, args: string[], directory: string): Promise { let output = await executeCommand(command, args, directory); return output.split("\\" + OS.EOL).join(" ").split(OS.EOL).filter(path => path.length > 0); } export async function executeCommandWithShellConfig(command: string): Promise { const sourceCommands = (await loginShell.existingConfigFiles()).map(fileName => `source ${fileName} &> /dev/null`); return await linedOutputOf(loginShell.executableName, [...loginShell.executeCommandSwitches, loginShell.combineCommands([...sourceCommands, command])], homeDirectory); } ================================================ FILE: src/PluginManager.ts ================================================ import {Prettyfier, EnvironmentObserverPlugin, AutocompletionProvider} from "./Interfaces"; import * as Path from "path"; import {io} from "./utils/Common"; import { defaultAutocompletionProvider } from "./plugins/completion_utils/Common"; // FIXME: Technical debt: register all the plugin types via single method. export class PluginManager { private static _prettyfiers: Prettyfier[] = []; private static _environmentObservers: EnvironmentObserverPlugin[] = []; private static _autocompletionProviders: Dictionary = {}; static registerPrettyfier(prettyfier: Prettyfier): void { this._prettyfiers.push(prettyfier); } static get prettyfiers(): Prettyfier[] { return this._prettyfiers; } static registerEnvironmentObserver(plugin: EnvironmentObserverPlugin): void { this._environmentObservers.push(plugin); } static get environmentObservers(): EnvironmentObserverPlugin[] { return this._environmentObservers; } static registerAutocompletionProvider(commandName: string, provider: AutocompletionProvider): void { this._autocompletionProviders[commandName] = provider; } static autocompletionProviderFor(commandName: string): AutocompletionProvider { return this._autocompletionProviders[commandName] || defaultAutocompletionProvider; } } export async function loadAllPlugins(): Promise { const pluginsDirectory = Path.join(__dirname, "plugins"); const filePaths = await io.recursiveFilesIn(pluginsDirectory); filePaths.map(require).map((module: any) => module.default); } ================================================ FILE: src/main/Main.ts ================================================ import {app, ipcMain, nativeImage, BrowserWindow, screen} from "electron"; import {readFileSync} from "fs"; import {windowBoundsFilePath} from "../utils/Common"; app.on("ready", () => { const bounds = windowBounds(); let options: Electron.BrowserWindowConstructorOptions = { webPreferences: { experimentalFeatures: true, experimentalCanvasFeatures: true, }, titleBarStyle: "hidden", resizable: true, minWidth: 500, minHeight: 300, width: bounds.width, height: bounds.height, x: bounds.x, y: bounds.y, show: false, }; const browserWindow = new BrowserWindow(options); if (app.dock) { app.dock.setIcon(nativeImage.createFromPath("build/icon.png")); } else { browserWindow.setIcon(nativeImage.createFromPath("build/icon.png")); } browserWindow.loadURL("file://" + __dirname + "/../views/index.html"); browserWindow.webContents.on("did-finish-load", () => { browserWindow.show(); browserWindow.focus(); }); app.on("open-file", (_event, file) => browserWindow.webContents.send("change-working-directory", file)); }); app.on("window-all-closed", () => app.quit()); ipcMain.on("quit", app.quit); function windowBounds(): Electron.Rectangle { try { return JSON.parse(readFileSync(windowBoundsFilePath).toString()); } catch (error) { const workAreaSize = screen.getPrimaryDisplay().workAreaSize; return { width: workAreaSize.width, height: workAreaSize.height, x: 0, y: 0, }; } } ================================================ FILE: src/monaco/Loader.ts ================================================ export function requireMonaco(callback: () => void) { const g = (global as any); const nodeRequire = g.require; const loaderScript = document.createElement("script"); loaderScript.type = "text/javascript"; loaderScript.src = "../../../node_modules/monaco-editor/min/vs/loader.js"; loaderScript.addEventListener("load", () => { const amdRequire = g.require; g.require = nodeRequire; // require node modules before loader.js comes in const path = require("path"); function uriFromPath(_path: string) { let pathName = path.resolve(_path).replace(/\\/g, "/"); if (pathName.length > 0 && pathName.charAt(0) !== "/") { pathName = "/" + pathName; } return encodeURI("file://" + pathName); } amdRequire.config({ baseUrl: uriFromPath(path.join(__dirname, "../../../node_modules/monaco-editor/dev")), }); // workaround monaco-css not understanding the environment (self as any).module = undefined; // workaround monaco-typescript not understanding the environment (self as any).process.browser = true; amdRequire(["vs/editor/editor.main"], callback); // window.require(["vs/editor/editor.main"], () => require("./Main")); }); document.body.appendChild(loaderScript); } ================================================ FILE: src/monaco/PromptTheme.ts ================================================ import {backgroundColor, colors, textColor} from "../views/css/colors"; monaco.editor.defineTheme("upterm-prompt-theme", { base: "vs-dark", inherit: true, rules: [ {token: "string", foreground: colors.green.slice(1)}, {token: "string.invalid", foreground: colors.red.slice(1)}, {token: "variable-name", foreground: colors.yellow.slice(1)}, {token: "variable-value", foreground: textColor.slice(1)}, {token: "command-name", foreground: colors.blue.slice(1), fontStyle: "bold"}, {token: "argument", foreground: textColor.slice(1)}, {token: "redirect-path", foreground: colors.yellow.slice(1)}, {token: "pipe", foreground: colors.yellow.slice(1)}, {token: "semicolon", foreground: colors.yellow.slice(1)}, {token: "and", foreground: colors.yellow.slice(1)}, {token: "or", foreground: colors.yellow.slice(1)}, {token: "appending-output-redirection-symbol", foreground: colors.yellow.slice(1)}, {token: "input-redirection-symbol", foreground: colors.yellow.slice(1)}, {token: "output-redirection-symbol", foreground: colors.yellow.slice(1)}, ], colors: { "editor.foreground": textColor, "editor.background": backgroundColor, "editor.lineHighlightBackground": backgroundColor, "editorSuggestWidget.background": backgroundColor, "editorSuggestWidget.highlightForeground": colors.blue, }, }); ================================================ FILE: src/monaco/ShellHistoryLanguage.ts ================================================ import {services} from "../services/index"; import * as _ from "lodash"; monaco.languages.setMonarchTokensProvider("shell-history", { tokenizer: { root: [ { regex: /.+/, action: {token: "history-item"}, }, ], }, defaultToken: "invalid", tokenPostfix: ".shell-history", }); monaco.languages.register({ id: "shell-history", }); monaco.languages.registerCompletionItemProvider("shell-history", { triggerCharacters: [" ", "/"], provideCompletionItems: () => { return { isIncomplete: false, items: _.uniqBy(services.history.all, record => record.command).map(record => ({ label: record.command, kind: monaco.languages.CompletionItemKind.Text, })), }; }, }); monaco.languages.setLanguageConfiguration("shell-history", { wordPattern: /.*/g, }); ================================================ FILE: src/monaco/ShellLanguage.ts ================================================ import {SessionID} from "../shell/Session"; import {getSuggestions} from "../Autocompletion"; import {services} from "../services/index"; import {scan} from "../shell/Scanner"; import {CompleteCommand} from "../shell/Parser"; import {io} from "../utils/Common"; monaco.languages.setMonarchTokensProvider("shell", { variableName: /[a-zA-Z][a-zA-Z0-9_]*/, word: /[a-zA-Z0-9\u0080-\uFFFF+~!@#%^*_,.:/?\\-]+/, escapes: /\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/, defaultToken: "invalid", tokenizer: { root: [ { regex: /\s+/, action: {token: "spaces"}, }, { regex: /@variableName=/, action: {token: "variable-name", next: "@variableValue"}, }, { regex: /@word/, action: {token: "command-name", next: "@arguments"}, }, ], arguments: [ [/\s+/, "spaces"], [/\$@variableName/, "variable-name"], [/@word/, "argument"], [/[=[\]]/, "argument"], { regex: /\|\|/, action: {token: "or", next: "@pop"}, }, { regex: /\|/, action: {token: "pipe", next: "@pop"}, }, { regex: /;/, action: {token: "semicolon", next: "@pop"}, }, { regex: /&&/, action: {token: "and", next: "@pop"}, }, { regex: />>/, action: {token: "appending-output-redirection-symbol", next: "@redirect"}, }, { regex: //, action: {token: "output-redirection-symbol", next: "@redirect"}, }, { include: "@allowStringLiterals" }, ], redirect: [ { regex: /\s+/, action: {token: "spaces"}, }, { regex: /@word/, action: {token: "redirect-path", next: "@pop"}, }, ], variableValue: [ { regex: /@word/, action: {token: "variable-value", next: "@pop"}, }, {include: "@checkInvalidStringLiteral"}, { regex: /"/, action: {token: "string", switchTo: "@doubleQuotedString"}, }, { regex: /'/, action: {token: "string", switchTo: "@singleQuotedString"}, }, { regex: /.*/, action: {token: "invalid"}, }, ], checkInvalidStringLiteral: [ // strings: recover on non-terminated strings [/"([^"\\]|\\.)*$/, "string.invalid"], // non-teminated string [/'([^'\\]|\\.)*$/, "string.invalid"], // non-teminated string ], allowStringLiterals: [ {include: "@checkInvalidStringLiteral"}, [/"/, "string", "@doubleQuotedString"], [/'/, "string", "@singleQuotedString"], ], singleQuotedString: [ [/[^\\']+/, "string"], [/@escapes/, "string.escape"], [/\\./, "string.escape.invalid"], [/'/, "string", "@pop"], ], doubleQuotedString: [ [/\$@variableName/, "variable-name"], [/\${@variableName}/, "variable-name"], [/[^\\"$]+/, "string"], [/@escapes/, "string.escape"], [/\\./, "string.escape.invalid"], [/"/, "string", "@pop"], ], }, tokenPostfix: ".shell", } as any); monaco.languages.register({ id: "shell", }); monaco.languages.setLanguageConfiguration("shell", { brackets: [ ["'", "'"], ['"', '"'], ["`", "`"], ["(", ")"], ["[", "]"], ["{", "}"], ], wordPattern: /(\d*\.\d\w*\$)|([^`~!#%^&*()=+\[{\]}\\|;:'",<>\/?\s]+)/g, }); monaco.editor.onDidCreateModel(model => { if (model.uri.scheme !== "shell") { return; } model.onDidChangeContent(async () => { const value = model.getValue(); const sessionID: SessionID = Number.parseInt(model.uri.authority, 10); const session = services.sessions.get(sessionID); const executables = await io.executablesInPaths(session.environment.path); const markers: monaco.editor.IMarkerData[] = []; monaco.editor.tokenize(value, "shell").forEach((lineTokens, lineIndex) => { lineTokens.forEach((token, tokenIndex) => { if (token.type !== "command-name.shell") { return; } const nextToken = lineTokens[tokenIndex + 1]; // Possibly still writing command name. if (!nextToken) { return; } const tokenRange = { startLineNumber: lineIndex + 1, endLineNumber: lineIndex + 1, startColumn: token.offset + 1, endColumn: nextToken ? (nextToken.offset + 1) : Infinity, }; const commandName = model.getValueInRange(tokenRange); if (!executables.includes(commandName) && !session.aliases.has(commandName)) { markers.push({ severity: monaco.Severity.Error, message: `Executable ${commandName} doesn't exist in $PATH.`, ...tokenRange, }); } }); }); monaco.editor.setModelMarkers(model, "upterm", markers); }); }); monaco.languages.registerCompletionItemProvider("shell", { triggerCharacters: [" ", "/", "$", "-", "."], provideCompletionItems: async function (model, position): Promise { model.getValue(); const sessionID: SessionID = Number.parseInt(model.uri.authority, 10); const session = services.sessions.get(sessionID); const text = model.getValue(); const ast = new CompleteCommand(scan(text)); return getSuggestions({ currentText: text, currentCaretPosition: position.column - 1, ast: ast, environment: session.environment, historicalPresentDirectoriesStack: session.historicalPresentDirectoriesStack, aliases: session.aliases, }); }, }); // https://github.com/Microsoft/monaco-editor/issues/346#issuecomment-277215371 export function getTokensAtLine(model: any, lineNumber: number) { // Force line's state to be accurate model.getLineTokens(lineNumber, /*inaccurateTokensAcceptable*/false); // Get the tokenization state at the beginning of this line const freshState = model._lines[lineNumber - 1].getState().clone(); // Get the human readable tokens on this line return model._tokenizationSupport.tokenize(model.getLineContent(lineNumber), freshState, 0).tokens; } ================================================ FILE: src/plugins/AliasSuggestions.ts ================================================ import {services} from "../services/index"; services.jobs.onStart.subscribe(job => { const input = job.prompt.value; const alias = job.session.aliases.getNameByValue(input); if (alias && alias.length < input.length) { /* tslint:disable:no-unused-expression */ new Notification("Alias Reminder", { body: `You have an alias "${alias}" for "${input}".` }); } }); ================================================ FILE: src/plugins/DotEnvLoader.ts ================================================ import {Session} from "../shell/Session"; import {PluginManager} from "../PluginManager"; import * as Path from "path"; import {io} from "../utils/Common"; import {sourceFile} from "../shell/BuiltInCommands"; PluginManager.registerEnvironmentObserver({ presentWorkingDirectoryWillChange: () => void 0, presentWorkingDirectoryDidChange: async(session: Session, directory: string) => { if (await io.fileExists(Path.join(directory, ".env"))) { sourceFile(session, ".env"); } }, }); ================================================ FILE: src/plugins/GitGrep.tsx ================================================ import * as React from "react"; import {PluginManager} from "../PluginManager"; import {Job} from "../shell/Job"; import {Link} from "../utils/Link"; import {join} from "path"; import {colors} from "../views/css/colors"; PluginManager.registerPrettyfier({ prettify: (job: Job): React.ReactElement => { return
{job.output.toLines().map((line, index) => { const match = line.match(/^(.*?):(\d+):(.*)$/); if (match) { const [, path, lineNum, rest] = match; if (path && lineNum && rest) { const absolutePath = join(job.environment.pwd, path); return
{path} : {lineNum} : {rest}
; } } return
{line}
; })}
; }, isApplicable: (job: Job): boolean => { try { const promptWords = job.prompt.expandedTokens.map(t => t.escapedValue); return promptWords.length === 3 && promptWords[0] === "git" && promptWords[1] === "grep"; } catch (e) { return false; } }, }); ================================================ FILE: src/plugins/JSON.tsx ================================================ import * as React from "react"; import {Job} from "../shell/Job"; import {PluginManager} from "../PluginManager"; import {JSONTree} from "../utils/JSONTree"; PluginManager.registerPrettyfier({ prettify: (job: Job): React.ReactElement => { return ; }, isApplicable: (job: Job): boolean => { try { const parseResult = JSON.parse(job.output.toString()); return parseResult && typeof parseResult === "object"; } catch (exception) { return false; } }, }); ================================================ FILE: src/plugins/JobFinishedNotifications.ts ================================================ import {services} from "../services/index"; import {remote} from "electron"; import {Status} from "../Enums"; services.jobs.onFinish.subscribe(job => { const electronWindow = remote.BrowserWindow.getAllWindows()[0]; if (remote.app.dock && !electronWindow.isFocused()) { remote.app.dock.bounce("informational"); remote.app.dock.setBadge(job.status === Status.Success ? "1" : "✕"); const title = job.status === Status.Success ? "Completed" : "Failed"; /* tslint:disable:no-unused-expression */ new Notification(title, {body: job.prompt.value}); } }); const electronWindow = remote.BrowserWindow.getAllWindows()[0]; electronWindow.on("focus", () => remote.app.dock && remote.app.dock.setBadge("")); ================================================ FILE: src/plugins/NVM.ts ================================================ import {Session} from "../shell/Session"; import {PluginManager} from "../PluginManager"; import * as Path from "path"; import {homeDirectory, io} from "../utils/Common"; async function withNvmPath(directory: string, callback: (path: string) => void) { const rcPath = Path.join(directory, ".nvmrc"); if (await io.fileExists(rcPath)) { const version = (await io.readFile(rcPath)).trim(); callback(Path.join(homeDirectory, ".nvm", "versions", "node", version, "bin")); } } PluginManager.registerEnvironmentObserver({ presentWorkingDirectoryWillChange: async(session: Session) => { withNvmPath(session.directory, path => session.environment.path.remove(path)); }, presentWorkingDirectoryDidChange: async(session: Session, directory: string) => { withNvmPath(directory, path => session.environment.path.prepend(path)); }, }); ================================================ FILE: src/plugins/PWDOperatingSystemIntegrator.ts ================================================ import {Session} from "../shell/Session"; import {PluginManager} from "../PluginManager"; import {remote} from "electron"; PluginManager.registerEnvironmentObserver({ presentWorkingDirectoryWillChange: () => { /* do nothing */ }, presentWorkingDirectoryDidChange: (_session: Session, directory: string) => { remote.app.addRecentDocument(directory); }, }); ================================================ FILE: src/plugins/RVM.ts ================================================ import {Session} from "../shell/Session"; import {PluginManager} from "../PluginManager"; import * as Path from "path"; import {homeDirectory, io} from "../utils/Common"; const rvmDirectory = Path.join(homeDirectory, ".rvm"); const rubyVersionFileName = ".ruby-version"; const gemSetNameFileName = ".ruby-gemset"; async function getRubyVersion(directory: string): Promise { if (await io.fileExists(Path.join(directory, rubyVersionFileName))) { return (await io.readFile(Path.join(directory, rubyVersionFileName))).trim(); } else { const resolvedPath = await io.realPath(Path.join(rvmDirectory, "rubies", "default")); return resolvedPath.split("-")[1]; } } async function getGemSetName(directory: string): Promise { const gemSetNameFilePath = Path.join(directory, gemSetNameFileName); if (await io.fileExists(gemSetNameFilePath)) { return (await io.readFile(gemSetNameFilePath)).trim(); } else { return "global"; } } /** * Contract: the non-global path should be first. */ function getGemSetPaths(rubyVersion: string, gemSetName: string): string[] { const suffixes = gemSetName === "global" ? ["", "@global"] : [`@${gemSetName}`, "@global"]; return suffixes.map(suffix => Path.join(rvmDirectory, "gems", `ruby-${rubyVersion}${suffix}`)); } function binPaths(rubyVersion: string, gemSetName: string): string[] { return [ Path.join(rvmDirectory, "bin"), Path.join(rvmDirectory, "rubies", `ruby-${rubyVersion}`, "bin"), ...getGemSetPaths(rubyVersion, gemSetName).map(path => Path.join(path, "bin")), ]; } async function withRvmData(directory: string, callback: (binPaths: string[], gemPaths: string[]) => void) { try { const rubyVersion = await getRubyVersion(directory); const gemSetName = await getGemSetName(directory); const gemPaths = getGemSetPaths(rubyVersion, gemSetName); callback(binPaths(rubyVersion, gemSetName), gemPaths); } catch (e) { if (e.code === "ENOENT") { // No RVM installed. Ignore exception. } else { throw e; } } } PluginManager.registerEnvironmentObserver({ presentWorkingDirectoryWillChange: () => async(session: Session, directory: string) => { withRvmData(directory, binPaths => { binPaths.forEach(path => session.environment.path.remove(path)); session.environment.setMany({ GEM_PATH: "", GEM_HOME: "", }); }); }, presentWorkingDirectoryDidChange: async(session: Session, directory: string) => { withRvmData(directory, (binPaths, gemPaths) => { binPaths.forEach(path => session.environment.path.prepend(path)); session.environment.setMany({ GEM_PATH: gemPaths.join(Path.delimiter), GEM_HOME: gemPaths[0], }); }); }, }); ================================================ FILE: src/plugins/SaveHistory.ts ================================================ import {appendFileSync} from "fs"; import {historyFilePath} from "../utils/Common"; import * as csvStringify from "csv-stringify"; import {services} from "../services/index"; services.jobs.onFinish.subscribe(job => services.history.add({ command: job.prompt.value, expandedCommand: job.prompt.expandedTokens.map(t => t.escapedValue).join(" "), timestamp: job.startTime, directory: job.environment.pwd, sessionID: job.session.id, })); services.history.onNewRecord.subscribe(record => csvStringify( [Object.values(record)], (_error, output) => appendFileSync(historyFilePath, output), )); ================================================ FILE: src/plugins/SaveWindowBounds.ts ================================================ import {services} from "../services/index"; import {windowBoundsFilePath} from "../utils/Common"; import {outputJSON} from "fs-extra"; services.window.onBoundsChange.subscribe(bounds => outputJSON(windowBoundsFilePath, bounds)); ================================================ FILE: src/plugins/Show.tsx ================================================ import * as React from "react"; import {PluginManager} from "../PluginManager"; import {Job} from "../shell/Job"; PluginManager.registerPrettyfier({ prettify: (job: Job): React.ReactElement => { const rows = job.output.toLines().map(path => ); return
{rows}
; }, isApplicable: (job: Job): boolean => { return job.hasOutput() && (job.prompt.commandName === "show"); }, }); ================================================ FILE: src/plugins/UpdateLastPresentWorkingDirectory.ts ================================================ import {services} from "../services/index"; import {outputJSON} from "fs-extra"; import {presentWorkingDirectoryFilePath} from "../utils/Common"; services.jobs.onFinish.subscribe(job => outputJSON(presentWorkingDirectoryFilePath, job.session.directory), ); ================================================ FILE: src/plugins/completion/Brew.ts ================================================ import { emptyProvider, longFlag, provide, shortFlag, staticSuggestionsProvider, Suggestion, } from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {PluginManager} from "../../PluginManager"; import {AutocompletionContext, AutocompletionProvider} from "../../Interfaces"; import {executeCommand} from "../../PTY"; import {concat, find, memoize, sortBy} from "lodash"; import {homeDirectory} from "../../utils/Common"; interface FormulaAttributes { name: string; path: string; } const getFormulae = memoize( async(brewArgs: string[]): Promise => { const text = await executeCommand("brew", brewArgs, homeDirectory); const matches = text.match(/^([\-a-zA-Z0-9]+\/)*([\-a-zA-Z0-9]+)$/gm); if (matches) { return matches.map((match: string) => { const matchParts = match.split("/"); return { name: matchParts[matchParts.length - 1], path: matchParts.length > 1 ? match.trim() : "", }; }); } return []; }, (brewArgs: string[]) => brewArgs.join(" "), ); const getAllFormulae = (cask: boolean) => getFormulae(cask ? ["cask", "search"] : ["search"]); const getInstalledFormulae = (cask: boolean) => getFormulae(cask ? ["cask", "list"] : ["list"]); const formulaSuggestions = async(formulae: FormulaAttributes[], query: string): Promise => { if (!formulae) { return []; } return formulae .filter(formula => !query || formula.name.startsWith(query) || formula.path.startsWith(query)) .map(formula => ({ label: formula.name, })); }; const availableFormulae = provide(async context => { const argument = context.argument.command.nthArgument(2); const query = argument ? argument.value : ""; return formulaSuggestions(await getAllFormulae(false), query); }); const installedFormulae = provide(async context => { const argument = context.argument.command.nthArgument(2); const query = argument ? argument.value : ""; return formulaSuggestions(await getInstalledFormulae(false), query); }); const caskAvailableFormulae = provide(async context => { const argument = context.argument.command.nthArgument(3); const query = argument ? argument.value : ""; return formulaSuggestions(await getAllFormulae(true), query); }); const caskInstalledFormulae = provide(async context => { const argument = context.argument.command.nthArgument(3); const query = argument ? argument.value : ""; return formulaSuggestions(await getInstalledFormulae(true), query); }); interface BrewCommandData { name: string; description: string; provider?: AutocompletionProvider; commands?: BrewCommandData[]; } const commonCommands: BrewCommandData[] = [ { name: "search", description: "Perform a substring search of formula names", provider: combine([ longFlag("desc"), ]), }, { name: "update", description: "Fetch the newest version of Homebrew", provider: combine([ longFlag("merge"), longFlag("force"), ]), }, { name: "list", description: "List all installed formulae", provider: combine([ longFlag("versions"), longFlag("pinned"), ]), }, { name: "doctor", description: "Check your system for potential problems", provider: emptyProvider, }, { name: "create", description: "Generate a formula for a downloadable file", provider: combine([ longFlag("autotools"), longFlag("cmake"), longFlag("no-fetch"), longFlag("set-name"), longFlag("tap"), ]), }, ]; const caskCommands: BrewCommandData[] = [ { name: "install", description: "Install a Cask formula", provider: combine([ caskAvailableFormulae, longFlag("force"), longFlag("skip-cask-deps"), longFlag("require-sha"), ]), }, { name: "fetch", description: "Download a Cask formula", provider: combine([ caskAvailableFormulae, longFlag("force"), ]), }, { name: "remove", description: "Uninstall a Cask formula", provider: combine([ caskInstalledFormulae, longFlag("force"), ]), }, { name: "uninstall", description: "Uninstall a Cask formula", provider: combine([ caskInstalledFormulae, longFlag("force"), ]), }, { name: "cat", description: "Display the source to a Cask formula", provider: caskInstalledFormulae, }, { name: "cleanup", description: "Remove old version and download files for formula", provider: combine([ caskInstalledFormulae, longFlag("outdated"), ]), }, { name: "info", description: "Display information about a Cask formula", provider: caskAvailableFormulae, }, { name: "home", description: "Open a Cask formula homepage in the browser", provider: caskAvailableFormulae, }, { name: "edit", description: "Edit a Cask formula", provider: caskInstalledFormulae, }, ]; const brewCommands: BrewCommandData[] = [ { name: "install", description: "Install a formula", provider: combine([ availableFormulae, longFlag("debug"), longFlag("env"), longFlag("ignore-dependencies"), longFlag("only-dependencies"), longFlag("build-from-source"), longFlag("devel"), longFlag("keep-temp"), longFlag("cc"), ]), }, { name: "cask", description: "Install a cask formula", commands: concat(caskCommands, commonCommands), }, { name: "fetch", description: "Install a formula", provider: combine([ availableFormulae, longFlag("force"), longFlag("retry"), longFlag("deps"), longFlag("build-from-source"), longFlag("force-bottle"), ]), }, { name: "remove", description: "Uninstall a formula", provider: combine([ installedFormulae, longFlag("force"), ]), }, { name: "uninstall", description: "Uninstall a formula", provider: combine([ installedFormulae, longFlag("force"), ]), }, { name: "upgrade", description: "Upgrade a formula", provider: combine([ installedFormulae, longFlag("upgrade"), longFlag("fetch-HEAD"), ]), }, { name: "cat", description: "Display the source to a formula", provider: installedFormulae, }, { name: "cleanup", description: "Remove old version and download files for formula", provider: combine([ installedFormulae, longFlag("prune"), longFlag("dry-run"), shortFlag("s"), ]), }, { name: "deps", description: "Show dependencies for a formula", provider: combine([ availableFormulae, longFlag("1"), shortFlag("n"), longFlag("union"), longFlag("installed"), longFlag("include-build"), longFlag("include-optional"), longFlag("skip-recommended"), longFlag("tree"), ]), }, { name: "info", description: "Display information about a formula", provider: combine([ availableFormulae, longFlag("github"), longFlag("json"), longFlag("installed"), longFlag("all"), ]), }, { name: "home", description: "Open a formula homepage in the browser", provider: availableFormulae, }, { name: "options", description: "Display install options for a formula", provider: availableFormulae, }, { name: "edit", description: "Edit a formula", provider: installedFormulae, }, ]; const fromData = (commandsData: BrewCommandData[]) => { const suggestions = sortBy( commandsData.map(command => ({label: command.name, detail: command.description || ""})), suggestion => !suggestion.detail, ); return staticSuggestionsProvider(suggestions); }; let getProvider = (context: AutocompletionContext, commandData: BrewCommandData[], argIndex: number): AutocompletionProvider => { if (context.argument.position === argIndex) { return fromData(commandData); } const argument = context.argument.command.nthArgument(argIndex); if (!argument) { return emptyProvider; } const name = argument.value; const data = find(commandData, {name}); if (data && data.commands) { return getProvider(context, data.commands, argIndex + 1); } else if (data && data.provider) { return data.provider; } return emptyProvider; }; PluginManager.registerAutocompletionProvider("brew", async context => { const provider = getProvider(context, concat(brewCommands, commonCommands), 1); return provider(context); }); ================================================ FILE: src/plugins/completion/Cat.ts ================================================ import {PluginManager} from "../../PluginManager"; import {anyFilesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; PluginManager.registerAutocompletionProvider("cat", combine([anyFilesSuggestionsProvider, manPageOptions("cat")])); ================================================ FILE: src/plugins/completion/Cd.ts ================================================ import {directoriesSuggestionsProvider, Suggestion} from "../completion_utils/Common"; import * as _ from "lodash"; import {PluginManager} from "../../PluginManager"; import {join} from "path"; import {userFriendlyPath} from "../../utils/Common"; PluginManager.registerAutocompletionProvider("cd", async(context) => { let suggestions: Suggestion[] = await directoriesSuggestionsProvider(context); if (context.argument.value.length === 0) { const cdpathDirectories = _.flatten(await Promise.all(context.environment.cdpath.filter(path => path !== "." && path !== context.environment.pwd) .map(async(directory) => (await directoriesSuggestionsProvider(context, directory)).map(suggestion => ({...suggestion, label: userFriendlyPath(join(directory, suggestion.label))}))))); suggestions.push(...cdpathDirectories); } return suggestions; }); ================================================ FILE: src/plugins/completion/Cp.ts ================================================ import {PluginManager} from "../../PluginManager"; import {anyFilesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; PluginManager.registerAutocompletionProvider("cp", combine([anyFilesSuggestionsProvider, manPageOptions("cp")])); ================================================ FILE: src/plugins/completion/Df.ts ================================================ import {PluginManager} from "../../PluginManager"; import {manPageOptions} from "../../utils/ManPages"; PluginManager.registerAutocompletionProvider("df", manPageOptions("df")); ================================================ FILE: src/plugins/completion/Executable.ts ================================================ export const commandDescriptions: Dictionary = { admin: "Create and administer SCCS files", alias: "Define or display aliases", ar: "Create and maintain library archives", asa: "Interpret carriage-control characters", at: "Execute commands at a later time", awk: "Pattern scanning and processing language", basename: "Return non-directory portion of a pathname; see also dirname", batch: "Schedule commands to be executed in a batch queue", bc: "Arbitrary-precision arithmetic language", bg: "Run jobs in the background", cc: "Compile standard C programs", cal: "Print a calendar", cat: "Concatenate and print files", cflow: "Generate a C-language flowgraph", chgrp: "Change the file group ownership", chmod: "Change the file modes/attributes/permissions", chown: "Change the file ownership", cksum: "Write file checksums and sizes", cmp: "Compare two files; see also diff", comm: "Select or reject lines common to two files", command: "Execute a simple command", compress: "Compress data", cp: "Copy files", crontab: "Schedule periodic background work", csplit: "Split files based on context", ctags: "Create a tags file", cut: "Cut out selected fields of each line of a file", cxref: "Generate a C-language program cross-reference table", date: "Display the date and time", dd: "Convert and copy a file", delta: "Make a delta (change) to an SCCS file", df: "Report free disk space", diff: "Compare two files; see also cmp", dirname: "Return the directory portion of a pathname; see also basename", du: "Estimate file space usage", echo: "Write arguments to standard output", ed: "The standard text editor", env: "Set the environment for command job", ex: "Text editor", expand: "Convert tabs to spaces", expr: "Evaluate arguments as an expression", FALSE: "Return false value", fc: "Process the command history list", fg: "Run jobs in the foreground", file: "Determine file type", find: "Find files", fold: "Filter for folding lines", fort77: "FORTRAN compiler", fuser: "List process IDs of all processes that have one or more files open", gencat: "Generate a formatted message catalog", get: "Get a version of an SCCS file", getconf: "Get configuration values", getopts: "Parse utility options", grep: "Search text for a pattern", hash: "hash database access method", head: "Copy the first part of files", iconv: "Codeset conversion", id: "Return user identity", ipcrm: "Remove a message queue, semaphore set, or shared memory segment identifier", ipcs: "Report interprocess communication facilities status", jobs: "Display status of jobs in the current session", join: "Merges two sorted text files based on the presence of a common field", kill: "Terminate or signal processes", lex: "Generate programs for lexical tasks", link: "Create a hard link to a file", ln: "Link files", locale: "Get locale-specific information", localedef: "Define locale environment", logger: "Log messages", logname: "Return the user\"s login name", lp: "Send files to a printer", ls: "List directory contents", m4: "Macro processor", mailx: "Process messages", make: "Maintain, update, and regenerate groups of programs", man: "Display system documentation", mesg: "Permit or deny messages", mkdir: "Make directories", mkfifo: "Make FIFO special files", more: "Display files on a page-by-page basis", mv: "Move files", newgrp: "Change to a new group (functionaliy similar to sg[1])", nice: "Invoke a utility with an altered nice value", nl: "Line numbering filter", nm: "Write the name list of an object file", nohup: "Invoke a utility immune to hangups", od: "Dump files in various formats", paste: "Merge corresponding or subsequent lines of files", patch: "Apply changes to files", pathchk: "Check pathnames", pax: "Portable archive interchange", pr: "Print files", printf: "Write formatted output", prs: "Print an SCCS file", ps: "Report process status", pwd: "print working directory - Return working directory name", qalter: "Alter batch job", qdel: "Delete batch jobs", qhold: "Hold batch jobs", qmove: "Move batch jobs", qmsg: "Send message to batch jobs", qrerun: "Rerun batch jobs", qrls: "Release batch jobs", qselect: "Select batch jobs", qsig: "Signal batch jobs", qstat: "Show status of batch jobs", qsub: "Submit a script", read: "Read a line from standard input", renice: "Set nice values of running processes", rm: "Remove directory entries", rmdel: "Remove a delta from an SCCS file", rmdir: "Remove directories", sact: "Print current SCCS file-editing activity", sccs: "Front end for the SCCS subsystem", sed: "Stream editor", sh: "Shell, the standard command language interpreter", sleep: "Suspend execution for an interval", sort: "Sort, merge, or sequence check text files", split: "Split files into pieces", strings: "Find printable strings in files", strip: "Remove unnecessary information from executable files", stty: "Set the options for a terminal", tabs: "Set terminal tabs", tail: "Copy the last part of a file", talk: "Talk to another user", tee: "Duplicate the standard output", test: "Evaluate expression", time: "Time a simple command", touch: "Change file access and modification times", tput: "Change terminal characteristics", tr: "Translate characters", TRUE: "Return true value", tsort: "Topological sort", tty: "Return user\"s terminal name ", type: "Displays how a name would be interpreted if used as a command", ulimit: "Set or report file size limit", umask: "Get or set the file mode creation mask", unalias: "Remove alias definitions", uname: "Return system name", uncompress: "Expand compressed data", unexpand: "Convert spaces to tabs", unget: "Undo a previous get of an SCCS file", uniq: "Report or filter out repeated lines in a file", unlink: "Call the unlink function", uucp: "System-to-system copy", uudecode: "Decode a binary file", uuencode: "Encode a binary file", uustat: "uucp status inquiry and job control", uux: "Remote command execution", val: "Validate SCCS files", vi: "Screen-oriented (visual) display editor", wait: "Await process completion", wc: "Line, word and byte or character count", what: "Identify SCCS files", who: "Display who is on the system", write: "Write to another user\"s terminal", xargs: "Construct argument lists and invoke utility", yacc: "Yet another compiler compiler", zcat: "Expand and concatenate data", }; ================================================ FILE: src/plugins/completion/Find.ts ================================================ import {PluginManager} from "../../PluginManager"; import {directoriesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; const findOptions = manPageOptions("find"); PluginManager.registerAutocompletionProvider("find", combine([directoriesSuggestionsProvider, findOptions])); ================================================ FILE: src/plugins/completion/Git.ts ================================================ import * as Git from "../../utils/Git"; import { commandWithSubcommands, emptyProvider, longFlag, provide, staticSuggestionsProvider, SubcommandConfig, Suggestion, unique, } from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {PluginManager} from "../../PluginManager"; import {executeCommand, linedOutputOf} from "../../PTY"; import {find, once, sortBy} from "lodash"; import {homeDirectory} from "../../utils/Common"; import {descriptions} from "../completion_utils/Descriptions"; const addOptions: Suggestion[] = [ { label: "-p", detail: descriptions.git.add.patch, }, { label: "--patch", detail: descriptions.git.add.patch, }, { label: "-i", detail: descriptions.git.add.interactive, }, { label: "--interactive", detail: descriptions.git.add.interactive, }, { label: "-n", detail: descriptions.git.add.dryRun, }, { label: "--dry-run", detail: descriptions.git.add.dryRun, }, { label: "-v", detail: descriptions.git.add.verbose, }, { label: "--verbose", }, { label: "-f", detail: descriptions.git.add.force, }, { label: "--force", detail: descriptions.git.add.force, }, { label: "-e", detail: descriptions.git.add.edit, }, { label: "--edit", detail: descriptions.git.add.edit, }, { label: "-u", detail: descriptions.git.add.update, }, { label: "--update", detail: descriptions.git.add.update, }, { label: "-A", detail: descriptions.git.add.noIgnoreRemoval, }, { label: "--all", detail: descriptions.git.add.noIgnoreRemoval, }, { label: "--no-ignore-removal", detail: descriptions.git.add.noIgnoreRemoval, }, { label: "--no-all", detail: descriptions.git.add.ignoreRemoval, }, { label: "--ignore-removal", detail: descriptions.git.add.ignoreRemoval, }, { label: "-N", detail: descriptions.git.add.intentToAdd, }, { label: "--intent-to-add", detail: descriptions.git.add.intentToAdd, }, { label: "--refresh", detail: descriptions.git.add.refresh, }, { label: "--ignore-errors", detail: descriptions.git.add.ignoreErrors, }, { label: "--ignore-missing", detail: descriptions.git.add.ignoreMissing, }, { label: "--chmod=", detail: descriptions.git.add.chmod, }, { label: "--", detail: descriptions.git.add.separator, }, ]; const commitOptions: Suggestion[] = [ { label: "--message", detail: descriptions.git.commit.message, kind: monaco.languages.CompletionItemKind.Snippet, insertText: {value: "--message \"${0:Commit message}\""}, }, { label: "-m", detail: descriptions.git.commit.message, kind: monaco.languages.CompletionItemKind.Snippet, insertText: {value: "-m \"${0:Commit message}\""}, }, { label: "--all", detail: descriptions.git.commit.all, }, { label: "-a", detail: descriptions.git.commit.all, }, { label: "--patch", detail: descriptions.git.commit.patch, }, { label: "-p", detail: descriptions.git.commit.patch, }, { label: "--null", detail: descriptions.git.commit.NULL, }, { label: "-z", detail: descriptions.git.commit.NULL, }, { label: "--template", detail: descriptions.git.commit.template, }, { label: "-t", detail: descriptions.git.commit.template, }, { label: "--signoff", detail: descriptions.git.commit.signoff, }, { label: "-s", detail: descriptions.git.commit.signoff, }, { label: "--no-verify", detail: descriptions.git.commit.noVerify, }, { label: "-n", detail: descriptions.git.commit.noVerify, }, { label: "--edit", detail: descriptions.git.commit.edit, }, { label: "-e", detail: descriptions.git.commit.edit, }, { label: "--include", detail: descriptions.git.commit.include, }, { label: "-i", detail: descriptions.git.commit.include, }, { label: "--only", detail: descriptions.git.commit.only, }, { label: "-o", detail: descriptions.git.commit.only, }, { label: "--verbose", detail: descriptions.git.commit.verbose, }, { label: "-v", detail: descriptions.git.commit.verbose, }, { label: "--quiet", detail: descriptions.git.commit.quiet, }, { label: "-q", detail: descriptions.git.commit.quiet, }, { label: "--reset-author", detail: descriptions.git.commit.resetAuthor, }, { label: "--short", detail: descriptions.git.commit.short, }, { label: "--branch", detail: descriptions.git.commit.branch, }, { label: "--porcelain", detail: descriptions.git.commit.porcelain, }, { label: "--long", detail: descriptions.git.commit.long, }, { label: "--allow-empty", detail: descriptions.git.commit.allowEmpty, }, { label: "--allow-empty-message", detail: descriptions.git.commit.allowEmptyMessage, }, { label: "--no-edit", detail: descriptions.git.commit.noEdit, }, { label: "--no-post-rewrite", detail: descriptions.git.commit.noPostRewrite, }, { label: "--dry-run", detail: descriptions.git.commit.dryRun, }, { label: "--status", detail: descriptions.git.commit.status, }, { label: "--no-status", detail: descriptions.git.commit.noStatus, }, { label: "--no-gpg-sign", detail: descriptions.git.commit.noGpgSign, }, ]; const pushOptions: Suggestion[] = [ { label: "--all", detail: descriptions.git.push.all, }, { label: "--prune", detail: descriptions.git.push.prune, }, { label: "--force", }, { label: "--force-with-lease", }, ]; const resetOptions: Suggestion[] = [ { label: "--soft", }, { label: "--mixed", }, { label: "--hard", }, { label: "--merge", }, { label: "--keep", }, ]; const stashOptions: Suggestion[] = [ { label: "list", }, { label: "show", }, { label: "drop", }, { label: "pop", }, { label: "apply", }, { label: "save", }, { label: "push", }, { label: "clear", }, { label: "create", }, { label: "store", }, ]; const statusOptions: Suggestion[] = [ { label: "-s", detail: descriptions.git.status.short, }, { label: "--short", detail: descriptions.git.status.short, }, { label: "-b", detail: descriptions.git.status.branch, }, { label: "--branch", detail: descriptions.git.status.branch, }, { label: "--porcelain", detail: descriptions.git.status.porcelain, }, { label: "--long", detail: descriptions.git.status.long, }, { label: "-v", detail: descriptions.git.status.verbose, }, { label: "--verbose", detail: descriptions.git.status.verbose, }, { label: "-u", detail: descriptions.git.status.untrackedFiles, }, { label: "--untracked-files", detail: descriptions.git.status.untrackedFiles, }, { label: "--ignore-submodules", detail: descriptions.git.status.ignoreSubmodules, }, { label: "--ignored", detail: descriptions.git.status.ignored, }, { label: "-z", detail: descriptions.git.status.terminateWithNull, }, { label: "--column", detail: descriptions.git.status.column, }, { label: "--no-column", detail: descriptions.git.status.column, }, ]; const configOptions: Suggestion[] = [ { label: "--global", }, { label: "--system", }, { label: "--list", }, { label: "-l", }, { label: "--edit", }, { label: "-e", }, ]; const fetchOptions: Suggestion[] = [ { label: "--quiet", }, { label: "--verbose", }, { label: "--append", }, { label: "--upload-pack", }, { label: "--force", }, { label: "--keep", }, { label: "--depth=", }, { label: "--tags", }, { label: "--no-tags", }, { label: "--all", }, { label: "--prune", }, { label: "--dry-run", }, { label: "--recurse-submodules=", }, ]; const checkoutOptions: Suggestion[] = [ { label: "-b", detail: descriptions.git.checkout.branch, }, ]; const commonMergeOptions: Suggestion[] = [ { label: "--no-commit", }, { label: "--no-stat", }, { label: "--log", }, { label: "--no-log", }, { label: "--squash", }, { label: "--strategy", }, { label: "--commit", }, { label: "--stat", }, { label: "--no-squash", }, { label: "--ff", }, { label: "--no-ff", }, { label: "--ff-only", }, { label: "--edit", }, { label: "--no-edit", }, { label: "--verify-signatures", }, { label: "--no-verify-signatures", }, { label: "--gpg-sign", }, { label: "--quiet", }, { label: "--verbose", }, { label: "--progress", }, { label: "--no-progress", }, ]; function doesLookLikeBranchAlias(word: string) { if (!word) return false; return word.startsWith("-") || word.includes("@") || word.includes("HEAD") || /\d/.test(word); } function canonizeBranchAlias(alias: string) { if (alias[0] === "-") { const steps = parseInt(alias.slice(1), 10) || 1; alias = `@{-${steps}}`; } return alias; } const remotes = provide(async context => { if (Git.isGitDirectory(context.environment.pwd)) { const names = await Git.remotes(context.environment.pwd); return names.map(name => ({label: name})); } return []; }); const configVariables = unique(provide(async context => { const variables = await Git.configVariables(context.environment.pwd); return variables.map(variable => ({label: variable.name, detail: variable.value})); })); const branchesExceptCurrent = provide(async context => { if (Git.isGitDirectory(context.environment.pwd)) { const allBranches = (await Git.branches({ directory: context.environment.pwd, remotes: true, tags: false, })); const nonCurrentBranches = allBranches.filter(branch => !branch.isCurrent()); return nonCurrentBranches.map(branch => ({label: branch.toString()})); } else { return []; } }); const branchAlias = provide(async context => { if (doesLookLikeBranchAlias(context.argument.value)) { let nameOfAlias = (await linedOutputOf("git", ["name-rev", "--name-only", canonizeBranchAlias(context.argument.value)], context.environment.pwd))[0]; if (nameOfAlias && !nameOfAlias.startsWith("Could not get")) { return [{label: context.argument.value, detail: nameOfAlias}]; } } return []; }); const notStagedFiles = unique(provide(async context => { if (Git.isGitDirectory(context.environment.pwd)) { const fileStatuses = await Git.status(context.environment.pwd); return fileStatuses.map(fileStatus => ({label: fileStatus.value})); } else { return []; } })); const commandsData: SubcommandConfig[] = [ { name: "add", detail: descriptions.git.subcommands.add, provider: combine([notStagedFiles, staticSuggestionsProvider(addOptions)]), }, { name: "am", detail: descriptions.git.subcommands.am, }, { name: "archive", detail: descriptions.git.subcommands.archive, }, { name: "bisect", detail: descriptions.git.subcommands.bisect, }, { name: "branch", detail: descriptions.git.subcommands.branch, provider: branchesExceptCurrent, }, { name: "bundle", detail: descriptions.git.subcommands.bundle, }, { name: "checkout", detail: descriptions.git.subcommands.checkout, provider: combine([branchesExceptCurrent, branchAlias, notStagedFiles, staticSuggestionsProvider(checkoutOptions)]), }, { name: "cherry-pick", detail: descriptions.git.subcommands.cherryPick, }, { name: "citool", detail: descriptions.git.subcommands.citool, }, { name: "clean", detail: descriptions.git.subcommands.clean, }, { name: "clone", detail: descriptions.git.subcommands.clone, }, { name: "commit", detail: descriptions.git.subcommands.commit, provider: staticSuggestionsProvider(commitOptions), }, { name: "config", detail: descriptions.git.subcommands.config, provider: combine([configVariables, staticSuggestionsProvider(configOptions)]), }, { name: "describe", detail: descriptions.git.subcommands.describe, }, { name: "diff", detail: descriptions.git.subcommands.diff, }, { name: "fetch", detail: descriptions.git.subcommands.fetch, provider: combine([remotes, staticSuggestionsProvider(fetchOptions)]), }, { name: "format-patch", detail: descriptions.git.subcommands.formatPatch, }, { name: "gc", detail: descriptions.git.subcommands.gc, }, { name: "grep", detail: descriptions.git.subcommands.grep, }, { name: "gui", detail: descriptions.git.subcommands.gui, }, { name: "init", detail: descriptions.git.subcommands.init, }, { name: "log", detail: descriptions.git.subcommands.log, }, { name: "merge", detail: descriptions.git.subcommands.merge, provider: combine([ branchesExceptCurrent, branchAlias, staticSuggestionsProvider(commonMergeOptions), longFlag("rerere-autoupdate"), longFlag("no-rerere-autoupdate"), longFlag("abort"), ]), }, { name: "mv", detail: descriptions.git.subcommands.mv, }, { name: "notes", detail: descriptions.git.subcommands.notes, }, { name: "pull", detail: descriptions.git.subcommands.pull, provider: combine([ longFlag("rebase"), longFlag("no-rebase"), staticSuggestionsProvider(commonMergeOptions), staticSuggestionsProvider(fetchOptions), ]), }, { name: "push", detail: descriptions.git.subcommands.push, provider: staticSuggestionsProvider(pushOptions), }, { name: "rebase", detail: descriptions.git.subcommands.rebase, }, { name: "reset", detail: descriptions.git.subcommands.reset, provider: staticSuggestionsProvider(resetOptions), }, { name: "revert", detail: descriptions.git.subcommands.revert, }, { name: "rm", detail: descriptions.git.subcommands.rm, }, { name: "shortlog", detail: descriptions.git.subcommands.shortlog, }, { name: "show", detail: descriptions.git.subcommands.show, }, { name: "stash", detail: descriptions.git.subcommands.stash, provider: staticSuggestionsProvider(stashOptions), }, { name: "status", detail: descriptions.git.subcommands.status, provider: staticSuggestionsProvider(statusOptions), }, { name: "submodule", detail: descriptions.git.subcommands.submodule, }, { name: "tag", detail: descriptions.git.subcommands.tag, }, { name: "worktree", detail: descriptions.git.subcommands.worktree, }, ]; const commands = once(async(): Promise => { const text = await executeCommand("git", ["help", "-a"], homeDirectory); const matches: string[] | null = text.match(/ ([\-a-zA-Z0-9]+)/gm); if (matches) { const suggestions = matches .filter(match => match.indexOf("--") === -1) .map(match => { const name = match.trim(); const data = find(commandsData, {name}); return { name, detail: data ? data.detail : "", provider: data ? data.provider : emptyProvider, }; }); return sortBy(suggestions, suggestion => !suggestion.detail); } return []; }); const aliases = once(async(): Promise => { const aliasList = await Git.aliases(homeDirectory); return aliasList.map(({ name, value }) => { let result: SubcommandConfig = { name: name, }; const expandedAliasConfig = find(commandsData, data => data.name === value); if (expandedAliasConfig && expandedAliasConfig.provider) { result.provider = expandedAliasConfig.provider; } return result; }); }); PluginManager.registerAutocompletionProvider("git", async context => { const allCommands = [...(await aliases()), ...(await commands())]; return commandWithSubcommands(allCommands)(context); }); ================================================ FILE: src/plugins/completion/Grep.ts ================================================ import {PluginManager} from "../../PluginManager"; import { longFlag, longAndShortFlag, mapSuggestions, anyFilesSuggestionsProvider, anyFilesSuggestions, directoriesSuggestions, provide, } from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {mapObject} from "../../utils/Common"; // Grep option suggestions based on linux man file: // http://linux.die.net/man/1/grep const baseOptions = combine(mapObject( { // Generic Program Information "version": { short: "V", description: `Print the version number of grep to standard error.`, }, // Matcher Selection "basic-regexp": { short: "G", description: `Interpret PATTERN as a basic regular expression. This is the default.`, }, "perl-regexp": { short: "P", description: `Interpret PATTERN as a Perl regular expression.`, }, // Matching Control "regexp": { short: "e", description: `Print the byte offset within the input file before each line of output`, }, "file=": { short: "", description: `Obtain patterns from FILE, one per line`, }, "ignore-case": { short: "i", description: `Ignore case distinctions in both the PATTERN and the input files`, }, "invert-match": { short: "v", description: `Invert the sense of matching, to select non-matching lines`, }, "word-regexp": { short: "w", description: `Select only those lines containing matches that form whole words`, }, "line-regexp": { short: "x", description: `Select only those matches that exactly match the whole line`, }, // General Output Control "count": { short: "c", description: `Suppress normal output; instead print a count of matching lines for each input file. With the -v, --invert-match option (see below), count non-matching lines.`, }, "color=": { short: "", description: `Surround the matching string with the marker find in GREP_COLOR environment variable`, }, "files-without-match": { short: "L", description: `Suppress normal output; instead print the name of each input file from which no output would normally have been printed. The scanning will stop on the first match`, }, "files-with-match": { short: "l", description: `Suppress normal output; instead print the name of each input file from which output would normally have been printed. The scanning will stop on the first match.`, }, "max-count": { short: "m", description: `Stop reading a file after NUM matching lines. If the input is standard input from a regular file, and NUM matching lines are output, grep ensures that the standard input is positioned to just after the last matching line before exiting, regardless of the presence of trailing context lines.`, }, "only-matching": { short: "o", description: `Print only the matched (non-empty) parts of a matching line, with each such part on a separate output line`, }, "quiet": { short: "q", description: `Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected.`, }, "silent": { short: "", description: `Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected.`, }, "no-messages": { short: "s", description: `Suppress error messages about nonexistent or unreadable files`, }, // Output Line Prefix Control "byte-offset": { short: "b", description: `Print the byte offset within the input file before each line of output.`, }, "with-filename": { short: "H", description: `Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected.`, }, "no-filename": { short: "h", description: `Suppress the prefixing of file names on output. This is the default when there is only one file (or only standard input) to search.`, }, "label=": { short: "", description: `Display input actually coming from standard input as input coming from file LABEL.`, }, "line-number": { short: "n", description: `Prefix each line of output with the 1-based line number within its input file.`, }, "initial-tab": { short: "T", description: `Make sure that the first character of actual line content lies on a tab stop, so that the alignment of tabs looks normal.`, }, "unix-byte-offsets": { short: "u", description: `Report Unix-style byte offsets. This switch causes grep to report byte offsets as if the file were Unix-style text file, i.e. with CR characters stripped off`, }, "null": { short: "Z", description: `Output a zero byte (the ASCII NUL character) instead of the character that normally follows a file name`, }, // Context Line Control "after-context": { short: "A", description: `Print NUM lines of trailing context after matching lines. Places a line containing a group separator (--) between contiguous groups of matches.`, }, "before-context": { short: "B", description: `Print NUM lines of leading context before matching lines. Places a line containing a group separator (--) between contiguous groups of matches.`, }, "context": { short: "C", description: `Print NUM lines of output context. Places a line containing a group separator (--) between contiguous groups of matches.`, }, // File and Directory Selection "text": { short: "a", description: `Process a binary file as if it were text, equivalent to --binary-files=text`, }, "binary-files=": { short: "r", description: `Read all files under each directory, recursively; this is equivalent to the -d recurse option`, }, "devices=": { short: "D", description: `If an input file is a device, FIFO or socket, use ACTION to process it`, }, "directories=": { short: "d", description: `If an input file is a directory, use ACTION to process it. By default, ACTION is read, which means that directories are read just as if they were ordinary files.`, }, "exclude=": { short: "", description: `Recurse in directories skip file matching PATTERN.`, }, "exclude-from=": { short: "", description: `Skip files whose base name matches any of the file-name globs read from FILE (using wildcard matching as described under --exclude).`, }, "exclude-dir=": { short: "", description: `Exclude directories matching the pattern DIR from recursive searches.`, }, "include=": { short: "", description: `Search only files whose base name matches GLOB (using wildcard matching as described under --exclude).`, }, "recursive": { short: "r", description: `Read all files under each directory, recursively; this is equivalent to the -d recurse option.`, }, "line-buffered": { short: "", description: `Use line buffering on output. This can cause a performance penalty.`, }, "binary": { short: "U", description: `Treat the file(s) as binary`, }, "null-data": { short: "z", description: `Treat the input as a set of lines, each terminated by a zero byte (the ASCII NUL character) instead of a newline.`, }, }, (option, info) => { if (info.short) { return mapSuggestions( longAndShortFlag(option, info.short), suggestion => ({...suggestion, description: info.description}), ); } else { return mapSuggestions( longFlag(option), suggestion => ({...suggestion, description: info.description}), ); } }, )); const extendedRegexOption = combine([ mapSuggestions( longAndShortFlag("extended-regexp", "E"), suggestion => ({ ...suggestion, description: `Interpret (defined using --regexp= as an extended regular expression`, })), ]); const fixedStringsOption = combine([ mapSuggestions( longAndShortFlag("fixed-strings", "F"), suggestion => ({ ...suggestion, description: `Interpret (defined using --regexp=) as a list of fixed strings, separated by new-lines lines, any of which is to be matched`, })), ]); const binaryFilesValues = [ { flag: "binary-files", displayValue: "binary", description: `(default) Outputs either a one-line message saying that a binary file matches or no message if there is no match`, }, { flag: "binary-files", displayValue: "without-match", description: `Assumes that a binary file does not match.`, }, { flag: "binary-files", displayValue: "text", description: `Processes a binary file as if it were text.`, }]; const devicesValues = [ { flag: "devices", displayValue: "read", description: `(default) Devices are read just as if they were ordinary files`, }, { flag: "devices", displayValue: "skip", description: `Devices are silently skipped`, }]; const colorValues = [ { flag: "color", displayValue: "never", description: `Never highlight the matching pattern`, }, { flag: "color", displayValue: "always", description: `Always highlight the matching pattern`, }, { flag: "color", displayValue: "auto", description: `Auto highlight the matching pattern`, }]; const directoriesValues = [ { flag: "directories", displayValue: "read", description: `(Default) Directories are read just as if they were ordinary files.`, }, { flag: "directories", displayValue: "skip", description: `Directories are silently skipped.`, }, { flag: "directories", displayValue: "recurse", description: `grep reads all files under each directory, recursively; this is equivalent to the -r option.`, }]; const fixedValueSuggestions = provide(async context => { const token = context.argument.value; let optionValues: any[] = []; if (token.startsWith("--binary-files=")) { optionValues = binaryFilesValues; } else if (token.startsWith("--devices=")) { optionValues = devicesValues; } else if (token.startsWith("--color=")) { optionValues = colorValues; } else if (token.startsWith("--directories=")) { optionValues = directoriesValues; } else { return []; } return optionValues.map(item => ({ label: "--" + item.flag + "=" + item.displayValue, description: item.description, })); }); const fileValueSuggestions = provide(async context => { const tokenValue = "--file="; const token = context.argument.value; if (token.startsWith(tokenValue)) { const workingDirectory = context.environment.pwd; const optionValue = token.slice(tokenValue.length); const fileSuggestions = await anyFilesSuggestions(optionValue, workingDirectory); return fileSuggestions.map(item => ({label: tokenValue + item.label})); } else { return []; } }); const excludeFromSuggestions = provide(async context => { const tokenValue = "--exclude-from="; const token = context.argument.value; if (token.startsWith(tokenValue)) { const workingDirectory = context.environment.pwd; const optionValue = token.slice(tokenValue.length); const fileSuggestions = await anyFilesSuggestions(optionValue, workingDirectory); return fileSuggestions.map(item => ({ label: tokenValue + item.label, })); } else { return []; } }); const excludeDirSuggestions = provide(async context => { const tokenValue = "--exclude-dir="; const token = context.argument.value; if (token.startsWith(tokenValue)) { const workingDirectory = context.environment.pwd; const optionValue = token.slice(tokenValue.length); const directorySuggestions = await directoriesSuggestions(optionValue, workingDirectory); return directorySuggestions.map(item => ({ label: tokenValue + item.label, })); } else { return []; } }); const commonOptions = combine([baseOptions, fixedValueSuggestions, fileValueSuggestions, anyFilesSuggestionsProvider, excludeFromSuggestions, excludeDirSuggestions]); const grepOptions = combine([commonOptions, fixedStringsOption, extendedRegexOption]); const eGrepOptions = combine([commonOptions, extendedRegexOption]); const fGrepOptions = combine([commonOptions, fixedStringsOption]); PluginManager.registerAutocompletionProvider("grep", combine([grepOptions])); PluginManager.registerAutocompletionProvider("egrep", combine([eGrepOptions])); PluginManager.registerAutocompletionProvider("fgrep", combine([fGrepOptions])); ================================================ FILE: src/plugins/completion/History.ts ================================================ import {HistoryRecord} from "../../services/HistoryService"; import {scan} from "../../shell/Scanner"; import {isAbsolute} from "path"; import {services} from "../../services/index"; import {HistoryTrie} from "../../utils/HistoryTrie"; import {Suggestion} from "../completion_utils/Common"; function cdIntoRelativePathFilter(record: HistoryRecord, pwd: string): boolean { if (record.directory === pwd) { return true; } const tokens = scan(record.expandedCommand); if (tokens[0].value !== "cd") { return true; } const directoryToken = tokens[1]; if (directoryToken && isAbsolute(directoryToken.value)) { return true; } return false; } const historyTrie = new HistoryTrie(); services.history.all.forEach(record => historyTrie.add(record.command)); services.history.onNewRecord.subscribe(record => historyTrie.add(record.command)); export function getHistorySuggestions(input: string, pwd: string): Suggestion[] { const trieSuggestions = historyTrie.getContinuationsFor(input).map(continuation => ({ label: continuation.value, description: `×${continuation.occurrences}`, })); if (trieSuggestions.length) { return trieSuggestions; } else { return services.history.all .filter(record => cdIntoRelativePathFilter(record, pwd)) .map(record => record.command) .filter(command => command.toLowerCase().includes(input.toLowerCase())) .map(command => ({ label: command.trim(), })) .reverse(); } } ================================================ FILE: src/plugins/completion/Ln.ts ================================================ import {PluginManager} from "../../PluginManager"; import {anyFilesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; const combinedOptions = combine([anyFilesSuggestionsProvider, manPageOptions("ln")]); PluginManager.registerAutocompletionProvider("ln", combinedOptions); PluginManager.registerAutocompletionProvider("link", combinedOptions); ================================================ FILE: src/plugins/completion/Locate.ts ================================================ import {PluginManager} from "../../PluginManager"; import {manPageOptions} from "../../utils/ManPages"; PluginManager.registerAutocompletionProvider("locate", manPageOptions("locate")); ================================================ FILE: src/plugins/completion/Ls.ts ================================================ import {PluginManager} from "../../PluginManager"; import {directoriesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; const lsOptions = manPageOptions("ls"); PluginManager.registerAutocompletionProvider("ls", combine([directoriesSuggestionsProvider, lsOptions])); ================================================ FILE: src/plugins/completion/Mkdir.ts ================================================ import {PluginManager} from "../../PluginManager"; import {directoriesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; PluginManager.registerAutocompletionProvider("mkdir", combine([manPageOptions("mkdir"), directoriesSuggestionsProvider])); ================================================ FILE: src/plugins/completion/Mv.ts ================================================ import {PluginManager} from "../../PluginManager"; import {anyFilesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; PluginManager.registerAutocompletionProvider("mv", combine([manPageOptions("mv"), anyFilesSuggestionsProvider])); ================================================ FILE: src/plugins/completion/NPM.ts ================================================ import * as Path from "path"; import {commandWithSubcommands} from "../completion_utils/Common"; import {io, mapObject} from "../../utils/Common"; import {PluginManager} from "../../PluginManager"; import {AutocompletionContext} from "../../Interfaces"; const npmCommandConfig = [ { name: "access", detail: "Set access level on published packages", }, { name: "adduser", detail: "Add a registry user account", }, { name: "bin", detail: "Display npm bin folder", }, { name: "bugs", detail: "Bugs for a package in a web browser maybe", }, { name: "build", detail: "Build a package", }, { name: "bundle", detail: "REMOVED", }, { name: "cache", detail: "Manipulates packages cache", }, { name: "completion", detail: "Tab Completion for npm", }, { name: "config", detail: "Manage the npm configuration files", }, { name: "dedupe", detail: "Reduce duplication", }, { name: "deprecate", detail: "Deprecate a version of a package", }, { name: "dist-tag", detail: "Modify package distribution tags", }, { name: "docs", detail: "Docs for a package in a web browser maybe", }, { name: "edit", detail: "Edit an installed package", }, { name: "explore", detail: "Browse an installed package", }, { name: "help", detail: "Get help on npm", }, { name: "help-search", detail: "Search npm help documentation", }, { name: "init", detail: "Interactively create a package.json file", }, { name: "install", detail: "Install a package", }, { name: "install-test", detail: "", }, { name: "link", detail: "Symlink a package folder", }, { name: "logout", detail: "Log out of the registry", }, { name: "ls", detail: "List installed packages", }, { name: "npm", detail: "javascript package manager", }, { name: "outdated", detail: "Check for outdated packages", }, { name: "owner", detail: "Manage package owners", }, { name: "pack", detail: "Create a tarball from a package", }, { name: "ping", detail: "Ping npm registry", }, { name: "prefix", detail: "Display prefix", }, { name: "prune", detail: "Remove extraneous packages", }, { name: "publish", detail: "Publish a package", }, { name: "rebuild", detail: "Rebuild a package", }, { name: "repo", detail: "Open package repository page in the browser", }, { name: "restart", detail: "Restart a package", }, { name: "root", detail: "Display npm root", }, { name: "run", detail: "Run arbitrary package scripts", provider: async (context: AutocompletionContext) => { const packageFilePath = Path.join(context.environment.pwd, "package.json"); if (await io.fileExists(packageFilePath)) { const parsed = JSON.parse(await io.readFile(packageFilePath)).scripts || {}; return mapObject(parsed, (key: string, value: string) => ({ label: key, detail: value, })); } else { return []; } }, }, { name: "search", detail: "Search for packages", }, { name: "shrinkwrap", detail: "Lock down dependency versions", }, { name: "star", detail: "Mark your favorite packages", }, { name: "stars", detail: "View packages marked as favorites", }, { name: "start", detail: "Start a package", }, { name: "stop", detail: "Stop a package", }, { name: "tag", detail: "Tag a published version", }, { name: "team", detail: "Manage organization teams and team memberships", }, { name: "test", detail: "Test a package", }, { name: "uninstall", detail: "Remove a package", }, { name: "unpublish", detail: "Remove a package from the registry", }, { name: "update", detail: "Update a package", }, { name: "version", detail: "Bump a package version", }, { name: "view", detail: "View registry info", }, { name: "whoami", detail: "Display npm username", }, ]; PluginManager.registerAutocompletionProvider("npm", commandWithSubcommands(npmCommandConfig)); ================================================ FILE: src/plugins/completion/Ps.ts ================================================ import {PluginManager} from "../../PluginManager"; import {combine} from "../completion_utils/Combine"; import {provide, Suggestion} from "../completion_utils/Common"; import {AutocompletionContext, AutocompletionProvider} from "../../Interfaces"; import * as Process from "../../utils/Process"; import {find} from "lodash"; // ps option suggestions based on linux man file: // http://linux.die.net/man/1/ps interface ShortFlagItem { flag: string; detail: string; } const shortOptions: ShortFlagItem[] = [ { flag: "C", detail: `Select by command name.`, }, { flag: "G", detail: `Select by real group ID (RGID) or name.`, }, { flag: "U", detail: `Select by effective user ID (EUID) or name.`, }, ]; interface TokenInfo { params: string[]; start: string; } const argInfo = (context: AutocompletionContext): TokenInfo => { const token: string = context.argument.value; const flag = token.substring(0, token.indexOf("=") + 1); let params: string[] = []; let start = flag; if (token.includes(",")) { params = token.substring(start.length, token.lastIndexOf(",")).split(","); start = token.substring(0, token.lastIndexOf(",") + 1); } return {params: params, start: start}; }; interface LongFlagItem { flag: string; detail: string; provider: AutocompletionProvider; } const realUserSuggestions = provide(async context => { const arg = argInfo(context); const users = await Process.users(); return users .filter(i => !arg.params.includes(i.ruser)) .map(i => ({ label: i.ruser, displayValue: i.ruser, detail: `User '${i.ruser}' with id '${i.ruserid}'`, })); }); const effectiveUserSuggestions = provide(async context => { const arg = argInfo(context); const users = await Process.users(); return users .filter(i => !arg.params.includes(i.euser)) .map(i => ({ label: i.euser, displayValue: i.euser, detail: `User '${i.euser}' with id '${i.euserid}'`, })); }); const effectiveGroupSuggestions = provide(async context => { const arg = argInfo(context); const groups = await Process.groups(); return groups .filter(i => !arg.params.includes(i.egroup)) .map(i => ({ label: i.egroup, displayValue: i.egroup, detail: `Group '${i.egroup}' with id '${i.egroupid}'`, })); }); const realGroupSuggestions = provide(async context => { const arg = argInfo(context); const groups = await Process.groups(); return groups .filter(i => !arg.params.includes(i.rgroup)) .map(i => ({ label: i.rgroup, displayValue: i.rgroup, detail: `Group '${i.rgroup}' with id '${i.rgroupid}'`, })); }); const terminalSuggestions = provide(async context => { const arg = argInfo(context); const terminals = await Process.terminals(); return terminals .filter(i => !arg.params.includes(i.name)) .map(i => ({ label: i.name, displayValue: i.name, detail: `Terminal '${i.name}' with ruser '${i.ruser}'`, })); }); const processSuggestions = provide(async context => { const arg = argInfo(context); const processes = await Process.processes(); return processes .filter(i => !arg.params.includes(i.pid)) .map(i => ({ label: i.pid, displayValue: i.pid, detail: `Process with command '${i.cmd.slice(0, 25)}' and ruser '${i.ruser}'`, })); }); const sessionSuggestions = provide(async context => { const arg = argInfo(context); const sessions = await Process.sessions(); return sessions .filter(i => !arg.params.includes(i.sid)) .map(i => ({ label: i.sid, displayValue: i.sid, detail: `Session '${i.sid}' with ruser '${i.ruser} and rgroup '${i.rgroup}'`, })); }); const longOptions: LongFlagItem[] = [ { flag: "user=", detail: `Select by effective user ID (EUID) or name. Identical to -u and U.`, provider: effectiveUserSuggestions, }, { flag: "User=", detail: `Select by real user ID (RUID) or name. Identical to -U.`, provider: realUserSuggestions, }, { flag: "group=", detail: `Select by effective group ID (EGID) or name.`, provider: effectiveGroupSuggestions, }, { flag: "Group=", detail: `Select by real group ID (RGID) or name. Identical to -G.`, provider: realGroupSuggestions, }, { flag: "tty=", detail: `selects the processes associated with the terminals given in ttylist. Identical to -T.`, provider: terminalSuggestions, }, { flag: "pid=", detail: `Select by process ID. Identical to -p and p.`, provider: processSuggestions, }, { flag: "sid=", detail: `Select by session ID. Identical to -s.`, provider: sessionSuggestions, }, ]; const psOptions = provide(async context => { let suggestions: Suggestion[] = []; const token: string = context.argument.value; if (!token.includes("=")) { const shortOptSuggestions = shortOptions.map(s => {label: "-" + s.flag, detail: s.detail}); const longOptSuggestions = longOptions.map(l => {label: "--" + l.flag, detail: l.detail}); suggestions = [...shortOptSuggestions, ...longOptSuggestions]; } return suggestions; }); const psLongOptionValues = provide(async context => { let suggestions: Suggestion[] = []; const token: string = context.argument.value; if (token.startsWith("--") && token.includes("=")) { const flag = token.slice(2, token.indexOf("=") + 1); const longOption = find(longOptions, {flag}); suggestions = longOption ? await longOption.provider(context) : []; } return suggestions; }); PluginManager.registerAutocompletionProvider("ps", combine([psOptions, psLongOptionValues])); ================================================ FILE: src/plugins/completion/Pwd.ts ================================================ import {PluginManager} from "../../PluginManager"; import {manPageOptions} from "../../utils/ManPages"; PluginManager.registerAutocompletionProvider("pwd", manPageOptions("pwd")); ================================================ FILE: src/plugins/completion/Rails.ts ================================================ import {commandWithSubcommands} from "../completion_utils/Common"; import {PluginManager} from "../../PluginManager"; const railsCommandConfig = [ { name: "runner", description: "Run a piece of code in the application environment", }, { name: "console", description: "Start the Rails console", }, { name: "server", description: "Start the Rails server", }, { name: "generate", description: "Generate new code'g')", }, { name: "destroy", description: "generate", }, { name: "dbconsole", description: "Start a console for the Rails database", }, { name: "new", description: "Create a new Rails application", }, { name: "plugin new", description: "Generates skeleton for developing a Rails plugin", }, ]; PluginManager.registerAutocompletionProvider("rails", commandWithSubcommands(railsCommandConfig)); ================================================ FILE: src/plugins/completion/Rake.ts ================================================ import {PluginManager} from "../../PluginManager"; import {directoriesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; const rakeOptions = manPageOptions("rake", "OPTIONS"); PluginManager.registerAutocompletionProvider("rake", combine([directoriesSuggestionsProvider, rakeOptions])); ================================================ FILE: src/plugins/completion/Rm.ts ================================================ import {PluginManager} from "../../PluginManager"; import {anyFilesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; PluginManager.registerAutocompletionProvider("rm", combine([manPageOptions("rm"), anyFilesSuggestionsProvider])); ================================================ FILE: src/plugins/completion/Scp.ts ================================================ import {PluginManager} from "../../PluginManager"; import {directoriesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; const scpOptions = manPageOptions("scp"); PluginManager.registerAutocompletionProvider("scp", combine([directoriesSuggestionsProvider, scpOptions])); ================================================ FILE: src/plugins/completion/Shutdown.ts ================================================ import {PluginManager} from "../../PluginManager"; import {manPageOptions} from "../../utils/ManPages"; PluginManager.registerAutocompletionProvider("shutdown", manPageOptions("shutdown")); ================================================ FILE: src/plugins/completion/Tail.ts ================================================ import {PluginManager} from "../../PluginManager"; import {directoriesSuggestionsProvider} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {manPageOptions} from "../../utils/ManPages"; const tailOptions = manPageOptions("tail"); PluginManager.registerAutocompletionProvider("tail", combine([directoriesSuggestionsProvider, tailOptions])); ================================================ FILE: src/plugins/completion/Top.ts ================================================ import {PluginManager} from "../../PluginManager"; import {shortFlag, mapSuggestions} from "../completion_utils/Common"; import {combine} from "../completion_utils/Combine"; import {mapObject} from "../../utils/Common"; const options = combine(mapObject( { b: { short: "Batch mode operation", long: "Starts top in ’Batch mode’, which could be useful for sending\ output from top to other programs or to a file. In this mode, top\ will not accept input and runs until the iterations limit you’ve\ set with the ’-n’ command-line option or until killed.", }, c: { short: "Command line/Program name toggle", long: "Starts top with the last remembered ’c’ state reversed. Thus, if\ top was displaying command lines, now that field will show program\ names, and visa versa. See the ’c’ interactive command for\ additional information.", }, d: { short: "Delay time interval as: -d ss.tt (seconds.tenths)", long: "Specifies the delay between screen updates, and overrides the\ corresponding value in one’s personal configuration file or the\ startup default. Later this can be changed with the ’d’ or ’s’\ interactive commands.\ \ Fractional seconds are honored, but a negative number is not\ allowed. In all cases, however, such changes are prohibited if\ top is running in ’Secure mode’, except for root (unless the ’s’\ command-line option was used). For additional information on\ ’Secure mode’ see topic 5a. SYSTEM Configuration File.", }, h: { short: "Help", long: "Show library version and the usage prompt, then quit.", }, H: { short: "Threads toggle", long: "Starts top with the last remembered ’H’ state reversed. When this\ toggle is On, all individual threads will be displayed.\ Otherwise, top displays a summation of all threads in a process.", }, i: { short: "Idle Processes toggle", long: "Starts top with the last remembered ’i’ state reversed. When this\ toggle is Off, tasks that are idled or zombied will not be\ displayed.", }, n: { short: "Number of iterations limit as: -n number", long: "Specifies the maximum number of iterations, or frames, top should\ produce before ending.", }, u: { short: "Monitor by user as: -u somebody", long: "Monitor only processes with an effective UID or user name matching\ that given.", }, U: { short: "Monitor by user as: -U somebody", long: "Monitor only processes with a UID or user name matching that\ given. This matches real, effective, saved, and filesystem UIDs.", }, p: { short: "Monitor PIDs as: -pN1 -pN2 ... or -pN1, N2 [,...]", long: "Monitor only processes with specified process IDs. This option\ can be given up to 20 times, or you can provide a comma delimited\ list with up to 20 pids. Co-mingling both approaches is\ permitted.\ \ This is a command-line option only. And should you wish to return\ to normal operation, it is not necessary to quit and and restart\ top -- just issue the ’=’ interactive command.", }, s: { short: "Secure mode operation", long: "Starts top with secure mode forced, even for root. This mode is\ far better controlled through the system configuration file (see\ topic 5. FILES).", }, S: { short: "Cumulative time mode toggle", long: "Starts top with the last remembered ’S’ state reversed. When\ ’Cumulative mode’ is On, each process is listed with the cpu time\ that it and its dead children have used. See the ’S’ interactive\ command for additional information regarding this mode.", }, v: { short: "Version", long: "Show library version and the usage prompt, then quit.", }, }, (option, descriptions) => mapSuggestions(shortFlag(option), suggestion => ({...suggestion, detail: descriptions.short})), )); PluginManager.registerAutocompletionProvider("top", options); ================================================ FILE: src/plugins/completion/Vagrant.ts ================================================ import {PluginManager} from "../../PluginManager"; import {linedOutputOf} from "../../PTY"; import {commandWithSubcommands, emptyProvider, SubcommandConfig} from "../completion_utils/Common"; import {once} from "lodash"; import {homeDirectory} from "../../utils/Common"; const vargrantCommandConfig = once(async() => { try { return (await linedOutputOf("vagrant", ["list-commands"], homeDirectory)) .map(line => { const matches = line.match(/([\-a-zA-Z0-9]+) /); if (matches) { const name = matches[1]; const description = line.replace(matches[1], "").trim(); return { name, description, provider: emptyProvider, }; } }) .filter(suggestion => suggestion) as SubcommandConfig[]; } catch (e) { return [] as SubcommandConfig[]; } }); PluginManager.registerAutocompletionProvider("vagrant", async (context) => { return commandWithSubcommands(await vargrantCommandConfig())(context); }); ================================================ FILE: src/plugins/completion_utils/Button.tsx ================================================ import * as React from "react"; import {colors} from "../../views/css/colors"; const buttonStyles = (color: string) => ({ borderColor: color, borderStyle: "solid", borderRadius: "4px", borderWidth: "1px", padding: "2px", color: color, WebkitUserSelect: "none", fontSize: "10px", margin: "4px", cursor: "pointer", }); type ButtonProps = { onClick: () => void; children: string; color?: string; }; export const Button = ({ onClick, children, color = colors.blue }: ButtonProps) => {children}; ================================================ FILE: src/plugins/completion_utils/Combine.ts ================================================ import * as _ from "lodash"; import {AutocompletionContext, AutocompletionProvider} from "../../Interfaces"; import {Suggestion} from "./Common"; export const combine = (providers: AutocompletionProvider[]): AutocompletionProvider => { return async(context: AutocompletionContext): Promise => { return _.flatten(await Promise.all(providers.map(provider => provider(context)))); }; }; ================================================ FILE: src/plugins/completion_utils/Common.ts ================================================ import {directoryName, escapeFilePath, io, resolveDirectory, userFriendlyPath} from "../../utils/Common"; import {AutocompletionContext, AutocompletionProvider, FileInfo} from "../../Interfaces"; import {combine} from "./Combine"; import * as modeToPermissions from "mode-to-permissions"; import * as Path from "path"; import * as _ from "lodash"; // Copy of monaco.languages.CompletionItem, but with optional `kind`. export interface Suggestion { /** * The label of this completion item. By default * this is also the text that is inserted when selecting * this completion. */ label: string; /** * The kind of this completion item. Based on the kind * an icon is chosen by the editor. */ kind?: monaco.languages.CompletionItemKind; /** * A human-readable string with additional information * about this item, like type or symbol information. */ detail?: string; /** * A human-readable string that represents a doc-comment. */ documentation?: string; /** * A string that should be used when comparing this item * with other items. When `falsy` the [label](#CompletionItem.label) * is used. */ sortText?: string; /** * A string that should be used when filtering a set of * completion items. When `falsy` the [label](#CompletionItem.label) * is used. */ filterText?: string; /** * A string or snippet that should be inserted in a document when selecting * this completion. When `falsy` the [label](#CompletionItem.label) * is used. */ insertText?: string | monaco.languages.SnippetString; /** * A range of text that should be replaced by this completion item. * * Defaults to a range from the start of the [current word](#TextDocument.getWordRangeAtPosition) to the * current position. * * *Note:* The range must be a [single line](#Range.isSingleLine) and it must * [contain](#Range.contains) the position at which completion has been [requested](#CompletionItemProvider.provideCompletionItems). */ range?: monaco.Range; /** * @deprecated **Deprecated** in favor of `CompletionItem.insertText` and `CompletionItem.range`. * * ~~An [edit](#TextEdit) which is applied to a document when selecting * this completion. When an edit is provided the value of * [insertText](#CompletionItem.insertText) is ignored.~~ * * ~~The [range](#Range) of the edit must be single-line and on the same * line completions were [requested](#CompletionItemProvider.provideCompletionItems) at.~~ */ textEdit?: monaco.editor.ISingleEditOperation; } export function provide(provider: AutocompletionProvider): AutocompletionProvider { return provider; } export const unique = (provider: AutocompletionProvider) => provide(async context => { const suggestions = await provider(context); return suggestions.filter(suggestion => !context.argument.command.hasArgument(suggestion.label, context.argument)); }); const filesSuggestions = (filter: (info: FileInfo) => boolean) => async (tokenValue: string, directory: string): Promise => { const parentDirectory = userFriendlyPath(directory.replace(/\/$/, "").split(Path.sep).slice(0, -1).join(Path.sep)); const suggestions: Suggestion[] = []; if (_.last(tokenValue.split(Path.sep))!.startsWith(".")) { suggestions.push({label: "../", detail: parentDirectory}); } const tokenDirectory = directoryName(tokenValue); const basePath = tokenValue.slice(tokenDirectory.length); const directoryPath = resolveDirectory(directory, tokenDirectory); const stats = await io.lstatsIn(directoryPath); return stats .filter(info => info.name.startsWith(".") ? basePath.startsWith(".") : true) .filter(info => info.stat.isDirectory() || filter(info)) .map(info => { const escapedName: string = escapeFilePath(info.name); if (info.stat.isDirectory() || info.stat.isSymbolicLink()) { return {label: escapedName + "/"}; } else { return {label: escapedName}; } }).concat(suggestions); }; const filesSuggestionsProvider = (filter: (info: FileInfo) => boolean) => (context: AutocompletionContext, directory = context.environment.pwd): Promise => filesSuggestions(filter)(context.argument.value, directory); export const executableFilesSuggestions = filesSuggestions(info => info.stat.isFile() && modeToPermissions(info.stat.mode).execute.owner); export const anyFilesSuggestions = filesSuggestions(() => true); export const anyFilesSuggestionsProvider = unique(filesSuggestionsProvider(() => true)); export const directoriesSuggestions = filesSuggestions(info => info.stat.isDirectory() || info.stat.isSymbolicLink()); export const directoriesSuggestionsProvider = filesSuggestionsProvider(info => info.stat.isDirectory() || info.stat.isSymbolicLink()); export const environmentVariableSuggestions = provide(async context => { if (context.argument.value.startsWith("$")) { return context.environment.map((key, value) => ({label: "$" + key, detail: value}), ); } else { return []; } }); export function contextIndependent(provider: () => Promise) { return _.memoize(provider, () => ""); } export function staticSuggestionsProvider(suggestions: Suggestion[]) { return contextIndependent(async () => suggestions); } export const emptyProvider = provide(async() => []); export const defaultAutocompletionProvider = provide(async context => { if (context.argument.value.length) { return combine([environmentVariableSuggestions, anyFilesSuggestionsProvider])(context); } else { return []; } }); export const longAndShortFlag = (name: string, shortName = name[0]) => provide(async context => { const longValue = `--${name}`; const shortValue = `-${shortName}`; if (context.argument.command.hasArgument(longValue, context.argument) || context.argument.command.hasArgument(shortValue, context.argument)) { return []; } const value = context.argument.value === shortValue ? shortValue : longValue; return [{label: value}]; }); export const shortFlag = (char: string) => unique(async() => [{label: `-${char}`}]); export const longFlag = (name: string) => unique(async() => [{label: `--${name}`}]); export const mapSuggestions = (provider: AutocompletionProvider, mapper: (suggestion: Suggestion) => Suggestion) => provide(async context => (await provider(context)).map(mapper)); export interface SubcommandConfig { name: string; detail?: string; provider?: AutocompletionProvider; } export const commandWithSubcommands = (subCommands: SubcommandConfig[]) => { return async (context: AutocompletionContext) => { if (context.argument.position === 1) { return subCommands.map(({ name, detail, provider }) => ({ label: name, detail, space: provider !== undefined, })); } else if (context.argument.position === 2) { const firstArgument = context.argument.command.nthArgument(1); if (firstArgument) { const subCommandConfig = subCommands.find(config => config.name === firstArgument.value); if (subCommandConfig && subCommandConfig.provider) { return await subCommandConfig.provider(context); } } } return []; }; }; ================================================ FILE: src/plugins/completion_utils/Descriptions.ts ================================================ /* tslint:disable:max-line-length */ export const descriptions = { git: { subcommands: { add: "Add file contents to the index.", am: "Apply a series of patches from a mailbox.", archive: "Create an archive of files from a named tree.", bisect: "Find by binary search the change that introduced a bug.", branch: "List, create, or delete branches.", bundle: "Move objects and refs by archive.", checkout: "Switch branches or restore working tree files.", cherryPick: "Apply the changes introduced by some existing commits.", citool: "Graphical alternative to git-commit.", clean: "Remove untracked files from the working tree.", clone: "Clone a repository into a new directory.", commit: "Record changes to the repository.", config: "Get and set repository or global options", describe: "Describe a commit using the most recent tag reachable from it.", diff: "Show changes between commits, commit and working tree, etc.", fetch: "Download objects and refs from another repository.", formatPatch: "Prepare patches for e-mail submission.", gc: "Cleanup unnecessary files and optimize the local repository.", grep: "Print lines matching a pattern.", gui: "A portable graphical interface to Git.", init: "Create an empty Git repository or reinitialize an existing one.", log: "Show commit logs.", merge: "Join two or more development histories together.", mv: "Move or rename a file, a directory, or a symlink.", notes: "Add or inspect object notes.", pull: "Fetch from and integrate with another repository or a local branch.", push: "Update remote refs along with associated objects.", rebase: "Forward-port local commits to the updated upstream head.", reset: "Reset current HEAD to the specified state.", revert: "Revert some existing commits.", rm: "Remove files from the working tree and from the index.", shortlog: "Summarize git log output.", show: "Show various types of objects.", stash: "Stash the changes in a dirty working directory away.", status: "Show the working tree status.", submodule: "Initialize, update or inspect submodules.", tag: "Create, list, delete or verify a tag object signed with GPG.", worktree: "Manage multiple worktrees.", }, add: { patch: 'Interactively choose hunks of patch between the index and the work tree and add them to the index. This gives the user a chance to review the difference before adding modified contents to the index. This effectively runs add --interactive, but bypasses the initial command menu and directly jumps to the patch subcommand. See "Interactive mode" for details.', interactive: 'Add modified contents in the working tree interactively to the index. Optional path arguments may be supplied to limit operation to a subset of the working tree. See "Interactive mode" for details.', dryRun: "Don't actually add the file(s), just show if they exist and/or will be ignored.", force: "Allow adding otherwise ignored files.", edit: "Open the diff vs. the index in an editor and let the user edit it. After the editor was closed, adjust the hunk headers and apply the patch to the index. The intent of this option is to pick and choose lines of the patch to apply, or even to modify the contents of lines to be staged. This can be quicker and more flexible than using the interactive hunk selector. However, it is easy to confuse oneself and create a patch that does not apply to the index. See EDITING PATCHES below.", update: "Update the index just where it already has an entry matching . This removes as well as modifies index entries to match the working tree, but adds no new files. If no is given when -u option is used, all tracked files in the entire working tree are updated (old versions of Git used to limit the update to the current directory and its subdirectories).", noIgnoreRemoval: "Update the index not only where the working tree has a file matching but also where the index already has an entry. This adds, modifies, and removes index entries to match the working tree. If no is given when -A option is used, all files in the entire working tree are updated (old versions of Git used to limit the update to the current directory and its subdirectories).", ignoreRemoval: 'Update the index by adding new files that are unknown to the index and files modified in the working tree, but ignore files that have been removed from the working tree. This option is a no-op when no is used. This option is primarily to help users who are used to older versions of Git, whose "git add ..." was a synonym for "git add --no-all ...", i.e. ignored removed files.', intentToAdd: "Record only the fact that the path will be added later. An entry for the path is placed in the index with no content. This is useful for, among other things, showing the unstaged content of such files with git diff and committing them with git commit -a.", refresh: "Don't add the file(s), but only refresh their stat() information in the index.", ignoreErrors: "If some files could not be added because of errors indexing them, do not abort the operation, but continue adding the others. The command shall still exit with non-zero status. The configuration variable add.ignoreErrors can be set to true to make this the default behaviour.", ignoreMissing: "This option can only be used together with --dry-run. By using this option the user can check if any of the given files would be ignored, no matter if they are already present in the work tree or not.", chmod: "Override the executable bit of the added files. The executable bit is only changed in the index, the files on disk are left unchanged.", separator: "This option can be used to separate command-line options from the list of files, (useful when filenames might be mistaken for command-line options).", verbose: "Be verbose.", }, checkout: { branch: "Create a new branch and start it at ; see git-branch(1) for details.", }, commit: { message: "Use the given as the commit message. If multiple -m options are given, their values are concatenated as separate paragraphs.", all: "Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected.", patch: "Use the interactive patch selection interface to chose which changes to commit. See git-add(1) for details.", NULL: "When showing short or porcelain status output, terminate entries in the status output with NULL, instead of LF. If no format is given, implies the --porcelain output format.", template: "When editing the commit message, start the editor with the contents in the given file. The commit.template configuration variable is often used to give this option implicitly to the command. This mechanism can be used by projects that want to guide participants with some hints on what to write in the message in what order. If the user exits the editor without editing the message, the commit is aborted. This has no effect when a message is given by other means, e.g. with the -m or -F options.", signoff: "Add Signed-off-by line by the committer at the end of the commit log message. The meaning of a signoff depends on the project, but it typically certifies that committer has the rights to submit this work under the same license and agrees to a Developer Certificate of Origin (see http://developercertificate.org/ for more information).", noVerify: "This option bypasses the pre-commit and commit-msg hooks. See also githooks(5).", edit: "The message taken from file with -F, command line with -m, and from commit object with -C are usually used as the commit log message unmodified. This option lets you further edit the message taken from these sources.", include: "Before making a commit out of staged contents so far, stage the contents of paths given on the command line as well. This is usually not what you want unless you are concluding a conflicted merge.", only: "Make a commit by taking the updated working tree contents of the paths specified on the command line, disregarding any contents that have been staged for other paths. This is the default mode of operation of git commit if any paths are given on the command line, in which case this option can be omitted. If this option is specified together with --amend, then no paths need to be specified, which can be used to amend the last commit without committing changes that have already been staged.", verbose: "Show unified diff between the HEAD commit and what would be committed at the bottom of the commit message template to help the user describe the commit by reminding what changes the commit has. Note that this diff output doesn't have its lines prefixed with #. This diff will not be a part of the commit message. See the commit.verbose configuration variable in git-config(1). If specified twice, show in addition the unified diff between what would be committed and the worktree files, i.e. the unstaged changes to tracked files.", quiet: "Suppress commit summary message.", resetAuthor: "When used with -C/-c/--amend options, or when committing after a a conflicting cherry-pick, declare that the authorship of the resulting commit now belongs to the committer. This also renews the author timestamp.", short: "When doing a dry-run, give the output in the short-format. See git-status(1) for details. Implies --dry-run.", branch: "Show the branch and tracking info even in short-format.", porcelain: "When doing a dry-run, give the output in a porcelain-ready format. See git-status(1) for details. Implies --dry-run.", long: "When doing a dry-run, give the output in a the long-format. Implies --dry-run.", allowEmpty: "Usually recording a commit that has the exact same tree as its sole parent commit is a mistake, and the command prevents you from making such a commit. This option bypasses the safety, and is primarily for use by foreign SCM interface scripts.", allowEmptyMessage: "Like --allow-empty this command is primarily for use by foreign SCM interface scripts. It allows you to create a commit with an empty commit message without using plumbing commands like git-commit-tree(1).", noEdit: "Use the selected commit message without launching an editor. For example, git commit --amend --no-edit amends a commit without changing its commit message.", noPostRewrite: "Bypass the post-rewrite hook.", dryRun: "Do not create a commit, but show a list of paths that are to be committed, paths with local changes that will be left uncommitted and paths that are untracked.", status: "Include the output of git-status(1) in the commit message template when using an editor to prepare the commit message. Defaults to on, but can be used to override configuration variable commit.status.", noStatus: "Do not include the output of git-status(1) in the commit message template when using an editor to prepare the default commit message.", noGpgSign: "Countermand commit.gpgSign configuration variable that is set to force each and every commit to be signed.", }, status: { short: "Give the output in the short-format.", branch: "Show the branch and tracking info even in short-format.", porcelain: "Give the output in an easy-to-parse format for scripts. This is similar to the short output, but will remain stable across Git versions and regardless of user configuration. See below for details. The version parameter is used to specify the format version. This is optional and defaults to the original version v1 format.", long: "Give the output in the long-format. This is the default.", verbose: "In addition to the names of files that have been changed, also show the textual changes that are staged to be committed (i.e., like the output of git diff --cached). If -v is specified twice, then also show the changes in the working tree that have not yet been staged (i.e., like the output of git diff).", untrackedFiles: "Show untracked files. The mode parameter is used to specify the handling of untracked files. It is optional: it defaults to all, and if specified, it must be stuck to the option (e.g. -uno, but not -u no). The possible options are: o no - Show no untracked files. o normal - Shows untracked files and directories. o all - Also shows individual files in untracked directories. When -u option is not used, untracked files and directories are shown (i.e. the same as specifying normal), to help you avoid forgetting to add newly created files. Because it takes extra work to find untracked files in the filesystem, this mode may take some time in a large working tree. Consider enabling untracked cache and split index if supported (see git update-index --untracked-cache and git update-index --split-index), Otherwise you can use no to have git status return more quickly without showing untracked files. The default can be changed using the status.showUntrackedFiles configuration variable documented in git-config(1).", ignoreSubmodules: 'Ignore changes to submodules when looking for changes. can be either "none", "untracked", "dirty" or "all", which is the default. Using "none" will consider the submodule modified when it either contains untracked or modified files or its HEAD differs from the commit recorded in the superproject and can be used to override any settings of the ignore option in git- config(1) or gitmodules(5). When "untracked" is used submodules are not considered dirty when they only contain untracked content (but they are still scanned for modified content). Using "dirty" ignores all changes to the work tree of submodules, only changes to the commits stored in the superproject are shown (this was the behavior before 1.7.0). Using "all" hides all changes to submodules (and suppresses the output of submodule summaries when the config option status.submoduleSummary is set).', ignored: "Show ignored files as well.", terminateWithNull: "Terminate entries with NUL, instead of LF. This implies the --porcelain=v1 output format if no other format is given.", column: "Display untracked files in columns. See configuration variable column.status for option syntax.--column and --no-column without options are equivalent to always and never respectively.", }, push: { all: "Push all branches (i.e. refs under refs/heads/); cannot be used with other .", prune: "Remove remote branches that don't have a local counterpart.", }, }, }; ================================================ FILE: src/references.d.ts ================================================ /// /// /// /// /// /// /// ================================================ FILE: src/services/FontService.ts ================================================ import {Subject} from "rxjs/Subject"; function getLetterSize(size: number, fontFamily: string) { const height = size + 2; if (process.env.NODE_ENV === "test") { return {width: (size / 2) + 1.5, height: height}; } else { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d")!; context.font = `${size}px ${fontFamily}`; const metrics = context.measureText("m"); return {width: metrics.width, height: height}; } } const fontSize = 16; const fontFamily = "'Ubuntu Mono', 'Fira Code', 'Menlo', monospace"; export class FontService { size: number; letterWidth: number; letterHeight: number; family: string; readonly onChange = new Subject(); constructor() { this.updateFont(fontSize, fontFamily); } resetSize() { this.updateFont(fontSize, fontFamily); this.onChange.next(); } increaseSize() { this.updateFont(this.size + 1, fontFamily); this.onChange.next(); } decreaseSize() { this.updateFont(Math.max(4, this.size - 1), fontFamily); this.onChange.next(); } private updateFont(size: number, family: string) { const letterSize = getLetterSize(size, family); this.size = size; this.family = family; this.letterWidth = letterSize.width; this.letterHeight = letterSize.height; } } ================================================ FILE: src/services/GitService.ts ================================================ import {Observable} from "rxjs/Observable"; import {BehaviorSubject} from "rxjs/BehaviorSubject"; import "rxjs/add/observable/timer"; import "rxjs/add/operator/concatMap"; import "rxjs/add/operator/filter"; import "rxjs/add/operator/merge"; import "rxjs/add/operator/share"; import "rxjs/add/operator/distinctUntilChanged"; import "rxjs/add/operator/multicast"; import {currentBranchName, GitDirectoryPath, repositoryState, RepositoryState} from "../utils/Git"; import {services} from "./index"; const INTERVAL = 5000; async function getState(directory: string): Promise { const state = await repositoryState(directory); if (state === RepositoryState.NotRepository) { return {kind: "not-repository"}; } else { return { kind: "repository", branch: await currentBranchName(directory), status: state, }; } } function createObservable(directory: string) { return Observable .timer(0, INTERVAL) .merge(services.jobs.onFinish.filter(job => job.session.directory === directory)) .concatMap(() => getState(directory)) // Don't emit if a value didn't change. .distinctUntilChanged((x, y) => JSON.stringify(x) === JSON.stringify(y)) // Remember the last value to emit immediately to new subscriptions. .multicast(new BehaviorSubject({kind: "not-repository"})) // Automatically stop checking git status when there are no subscriptions anymore. .refCount(); } export class GitService { private observables: Map> = new Map(); observableFor(directory: string): Observable { if (!this.observables.has(directory)) { this.observables.set(directory, createObservable(directory)); } return this.observables.get(directory)!; } } ================================================ FILE: src/services/HistoryService.ts ================================================ import {readFileSync} from "fs"; import {historyFilePath} from "../utils/Common"; import * as _ from "lodash"; import csvParse = require("csv-parse/lib/sync"); import {SessionID} from "../shell/Session"; import {Subject} from "rxjs/Subject"; interface HistoryRecordWithoutID { command: string; expandedCommand: string; timestamp: number; directory: string; sessionID: SessionID; } export interface HistoryRecord extends HistoryRecordWithoutID { id: number; } const readHistoryFileData = (): HistoryRecord[] => { try { return csvParse(readFileSync(historyFilePath).toString()).map((array: string[]) => ({ id: Number.parseInt(array[0], 10), command: array[1], expandedCommand: array[2], timestamp: Number.parseInt(array[3], 10), directory: array[4], sessionID: Number.parseInt(array[5], 10), })); } catch (e) { return []; } }; export class HistoryService { readonly onNewRecord = new Subject(); private maxRecordsCount: number = 5000; private storage: HistoryRecord[] = []; constructor() { this.storage = readHistoryFileData(); } get all(): HistoryRecord[] { return this.storage; } get latest(): HistoryRecord | undefined { return _.last(this.storage); } add(recordWithoutID: HistoryRecordWithoutID) { const record = {id: this.nextID, ...recordWithoutID}; this.storage.push(record); if (this.storage.length > this.maxRecordsCount) { this.storage.shift(); } this.onNewRecord.next(record); return record; } get(id: number): HistoryRecord { return this.all.find(record => record.id === id)!; } private get nextID(): number { if (this.latest) { return this.latest.id + 1; } else { return 1; } } } ================================================ FILE: src/services/JobsService.ts ================================================ import {Subject} from "rxjs/Subject"; import "rxjs/add/observable/fromEvent"; import {Job} from "../shell/Job"; export class JobsService { readonly onStart = new Subject(); readonly onFinish = new Subject(); } ================================================ FILE: src/services/SessionsService.ts ================================================ import {Session, SessionID} from "../shell/Session"; import {Observable} from "rxjs/Observable"; import {Subject} from "rxjs/Subject"; import "rxjs/add/observable/fromEvent"; import {services} from "./index"; export class SessionsService { readonly onClose = new Subject(); private readonly sessions: Map = new Map; create() { const session = new Session(); this.sessions.set(session.id, session); Observable.fromEvent(session, "job-started").subscribe( () => services.jobs.onStart.next(session.lastJob!), ); Observable.fromEvent(session, "job-finished").subscribe( () => services.jobs.onFinish.next(session.lastJob!), ); return session.id; } get(id: SessionID) { return this.sessions.get(id)!; } close(ids: SessionID | SessionID[]) { if (Array.isArray(ids)) { ids.forEach(id => this.close(id)); return; } const id = ids; const session = this.get(id); session.jobs.forEach(job => { job.removeAllListeners(); job.interrupt(); }); session.removeAllListeners(); this.sessions.delete(id); this.onClose.next(id); } closeAll() { this.sessions.forEach((_session, id) => this.close(id)); } } ================================================ FILE: src/services/UpdatesService.ts ================================================ import {remote} from "electron"; import * as https from "https"; export class UpdatesService { isAvailable = false; private currentVersion: string; private INTERVAL = 1000 * 60 * 60 * 12; constructor() { if (process.env.NODE_ENV === "test") { return; } this.currentVersion = "v" + remote.app.getVersion(); this.checkUpdate(); setInterval(() => this.checkUpdate(), this.INTERVAL); } private checkUpdate() { if (this.isAvailable || !navigator.onLine) { return; } https.get( { host: "api.github.com", path: "/repos/railsware/upterm/releases/latest", headers: { "User-Agent": "Upterm", }, }, (response) => { let body = ""; response.on("data", data => body += data); response.on("end", () => { const parsed = JSON.parse(body); this.isAvailable = parsed.tag_name !== this.currentVersion; }); }, ); } } ================================================ FILE: src/services/WindowService.ts ================================================ import {remote} from "electron"; import {Observable} from "rxjs/Observable"; import "rxjs/add/observable/fromEvent"; import "rxjs/add/operator/map"; import "rxjs/add/operator/do"; import {NeverObservable} from "rxjs/observable/NeverObservable"; import {Subject} from "rxjs/Subject"; export class WindowService { readonly onResize: Observable<{}>; readonly onClose = new Subject<{}>(); readonly onBoundsChange: Observable; constructor() { if (remote) { const electronWindow = remote.BrowserWindow.getAllWindows()[0]; this.onResize = Observable.fromEvent(electronWindow, "resize") .merge(Observable.fromEvent(electronWindow.webContents, "devtools-opened")) .merge(Observable.fromEvent(electronWindow.webContents, "devtools-closed")); this.onBoundsChange = Observable.fromEvent(electronWindow, "move") .merge(Observable.fromEvent(electronWindow, "resize")) .map(() => electronWindow.getBounds()); window.onbeforeunload = () => { electronWindow .removeAllListeners() .webContents .removeAllListeners("devtools-opened") .removeAllListeners("devtools-closed") .removeAllListeners("found-in-page"); this.onClose.next(); }; } else { this.onResize = new NeverObservable(); this.onBoundsChange = new NeverObservable(); } } } ================================================ FILE: src/services/index.ts ================================================ import {FontService} from "./FontService"; import {HistoryService} from "./HistoryService"; import {UpdatesService} from "./UpdatesService"; import {GitService} from "./GitService"; import {SessionsService} from "./SessionsService"; import {WindowService} from "./WindowService"; import {JobsService} from "./JobsService"; // To help IDE with "find usages" and "go to definition". interface Services { font: FontService; history: HistoryService; updates: UpdatesService; git: GitService; sessions: SessionsService; jobs: JobsService; window: WindowService; } export const services: Services = { font: new FontService(), history: new HistoryService(), updates: new UpdatesService, git: new GitService(), sessions: new SessionsService(), jobs: new JobsService(), window: new WindowService(), }; ================================================ FILE: src/shell/Aliases.ts ================================================ import {loginShell} from "../utils/Shell"; import * as _ from "lodash"; export const aliasesFromConfig: Dictionary = {}; export async function loadAliasesFromConfig(): Promise { const lines = await loginShell.loadAliases(); lines.map(parseAlias).forEach(parsed => aliasesFromConfig[parsed.name] = parsed.value); } export function parseAlias(line: string) { let [short, long] = line.split("="); if (short && long) { const nameCapture = /(alias )?(.*)/.exec(short); const valueCapture = /'?([^']*)'?/.exec(long); if (nameCapture && valueCapture) { return { name: nameCapture[2], value: valueCapture[1], }; } else { throw `Alias line is incorrect: ${line}`; } } else { throw `Can't parse alias line: ${line}`; } } export class Aliases { private storage: Dictionary; constructor(aliases: Dictionary) { this.storage = _.clone(aliases); } add(name: string, value: string) { this.storage[name] = value; } has(name: string): name is ExistingAlias { return name in this.storage; } get(name: ExistingAlias): string { return this.storage[name]; } getNameByValue(value: string): string | undefined { return _.findKey(this.storage, storageValue => storageValue === value); } remove(name: string) { delete this.storage[name]; } toObject(): Dictionary { return this.storage; } } ================================================ FILE: src/shell/BuiltInCommands.ts ================================================ import {Job} from "./Job"; import {existsSync, statSync} from "fs"; import {homeDirectory, pluralize, resolveDirectory, resolveFile, mapObject} from "../utils/Common"; import {readFileSync} from "fs"; import {EOL} from "os"; import {Session} from "./Session"; import {OrderedSet} from "../utils/OrderedSet"; import {parseAlias} from "./Aliases"; import {stringLiteralValue} from "./Scanner"; const executors: Dictionary<(i: Job, a: string[]) => void> = { cd: (job: Job, args: string[]): void => { let fullPath: string; if (!args.length) { fullPath = homeDirectory; } else { const enteredPath = args[0]; if (isHistoricalDirectory(enteredPath)) { fullPath = expandHistoricalDirectory(enteredPath, job.session.historicalPresentDirectoriesStack); } else { fullPath = job.environment.cdpath .map(path => resolveDirectory(path, enteredPath)) .filter(resolved => existsSync(resolved)) .filter(resolved => statSync(resolved).isDirectory())[0]; if (!fullPath) { throw new Error(`The directory "${enteredPath}" doesn't exist.`); } } } job.session.directory = fullPath; }, clear: (job: Job, _args: string[]): void => { setTimeout(() => job.session.clearJobs(), 0); }, exit: (job: Job, _args: string[]): void => { job.session.close(); }, export: (job: Job, args: string[]): void => { if (args.length === 0) { job.output.write(job.environment.map((key, value) => `${key}=${value}`).join("\r\n")); } else { args.forEach(argument => { const firstEqualIndex = argument.indexOf("="); const key = argument.slice(0, firstEqualIndex); const value = argument.slice(firstEqualIndex + 1); job.session.environment.set(key, value); }); } }, // FIXME: make the implementation more reliable. source: (job: Job, args: string[]): void => { sourceFile(job.session, args[0]); }, alias: (job: Job, args: string[]): void => { if (args.length === 0) { job.output.write(mapObject(job.session.aliases.toObject(), (key, value) => `${key}=${value}`).join("\r\n")); } else if (args.length === 1) { const parsed = parseAlias(args[0]); job.session.aliases.add(parsed.name, parsed.value); } else { throw `Don't know what to do with ${args.length} arguments.`; } }, unalias: (job: Job, args: string[]): void => { if (args.length === 1) { const name = args[0]; if (job.session.aliases.has(name)) { job.session.aliases.remove(args[0]); } else { throw `There is such alias: ${name}.`; } } else { throw `Don't know what to do with ${args.length} arguments.`; } }, show: (job: Job, args: string[]): void => { const imgs = args.map(argument => resolveFile(job.environment.pwd, argument)); job.output.write(imgs.join(EOL)); }, }; export function sourceFile(session: Session, fileName: string) { const content = readFileSync(resolveFile(session.directory, fileName)).toString(); content.split(EOL).forEach(line => { if (line.startsWith("export ")) { const [variableName, variableValueLiteral] = line.split(" ")[1].split("="); const variableValue = stringLiteralValue(variableValueLiteral); if (variableValue) { session.environment.set(variableName, variableValue); } } }); } // A class representing built in commands export class Command { static allCommandNames = Object.keys(executors); static executor(command: string): (i: Job, args: string[]) => void { return executors[command]; } static isBuiltIn(command: string): boolean { return this.allCommandNames.includes(command); } } export function expandHistoricalDirectory(alias: string, historicalDirectories: OrderedSet): string { if (alias === "-") { alias = "-1"; } const index = historicalDirectories.size + parseInt(alias, 10); if (index < 0) { throw new Error(`Error: you only have ${historicalDirectories.size} ${pluralize("directory", historicalDirectories.size)} in the stack.`); } else { const directory = historicalDirectories.at(index); if (directory) { return directory; } else { throw `No directory with index ${index}`; } } } export function isHistoricalDirectory(directory: string): boolean { return /^-\d*$/.test(directory); } ================================================ FILE: src/shell/CommandExecutor.ts ================================================ import {Job} from "./Job"; import {Command} from "./BuiltInCommands"; import {PTY} from "../PTY"; import * as Path from "path"; import {resolveFile, isWindows, filterAsync, io} from "../utils/Common"; import {loginShell} from "../utils/Shell"; export class NonZeroExitCodeError extends Error { } abstract class CommandExecutionStrategy { static async canExecute(_job: Job): Promise { return false; } constructor(protected job: Job) { } abstract startExecution(): Promise<{}>; } class BuiltInCommandExecutionStrategy extends CommandExecutionStrategy { static async canExecute(job: Job) { return Command.isBuiltIn(job.prompt.commandName); } startExecution() { return new Promise((resolve, reject) => { try { Command.executor(this.job.prompt.commandName)(this.job, this.job.prompt.arguments.map(token => token.value)); resolve(); } catch (error) { reject(error.message); } }); } } class ShellExecutionStrategy extends CommandExecutionStrategy { static async canExecute(job: Job) { return loginShell.preCommandModifiers.includes(job.prompt.commandName) || await this.isExecutableFromPath(job) || await this.isPathOfExecutable(job) || this.isBashFunc(job); } private static isBashFunc(job: Job): boolean { return job.environment.has(`BASH_FUNC_${job.prompt.commandName}%%`); } private static async isExecutableFromPath(job: Job): Promise { return (await io.executablesInPaths(job.environment.path)).includes(job.prompt.commandName); } private static async isPathOfExecutable(job: Job): Promise { return await io.fileExists(resolveFile(job.session.directory, job.prompt.commandName)); } startExecution() { return new Promise((resolve, reject) => { this.job.setPty(new PTY( this.job.prompt.expandedTokens.map(token => token.escapedValue), this.job.environment.toObject(), this.job.session.dimensions, (data: string) => this.job.output.write(data), (exitCode: number) => exitCode === 0 ? resolve() : reject(new NonZeroExitCodeError(exitCode.toString())), )); }); } } class WindowsShellExecutionStrategy extends CommandExecutionStrategy { static async canExecute(_job: Job) { return isWindows; } startExecution() { return new Promise((resolve) => { this.job.setPty(new PTY( [ this.cmdPath, "/s" as EscapedShellWord, "/c" as EscapedShellWord, ...this.job.prompt.expandedTokens.map(token => token.escapedValue), ], this.job.environment.toObject(), this.job.session.dimensions, (data: string) => this.job.output.write(data), (_exitCode: number) => resolve(), )); }); } private get cmdPath(): EscapedShellWord { if (this.job.environment.has("comspec")) { return this.job.environment.get("comspec") as EscapedShellWord; } else if (this.job.environment.has("SystemRoot")) { return Path.join(this.job.environment.get("SystemRoot"), "System32", "cmd.exe") as EscapedShellWord; } else { return "cmd.exe" as EscapedShellWord; } } } export class CommandExecutor { private static executors = [ BuiltInCommandExecutionStrategy, WindowsShellExecutionStrategy, ShellExecutionStrategy, ]; static async execute(job: Job): Promise<{}> { const applicableExecutors = await filterAsync(this.executors, executor => executor.canExecute(job)); if (applicableExecutors.length) { return new applicableExecutors[0](job).startExecution(); } else { throw `Upterm: command "${job.prompt.commandName}" not found.\n`; } } } ================================================ FILE: src/shell/Environment.ts ================================================ import {delimiter} from "path"; import * as _ from "lodash"; import {executeCommandWithShellConfig} from "../PTY"; import {clone} from "lodash"; import {homeDirectory, resolveDirectory} from "../utils/Common"; import * as Path from "path"; import {AbstractOrderedSet} from "../utils/OrderedSet"; import {loginShell} from "../utils/Shell"; const ignoredEnvironmentVariables = [ "NODE_ENV", ]; const isIgnoredEnvironmentVariable = (varName: string) => { if (ignoredEnvironmentVariables.includes(varName)) { return true; } else { return false; } }; export const preprocessEnv = (lines: string[]) => { // Bash functions in the env have newlines in them, which need to be removed const joinedFunctionLines: string[] = []; for (let i = 0; i < lines.length; i++) { if (/^BASH_FUNC\w+%%/.test(lines[i])) { const finalLineOfFunction = lines.indexOf("}", i); joinedFunctionLines.push(lines.slice(i, finalLineOfFunction + 1).join("\n")); i = finalLineOfFunction; } else { joinedFunctionLines.push(lines[i]); } } return joinedFunctionLines; }; export const processEnvironment: Dictionary = {}; export async function loadEnvironment(): Promise { const lines = preprocessEnv(await executeCommandWithShellConfig(loginShell.environmentCommand)); lines.forEach(line => { const [key, ...valueComponents] = line.trim().split("="); const value = valueComponents.join("="); if (!isIgnoredEnvironmentVariable(key)) { processEnvironment[key] = value; } }); } export class Environment { private storage: Dictionary; constructor(environment: Dictionary) { this.storage = clone(environment); } set(key: string, value: string): void { this.storage[key] = value; } setMany(pairs: Dictionary): void { for (const key of Object.keys(pairs)) { this.set(key, pairs[key]); } } toObject(): ProcessEnvironment { return this.storage; } map(mapper: (key: string, value: string) => R): Array { const result: Array = []; for (const key of Object.keys(this.storage)) { result.push(mapper(key, this.storage[key])); } return result; } get(key: string): string { return this.storage[key]; } has(key: string): boolean { return key in this.storage; } get path(): EnvironmentPath { return new EnvironmentPath(this); } get cdpath(): FullPath[] { return _.uniq((this.get("CDPATH") || ".").split(delimiter).map(path => path || this.pwd).map(path => resolveDirectory(this.pwd, path))); } get pwd(): string { if (!this.get("PWD")) { this.pwd = homeDirectory; } return this.get("PWD"); } set pwd(value: string) { this.set("PWD", value); } } export class EnvironmentPath extends AbstractOrderedSet { constructor(private environment: Environment) { super( () => { const path = this.environment.get("PATH"); if (path) { return path.split(Path.delimiter); } else { return []; } }, updatedPaths => this.environment.set("PATH", updatedPaths.join(Path.delimiter)), ); } } ================================================ FILE: src/shell/Job.ts ================================================ import * as _ from "lodash"; import * as i from "../Interfaces"; import * as React from "react"; import {Session} from "./Session"; import {Prompt} from "./Prompt"; import {Output} from "../Output"; import {CommandExecutor, NonZeroExitCodeError} from "./CommandExecutor"; import {PTY} from "../PTY"; import {PluginManager} from "../PluginManager"; import {EmitterWithUniqueID} from "../EmitterWithUniqueID"; import {Status} from "../Enums"; import {Environment} from "./Environment"; import {normalizeProcessInput} from "../utils/Common"; import {TerminalLikeDevice} from "../Interfaces"; export class Job extends EmitterWithUniqueID implements TerminalLikeDevice { public status: Status = Status.InProgress; readonly startTime = Date.now(); private readonly _output: Output; private readonly throttledDataEmitter = _.throttle(() => this.emit("data"), 1000 / 60); private pty: PTY | undefined; constructor(private _session: Session, private _prompt: Prompt) { super(); this._output = new Output(this, this._session.dimensions); this._output.on("data", this.throttledDataEmitter); } async execute(): Promise { try { await CommandExecutor.execute(this); // Need to wipe out PTY so that we // don't keep trying to write to it. this.pty = undefined; // Need to check the status here because it's // executed even after the process was interrupted. if (this.status === Status.InProgress) { this.setStatus(Status.Success); } } catch (exception) { this.handleError(exception); } finally { this.emit("end"); } } handleError(message: NonZeroExitCodeError | string): void { this.setStatus(Status.Failed); if (message) { if (message instanceof NonZeroExitCodeError) { // Do nothing. } else { this._output.write(message); } } this.emit("end"); } isRunningPty(): boolean { return this.pty !== undefined; } setPty(pty: PTY) { this.pty = pty; } // Writes to the process' STDIN. write(input: string | KeyboardEvent) { this.pty!.write(normalizeProcessInput(input, this.output.isCursorKeysModeSet)); } get session(): Session { return this._session; } hasOutput(): boolean { return !this._output.isEmpty(); } interrupt(): void { if (this.pty && this.status === Status.InProgress) { this.pty.kill("SIGINT"); this.setStatus(Status.Failed); this.emit("end"); } } resize(): void { if (this.pty && this.status === Status.InProgress) { this.pty.resize(this.session.dimensions); this.output.dimensions = this.session.dimensions; } } canBePrettified(): boolean { return this.status !== Status.InProgress && !!this.firstApplicablePrettyfier; } prettify(): React.ReactElement { if (this.firstApplicablePrettyfier) { return this.firstApplicablePrettyfier.prettify(this); } else { throw "No applicable prettyfier found."; } } get environment(): Environment { return this.session.environment; } private get firstApplicablePrettyfier(): i.Prettyfier | undefined { return PluginManager.prettyfiers.find(prettyfier => prettyfier.isApplicable(this)); } get output(): Output { return this._output; } get prompt(): Prompt { return this._prompt; } setStatus(status: Status): void { this.status = status; this.emit("status", status); } } ================================================ FILE: src/shell/Parser.ts ================================================ import * as Scanner from "./Scanner"; import * as _ from "lodash"; import {memoizeAccessor} from "../Decorators"; import {commandDescriptions} from "../plugins/completion/Executable"; import {io, mapObject} from "../utils/Common"; import {loginShell} from "../utils/Shell"; import {PreliminaryAutocompletionContext} from "../Interfaces"; import {PluginManager} from "../PluginManager"; import {Aliases} from "./Aliases"; import {combine} from "../plugins/completion_utils/Combine"; import { anyFilesSuggestions, environmentVariableSuggestions, executableFilesSuggestions, Suggestion, } from "../plugins/completion_utils/Common"; export abstract class ASTNode { abstract get fullStart(): number; abstract get fullEnd(): number; } const isEndOfCommand = (token: Scanner.Token) => token instanceof Scanner.Semicolon || token instanceof Scanner.NewLine; abstract class LeafNode extends ASTNode { constructor(private token: Scanner.Token) { super(); } get fullStart(): number { return this.token.fullStart; } get fullEnd(): number { return this.fullStart + this.token.raw.length; } get precedingSpaces(): string { const match = this.token.raw.match(/^(\s*)/); if (match) { return match[1]; } else { return ""; } } get followingSpaces(): string { // Consists only of spaces. They're considered preceding. if (this.token.raw.match(/^(\s*)$/)) { return ""; } const match = this.token.raw.match(/(\s*)$/); if (match) { return match[1]; } else { return ""; } } get raw(): string { return this.token.raw; } get value(): string { return this.token.value; } abstract suggestions(context: PreliminaryAutocompletionContext): Promise; } abstract class BranchNode extends ASTNode { readonly tokens: Scanner.Token[]; abstract get children(): ASTNode[]; constructor(tokens: Scanner.Token[]) { super(); this.tokens = tokens; } get fullStart(): number { return this.children[0].fullStart; } get fullEnd(): number { return _.last(this.children)!.fullEnd; } } /** * The whole command, the input string. */ export class CompleteCommand extends BranchNode { @memoizeAccessor get children(): ASTNode[] { const lastChild = _.last(this.tokens)!; const endsWithSeparator = isEndOfCommand(lastChild); if (endsWithSeparator) { return [ new List(this.tokens.slice(0, -1)), new ShellSyntaxNode(lastChild), ]; } else { return [ new List(this.tokens), ]; } } get firstCommand(): Command { for (const current of traverse(this)) { if (current instanceof Command) { return current; } } return undefined as any as Command; } } class List extends BranchNode { @memoizeAccessor get children(): ASTNode[] { const separatorOpIndex = _.findLastIndex(this.tokens, isEndOfCommand); if (separatorOpIndex !== -1) { return [ new List(this.tokens.slice(0, separatorOpIndex)), new ShellSyntaxNode(this.tokens[separatorOpIndex]), new AndOr(this.tokens.slice(separatorOpIndex + 1)), ]; } else { return [ new AndOr(this.tokens), ]; } } } class AndOr extends BranchNode { @memoizeAccessor get children(): ASTNode[] { const andOrTokenIndex = _.findLastIndex(this.tokens, token => token instanceof Scanner.And || token instanceof Scanner.Or); if (andOrTokenIndex !== -1) { return [ new AndOr(this.tokens.slice(0, andOrTokenIndex)), new ShellSyntaxNode(this.tokens[andOrTokenIndex]), new Pipeline(this.tokens.slice(andOrTokenIndex + 1)), ]; } else { return [ new Pipeline(this.tokens), ]; } } } class Pipeline extends BranchNode { @memoizeAccessor get children(): ASTNode[] { return [new PipeSequence(this.tokens)]; } } class PipeSequence extends BranchNode { @memoizeAccessor get children(): ASTNode[] { const pipeIndex = _.findLastIndex(this.tokens, token => token instanceof Scanner.Pipe); if (pipeIndex !== -1) { return [ new PipeSequence(this.tokens.slice(0, pipeIndex)), new ShellSyntaxNode(this.tokens[pipeIndex]), new Command(this.tokens.slice(pipeIndex + 1)), ]; } else { return [ new Command(this.tokens), ]; } } } class Command extends BranchNode { @memoizeAccessor get children(): ASTNode[] { if (!this.tokens.length) { return [new EmptyNode()]; } const children: ASTNode[] = []; if (this.parameterAssignments) { children.push(this.parameterAssignments); } if (this.commandWord) { children.push(this.commandWord); } if (this.argumentList) { children.push(this.argumentList); } if (this.ioRedirect) { children.push(this.ioRedirect); } return children; } @memoizeAccessor get commandWord(): CommandWord | undefined { if (this.categorizedTokens.commandWord) { return new CommandWord(this.categorizedTokens.commandWord); } } @memoizeAccessor get parameterAssignments(): ParameterAssignmentList | undefined { if (this.categorizedTokens.parameterAssignment.length) { return new ParameterAssignmentList(this.categorizedTokens.parameterAssignment); } } @memoizeAccessor get argumentList(): ArgumentList | undefined { if (this.categorizedTokens.argumentList.length) { return new ArgumentList(this.categorizedTokens.argumentList, this); } } nthArgument(position: OneBasedPosition): Argument | undefined { if (this.argumentList) { return this.argumentList.arguments[position - 1]; } } hasArgument(value: string, currentArgument: Argument): boolean { if (this.argumentList) { return this.argumentList.arguments.filter(argument => argument !== currentArgument).map(argument => argument.value).includes(value); } else { return false; } } @memoizeAccessor private get ioRedirect(): IORedirect | undefined { if (this.categorizedTokens.ioRedirect.length) { return new IORedirect(this.categorizedTokens.ioRedirect); } } @memoizeAccessor private get categorizedTokens() { /** * @link http://stackoverflow.com/a/10939280/1149074 */ const parameterAssignmentTokens = _.takeWhile(this.tokens, token => token.value.includes("=")); const commandWordToken = this.tokens[parameterAssignmentTokens.length]; const beforeArgumentListTokensCount = parameterAssignmentTokens.length + 1; const argumentListTokens = _.takeWhile(this.tokens.slice(beforeArgumentListTokensCount), token => !( token instanceof Scanner.InputRedirectionSymbol || token instanceof Scanner.OutputRedirectionSymbol || token instanceof Scanner.AppendingOutputRedirectionSymbol )); const ioRedirectTokens = this.tokens.slice(beforeArgumentListTokensCount + argumentListTokens.length); return { parameterAssignment: parameterAssignmentTokens, commandWord: commandWordToken, argumentList: argumentListTokens, ioRedirect: ioRedirectTokens, }; } } class IORedirect extends BranchNode { @memoizeAccessor get children(): ASTNode[] { if (this.tokens.length === 1) { return [ new ShellSyntaxNode(this.tokens[0]), new EmptyNode(), ]; } else { return [ new ShellSyntaxNode(this.tokens[0]), new IOFile(this.tokens[1]), ...this.tokens.slice(2).map(token => new UnknownNode(token)), ]; } } } class IOFile extends LeafNode { suggestions(context: PreliminaryAutocompletionContext): Promise { return anyFilesSuggestions(this.value, context.environment.pwd); } } class ShellSyntaxNode extends LeafNode { async suggestions(_context: PreliminaryAutocompletionContext): Promise { return []; } } class ParameterAssignmentList extends BranchNode { @memoizeAccessor get children(): ASTNode[] { return this.tokens.map(token => new ParameterAssignment(token)); } } export class ParameterAssignment extends LeafNode { async suggestions(_context: PreliminaryAutocompletionContext): Promise { return []; } } export class CommandWord extends LeafNode { async suggestions({ environment, aliases, }: PreliminaryAutocompletionContext): Promise { if (this.value.length === 0) { return []; } const relativeExecutablesSuggestions = await executableFilesSuggestions(this.value, environment.pwd); const executables = await io.executablesInPaths(environment.path); return [ ...mapObject(aliases.toObject(), (key, value) => ({label: key, detail: value})), ...loginShell.preCommandModifiers.map(modifier => ({label: modifier})), ...executables.map(name => ({label: name, detail: commandDescriptions[name] || ""})), ...relativeExecutablesSuggestions, ]; } } class ArgumentList extends BranchNode { constructor(childTokens: Scanner.Token[], private command: Command) { super(childTokens); } @memoizeAccessor get children(): ASTNode[] { return this.arguments; } @memoizeAccessor get arguments(): Argument[] { return this.tokens.map((token, index) => new Argument(token, this.command, index + 1)); } } export class Argument extends LeafNode { readonly position: number; readonly command: Command; constructor(token: Scanner.Token, command: Command, position: number) { super(token); this.command = command; this.position = position; } async suggestions(context: PreliminaryAutocompletionContext): Promise { const argument = argumentOfExpandedAST(this, context.aliases); const provider = combine([ environmentVariableSuggestions, PluginManager.autocompletionProviderFor(argument.command.commandWord!.value), ]); return provider({...context, argument: argument}); } } // FIXME: find a better way to search for the argument in the new tree. function argumentOfExpandedAST(argument: Argument, aliases: Aliases) { const commandWord = argument.command.commandWord!; if (aliases.has(commandWord.value)) { const tree = new CompleteCommand(Scanner.scan(serializeReplacing(argument.command, commandWord, aliases.get(commandWord.value)))); for (const current of traverse(tree)) { if (current instanceof Argument && current.value === argument.value) { return current; } } throw "Couldn't find the argument."; } else { return argument; } } export class UnknownNode extends LeafNode { async suggestions(_context: PreliminaryAutocompletionContext): Promise { return []; } } export class EmptyNode extends LeafNode { constructor() { super(new Scanner.Empty()); } // FIXME: a workaround for leafNodeAt to parse, say, `ls |` correctly. get fullEnd() { return Number.MAX_SAFE_INTEGER; } async suggestions(_context: PreliminaryAutocompletionContext): Promise { return []; } } export function leafNodeAt(position: number, node: ASTNode): LeafNode { if (node instanceof LeafNode) { return node; } else if (node instanceof BranchNode) { const childAt = node.children.find(child => child.fullStart <= position && child.fullEnd >= position); if (childAt) { return leafNodeAt(position, childAt); } else { throw `Couldn't find a child at position ${position}`; } } else { throw "Should never happen"; } } function *traverse(node: ASTNode): Iterable { yield node; if (node instanceof BranchNode) { for (const child of node.children) { yield * traverse(child); } } } export function serializeReplacing(tree: ASTNode, focused: LeafNode, replacement: string) { let serialized = ""; for (const current of traverse(tree)) { if (current instanceof LeafNode) { if (current === focused) { serialized += focused.precedingSpaces + replacement + focused.followingSpaces; } else { serialized += current.raw; } } } return serialized; } ================================================ FILE: src/shell/Prompt.ts ================================================ import * as events from "events"; import {scan, Token, expandAliases} from "./Scanner"; import {CompleteCommand} from "./Parser"; import {Session} from "./Session"; export class Prompt extends events.EventEmitter { private _value = ""; private _expandedAst: CompleteCommand; constructor(private session: Session) { super(); } get value(): string { return this._value; } setValue(value: string): void { this._value = value; const tokens = scan(this.value); this._expandedAst = new CompleteCommand(expandAliases(tokens, this.session.aliases)); } get expandedTokens(): Token[] { return this._expandedAst.tokens; } get commandName(): string { if (!this._expandedAst || !this._expandedAst.firstCommand.commandWord) { return ""; } return this._expandedAst.firstCommand.commandWord.value; } get arguments(): Token[] { const argumentList = this._expandedAst.firstCommand.argumentList; if (argumentList) { return argumentList.tokens; } else { return []; } } } ================================================ FILE: src/shell/Scanner.ts ================================================ import {Aliases} from "./Aliases"; export abstract class Token { readonly raw: string; readonly fullStart: number; constructor(raw: string, fullStart: number) { this.raw = raw; this.fullStart = fullStart; } abstract get value(): string; /** * @deprecated */ abstract get escapedValue(): EscapedShellWord; } export class Empty extends Token { constructor() { super("", 0); } get value() { return ""; } get escapedValue() { return this.raw.trim() as EscapedShellWord; } } export abstract class StringLiteral extends Token { get value() { return this.raw.trim().slice(1, -1); } } export class Word extends StringLiteral { get value() { return this.raw.trim().replace(/\\\s/g, " "); } get escapedValue() { return this.raw.trim() as EscapedShellWord; } } export class Pipe extends Token { get value() { return this.raw.trim(); } get escapedValue(): EscapedShellWord { return this.value as EscapedShellWord; } } export class Semicolon extends Token { get value() { return this.raw.trim(); } get escapedValue(): EscapedShellWord { return this.value as EscapedShellWord; } } export class And extends Token { get value() { return this.raw.trim(); } get escapedValue(): EscapedShellWord { return this.value as EscapedShellWord; } } export class Or extends Token { get value() { return this.raw.trim(); } get escapedValue(): EscapedShellWord { return this.value as EscapedShellWord; } } export class InputRedirectionSymbol extends Token { get value() { return this.raw.trim(); } get escapedValue(): EscapedShellWord { return this.value as EscapedShellWord; } } export class OutputRedirectionSymbol extends Token { get value() { return this.raw.trim(); } get escapedValue(): EscapedShellWord { return this.value as EscapedShellWord; } } export class AppendingOutputRedirectionSymbol extends Token { get value() { return this.raw.trim(); } get escapedValue(): EscapedShellWord { return this.value as EscapedShellWord; } } export class CompositeStringLiteral extends StringLiteral { private tokens: Token[]; constructor(tokens: Token[]) { let raw = tokens.map(token => token.raw).join(""); super(raw, tokens[0].fullStart); this.tokens = tokens; } get value() { return this.tokens.map(token => token.value).join(""); } get escapedValue() { return this.tokens.map(token => token.escapedValue).join("") as EscapedShellWord; } } export class SingleQuotedStringLiteral extends StringLiteral { get escapedValue(): EscapedShellWord { return `'${this.value}'` as EscapedShellWord; } } export class DoubleQuotedStringLiteral extends StringLiteral { get escapedValue(): EscapedShellWord { return `"${this.value}"` as EscapedShellWord; } } export class NewLine extends Token { get value() { return this.raw; } get escapedValue() { return this.value as EscapedShellWord; } } export class Invalid extends Token { get value() { return this.raw.trim(); } get escapedValue(): EscapedShellWord { return this.value as EscapedShellWord; } } // All these regex start ^ so that we can look only at the first token. Maybe that should // be part of the scanner so that the regex can be just for the token const patterns = [ { regularExpression: /^(\n)/, tokenConstructor: NewLine, }, { regularExpression: /^(\s*\|\s*)/, tokenConstructor: Pipe, }, { regularExpression: /^(\s*;)/, tokenConstructor: Semicolon, }, { regularExpression: /^(\s*&&)/, tokenConstructor: And, }, { regularExpression: /^(\s*\|\|)/, tokenConstructor: Or, }, { regularExpression: /^(\s*>>)/, tokenConstructor: AppendingOutputRedirectionSymbol, }, { regularExpression: /^(\s*<)/, tokenConstructor: InputRedirectionSymbol, }, { regularExpression: /^(\s*[012]?>)/, tokenConstructor: OutputRedirectionSymbol, }, { regularExpression: /^(\s*"(?:\\"|[^"])*")/, tokenConstructor: DoubleQuotedStringLiteral, }, { regularExpression: /^(\s*'(?:\\'|[^'])*')/, tokenConstructor : SingleQuotedStringLiteral, }, { regularExpression: /^(\s*(?:\\\(|\\\)|\\\s|[a-zA-Z0-9\u0080-\uFFFF+~!@#%^&*_=,.:/?\\-])+)/, tokenConstructor : Word, }, ]; export function scan(input: string): Token[] { const tokens: Token[] = []; let position = 0; while (true) { if (input.length === 0) { return squashLiterals(tokens); } let foundMatch = false; for (const pattern of patterns) { const match = input.match(pattern.regularExpression); if (match) { const token = match[1]; tokens.push(new pattern.tokenConstructor(token, position)); position += token.length; input = input.slice(token.length); foundMatch = true; break; } } if (!foundMatch) { tokens.push(new Invalid(input, position)); return squashLiterals(tokens); } } } // Find sequences of literals with no spaces between them and squash: // they should be one token. function squashLiterals(tokens: Token[]): Token[] { let result: Token[] = []; let i: number = 0; while (i < tokens.length) { let currentComposite: Token[] = []; while (i < tokens.length - 1 && shouldSquash(tokens[i], tokens[i + 1])) { currentComposite.push(tokens[i++]); } if (currentComposite.length > 0) { currentComposite.push(tokens[i++]); // last token in the sequence result.push(new CompositeStringLiteral(currentComposite)); } if (i < tokens.length) { result.push(tokens[i++]); } } return result; } function concatTokens(left: Token[], right: Token[]): Token[] { return left.concat(right); } function shouldSquash(left: Token, right: Token): boolean { return left instanceof StringLiteral && right instanceof StringLiteral && !/\s$/.test(left.raw) // ends with spaces && !/^\s/.test(right.raw); // starts with spaces } export function expandAliases(tokens: Token[], aliases: Aliases): Token[] { if (tokens.length === 0) { return []; } const commandWordToken = tokens[0]; const argumentTokens = tokens.slice(1); if (aliases.has(commandWordToken.value)) { const alias = aliases.get(commandWordToken.value); const aliasTokens = scan(alias); const isRecursive = aliasTokens[0].value === commandWordToken.value; if (isRecursive) { return concatTokens(aliasTokens, argumentTokens); } else { return concatTokens(expandAliases(scan(alias), aliases), argumentTokens); } } else { return tokens; } } export function stringLiteralValue(literal: string): string | undefined { const tokens = scan(literal); if (tokens.length !== 1) { return; } const token = tokens[0]; if (token instanceof DoubleQuotedStringLiteral) { return token.value; } if (token instanceof SingleQuotedStringLiteral) { return token.value; } if (token instanceof Word) { return token.value; } } ================================================ FILE: src/shell/Session.ts ================================================ import {readFileSync} from "fs"; import {Job} from "./Job"; import * as events from "events"; import {PluginManager} from "../PluginManager"; import {Environment, processEnvironment} from "./Environment"; import { homeDirectory, normalizeDirectory, presentWorkingDirectoryFilePath, } from "../utils/Common"; import {OrderedSet} from "../utils/OrderedSet"; import {Aliases, aliasesFromConfig} from "./Aliases"; import * as _ from "lodash"; import {Prompt} from "./Prompt"; import {services} from "../services/index"; export type SessionID = number & {__isSessionID: true}; export class Session extends events.EventEmitter { readonly id: SessionID = Date.now(); jobs: Array = []; readonly environment = new Environment(processEnvironment); readonly aliases = new Aliases(aliasesFromConfig); historicalPresentDirectoriesStack = new OrderedSet(); constructor(private _dimensions: Dimensions = {columns: 80, rows: 25}) { super(); // TODO: We want to deserialize properties only for the first instance // TODO: of Session for the application. this.deserialize(); } createJob(prompt: Prompt): void { const job = new Job(this, prompt); job.execute(); job.once("end", () => { this.emit("job-finished"); this.emit("jobs-changed"); }); this.jobs.push(job); this.emit("job-started"); this.emit("jobs-changed"); } get dimensions(): Dimensions { return this._dimensions; } set dimensions(value: Dimensions) { this._dimensions = value; this.jobs.forEach(job => job.resize()); } get lastJob(): Job | undefined { return _.last(this.jobs); } clearJobs(): void { this.jobs = []; this.emit("jobs-changed"); } close(): void { services.sessions.close(this.id); } get directory(): string { return this.environment.pwd; } set directory(value: string) { let normalizedDirectory = normalizeDirectory(value); if (normalizedDirectory === this.directory) { return; } PluginManager.environmentObservers.forEach(observer => observer.presentWorkingDirectoryWillChange(this, normalizedDirectory), ); this.environment.pwd = normalizedDirectory; this.historicalPresentDirectoriesStack.prepend(normalizedDirectory); PluginManager.environmentObservers.forEach(observer => observer.presentWorkingDirectoryDidChange(this, normalizedDirectory), ); } private deserialize(): void { this.directory = this.readSerialized(presentWorkingDirectoryFilePath, homeDirectory); } private readSerialized(file: string, defaultValue: T): T { try { return JSON.parse(readFileSync(file).toString()); } catch (error) { return defaultValue; } } } ================================================ FILE: src/utils/Common.ts ================================================ import * as walk from "klaw"; import * as Path from "path"; import * as i from "./../Interfaces"; import * as e from "./../Enums"; import * as _ from "lodash"; import * as fs from "fs-extra"; import {KeyCode} from "./../Enums"; import {EnvironmentPath} from "../shell/Environment"; interface FSExtraWalkObject { path: string; stats: fs.Stats; } export function info(...args: any[]): void { print(e.LogLevel.Info, args); } export function log(...args: any[]): void { print(e.LogLevel.Log, args); } export function error(...args: any[]): void { print(e.LogLevel.Error, args); } export function print(level: e.LogLevel, args: Array): void { if ((typeof window !== "undefined") && window.DEBUG) { ((console)[level])(...args); } } export function times(n: number, action: Function): void { for (let i = 0; i !== n; ++i) { action(); } } export const io = { filesIn: async (directoryPath: FullPath): Promise => { if (await io.directoryExists(directoryPath)) { return await fs.readdir(directoryPath); } else { return []; } }, recursiveFilesIn: (directoryPath: string): Promise => { let files: string[] = []; return new Promise(resolve => walk(directoryPath) .on("data", (file: FSExtraWalkObject) => file.stats.isFile() && files.push(file.path)) .on("end", () => resolve(files)), ); }, lstatsIn: async (directoryPath: FullPath): Promise => { return Promise.all((await io.filesIn(directoryPath)).map(async (fileName) => { return {name: fileName, stat: await fs.lstat(Path.join(directoryPath, fileName))}; })); }, fileExists: async (filePath: string): Promise => { if (!await fs.pathExists(filePath)) { return false; } const stat = await fs.lstat(filePath); if (stat.isFile()) { return true; } if (stat.isSymbolicLink()) { const realPath = await io.realPath(filePath); return io.fileExists(realPath); } return false; }, directoryExists: async (directoryPath: string): Promise => { if (await fs.pathExists(directoryPath)) { return (await fs.lstat(directoryPath)).isDirectory(); } else { return false; } }, readFile: async (filePath: string) => (await fs.readFile(filePath)).toString(), executablesInPaths: async (path: EnvironmentPath): Promise => { const allFiles: string[][] = await Promise.all(path.toArray().map(io.filesIn)); return _.uniq(_.flatten(allFiles)); }, realPath: fs.realpath, }; /** * Unlike Path.join, doesn't remove ./ and ../ parts. */ export function joinPath(...parts: string[]) { const initialParts = parts.slice(0, -1).filter(part => part.length); const lastPart = parts[parts.length - 1]; return initialParts.map(normalizeDirectory).join("") + lastPart; } export function normalizeDirectory(directoryPath: string): string { if (directoryPath.endsWith(Path.sep)) { return directoryPath; } else { return directoryPath + Path.sep; } } export function directoryName(path: string): string { const directoryParts = path.split(Path.sep).slice(0, -1); if (directoryParts.length === 0) { return ""; } else { return normalizeDirectory(directoryParts.join(Path.sep)); } } export const isWindows = process.platform === "win32"; export const homeDirectory = process.env[(isWindows) ? "USERPROFILE" : "HOME"]!; export function resolveDirectory(pwd: string, directory: string): FullPath { return normalizeDirectory(resolveFile(pwd, directory)); } export function resolveFile(pwd: string, file: string): FullPath { return Path.resolve(pwd, file.replace(/^~/, homeDirectory)); } export function userFriendlyPath(path: string): string { return path.replace(homeDirectory, "~"); } export async function filterAsync(values: T[], asyncPredicate: (t: T) => Promise): Promise { const filtered = await Promise.all(values.map(asyncPredicate)); return values.filter((_value: T, index: number) => filtered[index]); } export async function reduceAsync(array: E[], initial: A, callback: (a: A, e: E) => Promise): Promise { let accumulator = initial; for (const element of array) { accumulator = await callback(accumulator, element); } return accumulator; } export function pluralize(word: string, count = 2) { return count === 1 ? word : pluralFormOf(word); } const imageExtensions = [ ".jpg", ".jpeg", ".png", ".gif", ]; export function isImage(extension: string) { return imageExtensions.includes(extension); } function pluralFormOf(word: string) { if (word.endsWith("y")) { return word.substring(0, word.length - 1) + "ies"; } else { return word + "s"; } } export function groupWhen(grouper: (a: T, b: T) => boolean, row: T[]): T[][] { if (row.length === 0) return []; if (row.length === 1) return [row]; const result: T[][] = []; const firstValue = row[0]; let currentGroup: T[] = [firstValue]; let previousValue: T = firstValue; row.slice(1).forEach(currentValue => { if (grouper(currentValue, previousValue)) { currentGroup.push(currentValue); } else { result.push(currentGroup); currentGroup = [currentValue]; } previousValue = currentValue; }); result.push(currentGroup); return result; } export function csi(char: string) { return `\x1b[${char}`; } export function ss3(char: string) { return `\x1bO${char}`; } export function normalizeProcessInput(input: string | KeyboardEvent, isCursorKeysModeSet: boolean): string { let text: string; if (typeof input === "string") { text = input; } else { if (input.ctrlKey) { /** * @link https://unix.stackexchange.com/a/158298/201739 */ text = String.fromCharCode(input.key.toUpperCase().charCodeAt(0) - 64); } else if (input.altKey) { /** * The alt key can mean two things: * - send an escape character before special keys such as cursor-keys, or * - act as an extended shift, allowing you to enter codes for Latin-1 values from 160 to 255. * * We currently don't support the second one since it's less frequently used. * For future reference, the correct extended code would be keyCode + 160. * @link http://invisible-island.net/ncurses/ncurses.faq.html#bash_meta_mode */ let char = String.fromCharCode(input.keyCode); if (input.shiftKey) { char = char.toUpperCase(); } else { char = char.toLowerCase(); } text = `\x1b${char}`; } else { text = normalizeKey(input.key, isCursorKeysModeSet); } } return text; } /** * @link https://www.w3.org/TR/uievents/#widl-KeyboardEvent-key */ function normalizeKey(key: string, isCursorKeysModeSet: boolean): string { switch (key) { case "Backspace": return String.fromCharCode(127); case "Delete": /** * @link http://www.macfreek.nl/memory/Backspace_and_Delete_key_reversed */ return csi("3~"); case "Tab": return String.fromCharCode(KeyCode.Tab); case "Enter": return String.fromCharCode(KeyCode.CarriageReturn); case "Escape": return String.fromCharCode(KeyCode.Escape); case "ArrowLeft": return isCursorKeysModeSet ? ss3("D") : csi("D"); case "ArrowUp": return isCursorKeysModeSet ? ss3("A") : csi("A"); case "ArrowRight": return isCursorKeysModeSet ? ss3("C") : csi("C"); case "ArrowDown": return isCursorKeysModeSet ? ss3("B") : csi("B"); case "F1": return ss3("P"); case "F2": return ss3("Q"); case "F3": return ss3("R"); case "F4": return ss3("S"); case "F5": return csi("15~"); case "F6": return csi("17~"); case "F7": return csi("18~"); case "F8": return csi("19~"); case "F9": return csi("20~"); case "F10": return csi("21~"); case "F11": return csi("23~"); case "F12": return csi("24~"); default: return key; } } export function commonPrefix(left: string, right: string) { let i = 0; while (i < left.length && left.charAt(i) === right.charAt(i)) { ++i; } return left.substring(0, i); } export function mapObject(object: Dictionary, mapper: (key: string, value: T) => R): R[] { const result: R[] = []; for (const key of Object.keys(object)) { result.push(mapper(key, object[key])); } return result; } export function escapeFilePath(unescaped: string): string { return unescaped.replace(/([\s'"\[\]<>#$%^&*()])/g, "\\$1"); } const baseConfigDirectory = Path.join(homeDirectory, ".upterm"); export const presentWorkingDirectoryFilePath = Path.join(baseConfigDirectory, "presentWorkingDirectory"); export const historyFilePath = Path.join(baseConfigDirectory, "history.csv"); export const windowBoundsFilePath = Path.join(baseConfigDirectory, "windowBounds"); export function fuzzyMatch(input: string, candidate: string): boolean { function tokenize(string: string) { return string.split(/-|_|:|\//); } const lowerCasedInput = input.toLowerCase(); // A user wants to match by an exact prefix. if (candidate.toLowerCase().startsWith(lowerCasedInput)) { return true; } // A user wants to match by a part of the word, e.g. chr for google-chrome. if (tokenize(candidate).find(token => token.toLowerCase().startsWith(lowerCasedInput))) { return true; } return false; } ================================================ FILE: src/utils/Git.ts ================================================ import {linedOutputOf} from "../PTY"; import * as Path from "path"; import * as fs from "fs"; import {executeCommand} from "../PTY"; import * as _ from "lodash"; import * as ChildProcess from "child_process"; export class Branch { constructor(private refName: string, private _isCurrent: boolean) { } toString(): string { return this.refName; } isCurrent(): boolean { return this._isCurrent; } } export interface ConfigVariable { name: string; value: string; } export type StatusCode = "Unmodified" | "UnstagedModified" | "UnstagedDeleted" | "StagedModified" | "StagedModifiedUnstagedModified" | "StagedModifiedUnstagedDeleted" | "StagedAdded" | "StagedAddedUnstagedModified" | "StagedAddedUnstagedDeleted" | "StagedDeleted" | "StagedDeletedUnstagedModified" | "StagedRenamed" | "StagedRenamedUnstagedModified" | "StagedRenamedUnstagedDeleted" | "StagedCopied" | "StagedCopiedUnstagedModified" | "StagedCopiedUnstagedDeleted" | "UnmergedBothDeleted" | "UnmergedAddedByUs" | "UnmergedDeletedByThem" | "UnmergedAddedByThem" | "UnmergedDeletedByUs" | "UnmergedBothAdded" | "UnmergedBothModified" | "Untracked" | "Ignored" | "Invalid" ; function lettersToStatusCode(letters: string): StatusCode { switch (letters) { case " ": return "Unmodified"; case " M": return "UnstagedModified"; case " D": return "UnstagedDeleted"; case "M ": return "StagedModified"; case "MM": return "StagedModifiedUnstagedModified"; case "MD": return "StagedModifiedUnstagedDeleted"; case "A ": return "StagedAdded"; case "AM": return "StagedAddedUnstagedModified"; case "AD": return "StagedAddedUnstagedDeleted"; case "D ": return "StagedDeleted"; case "DM": return "StagedDeletedUnstagedModified"; case "R ": return "StagedRenamed"; case "RM": return "StagedRenamedUnstagedModified"; case "RD": return "StagedRenamedUnstagedDeleted"; case "C ": return "StagedCopied"; case "CM": return "StagedCopiedUnstagedModified"; case "CD": return "StagedCopiedUnstagedDeleted"; case "DD": return "UnmergedBothDeleted"; case "AU": return "UnmergedAddedByUs"; case "UD": return "UnmergedDeletedByThem"; case "UA": return "UnmergedAddedByThem"; case "DU": return "UnmergedDeletedByUs"; case "AA": return "UnmergedBothAdded"; case "UU": return "UnmergedBothModified"; case "??": return "Untracked"; case "!!": return "Ignored"; default: return "Invalid"; } } export class FileStatus { constructor(private _line: string) { } get value(): string { return this._line.slice(3).trim(); } get code(): StatusCode { return lettersToStatusCode(this._line.slice(0, 2)); } } export type GitDirectoryPath = string & { __isGitDirectoryPath: boolean }; export function isGitDirectory(directory: string): directory is GitDirectoryPath { return fs.existsSync(Path.join(directory, ".git") ); } type BranchesOptions = { directory: GitDirectoryPath; remotes: boolean; tags: boolean; }; export async function currentBranchName(directory: GitDirectoryPath): Promise { try { const output = await executeCommand( "git", ['"symbolic-ref"', '"--short"', '"-q"', '"HEAD"'], directory, ); return output.trim(); } catch (error) { // Doesn't have a name. const output = await executeCommand( "git", ['"rev-parse"', '"--short"', '"HEAD"'], directory, ); return output.trim(); } } export enum RepositoryState { Clean = "clean", Dirty = "dirty", NotRepository = "not-repository", } /** * @link https://stackoverflow.com/questions/3878624/how-do-i-programmatically-determine-if-there-are-uncommited-changes */ export async function repositoryState(directory: string): Promise { return new Promise((resolve, reject) => { const process = ChildProcess.spawn( "git", ["diff-index", "--quiet", "HEAD", "--"], {cwd: directory}, ); process.once("exit", code => { switch (code) { case 0: resolve(RepositoryState.Clean); break; case 1: resolve(RepositoryState.Dirty); break; case 128: resolve(RepositoryState.NotRepository); break; default: reject(`Unknown Git code: ${code}.`); } }); }); } export async function branches({ directory, remotes, tags, }: BranchesOptions): Promise { const currentBranch = await currentBranchName(directory); const promiseHeadsTags = linedOutputOf( "git", ["for-each-ref", "refs/heads ", tags ? "refs/tags " : "", "--format='%(refname:strip=2)'"], directory, ); const promiseRemotes = linedOutputOf( "git", ["for-each-ref", "refs/remotes", "--format='%(refname:strip=3)'"], directory, ); let promiseBranches = [promiseHeadsTags]; if (remotes) promiseBranches.push(promiseRemotes); const allBranches = _.flatten(await Promise.all(promiseBranches)); return allBranches.map(branch => { return new Branch(branch, branch === currentBranch); }); } export async function configVariables(directory: string): Promise { const lines = await linedOutputOf( "git", ["config", "--list"], directory, ); return lines.map(line => { const parts = line.split("="); return { name: parts[0].trim(), value: parts[1] ? parts[1].trim() : "", }; }); } export async function aliases(directory: string): Promise { const variables = await configVariables(directory); return variables .filter(variable => variable.name.indexOf("alias.") === 0) .map(variable => { return { name: variable.name.replace("alias.", ""), value: variable.value, }; }); } export async function remotes(directory: GitDirectoryPath): Promise { return await linedOutputOf("git", ["remote"], directory); } export async function status(directory: GitDirectoryPath): Promise { let lines = await linedOutputOf("git", ["status", "--porcelain"], directory); return lines.map(line => new FileStatus(line)); } export async function repoRoot(directory: GitDirectoryPath): Promise { return (await linedOutputOf("git", ["rev-parse", "--show-toplevel"], directory))[0] as GitDirectoryPath; } ================================================ FILE: src/utils/HistoryTrie.ts ================================================ import * as _ from "lodash"; import {fuzzyMatch} from "./Common"; import {scan} from "../shell/Scanner"; interface TrieNode { value: string; occurrences: number; children: Map; } interface Continuation { value: string; occurrences: number; space: boolean; } function untokenize(tokens: string[]): string { return tokens.join(" "); } function tokenize(string: string) { return scan(string).map(token => token.escapedValue); } function getContinuation(node: TrieNode): Continuation { return { value: node.value, occurrences: node.occurrences, space: node.children.size > 0, }; } export class HistoryTrie { private root: TrieNode = {value: "", occurrences: 0, children: new Map()}; add(input: string | string[], node: TrieNode = this.root) { const tokens = (typeof input === "string") ? tokenize(input.trim()) : input; const token = tokens[0]; if (!token) { return; } if (!node.children.has(token)) { node.children.set(token, {value: token, occurrences: 0, children: new Map()}); } const value = node.children.get(token)!; value.occurrences += 1; this.add(tokens.slice(1), value); } getContinuationsFor(input: string): Continuation[] { const tokens = tokenize(input); if (!tokens.length) { return []; } const path = tokens.slice(0, -1); const currentToken = tokens[tokens.length - 1]; const parentNode = this.getNodeAt(path); if (!parentNode) { return []; } const continuationNodes: TrieNode[] = []; for (let node of parentNode.children.values()) { if (fuzzyMatch(currentToken, node.value)) { continuationNodes.push(node); } } if (continuationNodes.length === 0) { return []; } const isContinuationNonAmbiguous = continuationNodes.length === 1; if (isContinuationNonAmbiguous) { const continuationNode = continuationNodes[0]; const longestNonAmbiguousContinuation = this.getLongestNonAmbiguousPrefix(continuationNode, [continuationNode.value]); const continuation = getContinuation(continuationNode); if (longestNonAmbiguousContinuation.value !== continuation.value) { return [continuation, longestNonAmbiguousContinuation]; } else { return [continuation]; } } else { return _.sortBy(continuationNodes, node => -node.occurrences).map(node => getContinuation(node)); } } private getNodeAt(path: string[], node: TrieNode = this.root): TrieNode | undefined { const token = path[0]; if (!token) { return node; } if (node.children.has(token)) { return this.getNodeAt(path.slice(1), node.children.get(token)!); } } private getLongestNonAmbiguousPrefix(node: TrieNode, path: string[]): Continuation { if (node.children.size === 1) { const key = node.children.keys().next().value; path.push(key); return this.getLongestNonAmbiguousPrefix(node.children.get(key)!, path); } else { const continuation = getContinuation(node); return { value: untokenize(path), occurrences: continuation.occurrences, space: continuation.space, }; } } } ================================================ FILE: src/utils/JSONTree.tsx ================================================ import * as React from "react"; import {colors} from "../views/css/colors"; /* ========================= * React JSONTree * http://eskimospy.com/stuff/react/json/ * Copyright 2014, David Vedder * MIT Licence ========================= */ const listStyle = { padding: "2px 0", listStyleType: "none", }; const labelStyle = { display: "inline-block", marginRight: "0.5em", color: colors.magenta, }; const childrenCount = { WebkitUserSelect: "none", userSelect: "none", cursor: "default", }; type JSONProps = { data: any, keyName?: any, key?: any, initialExpanded?: boolean, }; type JSONState = { expanded?: any, createdChildNodes?: any, }; type JSONValueProps = { value: any, keyName: any, key: any, }; /** * Returns the type of an object as a string. * * @param obj Object The object you want to inspect * @return String The object's type */ let objType = function (obj: any) { return Object.prototype.toString.call(obj).slice(8, -1); }; /** * Creates a React JSON Viewer component for a key and it's associated data * * @param key String The JSON key (property name) for the node * @param value Mixed The associated data for the JSON key * @return Component The React Component for that node */ let grabNode = function (key: any, value: any): any { let nodeType = objType(value); let aKey = key + Date.now(); if (nodeType === "Object") { return ; } else if (nodeType === "Array") { return ; } else if (nodeType === "String") { return ; } else if (nodeType === "Number") { return ; } else if (nodeType === "Boolean") { return ; } else if (nodeType === "Null") { return ; } else { return
How did this happen? {nodeType}
; } }; /** * Mixin for stopping events from propagating and collapsing our tree all * willy nilly. */ let squashClick = function (e: any) { e.stopPropagation(); }; /** * Array node class. If you have an array, this is what you should use to * display it. */ class JSONArrayNode extends React.Component { private itemString: boolean | string; private needsChildNodes: boolean | Array; private renderedChildren: Array; constructor(props: JSONProps) { super(props); this.renderedChildren = []; this.needsChildNodes = []; this.itemString = false; this.state = { expanded: props.initialExpanded, createdChildNodes: false, }; } getDefaultProps() { return { data: [], initialExpanded: false }; } componentWillReceiveProps() { // resets our caches and flags we need to build child nodes again this.renderedChildren = []; this.itemString = false; this.needsChildNodes = true; } /** * Returns the child nodes for each element in the array. If we have * generated them previously, we return from cache, otherwise we create * them. */ getChildNodes() { let childNodes: Array = []; if (this.state.expanded && this.needsChildNodes) { for (let i = 0; i < this.props.data.length; i += 1) { childNodes.push(grabNode(i, this.props.data[i])); } this.needsChildNodes = false; this.renderedChildren = childNodes; } return this.renderedChildren; } /** * Returns the "n Items" string for this node, generating and * caching it if it hasn't been created yet. */ getItemString() { if (!this.itemString) { let lenWord = (this.props.data.length === 1) ? " Item" : " Items"; this.itemString = this.props.data.length + lenWord; } return this.itemString; } render(): JSX.Element { let childNodes = this.getChildNodes(); let childListStyle = { display: (this.state.expanded) ? "block" : "none", }; let cls = "array jsonTreeParentNode"; cls += (this.state.expanded) ? " expanded" : ""; return
  • { e.stopPropagation(); this.setState({expanded: !this.state.expanded}); }}> {"[...] "} {this.getItemString()}
      {childNodes}
  • ; } } /** * Object node class. If you have an object, this is what you should use to * display it. */ class JSONObjectNode extends React.Component { private itemString: boolean | string; private needsChildNodes: boolean | Array; private renderedChildren: Array; constructor(props: JSONProps) { super(props); this.renderedChildren = []; this.needsChildNodes = []; this.itemString = false; this.state = { expanded: props.initialExpanded, createdChildNodes: false, }; } getDefaultProps() { return { data: [], initialExpanded: false }; } componentWillReceiveProps() { // resets our caches and flags we need to build child nodes again this.renderedChildren = []; this.itemString = false; this.needsChildNodes = true; } /** * Returns the child nodes for each element in the object. If we have * generated them previously, we return from cache, otherwise we create * them. */ getChildNodes() { if (this.state.expanded && this.needsChildNodes) { let obj = this.props.data; let childNodes: Array = []; for (let k in obj) { if (obj.hasOwnProperty(k)) { childNodes.push( grabNode(k, obj[k])); } } this.needsChildNodes = false; this.renderedChildren = childNodes; } return this.renderedChildren; } /** * Returns the "n Items" string for this node, generating and * caching it if it hasn't been created yet. */ getItemString() { if (!this.itemString) { let obj = this.props.data; let len = 0; let lenWord = " Items"; for (let k in obj) { if (obj.hasOwnProperty(k)) { len += 1; } } if (len === 1) { lenWord = " Item"; } this.itemString = len + lenWord; } return this.itemString; } render(): JSX.Element { let childListStyle = { display: (this.state.expanded) ? "block" : "none", }; let cls = "object jsonTreeParentNode"; cls += (this.state.expanded) ? " expanded" : ""; return (
  • { e.stopPropagation(); this.setState({expanded: !this.state.expanded}); }}> {"{...} "} {this.getItemString()}
      {this.getChildNodes()}
  • ); } } /** * String node component */ class JSONStringNode extends React.Component { render(): JSX.Element { return (
  • "{this.props.value}"
  • ); } } /** * Number node component */ class JSONNumberNode extends React.Component { render(): JSX.Element { return (
  • {this.props.value}
  • ); } } /** * Null node component */ class JSONNullNode extends React.Component { render(): JSX.Element { return (
  • null
  • ); } } /** * Boolean node component */ class JSONBooleanNode extends React.Component { render(): JSX.Element { let truthString = (this.props.value) ? "true" : "false"; return
  • {truthString}
  • ; } } /** * JSONTree component. This is the 'viewer' base. Pass it a `data` prop and it * will render that data, or pass it a `source` URL prop and it will make * an XMLHttpRequest for said URL and render that when it loads the data. * * The first node it draws will be expanded by default. */ export class JSONTree extends React.Component { render(): JSX.Element { let nodeType = objType(this.props.data); let rootNode: JSX.Element; if (nodeType === "Object") { rootNode = ; } else if (nodeType === "Array") { rootNode = ; } else { return How did you manage that?; } return
      {rootNode}
    ; } } ================================================ FILE: src/utils/Link.tsx ================================================ import * as React from "react"; import * as e from "electron"; export const Link: React.StatelessComponent<{absolutePath: string, children: any}> = ({ absolutePath, children, }) => e.shell.openExternal(`file://${absolutePath}`)} >{children}; ================================================ FILE: src/utils/ManPageParsingUtils.ts ================================================ import {Suggestion} from "../plugins/completion_utils/Common"; export const combineManPageLines = (lines: string[]) => lines .map(line => line.trim()) .reduce( (memo, next) => { if (next.endsWith("-")) { return memo.concat(next.slice(0, -1)); } else { return memo.concat(next, " "); } }, "", ) .trim(); // Man pages have backspace literals, so "apply" them, and remove excess whitespace. export const preprocessManPage = (contents: string) => contents.replace(/.\x08/g, "").trim(); export const extractManPageSections = (contents: string) => { const lines = contents.split("\n"); let currentSection = ""; let sections: { [section: string]: string[] } = {}; lines.forEach((line: string) => { if (line.startsWith(" ") || line === "") { sections[currentSection].push(line); } else { currentSection = line; if (!sections[currentSection]) { sections[currentSection] = []; } } }); return sections; }; const isShortFlagWithoutArgument = (manPageLine: string) => /^ *-(\w) *(.*)$/.test(manPageLine); export const extractManPageSectionParagraphs = (contents: string[]) => { let filteredContents: string[] | undefined = undefined; const firstFlag = contents.find(isShortFlagWithoutArgument); if (firstFlag) { const flagMatch = firstFlag.match(/^( *-\w *)/); const flagIndentation = " ".repeat(((flagMatch || [""])[0]).length); filteredContents = contents.filter((line, index, array) => { if (index === 0 || index === array.length - 1) { return true; } if ( line === "" && array[index - 1].startsWith(flagIndentation) && array[index + 1].startsWith(flagIndentation) ) { return false; } return true; }); } return (filteredContents ? filteredContents : contents) .reduce( (memo, next) => { if (next === "") { memo.push([]); } else { memo[memo.length - 1].push(next); } return memo; }, [[]], ) .filter(lines => lines.length > 0); }; export const suggestionFromFlagParagraph = (paragraph: string[]): Suggestion | undefined => { const shortFlagWithArgument = paragraph[0].match(/^ *-(\w) (\w*)$/); const shortFlagWithoutArgument = paragraph[0].match(/^ *-(\w) *(.*)$/); if (shortFlagWithArgument) { const flag = shortFlagWithArgument[1]; const detail = combineManPageLines(paragraph.slice(1)); return { label: `-${flag}`, detail: detail, }; } else if (shortFlagWithoutArgument) { const flag = shortFlagWithoutArgument[1]; const detail = combineManPageLines([shortFlagWithoutArgument[2], ...paragraph.slice(1)]); return { label: `-${flag}`, detail: detail, }; } else { return undefined; } }; ================================================ FILE: src/utils/ManPages.ts ================================================ import {execFile} from "child-process-promise"; import {contextIndependent, unique, Suggestion} from "../plugins/completion_utils/Common"; import { preprocessManPage, extractManPageSections, extractManPageSectionParagraphs, suggestionFromFlagParagraph, } from "./ManPageParsingUtils"; // Note: this is still pretty experimental. If you want to do man page parsing // for a new command, expect to have to make some changes here. // TODO: Fix -l option for locate // TODO: Handle nested options. Unblocks: // dd const manPageToOptions = async (command: string, section = "DESCRIPTION"): Promise => { // use execFile to prevent a command like "; echo test" from running the "echo test" const {stdout, stderr} = await execFile("man", [command], {}); if (stderr) { throw `Error in retrieving man page: ${command}`; } // "Apply" backspace literals const manContents = preprocessManPage(stdout); const manSections = extractManPageSections(manContents); // Make sure section is in manSections if (!(section in manSections)) { throw `Error in retrieving section "${section}" from man page: ${command}`; } // Split the description section (which contains the flags) into paragraphs /* tslint:disable:no-string-literal */ const manDescriptionParagraphs: string[][] = extractManPageSectionParagraphs(manSections[section]); /* tslint:enable:no-string-literal */ // Extract the paragraphs that describe flags, and parse out the flag data return manDescriptionParagraphs.map(suggestionFromFlagParagraph).filter((s: Suggestion | undefined) => s !== undefined) as Suggestion[]; }; export const manPageOptions = (command: string, section = "DESCRIPTION") => unique(contextIndependent(() => manPageToOptions(command, section))); ================================================ FILE: src/utils/OrderedSet.ts ================================================ export abstract class AbstractOrderedSet { constructor(private storageGetter: () => T[], private storageSetter: (t: T[]) => void) { } prepend(element: T) { this.remove(element); this.storageSetter([element].concat(this.storageGetter())); } remove(toRemove: T) { this.removeWhere(existing => existing === toRemove); } removeWhere(removePredicate: (existing: T) => boolean) { this.storageSetter(this.storageGetter().filter(path => !removePredicate(path))); } get size() { return this.storageGetter().length; } at(index: number): T | undefined { if (index >= this.size) { return undefined; } else { return this.storageGetter()[index]; } } toArray() { return this.storageGetter(); } } export class OrderedSet extends AbstractOrderedSet { constructor() { let storage: T[] = []; super(() => storage, updatedStorage => storage = updatedStorage); } } ================================================ FILE: src/utils/Process.ts ================================================ import {executeCommandWithShellConfig} from "../PTY"; import {intersection} from "lodash"; // http://linuxcommand.org/man_pages/ps1.html export interface User { ruserid: string; ruser: string; euserid: string; euser: string; } export interface Group { rgroupid: string; rgroup: string; egroupid: string; egroup: string; } export interface Terminal { name: string; ruser: string; } export interface Process { pid: string; time: string; ruser: string; cmd: string; } export interface Session { sid: string; ruser: string; rgroup: string; } const ignoreUsers: string[] = ["nobody"]; const ignoreGroups: string[] = ["nobody"]; const resultSet = (result: string[]) => { const numColumns = result[0].trim().replace(/ +(?= )/g, "").split(" ").length; return result.splice(1) .map(i => i.trim().replace(/ +(?= )/g, "").split(" ", numColumns)); }; export const users = async(): Promise => { const pInfo: string[][] = resultSet(await executeCommandWithShellConfig("ps -eo ruid,ruser,euid,euser")); return pInfo.map(p => {ruserid: p[0], ruser: p[1], euserid: p[2], euser: p[3]}) .filter(p => intersection(ignoreUsers, [p.ruser, p.euser]).length === 0); }; export const groups = async(): Promise => { const pInfo: string[][] = resultSet(await executeCommandWithShellConfig("ps -eo rgid,rgroup,egid,egroup")); return pInfo.map(p => {rgroupid: p[0], rgroup: p[1], egroupid: p[2], egroup: p[3]}) .filter(p => intersection(ignoreGroups, [p.rgroup, p.egroup]).length === 0); }; export const terminals = async(): Promise => { const pInfo: string[][] = resultSet(await executeCommandWithShellConfig("ps -eo tty,ruser")); return pInfo.filter(p => p[0] !== "?") .map(p => {name: p[0], ruser: p[1]}); }; export const processes = async(): Promise => { const pInfo: string[][] = resultSet(await executeCommandWithShellConfig("ps -eo pid,time,ruser,cmd")); return pInfo.map(p => {pid: p[0], time: p[1], ruser: p[2], cmd: p[3]}); }; export const sessions = async(): Promise => { const pInfo: string[][] = resultSet(await executeCommandWithShellConfig("ps -eo sid,ruser,rgroup")); return pInfo.map(p => {sid: p[0], rgroup: p[1]}); }; ================================================ FILE: src/utils/Shell.ts ================================================ import {basename} from "path"; import * as Path from "path"; import {resolveFile, io, isWindows, filterAsync, homeDirectory} from "./Common"; import {executeCommandWithShellConfig} from "../PTY"; abstract class Shell { abstract get executableName(): string; abstract get configFiles(): string[]; abstract get noConfigSwitches(): string[]; abstract get executeCommandSwitches(): string[]; abstract get interactiveCommandSwitches(): string[]; abstract get preCommandModifiers(): string[]; abstract get commandExecutorPath(): string; abstract get environmentCommand(): string; abstract loadAliases(): Promise; abstract combineCommands(commands: string[]): string; async existingConfigFiles(): Promise { const resolvedConfigFiles = this.configFiles.map(fileName => resolveFile(homeDirectory, fileName)); return await filterAsync(resolvedConfigFiles, io.fileExists); } } abstract class UnixShell extends Shell { get executeCommandSwitches() { return ["-c"]; } get interactiveCommandSwitches() { return ["-i", "-c"]; } get commandExecutorPath() { return "/bin/bash"; } get environmentCommand() { return "env"; } loadAliases() { return executeCommandWithShellConfig("alias"); } combineCommands(commands: string[]) { return `'${commands.join("; ")}'`; } } class Bash extends UnixShell { get executableName() { return "bash"; } get configFiles() { return [ // List drawn from GNU bash 4.3 man page INVOCATION section. // ~/.bashrc is only supposed to be used for non-login shells // and ~/.bash_profile is only supposed to be used for login // shells, but load both anyway because that's what people expect. "/etc/profile", "~/.bash_profile", "~/.bash_login", "~/.profile", "~/.bashrc", ]; } get noConfigSwitches() { return ["--noprofile", "--norc"]; } get preCommandModifiers(): string[] { return []; } } class ZSH extends UnixShell { get executableName() { return "zsh"; } get configFiles() { return [ // List drawn from zhs 5.0.8 man page STARTUP/SHUTDOWN FILES section. "/etc/zshenv", Path.join(process.env.ZDOTDIR || "~", ".zshenv"), "/etc/zprofile", Path.join(process.env.ZDOTDIR || "~", ".zprofile"), "/etc/zshrc", Path.join(process.env.ZDOTDIR || "~", ".zshrc"), "/etc/zlogin", Path.join(process.env.ZDOTDIR || "~", ".zlogin"), // This one is not listed in the man pages, but some zsh installations do use it. "~/.zsh_profile", ]; } get noConfigSwitches() { return ["--no-globalrcs", "--no-rcs"]; } get preCommandModifiers() { return [ "-", "noglob", "nocorrect", "exec", "command", ]; } } class Cmd extends Shell { static get cmdPath() { return Path.join(process.env.WINDIR!, "System32", "cmd.exe"); } get executableName() { return "cmd.exe"; } get configFiles() { return [ ]; } get executeCommandSwitches() { return ["/c"]; } get interactiveCommandSwitches() { return ["/c"]; } get noConfigSwitches() { return []; } get preCommandModifiers(): string[] { return []; } get commandExecutorPath() { return Cmd.cmdPath; } get environmentCommand() { return "set"; } combineCommands(commands: string[]) { return `"${commands.join(" && ")}`; } async loadAliases() { return []; } } const supportedShells: Dictionary = { bash: new Bash(), zsh: new ZSH(), "cmd.exe": new Cmd(), }; const shell = (): string => { const shellName = process.env.SHELL ? basename(process.env.SHELL!) : ""; if (shellName in supportedShells) { return shellName; } else { const defaultShell = isWindows ? Cmd.cmdPath : "/bin/bash"; console.error(`${shellName} is not supported; defaulting to ${defaultShell}`); return defaultShell; } }; export const loginShell: Shell = supportedShells[basename(shell())]; ================================================ FILE: src/views/ApplicationComponent.tsx ================================================ import {type as osType} from "os"; import * as classNames from "classnames"; import {TabHeaderComponent, Props} from "./TabHeaderComponent"; import * as React from "react"; import {ipcRenderer} from "electron"; import {remote} from "electron"; import * as css from "./css/styles"; import {SearchComponent} from "./SearchComponent"; import {TabComponent} from "./TabComponent"; import {SessionID} from "../shell/Session"; import {services} from "../services"; import * as _ from "lodash"; type ApplicationState = { tabs: Array<{id: number, sessionIDs: SessionID[]; focusedSessionID: SessionID}>; focusedTabIndex: number; }; export class ApplicationComponent extends React.Component<{}, ApplicationState> { tabComponents: TabComponent[]; constructor(props: {}) { super(props); const sessionID = services.sessions.create(); this.state = { tabs: [{ id: Date.now(), sessionIDs: [sessionID], focusedSessionID: sessionID, }], focusedTabIndex: 0, }; services.window.onResize.subscribe(() => this.resizeAllSessions()); services.window.onClose.subscribe(() => services.sessions.closeAll()); services.sessions.onClose.subscribe(id => this.removeSessionFromState(id)); services.font.onChange.subscribe(() => { this.forceUpdate(); this.resizeAllSessions(); }); ipcRenderer.on("change-working-directory", (_event: Event, directory: string) => this.focusedSession.directory = directory, ); } render() { let tabs: React.ReactElement[] | undefined; if (this.state.tabs.length > 1) { tabs = this.state.tabs.map((tab, index: number) => this.setState({focusedTabIndex: index})} closeHandler={(event: React.MouseEvent) => { services.sessions.close(this.state.tabs[index].sessionIDs); event.stopPropagation(); event.preventDefault(); }} />, ); } this.tabComponents = []; return (
      {tabs}
    {this.state.tabs.map((tabProps, index) => { const state = this.cloneState(); state.tabs[state.focusedTabIndex].focusedSessionID = id; this.setState(state); }} ref={tabComponent => this.tabComponents[index] = tabComponent!}/>)}
    ); } /** * is Mac OS */ isMacOS() { return "Darwin" === osType(); } /** * Tab methods. */ get focusedTabComponent() { return this.tabComponents[this.state.focusedTabIndex]; } addTab(): void { if (this.state.tabs.length < 9) { const id = services.sessions.create(); const state = this.cloneState(); state.tabs.push({ id: Date.now(), sessionIDs: [id], focusedSessionID: id, }); state.focusedTabIndex = state.tabs.length - 1; this.setState(state); } else { remote.shell.beep(); } } focusPreviousTab() { if (this.state.focusedTabIndex !== 0) { this.focusTab(this.state.focusedTabIndex - 1); } } focusNextTab() { if (this.state.focusedTabIndex !== this.state.tabs.length - 1) { this.focusTab(this.state.focusedTabIndex + 1); } } focusTab(index: number): void { if (index === 8) { index = this.state.tabs.length - 1; } if (this.state.tabs.length > index) { this.setState({focusedTabIndex: index}); } else { remote.shell.beep(); } } closeFocusedTab() { const sessionIDs = this.state.tabs[this.state.focusedTabIndex].sessionIDs; services.sessions.close(sessionIDs); } /** * Session methods. */ get focusedSession() { return services.sessions.get(this.state.tabs[this.state.focusedTabIndex].focusedSessionID); } closeFocusedSession() { services.sessions.close(this.focusedSession.id); } otherSession(): void { const state = this.cloneState(); const tabState = state.tabs[state.focusedTabIndex]; if (tabState.sessionIDs.length < 2) { const id = services.sessions.create(); tabState.sessionIDs.push(id); tabState.focusedSessionID = id; this.setState(state, () => this.resizeTabSessions(state.focusedTabIndex)); } else { tabState.focusedSessionID = tabState.sessionIDs.find(id => id !== tabState.focusedSessionID)!; this.setState(state); } } private resizeTabSessions(tabIndex: number): void { this.tabComponents[tabIndex].sessionComponents.forEach(sessionComponent => sessionComponent.resizeSession()); } private resizeAllSessions() { this.tabComponents.forEach(tabComponent => { tabComponent.sessionComponents.forEach(sessionComponent => sessionComponent.resizeSession()); }); } private removeSessionFromState(id: SessionID) { const state = this.cloneState(); const tabIndex = state.tabs.findIndex(tabState => tabState.sessionIDs.includes(id)); const tabState = state.tabs[tabIndex]; if (tabState.sessionIDs.length === 1) { this.removeTabFromState(tabIndex); } else { const sessionIndex = tabState.sessionIDs.findIndex(id => id === tabState.focusedSessionID); tabState.sessionIDs.splice(sessionIndex, 1); tabState.focusedSessionID = tabState.sessionIDs[0]; this.setState(state, () => this.resizeTabSessions(tabIndex)); } } private removeTabFromState(index: number): void { const state = this.cloneState(); state.tabs.splice(index, 1); state.focusedTabIndex = Math.max(0, index - 1); if (state.tabs.length === 0) { ipcRenderer.send("quit"); } else { this.setState(state); } } /** * Return a deep clone of the state in order not to * accidentally mutate it. */ private cloneState(): ApplicationState { return _.cloneDeep(this.state); } } ================================================ FILE: src/views/JobComponent.tsx ================================================ import * as React from "react"; import {Job} from "../shell/Job"; import {OutputComponent} from "./OutputComponent"; import {JobHeaderComponent} from "./JobHeaderComponent"; import {Status} from "../Enums"; interface Props { job: Job; jobStatus: Status; isFocused: boolean; } interface State { prettify: boolean; } export class JobComponent extends React.Component { constructor(props: Props) { super(props); this.state = { prettify: true, }; } componentDidMount() { this.props.job.on("status", () => this.forceUpdate()); /** * Without this a job is below viewport when you * scroll up and then execute a job. */ requestAnimationFrame(() => this.jobNode.scrollIntoView()); } shouldComponentUpdate(nextProps: Props, nextState: State) { return this.state.prettify !== nextState.prettify || this.props.jobStatus === Status.InProgress || this.props.jobStatus !== nextProps.jobStatus; } render() { let output: React.ReactElement; let canBePrettified = this.props.job.canBePrettified(); if (canBePrettified && this.state.prettify) { output = this.props.job.prettify(); } else { output = ; } return (
    { // Show not prettified output this.setState({prettify: !this.state.prettify}); }} isPrettified={this.state.prettify} /> {output}
    ); } private get jobNode(): HTMLDivElement { /* tslint:disable:no-string-literal */ return this.refs["job"] as HTMLDivElement; } } ================================================ FILE: src/views/JobHeaderComponent.tsx ================================================ import * as React from "react"; import {PrettifyToggleComponent} from "./PrettifyToggleComponent"; import {Job} from "../shell/Job"; interface Props { job: Job; prettifyToggler: () => void; isPrettified: boolean; showPrettifyToggle: boolean; } export class JobHeaderComponent extends React.Component { render() { const prettifyToggle = this.props.showPrettifyToggle ? : null; return (
    {this.props.job.prompt.value}
    {prettifyToggle}
    ); } } ================================================ FILE: src/views/Main.tsx ================================================ import {handleUserEvent} from "./keyevents/Keybindings"; import {handleMouseEvent} from "./mouseevents/MouseEvents"; process.env.PATH = "/usr/local/bin:" + process.env.PATH; process.env.NODE_ENV = process.env.NODE_ENV || "production"; process.env.LANG = process.env.LANG || "en_US.UTF-8"; process.env.COLORTERM = "truecolor"; process.env.TERM = "xterm-256color"; import {loadAliasesFromConfig} from "../shell/Aliases"; const reactDOM = require("react-dom"); import * as React from "react"; import {ApplicationComponent} from "./ApplicationComponent"; import {loadAllPlugins} from "../PluginManager"; import {loadEnvironment} from "../shell/Environment"; import {UserEvent, MouseEvent} from "../Interfaces"; import {remote} from "electron"; import {buildMenuTemplate} from "./menu/Menu"; const browserWindow = remote.BrowserWindow.getAllWindows()[0]; document.addEventListener( "dragover", function(event) { event.preventDefault(); return false; }, false, ); async function main() { // Should be required before mounting Application. require("../monaco/PromptTheme"); require("../monaco/ShellLanguage"); require("../monaco/ShellHistoryLanguage"); // FIXME: Remove loadAllPlugins after switching to Webpack (because all the files will be loaded at start anyway). await Promise.all([loadAllPlugins(), loadEnvironment(), loadAliasesFromConfig()]); const application: ApplicationComponent = reactDOM.render( , document.getElementById("react-entry-point"), ); const template = buildMenuTemplate(remote.app, browserWindow, application); remote.Menu.setApplicationMenu(remote.Menu.buildFromTemplate(template)); const userEventHandler = (event: UserEvent) => handleUserEvent( application, window.search, event, ); const mouseEventHandler = (event: MouseEvent) => handleMouseEvent( application, event, ); document.body.addEventListener("keydown", userEventHandler, true); document.body.addEventListener("paste", userEventHandler, true); document.body.addEventListener("drop", mouseEventHandler, true); require("../plugins/JobFinishedNotifications"); require("../plugins/UpdateLastPresentWorkingDirectory"); require("../plugins/SaveHistory"); require("../plugins/SaveWindowBounds"); require("../plugins/AliasSuggestions"); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", main, false); } else { main(); } ================================================ FILE: src/views/OutputComponent.tsx ================================================ import * as React from "react"; import {Char} from "../Char"; import {groupWhen} from "../utils/Common"; import {List} from "immutable"; import * as css from "./css/styles"; import {Job} from "../shell/Job"; import {Status} from "../Enums"; const CharGroupComponent = ({group}: {group: Char[]}) => { const attributes = group[0].attributes; return ( {group.map(char => char.value).join("")} ); }; interface RowProps { row: List; } export class RowComponent extends React.Component { shouldComponentUpdate(nextProps: RowProps) { return this.props.row !== nextProps.row; } render() { const charGroups = groupWhen((a, b) => a.attributes === b.attributes, this.props.row.toArray()); const charGroupComponents = charGroups.map((charGroup: Char[], index: number) => , ); return (
    {charGroupComponents}
    ); } } interface Props { job: Job; } export class OutputComponent extends React.Component { componentDidMount() { this.props.job.once("data", () => this.forceUpdate()); } componentDidUpdate() { this.props.job.once("data", () => this.forceUpdate()); } render() { const output = this.props.job.output; const buffer = output.activeBuffer; const showCursor = this.props.job.status === Status.InProgress && (buffer._showCursor || buffer._blinkCursor); const cursorComponent = showCursor ? : undefined; const rowComponents = buffer.map(row => ); return (
    {cursorComponent} {rowComponents}
    ); } } ================================================ FILE: src/views/PrettifyToggleComponent.tsx ================================================ import * as React from "react"; import {fontAwesome} from "./css/FontAwesome"; interface Props { prettifyToggler: () => void; isPrettified: boolean; } export const PrettifyToggleComponent = (props: Props) => {fontAwesome.magic} ; ================================================ FILE: src/views/PromptComponent.tsx ================================================ /// import * as _ from "lodash"; import * as React from "react"; import {Prompt} from "../shell/Prompt"; import {scan} from "../shell/Scanner"; import {Session} from "../shell/Session"; import {services} from "../services/index"; interface Props { session: Session; isFocused: boolean; } enum Mode { Normal = "normal", HistorySearch = "history-search", } interface State { displayedHistoryRecordID: number | undefined; mode: Mode; } export class PromptComponent extends React.Component { private prompt: Prompt; private editor: monaco.editor.IStandaloneCodeEditor; private model = monaco.editor.createModel("", "shell", monaco.Uri.parse(`shell://${this.props.session.id}`)); private historyModel = monaco.editor.createModel("", "shell-history", monaco.Uri.parse(`shell-history://${this.props.session.id}`)); /* tslint:disable:member-ordering */ constructor(props: Props) { super(props); this.prompt = new Prompt(this.props.session); this.state = { displayedHistoryRecordID: undefined, mode: Mode.Normal, }; } componentDidMount() { this.editor = monaco.editor.create(this.promptContentNode, { theme: "upterm-prompt-theme", model: this.model, lineNumbers: "off", fontSize: services.font.size + 2, fontFamily: services.font.family, suggestFontSize: services.font.size, minimap: {enabled: false}, scrollbar: { vertical: "hidden", horizontal: "hidden", }, overviewRulerLanes: 0, quickSuggestions: true, quickSuggestionsDelay: 0, parameterHints: true, iconsInSuggestions: false, wordBasedSuggestions: false, }); services.font.onChange.subscribe(() => { this.editor.updateOptions({ fontSize: services.font.size * 1.2, fontFamily: services.font.family, suggestFontSize: services.font.size, }); this.editor.layout(); }); this.editor.addCommand( monaco.KeyCode.UpArrow, () => this.setPreviousHistoryItem(), "!suggestWidgetVisible", ); this.editor.addCommand( monaco.KeyMod.WinCtrl | monaco.KeyCode.KEY_P, () => this.setPreviousHistoryItem(), "!suggestWidgetVisible", ); this.editor.addCommand( monaco.KeyCode.DownArrow, () => this.setNextHistoryItem(), "!suggestWidgetVisible", ); this.editor.addCommand( monaco.KeyMod.WinCtrl | monaco.KeyCode.KEY_N, () => this.setNextHistoryItem(), "!suggestWidgetVisible", ); this.addShortcut( monaco.KeyMod.WinCtrl | monaco.KeyCode.KEY_B, "cursorLeft", ); this.addShortcut( monaco.KeyMod.Alt | monaco.KeyCode.KEY_B, "cursorWordStartLeft", ); this.addShortcut( monaco.KeyMod.WinCtrl | monaco.KeyCode.KEY_F, "cursorRight", ); this.addShortcut( monaco.KeyMod.Alt | monaco.KeyCode.KEY_F, "cursorWordEndRight", ); this.addShortcut( monaco.KeyMod.WinCtrl | monaco.KeyCode.KEY_W, "deleteWordLeft", ); this.addShortcut( monaco.KeyMod.Alt | monaco.KeyCode.KEY_D, "deleteWordRight", ); this.addShortcut( monaco.KeyMod.WinCtrl | monaco.KeyCode.KEY_D, "deleteRight", ); this.unbindDefaultAction("editor.action.outdentLines"); this.unbindDefaultAction("editor.action.indentLines"); this.unbindDefaultAction("actions.find"); this.unbindDefaultAction("editor.action.gotoLine"); this.focus(); } componentDidUpdate(prevProps: Props, prevState: State) { if (!prevProps.isFocused && this.props.isFocused) { this.focus(); } if (prevState.mode !== this.state.mode) { if (this.isInHistorySearchMode) { this.editor.setModel(this.historyModel); this.setValue(this.model.getValue()); this.triggerSuggest(); } else { this.editor.setModel(this.model); this.setValue(this.historyModel.getValue()); } } } render() { return (
    ); } setHistorySearchMode() { this.setState({mode: Mode.HistorySearch}); } setNormalMode() { this.setState({mode: Mode.Normal}); } acceptSelectedSuggestion() { this.editor.trigger("", "acceptSelectedSuggestion", {}); if (this.isInHistorySearchMode) { this.setNormalMode(); } else { this.triggerSuggest(); } } get isInHistorySearchMode(): boolean { return this.state.mode === Mode.HistorySearch; } focus(): void { this.editor.focus(); } clear(): void { this.setValue(""); } onReturnKeyPress(): void { if (this.isInHistorySearchMode) { this.acceptSelectedSuggestion(); } else { this.execute(); } } async appendLastLArgumentOfPreviousCommand(): Promise { const latestHistoryRecord = services.history.latest; if (latestHistoryRecord) { this.setValue(this.prompt.value + _.last(scan(latestHistoryRecord.command))!.value); } } setValue(value: string): void { this.editor.setValue(value); this.editor.setPosition({lineNumber: 1, column: value.length + 1}); this.prompt.setValue(value); this.focus(); } insertValueInPlace(value: string): void { this.editor.trigger("keyboard", "type", {text: value}); this.focus(); } private async execute(): Promise { let promptText = this.editor.getValue(); this.prompt.setValue(promptText); if (!this.isEmpty()) { this.props.session.createJob(this.prompt); this.editor.setValue(""); this.setState({ displayedHistoryRecordID: undefined, }); } } private setPreviousHistoryItem(): void { const currentID = this.state.displayedHistoryRecordID; if (currentID) { const currentRecord = services.history.get(currentID); const previousRecord = _.findLast( services.history.all, record => record.id < currentID && record.command !== currentRecord.command, ); if (previousRecord) { this.setValue(previousRecord.command); this.setState({displayedHistoryRecordID: previousRecord.id}); } } else { const previousRecord = services.history.latest; if (previousRecord) { this.setValue(previousRecord.command); this.setState({displayedHistoryRecordID: previousRecord.id}); } } } private setNextHistoryItem(): void { const currentID = this.state.displayedHistoryRecordID; if (currentID) { const currentRecord = services.history.get(currentID); const nextRecord = _.find( services.history.all, record => record.id > currentID && record.command !== currentRecord.command, ); if (nextRecord) { this.setValue(nextRecord.command); this.setState({displayedHistoryRecordID: nextRecord.id}); } else { this.setValue(""); this.setState({displayedHistoryRecordID: undefined}); } } } private get promptContentNode(): HTMLDivElement { /* tslint:disable:no-string-literal */ return this.refs["prompt-content"] as HTMLDivElement; } private isEmpty(): boolean { return this.prompt.value.replace(/\s/g, "").length === 0; } private triggerSuggest() { this.editor.trigger(this.editor.getValue(), "editor.action.triggerSuggest", {}); } private addShortcut(keybinding: number, handlerId: string) { this.editor.addCommand( keybinding, () => this.editor.trigger("", handlerId, {}), "", ); } private unbindDefaultAction(handlerId: string) { (this.editor as any)._standaloneKeybindingService.addDynamicKeybinding(`-${handlerId}`); } } ================================================ FILE: src/views/SearchComponent.tsx ================================================ import * as React from "react"; import {remote} from "electron"; import {fontAwesome} from "./css/FontAwesome"; export class SearchComponent extends React.Component<{}, {}> { private webContents: Electron.WebContents = remote.BrowserWindow.getAllWindows()[0].webContents; constructor(props: any) { super(props); // FIXME: find a better design. window.search = this; } render() { return (
    {fontAwesome.search} this.handleInput(event)} type="search"/>
    ); } get isFocused(): boolean { return document.activeElement === this.input; } clearSelection(): void { this.webContents.stopFindInPage("clearSelection"); this.input.value = ""; } blur() { this.input.blur(); } private handleInput(event: React.KeyboardEvent) { const text = (event.target as HTMLInputElement).value; if (text) { this.webContents.findInPage(text); this.webContents.on("found-in-page", () => this.input.focus()); } else { this.clearSelection(); setTimeout(() => this.input.select(), 0); } } private get input(): HTMLInputElement { /* tslint:disable:no-string-literal */ return this.refs["input"] as HTMLInputElement; } } ================================================ FILE: src/views/SessionComponent.tsx ================================================ import * as React from "react"; import * as _ from "lodash"; import {SessionID} from "../shell/Session"; import {Job} from "../shell/Job"; import {JobComponent} from "./JobComponent"; import * as css from "./css/styles"; import {PromptComponent} from "./PromptComponent"; import {userFriendlyPath} from "../utils/Common"; import {shell} from "electron"; import {services} from "../services/index"; import {colors} from "./css/colors"; import {Subscription} from "rxjs/Subscription"; interface Props { sessionID: SessionID; isFocused: boolean; focus: () => void; } export class SessionComponent extends React.Component { RENDER_JOBS_COUNT = 10; promptComponent: PromptComponent; constructor(props: Props) { super(props); } componentDidMount() { this.resizeSession(); this.session.on("jobs-changed", () => this.forceUpdate()); } render() { const jobs = _.takeRight(this.session.jobs, this.RENDER_JOBS_COUNT).slice().reverse().map((job: Job, index: number) => , ); return (
    {jobs}
    {this.props.isFocused ? null :
    } this.promptComponent = component!} session={this.session} isFocused={this.props.isFocused} />
    {userFriendlyPath(this.session.directory)}
    ); } resizeSession(): void { this.session.dimensions = { columns: Math.floor(this.size.width / services.font.letterWidth), rows: Math.floor(this.size.height / services.font.letterHeight), }; } get status() { const job = this.session.lastJob; return job && job.status; } private get session() { return services.sessions.get(this.props.sessionID); } private get sessionRef() { return this.refs.session as HTMLDivElement | undefined; } private get footerRef() { return this.refs.footer as HTMLDivElement | undefined; } private handleClick() { if (!this.props.isFocused) { this.props.focus(); } } private get size(): Size { if (this.sessionRef && this.footerRef) { return { width: this.sessionRef.clientWidth - (2 * css.contentPadding), height: this.sessionRef.clientHeight - this.footerRef.clientHeight, }; } else { // For tests that are run in electron-mocha return { width: 800, height: 600, }; } } } type GitStatusProps = { directory: string }; class GitStatusComponent extends React.Component { private subscription: Subscription; constructor(props: GitStatusProps) { super(props); this.state = { kind: "not-repository", }; } componentDidMount() { this.subscribe(this.props.directory); } componentWillUpdate(nextProps: GitStatusProps) { if (this.props.directory !== nextProps.directory) { this.subscription.unsubscribe(); this.subscribe(nextProps.directory); } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { if (this.state.kind === "repository") { return ( {this.state.branch} ); } else { return null; } } private subscribe(directory: string) { this.subscription = services.git.observableFor(directory).subscribe((data: GitState) => { this.setState(data); }); } } const ReleaseComponent = () => { if (process.env.NODE_ENV === "production" && services.updates.isAvailable) { return ( shell.openExternal("http://l.rw.rw/upterm_releases")}> Download New Release ); } else { return null; } }; ================================================ FILE: src/views/TabComponent.tsx ================================================ import {SessionComponent} from "./SessionComponent"; import * as React from "react"; import {SessionID} from "../shell/Session"; type Props = { sessionIDs: SessionID[]; focusedSessionID: SessionID; isFocused: boolean; onSessionFocus: (id: SessionID) => void }; export class TabComponent extends React.Component { sessionComponents: SessionComponent[]; focusedSessionComponent: SessionComponent | undefined; render() { this.sessionComponents = []; const sessionComponents = this.props.sessionIDs.map((id, index) => { const isFocused = this.props.isFocused && id === this.props.focusedSessionID; return ( { // Unmount. if (!sessionComponent) { return; } if (isFocused) { this.focusedSessionComponent = sessionComponent!; } this.sessionComponents[index] = sessionComponent!; }} isFocused={isFocused} focus={() => { this.props.onSessionFocus(id); this.forceUpdate(); }}> ); }); return (
    {sessionComponents}
    ); } } ================================================ FILE: src/views/TabHeaderComponent.tsx ================================================ /* tslint:disable:no-unused-variable */ import * as React from "react"; import {fontAwesome} from "./css/FontAwesome"; export interface Props { isFocused: boolean; activate: () => void; position: number; closeHandler: React.EventHandler>; } export class TabHeaderComponent extends React.Component { render() { return (
  • {fontAwesome.times} ⌘{this.props.position}
  • ); } } ================================================ FILE: src/views/ViewUtils.ts ================================================ import {KeyCode} from "../Enums"; export function isModifierKey(event: KeyboardEvent) { return [KeyCode.Shift, KeyCode.Ctrl, KeyCode.Meta, KeyCode.Alt, KeyCode.CapsLock, KeyCode.AltGraph].includes(event.keyCode); } export function setCaretPosition(node: Node, position: number) { const selection = window.getSelection(); const range = document.createRange(); if (node.childNodes.length) { range.setStart(node.childNodes[0], position); } else { range.setStart(node, 0); } range.collapse(true); selection.removeAllRanges(); selection.addRange(range); } /** * @link http://stackoverflow.com/questions/4811822/get-a-ranges-start-and-end-offsets-relative-to-its-parent-container/4812022#4812022 */ export function getCaretPosition(element: Node): number { const selection = element.ownerDocument.defaultView.getSelection(); if (selection.rangeCount > 0) { const range = selection.getRangeAt(0); const preCaretRange = range.cloneRange(); preCaretRange.selectNodeContents(element); return preCaretRange.toString().length; } else { return 0; } } ================================================ FILE: src/views/css/FontAwesome.ts ================================================ export const fontAwesome = { adjust: "\uf042", adn: "\uf170", alignCenter: "\uf037", alignJustify: "\uf039", alignLeft: "\uf036", alignRight: "\uf038", amazon: "\uf270", ambulance: "\uf0f9", americanSignLanguageInterpreting: "\uf2a3", anchor: "\uf13d", android: "\uf17b", angellist: "\uf209", angleDoubleDown: "\uf103", angleDoubleLeft: "\uf100", angleDoubleRight: "\uf101", angleDoubleUp: "\uf102", angleDown: "\uf107", angleLeft: "\uf104", angleRight: "\uf105", angleUp: "\uf106", apple: "\uf179", archive: "\uf187", areaChart: "\uf1fe", arrowCircleDown: "\uf0ab", arrowCircleLeft: "\uf0a8", arrowCircleODown: "\uf01a", arrowCircleOLeft: "\uf190", arrowCircleORight: "\uf18e", arrowCircleOUp: "\uf01b", arrowCircleRight: "\uf0a9", arrowCircleUp: "\uf0aa", arrowDown: "\uf063", arrowLeft: "\uf060", arrowRight: "\uf061", arrowUp: "\uf062", arrows: "\uf047", arrowsAlt: "\uf0b2", arrowsH: "\uf07e", arrowsV: "\uf07d", aslInterpreting: "\uf2a3", assistiveListeningSystems: "\uf2a2", asterisk: "\uf069", at: "\uf1fa", audioDescription: "\uf29e", automobile: "\uf1b9", backward: "\uf04a", balanceScale: "\uf24e", ban: "\uf05e", bank: "\uf19c", barChart: "\uf080", barChartO: "\uf080", barcode: "\uf02a", bars: "\uf0c9", battery0: "\uf244", battery1: "\uf243", battery2: "\uf242", battery3: "\uf241", battery4: "\uf240", batteryEmpty: "\uf244", batteryFull: "\uf240", batteryHalf: "\uf242", batteryQuarter: "\uf243", batteryThreeQuarters: "\uf241", bed: "\uf236", beer: "\uf0fc", behance: "\uf1b4", behanceSquare: "\uf1b5", bell: "\uf0f3", bellO: "\uf0a2", bellSlash: "\uf1f6", bellSlashO: "\uf1f7", bicycle: "\uf206", binoculars: "\uf1e5", birthdayCake: "\uf1fd", bitbucket: "\uf171", bitbucketSquare: "\uf172", bitcoin: "\uf15a", blackTie: "\uf27e", blind: "\uf29d", bluetooth: "\uf293", bluetoothB: "\uf294", bold: "\uf032", bolt: "\uf0e7", bomb: "\uf1e2", book: "\uf02d", bookmark: "\uf02e", bookmarkO: "\uf097", braille: "\uf2a1", briefcase: "\uf0b1", btc: "\uf15a", bug: "\uf188", building: "\uf1ad", buildingO: "\uf0f7", bullhorn: "\uf0a1", bullseye: "\uf140", bus: "\uf207", buysellads: "\uf20d", cab: "\uf1ba", calculator: "\uf1ec", calendar: "\uf073", calendarCheckO: "\uf274", calendarMinusO: "\uf272", calendarO: "\uf133", calendarPlusO: "\uf271", calendarTimesO: "\uf273", camera: "\uf030", cameraRetro: "\uf083", car: "\uf1b9", caretDown: "\uf0d7", caretLeft: "\uf0d9", caretRight: "\uf0da", caretSquareODown: "\uf150", caretSquareOLeft: "\uf191", caretSquareORight: "\uf152", caretSquareOUp: "\uf151", caretUp: "\uf0d8", cartArrowDown: "\uf218", cartPlus: "\uf217", cc: "\uf20a", ccAmex: "\uf1f3", ccDinersClub: "\uf24c", ccDiscover: "\uf1f2", ccJcb: "\uf24b", ccMastercard: "\uf1f1", ccPaypal: "\uf1f4", ccStripe: "\uf1f5", ccVisa: "\uf1f0", certificate: "\uf0a3", chain: "\uf0c1", chainBroken: "\uf127", check: "\uf00c", checkCircle: "\uf058", checkCircleO: "\uf05d", checkSquare: "\uf14a", checkSquareO: "\uf046", chevronCircleDown: "\uf13a", chevronCircleLeft: "\uf137", chevronCircleRight: "\uf138", chevronCircleUp: "\uf139", chevronDown: "\uf078", chevronLeft: "\uf053", chevronRight: "\uf054", chevronUp: "\uf077", child: "\uf1ae", chrome: "\uf268", circle: "\uf111", circleO: "\uf10c", circleONotch: "\uf1ce", circleThin: "\uf1db", clipboard: "\uf0ea", clockO: "\uf017", clone: "\uf24d", close: "\uf00d", cloud: "\uf0c2", cloudDownload: "\uf0ed", cloudUpload: "\uf0ee", cny: "\uf157", code: "\uf121", codeFork: "\uf126", codepen: "\uf1cb", codiepie: "\uf284", coffee: "\uf0f4", cog: "\uf013", cogs: "\uf085", columns: "\uf0db", comment: "\uf075", commentO: "\uf0e5", commenting: "\uf27a", commentingO: "\uf27b", comments: "\uf086", commentsO: "\uf0e6", compass: "\uf14e", compress: "\uf066", connectdevelop: "\uf20e", contao: "\uf26d", copy: "\uf0c5", copyright: "\uf1f9", creativeCommons: "\uf25e", creditCard: "\uf09d", creditCardAlt: "\uf283", crop: "\uf125", crosshairs: "\uf05b", css3: "\uf13c", cube: "\uf1b2", cubes: "\uf1b3", cut: "\uf0c4", cutlery: "\uf0f5", dashboard: "\uf0e4", dashcube: "\uf210", database: "\uf1c0", deaf: "\uf2a4", deafness: "\uf2a4", dedent: "\uf03b", delicious: "\uf1a5", desktop: "\uf108", deviantart: "\uf1bd", diamond: "\uf219", digg: "\uf1a6", dollar: "\uf155", dotCircleO: "\uf192", download: "\uf019", dribbble: "\uf17d", dropbox: "\uf16b", drupal: "\uf1a9", edge: "\uf282", edit: "\uf044", eject: "\uf052", ellipsisH: "\uf141", ellipsisV: "\uf142", empire: "\uf1d1", envelope: "\uf0e0", envelopeO: "\uf003", envelopeSquare: "\uf199", envira: "\uf299", eraser: "\uf12d", eur: "\uf153", euro: "\uf153", exchange: "\uf0ec", exclamation: "\uf12a", exclamationCircle: "\uf06a", exclamationTriangle: "\uf071", expand: "\uf065", expeditedssl: "\uf23e", externalLink: "\uf08e", externalLinkSquare: "\uf14c", eye: "\uf06e", eyeSlash: "\uf070", eyedropper: "\uf1fb", fa: "\uf2b4", facebook: "\uf09a", facebookF: "\uf09a", facebookOfficial: "\uf230", facebookSquare: "\uf082", fastBackward: "\uf049", fastForward: "\uf050", fax: "\uf1ac", feed: "\uf09e", female: "\uf182", fighterJet: "\uf0fb", file: "\uf15b", fileArchiveO: "\uf1c6", fileAudioO: "\uf1c7", fileCodeO: "\uf1c9", fileExcelO: "\uf1c3", fileImageO: "\uf1c5", fileMovieO: "\uf1c8", fileO: "\uf016", filePdfO: "\uf1c1", filePhotoO: "\uf1c5", filePictureO: "\uf1c5", filePowerpointO: "\uf1c4", fileSoundO: "\uf1c7", fileText: "\uf15c", fileTextO: "\uf0f6", fileVideoO: "\uf1c8", fileWordO: "\uf1c2", fileZipO: "\uf1c6", filesO: "\uf0c5", film: "\uf008", filter: "\uf0b0", fire: "\uf06d", fireExtinguisher: "\uf134", firefox: "\uf269", firstOrder: "\uf2b0", flag: "\uf024", flagCheckered: "\uf11e", flagO: "\uf11d", flash: "\uf0e7", flask: "\uf0c3", flickr: "\uf16e", floppyO: "\uf0c7", folder: "\uf07b", folderO: "\uf114", folderOpen: "\uf07c", folderOpenO: "\uf115", font: "\uf031", fontAwesome: "\uf2b4", fonticons: "\uf280", fortAwesome: "\uf286", forumbee: "\uf211", forward: "\uf04e", foursquare: "\uf180", frownO: "\uf119", futbolO: "\uf1e3", gamepad: "\uf11b", gavel: "\uf0e3", gbp: "\uf154", ge: "\uf1d1", gear: "\uf013", gears: "\uf085", genderless: "\uf22d", getPocket: "\uf265", gg: "\uf260", ggCircle: "\uf261", gift: "\uf06b", git: "\uf1d3", gitSquare: "\uf1d2", github: "\uf09b", githubAlt: "\uf113", githubSquare: "\uf092", gitlab: "\uf296", gittip: "\uf184", glass: "\uf000", glide: "\uf2a5", glideG: "\uf2a6", globe: "\uf0ac", google: "\uf1a0", googlePlus: "\uf0d5", googlePlusCircle: "\uf2b3", googlePlusOfficial: "\uf2b3", googlePlusSquare: "\uf0d4", googleWallet: "\uf1ee", graduationCap: "\uf19d", gratipay: "\uf184", group: "\uf0c0", hSquare: "\uf0fd", hackerNews: "\uf1d4", handGrabO: "\uf255", handLizardO: "\uf258", handODown: "\uf0a7", handOLeft: "\uf0a5", handORight: "\uf0a4", handOUp: "\uf0a6", handPaperO: "\uf256", handPeaceO: "\uf25b", handPointerO: "\uf25a", handRockO: "\uf255", handScissorsO: "\uf257", handSpockO: "\uf259", handStopO: "\uf256", hardOfHearing: "\uf2a4", hashtag: "\uf292", hddO: "\uf0a0", header: "\uf1dc", headphones: "\uf025", heart: "\uf004", heartO: "\uf08a", heartbeat: "\uf21e", history: "\uf1da", home: "\uf015", hospitalO: "\uf0f8", hotel: "\uf236", hourglass: "\uf254", hourglass1: "\uf251", hourglass2: "\uf252", hourglass3: "\uf253", hourglassEnd: "\uf253", hourglassHalf: "\uf252", hourglassO: "\uf250", hourglassStart: "\uf251", houzz: "\uf27c", html5: "\uf13b", iCursor: "\uf246", ils: "\uf20b", image: "\uf03e", inbox: "\uf01c", indent: "\uf03c", industry: "\uf275", info: "\uf129", infoCircle: "\uf05a", inr: "\uf156", instagram: "\uf16d", institution: "\uf19c", internetExplorer: "\uf26b", intersex: "\uf224", ioxhost: "\uf208", italic: "\uf033", joomla: "\uf1aa", jpy: "\uf157", jsfiddle: "\uf1cc", key: "\uf084", keyboardO: "\uf11c", krw: "\uf159", language: "\uf1ab", laptop: "\uf109", lastfm: "\uf202", lastfmSquare: "\uf203", leaf: "\uf06c", leanpub: "\uf212", legal: "\uf0e3", lemonO: "\uf094", levelDown: "\uf149", levelUp: "\uf148", lifeBouy: "\uf1cd", lifeBuoy: "\uf1cd", lifeRing: "\uf1cd", lifeSaver: "\uf1cd", lightbulbO: "\uf0eb", lineChart: "\uf201", link: "\uf0c1", linkedin: "\uf0e1", linkedinSquare: "\uf08c", linux: "\uf17c", list: "\uf03a", listAlt: "\uf022", listOl: "\uf0cb", listUl: "\uf0ca", locationArrow: "\uf124", lock: "\uf023", longArrowDown: "\uf175", longArrowLeft: "\uf177", longArrowRight: "\uf178", longArrowUp: "\uf176", lowVision: "\uf2a8", magic: "\uf0d0", magnet: "\uf076", mailForward: "\uf064", mailReply: "\uf112", mailReplyAll: "\uf122", male: "\uf183", map: "\uf279", mapMarker: "\uf041", mapO: "\uf278", mapPin: "\uf276", mapSigns: "\uf277", mars: "\uf222", marsDouble: "\uf227", marsStroke: "\uf229", marsStrokeH: "\uf22b", marsStrokeV: "\uf22a", maxcdn: "\uf136", meanpath: "\uf20c", medium: "\uf23a", medkit: "\uf0fa", mehO: "\uf11a", mercury: "\uf223", microphone: "\uf130", microphoneSlash: "\uf131", minus: "\uf068", minusCircle: "\uf056", minusSquare: "\uf146", minusSquareO: "\uf147", mixcloud: "\uf289", mobile: "\uf10b", mobilePhone: "\uf10b", modx: "\uf285", money: "\uf0d6", moonO: "\uf186", mortarBoard: "\uf19d", motorcycle: "\uf21c", mousePointer: "\uf245", music: "\uf001", navicon: "\uf0c9", neuter: "\uf22c", newspaperO: "\uf1ea", objectGroup: "\uf247", objectUngroup: "\uf248", odnoklassniki: "\uf263", odnoklassnikiSquare: "\uf264", opencart: "\uf23d", openid: "\uf19b", opera: "\uf26a", optinMonster: "\uf23c", outdent: "\uf03b", pagelines: "\uf18c", paintBrush: "\uf1fc", paperPlane: "\uf1d8", paperPlaneO: "\uf1d9", paperclip: "\uf0c6", paragraph: "\uf1dd", paste: "\uf0ea", pause: "\uf04c", pauseCircle: "\uf28b", pauseCircleO: "\uf28c", paw: "\uf1b0", paypal: "\uf1ed", pencil: "\uf040", pencilSquare: "\uf14b", pencilSquareO: "\uf044", percent: "\uf295", phone: "\uf095", phoneSquare: "\uf098", photo: "\uf03e", pictureO: "\uf03e", pieChart: "\uf200", piedPiper: "\uf2ae", piedPiperAlt: "\uf1a8", piedPiperPp: "\uf1a7", pinterest: "\uf0d2", pinterestP: "\uf231", pinterestSquare: "\uf0d3", plane: "\uf072", play: "\uf04b", playCircle: "\uf144", playCircleO: "\uf01d", plug: "\uf1e6", plus: "\uf067", plusCircle: "\uf055", plusSquare: "\uf0fe", plusSquareO: "\uf196", powerOff: "\uf011", print: "\uf02f", productHunt: "\uf288", puzzlePiece: "\uf12e", qq: "\uf1d6", qrcode: "\uf029", question: "\uf128", questionCircle: "\uf059", questionCircleO: "\uf29c", quoteLeft: "\uf10d", quoteRight: "\uf10e", ra: "\uf1d0", random: "\uf074", rebel: "\uf1d0", recycle: "\uf1b8", reddit: "\uf1a1", redditAlien: "\uf281", redditSquare: "\uf1a2", refresh: "\uf021", registered: "\uf25d", remove: "\uf00d", renren: "\uf18b", reorder: "\uf0c9", repeat: "\uf01e", reply: "\uf112", replyAll: "\uf122", resistance: "\uf1d0", retweet: "\uf079", rmb: "\uf157", road: "\uf018", rocket: "\uf135", rotateLeft: "\uf0e2", rotateRight: "\uf01e", rouble: "\uf158", rss: "\uf09e", rssSquare: "\uf143", rub: "\uf158", ruble: "\uf158", rupee: "\uf156", safari: "\uf267", save: "\uf0c7", scissors: "\uf0c4", scribd: "\uf28a", search: "\uf002", searchMinus: "\uf010", searchPlus: "\uf00e", sellsy: "\uf213", send: "\uf1d8", sendO: "\uf1d9", server: "\uf233", share: "\uf064", shareAlt: "\uf1e0", shareAltSquare: "\uf1e1", shareSquare: "\uf14d", shareSquareO: "\uf045", shekel: "\uf20b", sheqel: "\uf20b", shield: "\uf132", ship: "\uf21a", shirtsinbulk: "\uf214", shoppingBag: "\uf290", shoppingBasket: "\uf291", shoppingCart: "\uf07a", signIn: "\uf090", signLanguage: "\uf2a7", signOut: "\uf08b", signal: "\uf012", signing: "\uf2a7", simplybuilt: "\uf215", sitemap: "\uf0e8", skyatlas: "\uf216", skype: "\uf17e", slack: "\uf198", sliders: "\uf1de", slideshare: "\uf1e7", smileO: "\uf118", snapchat: "\uf2ab", snapchatGhost: "\uf2ac", snapchatSquare: "\uf2ad", soccerBallO: "\uf1e3", sort: "\uf0dc", sortAlphaAsc: "\uf15d", sortAlphaDesc: "\uf15e", sortAmountAsc: "\uf160", sortAmountDesc: "\uf161", sortAsc: "\uf0de", sortDesc: "\uf0dd", sortDown: "\uf0dd", sortNumericAsc: "\uf162", sortNumericDesc: "\uf163", sortUp: "\uf0de", soundcloud: "\uf1be", spaceShuttle: "\uf197", spinner: "\uf110", spoon: "\uf1b1", spotify: "\uf1bc", square: "\uf0c8", squareO: "\uf096", stackExchange: "\uf18d", stackOverflow: "\uf16c", star: "\uf005", starHalf: "\uf089", starHalfEmpty: "\uf123", starHalfFull: "\uf123", starHalfO: "\uf123", starO: "\uf006", steam: "\uf1b6", steamSquare: "\uf1b7", stepBackward: "\uf048", stepForward: "\uf051", stethoscope: "\uf0f1", stickyNote: "\uf249", stickyNoteO: "\uf24a", stop: "\uf04d", stopCircle: "\uf28d", stopCircleO: "\uf28e", streetView: "\uf21d", strikethrough: "\uf0cc", stumbleupon: "\uf1a4", stumbleuponCircle: "\uf1a3", subscript: "\uf12c", subway: "\uf239", suitcase: "\uf0f2", sunO: "\uf185", superscript: "\uf12b", support: "\uf1cd", table: "\uf0ce", tablet: "\uf10a", tachometer: "\uf0e4", tag: "\uf02b", tags: "\uf02c", tasks: "\uf0ae", taxi: "\uf1ba", television: "\uf26c", tencentWeibo: "\uf1d5", terminal: "\uf120", textHeight: "\uf034", textWidth: "\uf035", th: "\uf00a", thLarge: "\uf009", thList: "\uf00b", themeisle: "\uf2b2", thumbTack: "\uf08d", thumbsDown: "\uf165", thumbsODown: "\uf088", thumbsOUp: "\uf087", thumbsUp: "\uf164", ticket: "\uf145", times: "\uf00d", timesCircle: "\uf057", timesCircleO: "\uf05c", tint: "\uf043", toggleDown: "\uf150", toggleLeft: "\uf191", toggleOff: "\uf204", toggleOn: "\uf205", toggleRight: "\uf152", toggleUp: "\uf151", trademark: "\uf25c", train: "\uf238", transgender: "\uf224", transgenderAlt: "\uf225", trash: "\uf1f8", trashO: "\uf014", tree: "\uf1bb", trello: "\uf181", tripadvisor: "\uf262", trophy: "\uf091", truck: "\uf0d1", try: "\uf195", tty: "\uf1e4", tumblr: "\uf173", tumblrSquare: "\uf174", turkishLira: "\uf195", tv: "\uf26c", twitch: "\uf1e8", twitter: "\uf099", twitterSquare: "\uf081", umbrella: "\uf0e9", underline: "\uf0cd", undo: "\uf0e2", universalAccess: "\uf29a", university: "\uf19c", unlink: "\uf127", unlock: "\uf09c", unlockAlt: "\uf13e", unsorted: "\uf0dc", upload: "\uf093", usb: "\uf287", usd: "\uf155", user: "\uf007", userMd: "\uf0f0", userPlus: "\uf234", userSecret: "\uf21b", userTimes: "\uf235", users: "\uf0c0", venus: "\uf221", venusDouble: "\uf226", venusMars: "\uf228", viacoin: "\uf237", viadeo: "\uf2a9", viadeoSquare: "\uf2aa", videoCamera: "\uf03d", vimeo: "\uf27d", vimeoSquare: "\uf194", vine: "\uf1ca", vk: "\uf189", volumeControlPhone: "\uf2a0", volumeDown: "\uf027", volumeOff: "\uf026", volumeUp: "\uf028", warning: "\uf071", wechat: "\uf1d7", weibo: "\uf18a", weixin: "\uf1d7", whatsapp: "\uf232", wheelchair: "\uf193", wheelchairAlt: "\uf29b", wifi: "\uf1eb", wikipediaW: "\uf266", windows: "\uf17a", won: "\uf159", wordpress: "\uf19a", wpbeginner: "\uf297", wpforms: "\uf298", wrench: "\uf0ad", xing: "\uf168", xingSquare: "\uf169", yCombinator: "\uf23b", yCombinatorSquare: "\uf1d4", yahoo: "\uf19e", yc: "\uf23b", ycSquare: "\uf1d4", yelp: "\uf1e9", yen: "\uf157", yoast: "\uf2b1", youtube: "\uf167", youtubePlay: "\uf16a", youtubeSquare: "\uf166", }; ================================================ FILE: src/views/css/colors.ts ================================================ import * as _ from "lodash"; import {ColorCode} from "../../Interfaces"; import {darken} from "./functions"; export const colors = { black: "#333", red: "#BF6E7C", white: "#999", green: "#88B379", yellow: "#D9BD86", blue: "#66A5DF", magenta: "#C699C5", cyan: "#6EC6C6", brightBlack: "#484c54", brightRed: "#dd8494", brightWhite: "#bccce8", brightGreen: "#9dcc8c", brightYellow: "#e9cc92", brightBlue: "#6cb2f0", brightMagenta: "#e8b6e7", brightCyan: "#7adada", }; const colorIndex = [ colors.black, colors.red, colors.green, colors.yellow, colors.blue, colors.magenta, colors.cyan, colors.white, colors.brightBlack, colors.brightRed, colors.brightGreen, colors.brightYellow, colors.brightBlue, colors.brightMagenta, colors.brightCyan, colors.brightWhite, ...generateIndexedColors(), ...generateGreyScaleColors(), ]; function toRgb(colorComponent: number) { if (colorComponent === 0) { return 0; } return 55 + colorComponent * 40; } function generateIndexedColors() { return _.range(0, 216).map(index => { const red = Math.floor(index / 36); const green = Math.floor((index % 36) / 6); const blue = Math.floor(index % 6); return `rgb(${toRgb(red)}, ${toRgb(green)}, ${toRgb(blue)})`; }); } function generateGreyScaleColors() { return _.range(0, 24).map(index => { const color = index * 10 + 8; return `rgb(${color}, ${color}, ${color})`; }); } export function colorValue(color: ColorCode, options = {isBright: false}) { if (Array.isArray(color)) { return `rgb(${color.join(", ")})`; } else { if (options.isBright && color < 8) { return colorIndex[color + 8]; } else { return colorIndex[color]; } } } export const textColor = "#EEEEEE"; export const backgroundColor = darken(colors.black, 4); ================================================ FILE: src/views/css/functions.ts ================================================ const tinyColor: any = require("tinycolor2"); export function lighten(color: string, percent: number) { return tinyColor(color).lighten(percent).toHexString(); } export function darken(color: string, percent: number) { return tinyColor(color).darken(percent).toHexString(); } export function failurize(color: string) { return tinyColor(color).spin(140).saturate(25).toHexString(); } ================================================ FILE: src/views/css/styles.ts ================================================ import {Weight, Brightness, Color} from "../../Enums"; import {backgroundColor, colors, colorValue, textColor} from "./colors"; import {failurize, lighten} from "./functions"; import {Attributes} from "../../Interfaces"; import {services} from "../../services/index"; const jobBackgroundColor = colors.black; export const contentPadding = 10; export const application = () => ({ "--font-size": `${services.font.size}px`, "--font-family": services.font.family, "--letter-width": `${services.font.letterWidth}px`, "--letter-height": `${services.font.letterHeight}px`, "--content-padding": `${contentPadding}px`, "--search-input-color": lighten(backgroundColor, 15), "--background-color": backgroundColor, "--job-background-color": jobBackgroundColor, "--failed-job-background-color": failurize(jobBackgroundColor), "--text-color": textColor, "--black-color": colors.black, "--red-color": colors.red, "--white-color": colors.white, "--green-color": colors.green, "--yellow-color": colors.yellow, "--blue-color": colors.blue, "--magenta-color": colors.magenta, "--cyan-color": colors.cyan, }); export const charGroup = (attributes: Attributes) => { const styles: any = { color: colorValue(attributes.color, {isBright: attributes.brightness === Brightness.Bright}), backgroundColor: colorValue(attributes.backgroundColor, {isBright: false}), }; if (attributes.inverse) { const color = styles.color; styles.color = styles.backgroundColor; styles.backgroundColor = color; } if (attributes.underline) { styles.textDecoration = "underline"; } if (attributes.weight === Weight.Bold) { styles.fontWeight = "bold"; } // Remove default colors to allow CSS override for failed commands and reverse mode. if (attributes.color === Color.White && !attributes.inverse) { delete styles.color; } if (attributes.backgroundColor === Color.Black && !attributes.inverse) { delete styles.backgroundColor; } return styles; }; ================================================ FILE: src/views/index.html ================================================ Upterm
    ================================================ FILE: src/views/keyevents/Keybindings.ts ================================================ import {KeyCode, KeyboardAction, Status} from "../../Enums"; import {error} from "../../utils/Common"; import {SearchComponent} from "../SearchComponent"; import {UserEvent} from "../../Interfaces"; import {isModifierKey} from "../ViewUtils"; import {services} from "../../services/index"; import {ApplicationComponent} from "../ApplicationComponent"; export type KeybindingType = { action: KeyboardAction, keybinding: (e: KeyboardEvent) => boolean, }; function isCtrlOrCmd(e: KeyboardEvent): boolean { /** * Decides if a keyboard event contains the meta key for all platforms * Linux does not support the metaKey so it can be manually changed here * Windows/OSX is simply e.metaKey */ if (e.metaKey) { return true; } else if (process.platform === "linux") { return e.ctrlKey; } return false; } export const KeybindingsForActions: KeybindingType[] = [ // CLI commands { action: KeyboardAction.cliClearJobs, keybinding: (e: KeyboardEvent) => e.ctrlKey && e.keyCode === KeyCode.L, }, { action: KeyboardAction.cliClearText, // Need to include !shiftKey otherwise it will clear instead of copying keybinding: (e: KeyboardEvent) => e.ctrlKey && e.keyCode === KeyCode.C && !e.shiftKey, }, { action: KeyboardAction.cliAppendLastArgumentOfPreviousCommand, keybinding: (e: KeyboardEvent) => e.altKey && e.keyCode === KeyCode.Period, }, { action: KeyboardAction.cliHistoryPrevious, keybinding: (e: KeyboardEvent) => { return (e.ctrlKey && e.keyCode === KeyCode.P) || (e.keyCode === KeyCode.Up); }, }, { action: KeyboardAction.cliHistoryNext, keybinding: (e: KeyboardEvent) => { return (e.ctrlKey && e.keyCode === KeyCode.N) || (e.keyCode === KeyCode.Down); }, }, // autocomplete commands { action: KeyboardAction.autocompleteInsertCompletion, keybinding: (e: KeyboardEvent) => e.keyCode === KeyCode.Tab, }, { action: KeyboardAction.autocompletePreviousSuggestion, keybinding: (e: KeyboardEvent) => { return (e.ctrlKey && e.keyCode === KeyCode.P) || (e.keyCode === KeyCode.Up); }, }, { action: KeyboardAction.autocompleteNextSuggestion, keybinding: (e: KeyboardEvent) => { return (e.ctrlKey && e.keyCode === KeyCode.N) || (e.keyCode === KeyCode.Down); }, }, // tab commands { action: KeyboardAction.tabFocus, keybinding: (e: KeyboardEvent) => { return ((e.ctrlKey || isCtrlOrCmd(e)) && e.keyCode >= KeyCode.One && e.keyCode <= KeyCode.Nine); }, }, // search commands { action: KeyboardAction.editFindClose, keybinding: (e: KeyboardEvent) => e.keyCode === KeyCode.Escape, }, ]; export function isKeybindingForEvent(event: KeyboardEvent, action: KeyboardAction): boolean { /** * Finds the keybinding for the given action and returns the result of the keybinding function */ let matchingKeyboardAction = KeybindingsForActions.find((keybinding) => { return keybinding.action === action; }); if (!matchingKeyboardAction) { error("No matching keybinding for action: " + KeyboardAction[action]); return false; } return matchingKeyboardAction.keybinding(event); } // Menu Stuff export type KeybindingMenuType = { action: KeyboardAction, accelerator: string, }; const CmdOrCtrl = process.platform === "darwin" ? "Cmd" : "Ctrl"; export const KeybindingsForMenu: KeybindingMenuType[] = [ { action: KeyboardAction.tabNew, accelerator: `${CmdOrCtrl}+T`, }, { action: KeyboardAction.tabPrevious, accelerator: `${CmdOrCtrl}+[`, }, { action: KeyboardAction.tabNext, accelerator: `${CmdOrCtrl}+]`, }, { action: KeyboardAction.sessionClose, accelerator: `${CmdOrCtrl}+W`, }, // edit/clipboard commands { action: KeyboardAction.clipboardCopy, accelerator: process.platform === "darwin" ? "Cmd+C" : "Ctrl+Shift+C", }, { action: KeyboardAction.clipboardPaste, accelerator: process.platform === "darwin" ? "Cmd+V" : "Ctrl+Shift+V", }, { action: KeyboardAction.editFind, accelerator: `${CmdOrCtrl}+F`, }, { action: KeyboardAction.editFindClose, accelerator: "Esc", }, { action: KeyboardAction.increaseFontSize, accelerator: `${CmdOrCtrl}+Plus`, }, { action: KeyboardAction.decreaseFontSize, accelerator: `${CmdOrCtrl}+-`, }, { action: KeyboardAction.resetFontSize, accelerator: `${CmdOrCtrl}+0`, }, // view commands { action: KeyboardAction.otherSession, accelerator: `${CmdOrCtrl}+\\`, }, { action: KeyboardAction.viewToggleFullScreen, accelerator: "Ctrl+Shift+F", }, { action: KeyboardAction.toggleDeveloperTools, accelerator: `${CmdOrCtrl}+Alt+I`, }, // Upterm commands { action: KeyboardAction.uptermQuit, accelerator: `${CmdOrCtrl}+Q`, }, ]; export function getAcceleratorForAction(action: KeyboardAction): string { /** * Returns the accelerator for a given keyboard action */ // Find the matching menu item by keyboardAction (should only ever return one item) let matchingMenuItem = KeybindingsForMenu.filter((menuAction) => { return menuAction.action === action; })[0]; return matchingMenuItem.accelerator; } export function isMenuShortcut(event: KeyboardEvent): boolean { const accelerator = toAccelerator(event); return !!KeybindingsForMenu.find(action => action.accelerator === accelerator); } function toAccelerator(event: KeyboardEvent): string { let parts: string[] = []; if (event.ctrlKey) { parts.push("Ctrl"); } if (event.shiftKey) { parts.push("Shift"); } if (event.metaKey) { parts.push("Cmd"); } if (event.altKey) { parts.push("Alt"); } // Cmd+Alt+I generates event.key Dead, but its code is KeyI. const key = event.key === "Dead" ? event.code.slice(3) : event.key.toUpperCase(); parts.push(key); return parts.join("+"); } export function handleUserEvent(application: ApplicationComponent, search: SearchComponent, event: UserEvent) { const sessionComponent = application.focusedTabComponent.focusedSessionComponent; if (!sessionComponent) { return; } const isJobRunning = sessionComponent.status === Status.InProgress; const promptComponent = sessionComponent.promptComponent; // Pasted data if (event instanceof ClipboardEvent) { if (search.isFocused) { return; } if (isJobRunning) { application.focusedSession.lastJob!.write(event.clipboardData.getData("text/plain")); event.stopPropagation(); event.preventDefault(); } else { promptComponent.focus(); } return; } if (isModifierKey(event) || isMenuShortcut(event)) { return; } // Change focused tab if (isKeybindingForEvent(event, KeyboardAction.tabFocus)) { const position = parseInt(event.key, 10); application.focusTab(position - 1); event.stopPropagation(); event.preventDefault(); return; } // Console clear if (!isJobRunning && isKeybindingForEvent(event, KeyboardAction.cliClearJobs)) { application.focusedSession.clearJobs(); event.stopPropagation(); event.preventDefault(); return; } if (search.isFocused) { // Search close if (isKeybindingForEvent(event, KeyboardAction.editFindClose)) { search.clearSelection(); search.blur(); event.stopPropagation(); event.preventDefault(); return; } return; } if (isJobRunning && application.focusedSession.lastJob!.isRunningPty()) { application.focusedSession.lastJob!.write(event); event.stopPropagation(); event.preventDefault(); return; } if (isJobRunning) { return; } promptComponent.focus(); // CLI execute command if (event.keyCode === KeyCode.CarriageReturn) { promptComponent.onReturnKeyPress(); event.stopPropagation(); event.preventDefault(); return; } // Append last argument to prompt if (isKeybindingForEvent(event, KeyboardAction.cliAppendLastArgumentOfPreviousCommand)) { promptComponent.appendLastLArgumentOfPreviousCommand(); event.stopPropagation(); event.preventDefault(); return; } // CLI clear if (isKeybindingForEvent(event, KeyboardAction.cliClearText)) { promptComponent.clear(); event.stopPropagation(); event.preventDefault(); return; } if (event.ctrlKey && event.keyCode === KeyCode.R && !promptComponent.isInHistorySearchMode) { promptComponent.setHistorySearchMode(); event.stopPropagation(); event.preventDefault(); return; } if (event.keyCode === KeyCode.Tab) { promptComponent.acceptSelectedSuggestion(); event.stopPropagation(); event.preventDefault(); return; } if (event.keyCode === KeyCode.Escape && promptComponent.isInHistorySearchMode) { promptComponent.setNormalMode(); return; } } ================================================ FILE: src/views/menu/Menu.ts ================================================ import {KeyboardAction} from "../../Enums"; import {remote} from "electron"; import {getAcceleratorForAction} from "../keyevents/Keybindings"; import {ApplicationComponent} from "../ApplicationComponent"; import {services} from "../../services"; export function buildMenuTemplate( app: Electron.App, browserWindow: Electron.BrowserWindow, application: ApplicationComponent, ): Electron.MenuItemConstructorOptions[] { return [ { label: "Upterm", submenu: [ { role: "about" }, { type: "separator" }, { role: "hide" }, { role: "hideothers" }, { role: "unhide" }, { type: "separator" }, { label: "Quit", accelerator: getAcceleratorForAction(KeyboardAction.uptermQuit), click: () => { app.quit(); }, }, ], }, { label: "Edit", submenu: [ { label: "Copy", accelerator: getAcceleratorForAction(KeyboardAction.clipboardCopy), role: "copy", }, { label: "Paste", accelerator: getAcceleratorForAction(KeyboardAction.clipboardPaste), role: "paste", }, { label: "Find", accelerator: getAcceleratorForAction(KeyboardAction.editFind), click: () => { (document.querySelector("input[type=search]") as HTMLInputElement).select(); }, }, { type: "separator", }, { label: "Increase Font Size", accelerator: getAcceleratorForAction(KeyboardAction.increaseFontSize), click: () => { services.font.increaseSize(); }, }, { label: "Decrease Font Size", accelerator: getAcceleratorForAction(KeyboardAction.decreaseFontSize), click: () => { services.font.decreaseSize(); }, }, { label: "Reset Font Size", accelerator: getAcceleratorForAction(KeyboardAction.resetFontSize), click: () => { services.font.resetSize(); }, }, ], }, { label: "View", submenu: [ { label: "Toggle Full Screen", accelerator: getAcceleratorForAction(KeyboardAction.viewToggleFullScreen), click: () => { browserWindow.setFullScreen(!browserWindow.isFullScreen()); }, }, { label: "Toggle Developer Tools", accelerator: getAcceleratorForAction(KeyboardAction.toggleDeveloperTools), click: () => { browserWindow.webContents.toggleDevTools(); }, }, ], }, { label: "Session", submenu: [ { label: "Other Session", accelerator: getAcceleratorForAction(KeyboardAction.otherSession), click: () => { application.otherSession(); }, }, { label: "Close Current Session", accelerator: getAcceleratorForAction(KeyboardAction.sessionClose), click: () => { application.closeFocusedSession(); }, }, ], }, { label: "Tab", submenu: [ { label: "New Tab", accelerator: getAcceleratorForAction(KeyboardAction.tabNew), click: () => { application.addTab(); }, }, { type: "separator", }, { label: "Previous Tab", accelerator: getAcceleratorForAction(KeyboardAction.tabPrevious), click: () => { application.focusPreviousTab(); }, }, { label: "Next Tab", accelerator: getAcceleratorForAction(KeyboardAction.tabNext), click: () => { application.focusNextTab(); }, }, { type: "separator", }, { label: "Close Current Tab", click: () => { application.closeFocusedTab(); }, }, ], }, { role: "window", submenu: [ { role: "minimize" }, { role: "close" }, ], }, { label: "Help", submenu: [ { label: "GitHub Repository", click: () => { /* tslint:disable:no-unused-expression */ remote.shell.openExternal("http://l.rw.rw/upterm_repository"); }, }, { label: "Leave Feedback", click: () => { /* tslint:disable:no-unused-expression */ remote.shell.openExternal("http://l.rw.rw/upterm_leave_feedback"); }, }, ], }, ]; } ================================================ FILE: src/views/mouseevents/MouseEvents.ts ================================================ import {ApplicationComponent} from "../ApplicationComponent"; import {MouseEvent} from "../../Interfaces"; import * as fs from "fs"; import {userFriendlyPath, escapeFilePath, normalizeDirectory} from "../../utils/Common"; import {Status} from "../../Enums"; function isDirectory(path: string): boolean { return fs.lstatSync(path).isDirectory(); } export function handleMouseEvent(application: ApplicationComponent, event: MouseEvent) { const sessionComponent = application.focusedTabComponent.focusedSessionComponent; if (!sessionComponent) { return; } const isJobRunning = sessionComponent.status === Status.InProgress; const promptComponent = sessionComponent.promptComponent; if (event instanceof DragEvent) { const path = event.dataTransfer.files[0].path; let formattedPath = userFriendlyPath(escapeFilePath(path)); if (isDirectory(path)) { formattedPath = normalizeDirectory(formattedPath); } if (!isJobRunning) { promptComponent.insertValueInPlace(formattedPath); } event.preventDefault(); return; } } ================================================ FILE: test/e2e.ts ================================================ import {Application, SpectronClient} from "spectron"; import {expect} from "chai"; import {join} from "path"; import {userFriendlyPath} from "../src/utils/Common"; const timeout = 50000; class Page { private promptSelector = ".monaco-editor"; constructor(private client: SpectronClient) {} waitTillLoaded() { return this.client.waitForExist(this.promptSelector, timeout); } executeCommand(command: string) { return new Promise(resolve => { this.client.keys(command + "\n"); setTimeout(() => resolve(), 500); }); } get prompt() { return this.client.element(this.promptSelector); } get job() { return new Job(this.client, this.client.element(".job")); } get footer() { return new Footer(this.client, this.client.element(".footer")); } } abstract class Block { constructor( protected client: SpectronClient, protected selector: WebdriverIO.Client> & WebdriverIO.RawResult, ) {} } class Job extends Block { get output() { return this.selector.element(".output"); } } class Footer extends Block { get presentDirectory() { return this.selector.element(".present-directory"); } } describe("application launch", function () { this.timeout(timeout); let app: Application; let page: Page; before(async () => { app = new Application({path: "node_modules/.bin/electron", args: ["."]}); }); beforeEach(async () => { if (app.isRunning()) { await app.restart(); } else { await app.start(); } await app.client.waitUntilWindowLoaded(); page = new Page(app.client); return page.waitTillLoaded(); }); after(() => { if (app.isRunning()) { return app.stop(); } }); it("can execute a command", async () => { await page.executeCommand("echo expected-text"); const output = await page.job.output.getText(); expect(output).to.contain("expected-text"); }); describe("status bar", () => { it("changes working directory on cd", async () => { const oldDirectory = userFriendlyPath(__dirname + "/"); const newDirectory = userFriendlyPath(join(oldDirectory, "utils") + "/"); await page.executeCommand(`cd ${oldDirectory}`); expect(await page.footer.presentDirectory.getText()).to.eql(oldDirectory); await page.executeCommand(`cd ${newDirectory}`); expect(await page.footer.presentDirectory.getText()).to.eql(newDirectory); }); }); }); ================================================ FILE: test/environment_spec.ts ================================================ import "mocha"; import {expect} from "chai"; import {Environment, preprocessEnv} from "../src/shell/Environment"; describe("EnvironmentPath", () => { describe("input method", () => { it("prepend", async() => { const environment = new Environment({}); environment.path.prepend("/usr/bin"); environment.path.prepend("/usr/local/bin"); expect(environment.toObject()).to.eql({ PATH: "/usr/local/bin:/usr/bin", }); }); }); describe("environment preprocessor", () => { it("preprocesses bash functions", () => { expect(preprocessEnv([ "BASH_FUNC_foo%%=() if 0; then", " x", " else", " y", " fi", "}", "var=val", ])).to.eql([ "BASH_FUNC_foo%%=() if 0; then\n x\n else\n y\n fi\n}", "var=val", ]); }); }); }); ================================================ FILE: test/output_spec.ts ================================================ /// import "mocha"; import {expect} from "chai"; import {Output} from "../src/Output"; import {TerminalLikeDevice} from "../src/Interfaces"; import {readFileSync} from "fs"; class DummyTerminal implements TerminalLikeDevice { output: Output; written = ""; write = (input: string) => this.written += input; constructor(dimensions: Dimensions = {columns: 80, rows: 80}) { this.output = new Output(this, dimensions); } } type CSIFinalCharacter = "A" | "B" | "C" | "D" | "E" | "F" | "R" | "m" | "n"; const esc = `\x1b`; const ri = `${esc}M`; const ich = `${esc}[1@`; const dl = (rows: number) => `${esc}[${rows}M`; const dch = (n: number) => `${esc}[${n}P`; const cup = (row: number, column: number) => `${esc}[${row};${column}H`; const decsel = (param: number) => `${esc}[${param}K`; const decstbm = (topMargin: number, bottomMargin: number) => `${esc}[${topMargin};${bottomMargin}r`; const csi = (params: number[], final: CSIFinalCharacter) => { return `${esc}[${params.join(";")}${final}`; }; const sgr = (params: number[]) => { return csi(params, "m"); }; describe("Output", () => { it("contains the first row even if there was no input (to show cursor on)", () => { const terminal = new DummyTerminal({columns: 5, rows: 5}); expect(terminal.output.toLines()).to.eql([ " ", ]); }); it("wraps long strings", () => { const terminal = new DummyTerminal({columns: 5, rows: 5}); terminal.output.write("0123456789"); expect(terminal.output.toLines()).to.eql([ "01234", "56789", ]); }); describe("movements", () => { it("can move down", () => { const terminal = new DummyTerminal({columns: 11, rows: 2}); terminal.output.write(`first${csi([1], "B")}second`); expect(terminal.output.toLines()).to.eql([ "first ", " second", ]); }); it("stays at the same line after writing last column character", () => { const terminal = new DummyTerminal({columns: 10, rows: 5}); terminal.output.write(`${esc}[1;10H*${esc}[5D*`); expect(terminal.output.toString()).to.eql(" * *"); expect(terminal.output.toString()).to.eql(" * *"); }); it("doesn't move outside of the current page", () => { const terminal = new DummyTerminal({columns: 10, rows: 5}); terminal.output.write(`1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7${esc}[1;1H42`); expect(terminal.output.toLines()).to.eql([ "1 ", "2 ", "42 ", "4 ", "5 ", "6 ", "7 ", ]); }); describe("Reverse Index", () => { it("scrolls down when cursor is at the beginning of page", () => { const terminal = new DummyTerminal({columns: 10, rows: 5}); terminal.output.write(`1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7${cup(1, 1)}${ri}`); expect(terminal.output.toLines()).to.eql([ "1 ", "2 ", " ", "3 ", "4 ", "5 ", "6 ", ]); }); it("scrolls down scrolling region", () => { const terminal = new DummyTerminal({columns: 10, rows: 5}); terminal.output.write(`1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7${decstbm(1, 3)}${cup(1, 1)}${ri}`); expect(terminal.output.toLines()).to.eql([ "1 ", "2 ", " ", "3 ", "4 ", "6 ", "7 ", ]); }); }); }); it("can parse an ASCII string", () => { const terminal = new DummyTerminal(); terminal.output.write("something"); expect(terminal.output.toString().trim()).to.eql("something"); }); it("ICH", () => { const terminal = new DummyTerminal(); terminal.output.write(`123${cup(1, 1)}${ich}0`); expect(terminal.output.toString().trim()).to.eql("0123"); }); describe("true color", () => { it("sets the correct foreground color", () => { const terminal = new DummyTerminal(); terminal.output.write(`${sgr([38, 2, 255, 100, 0])}A${sgr([0])}`); const firstChar = terminal.output.activeBuffer.at({rowIndex: 0, columnIndex: 0}); expect(firstChar.attributes.color).to.eql([255, 100, 0]); }); }); it("uses default attributes to fill in new lines", () => { const terminal = new DummyTerminal(); terminal.output.write(`${sgr([46])}${cup(2, 1)}`); const firstChar = terminal.output.activeBuffer.at({rowIndex: 1, columnIndex: 0}); expect(firstChar.attributes.backgroundColor).to.eql(0); }); describe("CSI", () => { describe("Device Status Report (DSR)", () => { describe("Report Cursor Position (CPR)", () => { it("report cursor position", () => { const terminal = new DummyTerminal(); terminal.output.write(`some text${csi([6], "n")}`); expect(terminal.written).to.eql(`${csi([1, 10], "R")}`); }); }); }); describe("DL", () => { // A temporary test. Can be removed when we have a test for the real behavior. it("doesn't fail", () => { const terminal = new DummyTerminal({columns: 10, rows: 5}); const input = `1\r\n2\r\n3${cup(3, 1)}${dl(1)}`; terminal.output.write(input); terminal.output.toLines(); }); }); describe("DCH", () => { it("Removes chars from cursor position", () => { const terminal = new DummyTerminal({columns: 10, rows: 5}); const input = `1234567890${cup(1, 2)}${dch(2)}`; terminal.output.write(input); expect(terminal.output.toLines()).to.deep.equal([ "14567890 ", ]); }); }); describe("DECSEL", () => { it("Erases line to right", () => { const terminal = new DummyTerminal({columns: 10, rows: 5}); const input = `1234567890${cup(1, 5)}${decsel(0)}`; terminal.output.write(input); expect(terminal.output.toLines()).to.deep.equal([ "1234 ", ]); }); it("Correctly erases line to right from beginning", () => { const terminal = new DummyTerminal({columns: 10, rows: 5}); const input = `1234567890${cup(1, 1)}${decsel(0)}`; terminal.output.write(input); expect(terminal.output.toLines()).to.deep.equal([ " ", ]); }); }); }); describe("vttest", () => { function vttest(fileName: string, expectedOutput: string[]) { const input = readFileSync(`${__dirname}/test_files/vttest/${fileName}`).toString(); return it(fileName, () => { const terminal = new DummyTerminal(); terminal.output.write(input); const actualOutput = terminal.output.toLines(); expect(actualOutput).to.deep.equal(expectedOutput); }); } describe("cursor movements", () => { vttest("1-1", [ "********************************************************************************", "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*", "*+ E E +*", "*+ E The screen should be cleared, and have an unbroken bor- E +*", "*+ E der of *'s and +'s around the edge, and exactly in the E +*", "*+ E middle there should be a frame of E's around this text E +*", "*+ E with one (1) free position around it. Push E +*", "*+ E E +*", "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*", "********************************************************************************", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", ]); vttest("1-2", [ "************************************************************************************************************************************", "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*", "*+ E E +*", "*+ E The screen should be cleared, and have an unbroken bor- E +*", "*+ E der of *'s and +'s around the edge, and exactly in the E +*", "*+ E middle there should be a frame of E's around this text E +*", "*+ E with one (1) free position around it. Push E +*", "*+ E E +*", "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*+ +*", "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*", "************************************************************************************************************************************", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", ]); vttest("1-3", [ "Test of autowrap, mixing control and print characters. ", "The left/right margins should have letters in order: ", "I i", "J j", "K k", "L l", "M m", "N n", "O o", "P p", "Q q", "R r", "S s", "T t", "U u", "V v", "W w", "X x", "Y y", "Z z", " ", "Push ", " ", ]); vttest("1-4", [ "Test of autowrap, mixing control and print characters. ", "The left/right margins should have letters in order: ", "I i", "J j", "K k", "L l", "M m", "N n", "O o", "P p", "Q q", "R r", "S s", "T t", "U u", "V v", "W w", "X x", "Y y", "Z z", " ", "Push ", " ", ]); vttest("1-5", [ "Test of cursor-control characters inside ESC sequences. ", "Below should be four identical lines: ", " ", "A B C D E F G H I ", "A B C D E F G H I ", "A B C D E F G H I ", "A B C D E F G H I ", " ", "Push ", " ", ]); vttest("1-6", [ "Test of leading zeros in ESC sequences. ", 'Two lines below you should see the sentence "This is a correct sentence". ', " ", "This is a correct sentence ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "Push ", " ", ]); vttest("2-1", [ "********************************************************************************", "********************************************************************************", "********************************************************************************", " ", "This should be three identical lines of *'s completely filling ", "the top of the screen without any empty lines between. ", "(Test of WRAP AROUND mode setting.) ", "Push ", " ", ]); vttest("2-2", [ " * * * * * * * * * * * * * ", " * * * * * * * * * * * * * ", " ", "Test of TAB setting/resetting. These two lines ", "should look the same. Push ", " ", ]); vttest("2-15", [ "AAAAA ", "AAAAA ", "AAAAA ", "AAAAA ", " ", " ", " ", " normal bold underscored blinking reversed ", " ", "stars: ********** ********** ********** ********** ********** ", " ", "line: ────────── ────────── ────────── ────────── ────────── ", " ", "x'es: xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx ", " ", "diamonds: ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ", " ", " ", " ", " ", "Test of the SAVE/RESTORE CURSOR feature. There should ", "be ten characters of each flavour, and a rectangle ", "of 5 x 4 A's filling the top left of the screen. ", "Push ", " ", ]); }); }); }); ================================================ FILE: test/pty_spec.ts ================================================ import "mocha"; import {expect} from "chai"; import {PTY} from "../src/PTY"; import {scan} from "../src/shell/Scanner"; import ProcessEnv = NodeJS.ProcessEnv; describe("PTY", () => { it("doesn't interpolate expressions inside single quotes", (done) => { let output = ""; const tokens = scan("echo '$('"); const env = process.env; return new PTY( tokens.map(token => token.escapedValue), env, {columns: 80, rows: 30}, (data: string) => output += data, (exitCode: number) => { expect(exitCode).to.eq(0); done(); }, ); }); }); ================================================ FILE: test/references.d.ts ================================================ /// ================================================ FILE: test/shell/scanner_spec.ts ================================================ import {expect} from "chai"; import { scan, Word, DoubleQuotedStringLiteral, SingleQuotedStringLiteral, CompositeStringLiteral, Pipe, OutputRedirectionSymbol, AppendingOutputRedirectionSymbol, InputRedirectionSymbol, Invalid, Semicolon, } from "../../src/shell/Scanner"; describe("scan", () => { it("returns no tokens on empty input", () => { const tokens = scan(""); expect(tokens.length).to.eq(0); }); it("returns an invalid token on input that consists only of spaces", () => { const tokens = scan(" "); expect(tokens.length).to.eq(1); expect(tokens[0]).to.be.an.instanceof(Invalid); }); it("splits on a space", () => { const tokens = scan("some words"); expect(tokens.length).to.eq(2); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["some", "words"]); }); it("doesn't split inside double quotes", () => { const tokens = scan('prefix "inside quotes"'); expect(tokens.length).to.eq(2); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(DoubleQuotedStringLiteral); expect(tokens.map(token => token.value)).to.eql(["prefix", "inside quotes"]); }); it("doesn't split inside single quotes", () => { const tokens = scan("prefix 'inside quotes'"); expect(tokens.length).to.eq(2); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(SingleQuotedStringLiteral); expect(tokens.map(token => token.value)).to.eql(["prefix", "inside quotes"]); }); it("doesn't split string literals with no spaces between them", () => { const tokens = scan("echo a'b'\"c\" d"); expect(tokens.length).to.eq(3); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(CompositeStringLiteral); expect(tokens[2]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["echo", "abc", "d"]); }); it("doesn't split on an escaped space", () => { const tokens = scan("prefix single\\ token"); expect(tokens.length).to.eq(2); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["prefix", "single token"]); }); it("doesn't split on a colon", () => { const tokens = scan("curl http://www.example.com"); expect(tokens.length).to.eq(2); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["curl", "http://www.example.com"]); }); it("can handle special characters", () => { const tokens = scan("ls --color=tty -lh"); expect(tokens.length).to.eq(3); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(Word); expect(tokens[2]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["ls", "--color=tty", "-lh"]); }); it("recognizes a pipe", () => { const tokens = scan("cat file | grep word"); expect(tokens.length).to.eq(5); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(Word); expect(tokens[2]).to.be.an.instanceof(Pipe); expect(tokens[3]).to.be.an.instanceof(Word); expect(tokens[4]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["cat", "file", "|", "grep", "word"]); }); it("recognizes a semicolon", () => { const tokens = scan("cd directory; rm file"); expect(tokens.length).to.eq(5); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(Word); expect(tokens[2]).to.be.an.instanceof(Semicolon); expect(tokens[3]).to.be.an.instanceof(Word); expect(tokens[4]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["cd", "directory", ";", "rm", "file"]); }); it("recognizes input redirection", () => { const tokens = scan("cat < file"); expect(tokens.length).to.eq(3); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(InputRedirectionSymbol); expect(tokens[2]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["cat", "<", "file"]); }); it("recognizes output redirection", () => { const tokens = scan("cat file > another_file"); expect(tokens.length).to.eq(4); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(Word); expect(tokens[2]).to.be.an.instanceof(OutputRedirectionSymbol); expect(tokens[3]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["cat", "file", ">", "another_file"]); }); it("recognizes appending output redirection", () => { const tokens = scan("cat file >> another_file"); expect(tokens.length).to.eq(4); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(Word); expect(tokens[2]).to.be.an.instanceof(AppendingOutputRedirectionSymbol); expect(tokens[3]).to.be.an.instanceof(Word); expect(tokens.map(token => token.value)).to.eql(["cat", "file", ">>", "another_file"]); }); it("can handle unicode é", () => { const tokens = scan("cd é/"); expect(tokens.map(token => token.value)).to.eql(["cd", "é/"]); }); it("can handle 'x+' (regression test for #753)", () => { const tokens = scan("cd x+"); expect(tokens.map(token => token.value)).to.eql(["cd", "x+"]); }); it("includes spaces at end in final token", () => { const tokens = scan("test space "); expect(tokens.map(token => token.value)).to.eql(["test", "space", ""]); }); it("handles escaped brackets in words", () => { const tokens = scan("file\\ with\\ brackets\\(\\)"); expect(tokens.map(token => token.value)).to.eql(["file with brackets\\(\\)"]); }); it("adds an invalid token on invalid input", () => { const tokens = scan("cd '"); expect(tokens.length).to.eq(2); expect(tokens[0]).to.be.an.instanceof(Word); expect(tokens[1]).to.be.an.instanceof(Invalid); expect(tokens.map(token => token.value)).to.eql(["cd", "'"]); }); it("handles file descriptor redirection", () => { const tokens = scan("find / -name x 2>/dev/null"); expect(tokens.map(token => token.value)).to.eql(["find", "/", "-name", "x", "2>", "/dev/null"]); }); }); ================================================ FILE: test/test_files/file_names_test/file with brackets() ================================================ ================================================ FILE: test/test_files/vttest/1-1 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 1 [?3l#8****************************************************************************************************************************************************************+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M**E**E**E**E**E**E**E**E** ** ** ** ** ** ** ** ** ** ** ** ** ** ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++      The screen should be cleared, and have an unbroken bor-der of *'s and +'s around the edge, and exactly in themiddle there should be a frame of E's around this textwith one (1) free position around it. Push ================================================ FILE: test/test_files/vttest/1-2 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 1 [?3l#8****************************************************************************************************************************************************************+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M**E**E**E**E**E**E**E**E** ** ** ** ** ** ** ** ** ** ** ** ** ** ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++      The screen should be cleared, and have an unbroken bor-der of *'s and +'s around the edge, and exactly in themiddle there should be a frame of E's around this textwith one (1) free position around it. Push [?3h#8************************************************************************************************************************************************************************************************************************************************************************+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M+M**E**E**E**E**E**E**E**E** ** ** ** ** ** ** ** ** ** ** ** ** ** ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++      The screen should be cleared, and have an unbroken bor-der of *'s and +'s around the edge, and exactly in themiddle there should be a frame of E's around this textwith one (1) free position around it. Push ================================================ FILE: test/test_files/vttest/1-3 ================================================ [?3l[?3lTest of autowrap, mixing control and print characters. The left/right margins should have letters in order: [?6hAa aBB b C cC  DdEe eFF f G gG  HhIi iJJ j K kK  LlMm mNN n O oO  PpQq qRR r S sS  TtUu uVV v W wW  XxYy yZZ z [?6lPush ================================================ FILE: test/test_files/vttest/1-4 ================================================ [?3hTest of autowrap, mixing control and print characters. The left/right margins should have letters in order: [?6hAa aBB b C cC  DdEe eFF f G gG  HhIi iJJ j K kK  LlMm mNN n O oO  PpQq qRR r S sS  TtUu uVV v W wW  XxYy yZZ z [?6lPush ================================================ FILE: test/test_files/vttest/1-5 ================================================ [?3lTest of cursor-control characters inside ESC sequences. Below should be four identical lines: A B C D E F G H I A[2CB[2CC[2CD[2CE[2CF[2CG[2CH[2CI[2C A [ 2CB[ 4CC[ 6CD[ 8CE[ 10CF[ 12CG[ 14CH[ 16CI A [1 AB [1 AC [1 AD [1 AE [1 AF [1 AG [1 AH [1 AI[1 A Push ================================================ FILE: test/test_files/vttest/1-6 ================================================ Test of leading zeros in ESC sequences. Two lines below you should see the sentence "This is a correct sentence".This is a correct sentencePush ================================================ FILE: test/test_files/vttest/2-1 ================================================ [?3l[?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push ================================================ FILE: test/test_files/vttest/2-10 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push [?5l[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.Push [?6h[?4hSoft scroll up region [12..13] size 2 Line 1 Soft scroll up region [12..13] size 2 Line 2 Soft scroll up region [12..13] size 2 Line 3 Soft scroll up region [12..13] size 2 Line 4 Soft scroll up region [12..13] size 2 Line 5 Soft scroll up region [12..13] size 2 Line 6 Soft scroll up region [12..13] size 2 Line 7 Soft scroll up region [12..13] size 2 Line 8 Soft scroll up region [12..13] size 2 Line 9 Soft scroll up region [12..13] size 2 Line 10 Soft scroll up region [12..13] size 2 Line 11 Soft scroll up region [12..13] size 2 Line 12 Soft scroll up region [12..13] size 2 Line 13 Soft scroll up region [12..13] size 2 Line 14 Soft scroll up region [12..13] size 2 Line 15 Soft scroll up region [12..13] size 2 Line 16 Soft scroll up region [12..13] size 2 Line 17 Soft scroll up region [12..13] size 2 Line 18 Soft scroll up region [12..13] size 2 Line 19 Soft scroll up region [12..13] size 2 Line 20 Soft scroll up region [12..13] size 2 Line 21 Soft scroll up region [12..13] size 2 Line 22 Soft scroll up region [12..13] size 2 Line 23 Soft scroll up region [12..13] size 2 Line 24 Soft scroll up region [12..13] size 2 Line 25 Soft scroll up region [12..13] size 2 Line 26 Soft scroll up region [12..13] size 2 Line 27 Soft scroll up region [12..13] size 2 Line 28 Soft scroll up region [12..13] size 2 Line 29 Soft scroll down region [12..13] size 2 Line 1 MMSoft scroll down region [12..13] size 2 Line 2 MMSoft scroll down region [12..13] size 2 Line 3 MMSoft scroll down region [12..13] size 2 Line 4 MMSoft scroll down region [12..13] size 2 Line 5 MMSoft scroll down region [12..13] size 2 Line 6 MMSoft scroll down region [12..13] size 2 Line 7 MMSoft scroll down region [12..13] size 2 Line 8 MMSoft scroll down region [12..13] size 2 Line 9 MMSoft scroll down region [12..13] size 2 Line 10 MMSoft scroll down region [12..13] size 2 Line 11 MMSoft scroll down region [12..13] size 2 Line 12 MMSoft scroll down region [12..13] size 2 Line 13 MMSoft scroll down region [12..13] size 2 Line 14 MMSoft scroll down region [12..13] size 2 Line 15 MMSoft scroll down region [12..13] size 2 Line 16 MMSoft scroll down region [12..13] size 2 Line 17 MMSoft scroll down region [12..13] size 2 Line 18 MMSoft scroll down region [12..13] size 2 Line 19 MMSoft scroll down region [12..13] size 2 Line 20 MMSoft scroll down region [12..13] size 2 Line 21 MMSoft scroll down region [12..13] size 2 Line 22 MMSoft scroll down region [12..13] size 2 Line 23 MMSoft scroll down region [12..13] size 2 Line 24 MMSoft scroll down region [12..13] size 2 Line 25 MMSoft scroll down region [12..13] size 2 Line 26 MMSoft scroll down region [12..13] size 2 Line 27 MMSoft scroll down region [12..13] size 2 Line 28 MMSoft scroll down region [12..13] size 2 Line 29 MMPush Soft scroll up region [1..24] size 24 Line 1 Soft scroll up region [1..24] size 24 Line 2 Soft scroll up region [1..24] size 24 Line 3 Soft scroll up region [1..24] size 24 Line 4 Soft scroll up region [1..24] size 24 Line 5 Soft scroll up region [1..24] size 24 Line 6 Soft scroll up region [1..24] size 24 Line 7 Soft scroll up region [1..24] size 24 Line 8 Soft scroll up region [1..24] size 24 Line 9 Soft scroll up region [1..24] size 24 Line 10 Soft scroll up region [1..24] size 24 Line 11 Soft scroll up region [1..24] size 24 Line 12 Soft scroll up region [1..24] size 24 Line 13 Soft scroll up region [1..24] size 24 Line 14 Soft scroll up region [1..24] size 24 Line 15 Soft scroll up region [1..24] size 24 Line 16 Soft scroll up region [1..24] size 24 Line 17 Soft scroll up region [1..24] size 24 Line 18 Soft scroll up region [1..24] size 24 Line 19 Soft scroll up region [1..24] size 24 Line 20 Soft scroll up region [1..24] size 24 Line 21 Soft scroll up region [1..24] size 24 Line 22 Soft scroll up region [1..24] size 24 Line 23 Soft scroll up region [1..24] size 24 Line 24 Soft scroll up region [1..24] size 24 Line 25 Soft scroll up region [1..24] size 24 Line 26 Soft scroll up region [1..24] size 24 Line 27 Soft scroll up region [1..24] size 24 Line 28 Soft scroll up region [1..24] size 24 Line 29 Soft scroll down region [1..24] size 24 Line 1 MMSoft scroll down region [1..24] size 24 Line 2 MMSoft scroll down region [1..24] size 24 Line 3 MMSoft scroll down region [1..24] size 24 Line 4 MMSoft scroll down region [1..24] size 24 Line 5 MMSoft scroll down region [1..24] size 24 Line 6 MMSoft scroll down region [1..24] size 24 Line 7 MMSoft scroll down region [1..24] size 24 Line 8 MMSoft scroll down region [1..24] size 24 Line 9 MMSoft scroll down region [1..24] size 24 Line 10 MMSoft scroll down region [1..24] size 24 Line 11 MMSoft scroll down region [1..24] size 24 Line 12 MMSoft scroll down region [1..24] size 24 Line 13 MMSoft scroll down region [1..24] size 24 Line 14 MMSoft scroll down region [1..24] size 24 Line 15 MMSoft scroll down region [1..24] size 24 Line 16 MMSoft scroll down region [1..24] size 24 Line 17 MMSoft scroll down region [1..24] size 24 Line 18 MMSoft scroll down region [1..24] size 24 Line 19 MMSoft scroll down region [1..24] size 24 Line 20 MMSoft scroll down region [1..24] size 24 Line 21 MMSoft scroll down region [1..24] size 24 Line 22 MMSoft scroll down region [1..24] size 24 Line 23 MMSoft scroll down region [1..24] size 24 Line 24 MMSoft scroll down region [1..24] size 24 Line 25 MMSoft scroll down region [1..24] size 24 Line 26 MMSoft scroll down region [1..24] size 24 Line 27 MMSoft scroll down region [1..24] size 24 Line 28 MMSoft scroll down region [1..24] size 24 Line 29 MMPush [?4lJump scroll up region [12..13] size 2 Line 1 Jump scroll up region [12..13] size 2 Line 2 Jump scroll up region [12..13] size 2 Line 3 Jump scroll up region [12..13] size 2 Line 4 Jump scroll up region [12..13] size 2 Line 5 Jump scroll up region [12..13] size 2 Line 6 Jump scroll up region [12..13] size 2 Line 7 Jump scroll up region [12..13] size 2 Line 8 Jump scroll up region [12..13] size 2 Line 9 Jump scroll up region [12..13] size 2 Line 10 Jump scroll up region [12..13] size 2 Line 11 Jump scroll up region [12..13] size 2 Line 12 Jump scroll up region [12..13] size 2 Line 13 Jump scroll up region [12..13] size 2 Line 14 Jump scroll up region [12..13] size 2 Line 15 Jump scroll up region [12..13] size 2 Line 16 Jump scroll up region [12..13] size 2 Line 17 Jump scroll up region [12..13] size 2 Line 18 Jump scroll up region [12..13] size 2 Line 19 Jump scroll up region [12..13] size 2 Line 20 Jump scroll up region [12..13] size 2 Line 21 Jump scroll up region [12..13] size 2 Line 22 Jump scroll up region [12..13] size 2 Line 23 Jump scroll up region [12..13] size 2 Line 24 Jump scroll up region [12..13] size 2 Line 25 Jump scroll up region [12..13] size 2 Line 26 Jump scroll up region [12..13] size 2 Line 27 Jump scroll up region [12..13] size 2 Line 28 Jump scroll up region [12..13] size 2 Line 29 Jump scroll down region [12..13] size 2 Line 1 MMJump scroll down region [12..13] size 2 Line 2 MMJump scroll down region [12..13] size 2 Line 3 MMJump scroll down region [12..13] size 2 Line 4 MMJump scroll down region [12..13] size 2 Line 5 MMJump scroll down region [12..13] size 2 Line 6 MMJump scroll down region [12..13] size 2 Line 7 MMJump scroll down region [12..13] size 2 Line 8 MMJump scroll down region [12..13] size 2 Line 9 MMJump scroll down region [12..13] size 2 Line 10 MMJump scroll down region [12..13] size 2 Line 11 MMJump scroll down region [12..13] size 2 Line 12 MMJump scroll down region [12..13] size 2 Line 13 MMJump scroll down region [12..13] size 2 Line 14 MMJump scroll down region [12..13] size 2 Line 15 MMJump scroll down region [12..13] size 2 Line 16 MMJump scroll down region [12..13] size 2 Line 17 MMJump scroll down region [12..13] size 2 Line 18 MMJump scroll down region [12..13] size 2 Line 19 MMJump scroll down region [12..13] size 2 Line 20 MMJump scroll down region [12..13] size 2 Line 21 MMJump scroll down region [12..13] size 2 Line 22 MMJump scroll down region [12..13] size 2 Line 23 MMJump scroll down region [12..13] size 2 Line 24 MMJump scroll down region [12..13] size 2 Line 25 MMJump scroll down region [12..13] size 2 Line 26 MMJump scroll down region [12..13] size 2 Line 27 MMJump scroll down region [12..13] size 2 Line 28 MMJump scroll down region [12..13] size 2 Line 29 MMPush Jump scroll up region [1..24] size 24 Line 1 Jump scroll up region [1..24] size 24 Line 2 Jump scroll up region [1..24] size 24 Line 3 Jump scroll up region [1..24] size 24 Line 4 Jump scroll up region [1..24] size 24 Line 5 Jump scroll up region [1..24] size 24 Line 6 Jump scroll up region [1..24] size 24 Line 7 Jump scroll up region [1..24] size 24 Line 8 Jump scroll up region [1..24] size 24 Line 9 Jump scroll up region [1..24] size 24 Line 10 Jump scroll up region [1..24] size 24 Line 11 Jump scroll up region [1..24] size 24 Line 12 Jump scroll up region [1..24] size 24 Line 13 Jump scroll up region [1..24] size 24 Line 14 Jump scroll up region [1..24] size 24 Line 15 Jump scroll up region [1..24] size 24 Line 16 Jump scroll up region [1..24] size 24 Line 17 Jump scroll up region [1..24] size 24 Line 18 Jump scroll up region [1..24] size 24 Line 19 Jump scroll up region [1..24] size 24 Line 20 Jump scroll up region [1..24] size 24 Line 21 Jump scroll up region [1..24] size 24 Line 22 Jump scroll up region [1..24] size 24 Line 23 Jump scroll up region [1..24] size 24 Line 24 Jump scroll up region [1..24] size 24 Line 25 Jump scroll up region [1..24] size 24 Line 26 Jump scroll up region [1..24] size 24 Line 27 Jump scroll up region [1..24] size 24 Line 28 Jump scroll up region [1..24] size 24 Line 29 Jump scroll down region [1..24] size 24 Line 1 MMJump scroll down region [1..24] size 24 Line 2 MMJump scroll down region [1..24] size 24 Line 3 MMJump scroll down region [1..24] size 24 Line 4 MMJump scroll down region [1..24] size 24 Line 5 MMJump scroll down region [1..24] size 24 Line 6 MMJump scroll down region [1..24] size 24 Line 7 MMJump scroll down region [1..24] size 24 Line 8 MMJump scroll down region [1..24] size 24 Line 9 MMJump scroll down region [1..24] size 24 Line 10 MMJump scroll down region [1..24] size 24 Line 11 MMJump scroll down region [1..24] size 24 Line 12 MMJump scroll down region [1..24] size 24 Line 13 MMJump scroll down region [1..24] size 24 Line 14 MMJump scroll down region [1..24] size 24 Line 15 MMJump scroll down region [1..24] size 24 Line 16 MMJump scroll down region [1..24] size 24 Line 17 MMJump scroll down region [1..24] size 24 Line 18 MMJump scroll down region [1..24] size 24 Line 19 MMJump scroll down region [1..24] size 24 Line 20 MMJump scroll down region [1..24] size 24 Line 21 MMJump scroll down region [1..24] size 24 Line 22 MMJump scroll down region [1..24] size 24 Line 23 MMJump scroll down region [1..24] size 24 Line 24 MMJump scroll down region [1..24] size 24 Line 25 MMJump scroll down region [1..24] size 24 Line 26 MMJump scroll down region [1..24] size 24 Line 27 MMJump scroll down region [1..24] size 24 Line 28 MMJump scroll down region [1..24] size 24 Line 29 MMPush ================================================ FILE: test/test_files/vttest/2-11 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push [?5l[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.Push [?6h[?4hSoft scroll up region [12..13] size 2 Line 1 Soft scroll up region [12..13] size 2 Line 2 Soft scroll up region [12..13] size 2 Line 3 Soft scroll up region [12..13] size 2 Line 4 Soft scroll up region [12..13] size 2 Line 5 Soft scroll up region [12..13] size 2 Line 6 Soft scroll up region [12..13] size 2 Line 7 Soft scroll up region [12..13] size 2 Line 8 Soft scroll up region [12..13] size 2 Line 9 Soft scroll up region [12..13] size 2 Line 10 Soft scroll up region [12..13] size 2 Line 11 Soft scroll up region [12..13] size 2 Line 12 Soft scroll up region [12..13] size 2 Line 13 Soft scroll up region [12..13] size 2 Line 14 Soft scroll up region [12..13] size 2 Line 15 Soft scroll up region [12..13] size 2 Line 16 Soft scroll up region [12..13] size 2 Line 17 Soft scroll up region [12..13] size 2 Line 18 Soft scroll up region [12..13] size 2 Line 19 Soft scroll up region [12..13] size 2 Line 20 Soft scroll up region [12..13] size 2 Line 21 Soft scroll up region [12..13] size 2 Line 22 Soft scroll up region [12..13] size 2 Line 23 Soft scroll up region [12..13] size 2 Line 24 Soft scroll up region [12..13] size 2 Line 25 Soft scroll up region [12..13] size 2 Line 26 Soft scroll up region [12..13] size 2 Line 27 Soft scroll up region [12..13] size 2 Line 28 Soft scroll up region [12..13] size 2 Line 29 Soft scroll down region [12..13] size 2 Line 1 MMSoft scroll down region [12..13] size 2 Line 2 MMSoft scroll down region [12..13] size 2 Line 3 MMSoft scroll down region [12..13] size 2 Line 4 MMSoft scroll down region [12..13] size 2 Line 5 MMSoft scroll down region [12..13] size 2 Line 6 MMSoft scroll down region [12..13] size 2 Line 7 MMSoft scroll down region [12..13] size 2 Line 8 MMSoft scroll down region [12..13] size 2 Line 9 MMSoft scroll down region [12..13] size 2 Line 10 MMSoft scroll down region [12..13] size 2 Line 11 MMSoft scroll down region [12..13] size 2 Line 12 MMSoft scroll down region [12..13] size 2 Line 13 MMSoft scroll down region [12..13] size 2 Line 14 MMSoft scroll down region [12..13] size 2 Line 15 MMSoft scroll down region [12..13] size 2 Line 16 MMSoft scroll down region [12..13] size 2 Line 17 MMSoft scroll down region [12..13] size 2 Line 18 MMSoft scroll down region [12..13] size 2 Line 19 MMSoft scroll down region [12..13] size 2 Line 20 MMSoft scroll down region [12..13] size 2 Line 21 MMSoft scroll down region [12..13] size 2 Line 22 MMSoft scroll down region [12..13] size 2 Line 23 MMSoft scroll down region [12..13] size 2 Line 24 MMSoft scroll down region [12..13] size 2 Line 25 MMSoft scroll down region [12..13] size 2 Line 26 MMSoft scroll down region [12..13] size 2 Line 27 MMSoft scroll down region [12..13] size 2 Line 28 MMSoft scroll down region [12..13] size 2 Line 29 MMPush Soft scroll up region [1..24] size 24 Line 1 Soft scroll up region [1..24] size 24 Line 2 Soft scroll up region [1..24] size 24 Line 3 Soft scroll up region [1..24] size 24 Line 4 Soft scroll up region [1..24] size 24 Line 5 Soft scroll up region [1..24] size 24 Line 6 Soft scroll up region [1..24] size 24 Line 7 Soft scroll up region [1..24] size 24 Line 8 Soft scroll up region [1..24] size 24 Line 9 Soft scroll up region [1..24] size 24 Line 10 Soft scroll up region [1..24] size 24 Line 11 Soft scroll up region [1..24] size 24 Line 12 Soft scroll up region [1..24] size 24 Line 13 Soft scroll up region [1..24] size 24 Line 14 Soft scroll up region [1..24] size 24 Line 15 Soft scroll up region [1..24] size 24 Line 16 Soft scroll up region [1..24] size 24 Line 17 Soft scroll up region [1..24] size 24 Line 18 Soft scroll up region [1..24] size 24 Line 19 Soft scroll up region [1..24] size 24 Line 20 Soft scroll up region [1..24] size 24 Line 21 Soft scroll up region [1..24] size 24 Line 22 Soft scroll up region [1..24] size 24 Line 23 Soft scroll up region [1..24] size 24 Line 24 Soft scroll up region [1..24] size 24 Line 25 Soft scroll up region [1..24] size 24 Line 26 Soft scroll up region [1..24] size 24 Line 27 Soft scroll up region [1..24] size 24 Line 28 Soft scroll up region [1..24] size 24 Line 29 Soft scroll down region [1..24] size 24 Line 1 MMSoft scroll down region [1..24] size 24 Line 2 MMSoft scroll down region [1..24] size 24 Line 3 MMSoft scroll down region [1..24] size 24 Line 4 MMSoft scroll down region [1..24] size 24 Line 5 MMSoft scroll down region [1..24] size 24 Line 6 MMSoft scroll down region [1..24] size 24 Line 7 MMSoft scroll down region [1..24] size 24 Line 8 MMSoft scroll down region [1..24] size 24 Line 9 MMSoft scroll down region [1..24] size 24 Line 10 MMSoft scroll down region [1..24] size 24 Line 11 MMSoft scroll down region [1..24] size 24 Line 12 MMSoft scroll down region [1..24] size 24 Line 13 MMSoft scroll down region [1..24] size 24 Line 14 MMSoft scroll down region [1..24] size 24 Line 15 MMSoft scroll down region [1..24] size 24 Line 16 MMSoft scroll down region [1..24] size 24 Line 17 MMSoft scroll down region [1..24] size 24 Line 18 MMSoft scroll down region [1..24] size 24 Line 19 MMSoft scroll down region [1..24] size 24 Line 20 MMSoft scroll down region [1..24] size 24 Line 21 MMSoft scroll down region [1..24] size 24 Line 22 MMSoft scroll down region [1..24] size 24 Line 23 MMSoft scroll down region [1..24] size 24 Line 24 MMSoft scroll down region [1..24] size 24 Line 25 MMSoft scroll down region [1..24] size 24 Line 26 MMSoft scroll down region [1..24] size 24 Line 27 MMSoft scroll down region [1..24] size 24 Line 28 MMSoft scroll down region [1..24] size 24 Line 29 MMPush [?4lJump scroll up region [12..13] size 2 Line 1 Jump scroll up region [12..13] size 2 Line 2 Jump scroll up region [12..13] size 2 Line 3 Jump scroll up region [12..13] size 2 Line 4 Jump scroll up region [12..13] size 2 Line 5 Jump scroll up region [12..13] size 2 Line 6 Jump scroll up region [12..13] size 2 Line 7 Jump scroll up region [12..13] size 2 Line 8 Jump scroll up region [12..13] size 2 Line 9 Jump scroll up region [12..13] size 2 Line 10 Jump scroll up region [12..13] size 2 Line 11 Jump scroll up region [12..13] size 2 Line 12 Jump scroll up region [12..13] size 2 Line 13 Jump scroll up region [12..13] size 2 Line 14 Jump scroll up region [12..13] size 2 Line 15 Jump scroll up region [12..13] size 2 Line 16 Jump scroll up region [12..13] size 2 Line 17 Jump scroll up region [12..13] size 2 Line 18 Jump scroll up region [12..13] size 2 Line 19 Jump scroll up region [12..13] size 2 Line 20 Jump scroll up region [12..13] size 2 Line 21 Jump scroll up region [12..13] size 2 Line 22 Jump scroll up region [12..13] size 2 Line 23 Jump scroll up region [12..13] size 2 Line 24 Jump scroll up region [12..13] size 2 Line 25 Jump scroll up region [12..13] size 2 Line 26 Jump scroll up region [12..13] size 2 Line 27 Jump scroll up region [12..13] size 2 Line 28 Jump scroll up region [12..13] size 2 Line 29 Jump scroll down region [12..13] size 2 Line 1 MMJump scroll down region [12..13] size 2 Line 2 MMJump scroll down region [12..13] size 2 Line 3 MMJump scroll down region [12..13] size 2 Line 4 MMJump scroll down region [12..13] size 2 Line 5 MMJump scroll down region [12..13] size 2 Line 6 MMJump scroll down region [12..13] size 2 Line 7 MMJump scroll down region [12..13] size 2 Line 8 MMJump scroll down region [12..13] size 2 Line 9 MMJump scroll down region [12..13] size 2 Line 10 MMJump scroll down region [12..13] size 2 Line 11 MMJump scroll down region [12..13] size 2 Line 12 MMJump scroll down region [12..13] size 2 Line 13 MMJump scroll down region [12..13] size 2 Line 14 MMJump scroll down region [12..13] size 2 Line 15 MMJump scroll down region [12..13] size 2 Line 16 MMJump scroll down region [12..13] size 2 Line 17 MMJump scroll down region [12..13] size 2 Line 18 MMJump scroll down region [12..13] size 2 Line 19 MMJump scroll down region [12..13] size 2 Line 20 MMJump scroll down region [12..13] size 2 Line 21 MMJump scroll down region [12..13] size 2 Line 22 MMJump scroll down region [12..13] size 2 Line 23 MMJump scroll down region [12..13] size 2 Line 24 MMJump scroll down region [12..13] size 2 Line 25 MMJump scroll down region [12..13] size 2 Line 26 MMJump scroll down region [12..13] size 2 Line 27 MMJump scroll down region [12..13] size 2 Line 28 MMJump scroll down region [12..13] size 2 Line 29 MMPush Jump scroll up region [1..24] size 24 Line 1 Jump scroll up region [1..24] size 24 Line 2 Jump scroll up region [1..24] size 24 Line 3 Jump scroll up region [1..24] size 24 Line 4 Jump scroll up region [1..24] size 24 Line 5 Jump scroll up region [1..24] size 24 Line 6 Jump scroll up region [1..24] size 24 Line 7 Jump scroll up region [1..24] size 24 Line 8 Jump scroll up region [1..24] size 24 Line 9 Jump scroll up region [1..24] size 24 Line 10 Jump scroll up region [1..24] size 24 Line 11 Jump scroll up region [1..24] size 24 Line 12 Jump scroll up region [1..24] size 24 Line 13 Jump scroll up region [1..24] size 24 Line 14 Jump scroll up region [1..24] size 24 Line 15 Jump scroll up region [1..24] size 24 Line 16 Jump scroll up region [1..24] size 24 Line 17 Jump scroll up region [1..24] size 24 Line 18 Jump scroll up region [1..24] size 24 Line 19 Jump scroll up region [1..24] size 24 Line 20 Jump scroll up region [1..24] size 24 Line 21 Jump scroll up region [1..24] size 24 Line 22 Jump scroll up region [1..24] size 24 Line 23 Jump scroll up region [1..24] size 24 Line 24 Jump scroll up region [1..24] size 24 Line 25 Jump scroll up region [1..24] size 24 Line 26 Jump scroll up region [1..24] size 24 Line 27 Jump scroll up region [1..24] size 24 Line 28 Jump scroll up region [1..24] size 24 Line 29 Jump scroll down region [1..24] size 24 Line 1 MMJump scroll down region [1..24] size 24 Line 2 MMJump scroll down region [1..24] size 24 Line 3 MMJump scroll down region [1..24] size 24 Line 4 MMJump scroll down region [1..24] size 24 Line 5 MMJump scroll down region [1..24] size 24 Line 6 MMJump scroll down region [1..24] size 24 Line 7 MMJump scroll down region [1..24] size 24 Line 8 MMJump scroll down region [1..24] size 24 Line 9 MMJump scroll down region [1..24] size 24 Line 10 MMJump scroll down region [1..24] size 24 Line 11 MMJump scroll down region [1..24] size 24 Line 12 MMJump scroll down region [1..24] size 24 Line 13 MMJump scroll down region [1..24] size 24 Line 14 MMJump scroll down region [1..24] size 24 Line 15 MMJump scroll down region [1..24] size 24 Line 16 MMJump scroll down region [1..24] size 24 Line 17 MMJump scroll down region [1..24] size 24 Line 18 MMJump scroll down region [1..24] size 24 Line 19 MMJump scroll down region [1..24] size 24 Line 20 MMJump scroll down region [1..24] size 24 Line 21 MMJump scroll down region [1..24] size 24 Line 22 MMJump scroll down region [1..24] size 24 Line 23 MMJump scroll down region [1..24] size 24 Line 24 MMJump scroll down region [1..24] size 24 Line 25 MMJump scroll down region [1..24] size 24 Line 26 MMJump scroll down region [1..24] size 24 Line 27 MMJump scroll down region [1..24] size 24 Line 28 MMJump scroll down region [1..24] size 24 Line 29 MMPush  Origin mode test. This line should be at the bottom of the screen.This line should be the one above the bottom of the screen. Push ================================================ FILE: test/test_files/vttest/2-12 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push [?5l[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.Push [?6h[?4hSoft scroll up region [12..13] size 2 Line 1 Soft scroll up region [12..13] size 2 Line 2 Soft scroll up region [12..13] size 2 Line 3 Soft scroll up region [12..13] size 2 Line 4 Soft scroll up region [12..13] size 2 Line 5 Soft scroll up region [12..13] size 2 Line 6 Soft scroll up region [12..13] size 2 Line 7 Soft scroll up region [12..13] size 2 Line 8 Soft scroll up region [12..13] size 2 Line 9 Soft scroll up region [12..13] size 2 Line 10 Soft scroll up region [12..13] size 2 Line 11 Soft scroll up region [12..13] size 2 Line 12 Soft scroll up region [12..13] size 2 Line 13 Soft scroll up region [12..13] size 2 Line 14 Soft scroll up region [12..13] size 2 Line 15 Soft scroll up region [12..13] size 2 Line 16 Soft scroll up region [12..13] size 2 Line 17 Soft scroll up region [12..13] size 2 Line 18 Soft scroll up region [12..13] size 2 Line 19 Soft scroll up region [12..13] size 2 Line 20 Soft scroll up region [12..13] size 2 Line 21 Soft scroll up region [12..13] size 2 Line 22 Soft scroll up region [12..13] size 2 Line 23 Soft scroll up region [12..13] size 2 Line 24 Soft scroll up region [12..13] size 2 Line 25 Soft scroll up region [12..13] size 2 Line 26 Soft scroll up region [12..13] size 2 Line 27 Soft scroll up region [12..13] size 2 Line 28 Soft scroll up region [12..13] size 2 Line 29 Soft scroll down region [12..13] size 2 Line 1 MMSoft scroll down region [12..13] size 2 Line 2 MMSoft scroll down region [12..13] size 2 Line 3 MMSoft scroll down region [12..13] size 2 Line 4 MMSoft scroll down region [12..13] size 2 Line 5 MMSoft scroll down region [12..13] size 2 Line 6 MMSoft scroll down region [12..13] size 2 Line 7 MMSoft scroll down region [12..13] size 2 Line 8 MMSoft scroll down region [12..13] size 2 Line 9 MMSoft scroll down region [12..13] size 2 Line 10 MMSoft scroll down region [12..13] size 2 Line 11 MMSoft scroll down region [12..13] size 2 Line 12 MMSoft scroll down region [12..13] size 2 Line 13 MMSoft scroll down region [12..13] size 2 Line 14 MMSoft scroll down region [12..13] size 2 Line 15 MMSoft scroll down region [12..13] size 2 Line 16 MMSoft scroll down region [12..13] size 2 Line 17 MMSoft scroll down region [12..13] size 2 Line 18 MMSoft scroll down region [12..13] size 2 Line 19 MMSoft scroll down region [12..13] size 2 Line 20 MMSoft scroll down region [12..13] size 2 Line 21 MMSoft scroll down region [12..13] size 2 Line 22 MMSoft scroll down region [12..13] size 2 Line 23 MMSoft scroll down region [12..13] size 2 Line 24 MMSoft scroll down region [12..13] size 2 Line 25 MMSoft scroll down region [12..13] size 2 Line 26 MMSoft scroll down region [12..13] size 2 Line 27 MMSoft scroll down region [12..13] size 2 Line 28 MMSoft scroll down region [12..13] size 2 Line 29 MMPush Soft scroll up region [1..24] size 24 Line 1 Soft scroll up region [1..24] size 24 Line 2 Soft scroll up region [1..24] size 24 Line 3 Soft scroll up region [1..24] size 24 Line 4 Soft scroll up region [1..24] size 24 Line 5 Soft scroll up region [1..24] size 24 Line 6 Soft scroll up region [1..24] size 24 Line 7 Soft scroll up region [1..24] size 24 Line 8 Soft scroll up region [1..24] size 24 Line 9 Soft scroll up region [1..24] size 24 Line 10 Soft scroll up region [1..24] size 24 Line 11 Soft scroll up region [1..24] size 24 Line 12 Soft scroll up region [1..24] size 24 Line 13 Soft scroll up region [1..24] size 24 Line 14 Soft scroll up region [1..24] size 24 Line 15 Soft scroll up region [1..24] size 24 Line 16 Soft scroll up region [1..24] size 24 Line 17 Soft scroll up region [1..24] size 24 Line 18 Soft scroll up region [1..24] size 24 Line 19 Soft scroll up region [1..24] size 24 Line 20 Soft scroll up region [1..24] size 24 Line 21 Soft scroll up region [1..24] size 24 Line 22 Soft scroll up region [1..24] size 24 Line 23 Soft scroll up region [1..24] size 24 Line 24 Soft scroll up region [1..24] size 24 Line 25 Soft scroll up region [1..24] size 24 Line 26 Soft scroll up region [1..24] size 24 Line 27 Soft scroll up region [1..24] size 24 Line 28 Soft scroll up region [1..24] size 24 Line 29 Soft scroll down region [1..24] size 24 Line 1 MMSoft scroll down region [1..24] size 24 Line 2 MMSoft scroll down region [1..24] size 24 Line 3 MMSoft scroll down region [1..24] size 24 Line 4 MMSoft scroll down region [1..24] size 24 Line 5 MMSoft scroll down region [1..24] size 24 Line 6 MMSoft scroll down region [1..24] size 24 Line 7 MMSoft scroll down region [1..24] size 24 Line 8 MMSoft scroll down region [1..24] size 24 Line 9 MMSoft scroll down region [1..24] size 24 Line 10 MMSoft scroll down region [1..24] size 24 Line 11 MMSoft scroll down region [1..24] size 24 Line 12 MMSoft scroll down region [1..24] size 24 Line 13 MMSoft scroll down region [1..24] size 24 Line 14 MMSoft scroll down region [1..24] size 24 Line 15 MMSoft scroll down region [1..24] size 24 Line 16 MMSoft scroll down region [1..24] size 24 Line 17 MMSoft scroll down region [1..24] size 24 Line 18 MMSoft scroll down region [1..24] size 24 Line 19 MMSoft scroll down region [1..24] size 24 Line 20 MMSoft scroll down region [1..24] size 24 Line 21 MMSoft scroll down region [1..24] size 24 Line 22 MMSoft scroll down region [1..24] size 24 Line 23 MMSoft scroll down region [1..24] size 24 Line 24 MMSoft scroll down region [1..24] size 24 Line 25 MMSoft scroll down region [1..24] size 24 Line 26 MMSoft scroll down region [1..24] size 24 Line 27 MMSoft scroll down region [1..24] size 24 Line 28 MMSoft scroll down region [1..24] size 24 Line 29 MMPush [?4lJump scroll up region [12..13] size 2 Line 1 Jump scroll up region [12..13] size 2 Line 2 Jump scroll up region [12..13] size 2 Line 3 Jump scroll up region [12..13] size 2 Line 4 Jump scroll up region [12..13] size 2 Line 5 Jump scroll up region [12..13] size 2 Line 6 Jump scroll up region [12..13] size 2 Line 7 Jump scroll up region [12..13] size 2 Line 8 Jump scroll up region [12..13] size 2 Line 9 Jump scroll up region [12..13] size 2 Line 10 Jump scroll up region [12..13] size 2 Line 11 Jump scroll up region [12..13] size 2 Line 12 Jump scroll up region [12..13] size 2 Line 13 Jump scroll up region [12..13] size 2 Line 14 Jump scroll up region [12..13] size 2 Line 15 Jump scroll up region [12..13] size 2 Line 16 Jump scroll up region [12..13] size 2 Line 17 Jump scroll up region [12..13] size 2 Line 18 Jump scroll up region [12..13] size 2 Line 19 Jump scroll up region [12..13] size 2 Line 20 Jump scroll up region [12..13] size 2 Line 21 Jump scroll up region [12..13] size 2 Line 22 Jump scroll up region [12..13] size 2 Line 23 Jump scroll up region [12..13] size 2 Line 24 Jump scroll up region [12..13] size 2 Line 25 Jump scroll up region [12..13] size 2 Line 26 Jump scroll up region [12..13] size 2 Line 27 Jump scroll up region [12..13] size 2 Line 28 Jump scroll up region [12..13] size 2 Line 29 Jump scroll down region [12..13] size 2 Line 1 MMJump scroll down region [12..13] size 2 Line 2 MMJump scroll down region [12..13] size 2 Line 3 MMJump scroll down region [12..13] size 2 Line 4 MMJump scroll down region [12..13] size 2 Line 5 MMJump scroll down region [12..13] size 2 Line 6 MMJump scroll down region [12..13] size 2 Line 7 MMJump scroll down region [12..13] size 2 Line 8 MMJump scroll down region [12..13] size 2 Line 9 MMJump scroll down region [12..13] size 2 Line 10 MMJump scroll down region [12..13] size 2 Line 11 MMJump scroll down region [12..13] size 2 Line 12 MMJump scroll down region [12..13] size 2 Line 13 MMJump scroll down region [12..13] size 2 Line 14 MMJump scroll down region [12..13] size 2 Line 15 MMJump scroll down region [12..13] size 2 Line 16 MMJump scroll down region [12..13] size 2 Line 17 MMJump scroll down region [12..13] size 2 Line 18 MMJump scroll down region [12..13] size 2 Line 19 MMJump scroll down region [12..13] size 2 Line 20 MMJump scroll down region [12..13] size 2 Line 21 MMJump scroll down region [12..13] size 2 Line 22 MMJump scroll down region [12..13] size 2 Line 23 MMJump scroll down region [12..13] size 2 Line 24 MMJump scroll down region [12..13] size 2 Line 25 MMJump scroll down region [12..13] size 2 Line 26 MMJump scroll down region [12..13] size 2 Line 27 MMJump scroll down region [12..13] size 2 Line 28 MMJump scroll down region [12..13] size 2 Line 29 MMPush Jump scroll up region [1..24] size 24 Line 1 Jump scroll up region [1..24] size 24 Line 2 Jump scroll up region [1..24] size 24 Line 3 Jump scroll up region [1..24] size 24 Line 4 Jump scroll up region [1..24] size 24 Line 5 Jump scroll up region [1..24] size 24 Line 6 Jump scroll up region [1..24] size 24 Line 7 Jump scroll up region [1..24] size 24 Line 8 Jump scroll up region [1..24] size 24 Line 9 Jump scroll up region [1..24] size 24 Line 10 Jump scroll up region [1..24] size 24 Line 11 Jump scroll up region [1..24] size 24 Line 12 Jump scroll up region [1..24] size 24 Line 13 Jump scroll up region [1..24] size 24 Line 14 Jump scroll up region [1..24] size 24 Line 15 Jump scroll up region [1..24] size 24 Line 16 Jump scroll up region [1..24] size 24 Line 17 Jump scroll up region [1..24] size 24 Line 18 Jump scroll up region [1..24] size 24 Line 19 Jump scroll up region [1..24] size 24 Line 20 Jump scroll up region [1..24] size 24 Line 21 Jump scroll up region [1..24] size 24 Line 22 Jump scroll up region [1..24] size 24 Line 23 Jump scroll up region [1..24] size 24 Line 24 Jump scroll up region [1..24] size 24 Line 25 Jump scroll up region [1..24] size 24 Line 26 Jump scroll up region [1..24] size 24 Line 27 Jump scroll up region [1..24] size 24 Line 28 Jump scroll up region [1..24] size 24 Line 29 Jump scroll down region [1..24] size 24 Line 1 MMJump scroll down region [1..24] size 24 Line 2 MMJump scroll down region [1..24] size 24 Line 3 MMJump scroll down region [1..24] size 24 Line 4 MMJump scroll down region [1..24] size 24 Line 5 MMJump scroll down region [1..24] size 24 Line 6 MMJump scroll down region [1..24] size 24 Line 7 MMJump scroll down region [1..24] size 24 Line 8 MMJump scroll down region [1..24] size 24 Line 9 MMJump scroll down region [1..24] size 24 Line 10 MMJump scroll down region [1..24] size 24 Line 11 MMJump scroll down region [1..24] size 24 Line 12 MMJump scroll down region [1..24] size 24 Line 13 MMJump scroll down region [1..24] size 24 Line 14 MMJump scroll down region [1..24] size 24 Line 15 MMJump scroll down region [1..24] size 24 Line 16 MMJump scroll down region [1..24] size 24 Line 17 MMJump scroll down region [1..24] size 24 Line 18 MMJump scroll down region [1..24] size 24 Line 19 MMJump scroll down region [1..24] size 24 Line 20 MMJump scroll down region [1..24] size 24 Line 21 MMJump scroll down region [1..24] size 24 Line 22 MMJump scroll down region [1..24] size 24 Line 23 MMJump scroll down region [1..24] size 24 Line 24 MMJump scroll down region [1..24] size 24 Line 25 MMJump scroll down region [1..24] size 24 Line 26 MMJump scroll down region [1..24] size 24 Line 27 MMJump scroll down region [1..24] size 24 Line 28 MMJump scroll down region [1..24] size 24 Line 29 MMPush  Origin mode test. This line should be at the bottom of the screen.This line should be the one above the bottom of the screen. Push [?6lOrigin mode test. This line should be at the bottom of the screen.This line should be at the top of the screen. Push ================================================ FILE: test/test_files/vttest/2-13 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push [?5l[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.Push [?6h[?4hSoft scroll up region [12..13] size 2 Line 1 Soft scroll up region [12..13] size 2 Line 2 Soft scroll up region [12..13] size 2 Line 3 Soft scroll up region [12..13] size 2 Line 4 Soft scroll up region [12..13] size 2 Line 5 Soft scroll up region [12..13] size 2 Line 6 Soft scroll up region [12..13] size 2 Line 7 Soft scroll up region [12..13] size 2 Line 8 Soft scroll up region [12..13] size 2 Line 9 Soft scroll up region [12..13] size 2 Line 10 Soft scroll up region [12..13] size 2 Line 11 Soft scroll up region [12..13] size 2 Line 12 Soft scroll up region [12..13] size 2 Line 13 Soft scroll up region [12..13] size 2 Line 14 Soft scroll up region [12..13] size 2 Line 15 Soft scroll up region [12..13] size 2 Line 16 Soft scroll up region [12..13] size 2 Line 17 Soft scroll up region [12..13] size 2 Line 18 Soft scroll up region [12..13] size 2 Line 19 Soft scroll up region [12..13] size 2 Line 20 Soft scroll up region [12..13] size 2 Line 21 Soft scroll up region [12..13] size 2 Line 22 Soft scroll up region [12..13] size 2 Line 23 Soft scroll up region [12..13] size 2 Line 24 Soft scroll up region [12..13] size 2 Line 25 Soft scroll up region [12..13] size 2 Line 26 Soft scroll up region [12..13] size 2 Line 27 Soft scroll up region [12..13] size 2 Line 28 Soft scroll up region [12..13] size 2 Line 29 Soft scroll down region [12..13] size 2 Line 1 MMSoft scroll down region [12..13] size 2 Line 2 MMSoft scroll down region [12..13] size 2 Line 3 MMSoft scroll down region [12..13] size 2 Line 4 MMSoft scroll down region [12..13] size 2 Line 5 MMSoft scroll down region [12..13] size 2 Line 6 MMSoft scroll down region [12..13] size 2 Line 7 MMSoft scroll down region [12..13] size 2 Line 8 MMSoft scroll down region [12..13] size 2 Line 9 MMSoft scroll down region [12..13] size 2 Line 10 MMSoft scroll down region [12..13] size 2 Line 11 MMSoft scroll down region [12..13] size 2 Line 12 MMSoft scroll down region [12..13] size 2 Line 13 MMSoft scroll down region [12..13] size 2 Line 14 MMSoft scroll down region [12..13] size 2 Line 15 MMSoft scroll down region [12..13] size 2 Line 16 MMSoft scroll down region [12..13] size 2 Line 17 MMSoft scroll down region [12..13] size 2 Line 18 MMSoft scroll down region [12..13] size 2 Line 19 MMSoft scroll down region [12..13] size 2 Line 20 MMSoft scroll down region [12..13] size 2 Line 21 MMSoft scroll down region [12..13] size 2 Line 22 MMSoft scroll down region [12..13] size 2 Line 23 MMSoft scroll down region [12..13] size 2 Line 24 MMSoft scroll down region [12..13] size 2 Line 25 MMSoft scroll down region [12..13] size 2 Line 26 MMSoft scroll down region [12..13] size 2 Line 27 MMSoft scroll down region [12..13] size 2 Line 28 MMSoft scroll down region [12..13] size 2 Line 29 MMPush Soft scroll up region [1..24] size 24 Line 1 Soft scroll up region [1..24] size 24 Line 2 Soft scroll up region [1..24] size 24 Line 3 Soft scroll up region [1..24] size 24 Line 4 Soft scroll up region [1..24] size 24 Line 5 Soft scroll up region [1..24] size 24 Line 6 Soft scroll up region [1..24] size 24 Line 7 Soft scroll up region [1..24] size 24 Line 8 Soft scroll up region [1..24] size 24 Line 9 Soft scroll up region [1..24] size 24 Line 10 Soft scroll up region [1..24] size 24 Line 11 Soft scroll up region [1..24] size 24 Line 12 Soft scroll up region [1..24] size 24 Line 13 Soft scroll up region [1..24] size 24 Line 14 Soft scroll up region [1..24] size 24 Line 15 Soft scroll up region [1..24] size 24 Line 16 Soft scroll up region [1..24] size 24 Line 17 Soft scroll up region [1..24] size 24 Line 18 Soft scroll up region [1..24] size 24 Line 19 Soft scroll up region [1..24] size 24 Line 20 Soft scroll up region [1..24] size 24 Line 21 Soft scroll up region [1..24] size 24 Line 22 Soft scroll up region [1..24] size 24 Line 23 Soft scroll up region [1..24] size 24 Line 24 Soft scroll up region [1..24] size 24 Line 25 Soft scroll up region [1..24] size 24 Line 26 Soft scroll up region [1..24] size 24 Line 27 Soft scroll up region [1..24] size 24 Line 28 Soft scroll up region [1..24] size 24 Line 29 Soft scroll down region [1..24] size 24 Line 1 MMSoft scroll down region [1..24] size 24 Line 2 MMSoft scroll down region [1..24] size 24 Line 3 MMSoft scroll down region [1..24] size 24 Line 4 MMSoft scroll down region [1..24] size 24 Line 5 MMSoft scroll down region [1..24] size 24 Line 6 MMSoft scroll down region [1..24] size 24 Line 7 MMSoft scroll down region [1..24] size 24 Line 8 MMSoft scroll down region [1..24] size 24 Line 9 MMSoft scroll down region [1..24] size 24 Line 10 MMSoft scroll down region [1..24] size 24 Line 11 MMSoft scroll down region [1..24] size 24 Line 12 MMSoft scroll down region [1..24] size 24 Line 13 MMSoft scroll down region [1..24] size 24 Line 14 MMSoft scroll down region [1..24] size 24 Line 15 MMSoft scroll down region [1..24] size 24 Line 16 MMSoft scroll down region [1..24] size 24 Line 17 MMSoft scroll down region [1..24] size 24 Line 18 MMSoft scroll down region [1..24] size 24 Line 19 MMSoft scroll down region [1..24] size 24 Line 20 MMSoft scroll down region [1..24] size 24 Line 21 MMSoft scroll down region [1..24] size 24 Line 22 MMSoft scroll down region [1..24] size 24 Line 23 MMSoft scroll down region [1..24] size 24 Line 24 MMSoft scroll down region [1..24] size 24 Line 25 MMSoft scroll down region [1..24] size 24 Line 26 MMSoft scroll down region [1..24] size 24 Line 27 MMSoft scroll down region [1..24] size 24 Line 28 MMSoft scroll down region [1..24] size 24 Line 29 MMPush [?4lJump scroll up region [12..13] size 2 Line 1 Jump scroll up region [12..13] size 2 Line 2 Jump scroll up region [12..13] size 2 Line 3 Jump scroll up region [12..13] size 2 Line 4 Jump scroll up region [12..13] size 2 Line 5 Jump scroll up region [12..13] size 2 Line 6 Jump scroll up region [12..13] size 2 Line 7 Jump scroll up region [12..13] size 2 Line 8 Jump scroll up region [12..13] size 2 Line 9 Jump scroll up region [12..13] size 2 Line 10 Jump scroll up region [12..13] size 2 Line 11 Jump scroll up region [12..13] size 2 Line 12 Jump scroll up region [12..13] size 2 Line 13 Jump scroll up region [12..13] size 2 Line 14 Jump scroll up region [12..13] size 2 Line 15 Jump scroll up region [12..13] size 2 Line 16 Jump scroll up region [12..13] size 2 Line 17 Jump scroll up region [12..13] size 2 Line 18 Jump scroll up region [12..13] size 2 Line 19 Jump scroll up region [12..13] size 2 Line 20 Jump scroll up region [12..13] size 2 Line 21 Jump scroll up region [12..13] size 2 Line 22 Jump scroll up region [12..13] size 2 Line 23 Jump scroll up region [12..13] size 2 Line 24 Jump scroll up region [12..13] size 2 Line 25 Jump scroll up region [12..13] size 2 Line 26 Jump scroll up region [12..13] size 2 Line 27 Jump scroll up region [12..13] size 2 Line 28 Jump scroll up region [12..13] size 2 Line 29 Jump scroll down region [12..13] size 2 Line 1 MMJump scroll down region [12..13] size 2 Line 2 MMJump scroll down region [12..13] size 2 Line 3 MMJump scroll down region [12..13] size 2 Line 4 MMJump scroll down region [12..13] size 2 Line 5 MMJump scroll down region [12..13] size 2 Line 6 MMJump scroll down region [12..13] size 2 Line 7 MMJump scroll down region [12..13] size 2 Line 8 MMJump scroll down region [12..13] size 2 Line 9 MMJump scroll down region [12..13] size 2 Line 10 MMJump scroll down region [12..13] size 2 Line 11 MMJump scroll down region [12..13] size 2 Line 12 MMJump scroll down region [12..13] size 2 Line 13 MMJump scroll down region [12..13] size 2 Line 14 MMJump scroll down region [12..13] size 2 Line 15 MMJump scroll down region [12..13] size 2 Line 16 MMJump scroll down region [12..13] size 2 Line 17 MMJump scroll down region [12..13] size 2 Line 18 MMJump scroll down region [12..13] size 2 Line 19 MMJump scroll down region [12..13] size 2 Line 20 MMJump scroll down region [12..13] size 2 Line 21 MMJump scroll down region [12..13] size 2 Line 22 MMJump scroll down region [12..13] size 2 Line 23 MMJump scroll down region [12..13] size 2 Line 24 MMJump scroll down region [12..13] size 2 Line 25 MMJump scroll down region [12..13] size 2 Line 26 MMJump scroll down region [12..13] size 2 Line 27 MMJump scroll down region [12..13] size 2 Line 28 MMJump scroll down region [12..13] size 2 Line 29 MMPush Jump scroll up region [1..24] size 24 Line 1 Jump scroll up region [1..24] size 24 Line 2 Jump scroll up region [1..24] size 24 Line 3 Jump scroll up region [1..24] size 24 Line 4 Jump scroll up region [1..24] size 24 Line 5 Jump scroll up region [1..24] size 24 Line 6 Jump scroll up region [1..24] size 24 Line 7 Jump scroll up region [1..24] size 24 Line 8 Jump scroll up region [1..24] size 24 Line 9 Jump scroll up region [1..24] size 24 Line 10 Jump scroll up region [1..24] size 24 Line 11 Jump scroll up region [1..24] size 24 Line 12 Jump scroll up region [1..24] size 24 Line 13 Jump scroll up region [1..24] size 24 Line 14 Jump scroll up region [1..24] size 24 Line 15 Jump scroll up region [1..24] size 24 Line 16 Jump scroll up region [1..24] size 24 Line 17 Jump scroll up region [1..24] size 24 Line 18 Jump scroll up region [1..24] size 24 Line 19 Jump scroll up region [1..24] size 24 Line 20 Jump scroll up region [1..24] size 24 Line 21 Jump scroll up region [1..24] size 24 Line 22 Jump scroll up region [1..24] size 24 Line 23 Jump scroll up region [1..24] size 24 Line 24 Jump scroll up region [1..24] size 24 Line 25 Jump scroll up region [1..24] size 24 Line 26 Jump scroll up region [1..24] size 24 Line 27 Jump scroll up region [1..24] size 24 Line 28 Jump scroll up region [1..24] size 24 Line 29 Jump scroll down region [1..24] size 24 Line 1 MMJump scroll down region [1..24] size 24 Line 2 MMJump scroll down region [1..24] size 24 Line 3 MMJump scroll down region [1..24] size 24 Line 4 MMJump scroll down region [1..24] size 24 Line 5 MMJump scroll down region [1..24] size 24 Line 6 MMJump scroll down region [1..24] size 24 Line 7 MMJump scroll down region [1..24] size 24 Line 8 MMJump scroll down region [1..24] size 24 Line 9 MMJump scroll down region [1..24] size 24 Line 10 MMJump scroll down region [1..24] size 24 Line 11 MMJump scroll down region [1..24] size 24 Line 12 MMJump scroll down region [1..24] size 24 Line 13 MMJump scroll down region [1..24] size 24 Line 14 MMJump scroll down region [1..24] size 24 Line 15 MMJump scroll down region [1..24] size 24 Line 16 MMJump scroll down region [1..24] size 24 Line 17 MMJump scroll down region [1..24] size 24 Line 18 MMJump scroll down region [1..24] size 24 Line 19 MMJump scroll down region [1..24] size 24 Line 20 MMJump scroll down region [1..24] size 24 Line 21 MMJump scroll down region [1..24] size 24 Line 22 MMJump scroll down region [1..24] size 24 Line 23 MMJump scroll down region [1..24] size 24 Line 24 MMJump scroll down region [1..24] size 24 Line 25 MMJump scroll down region [1..24] size 24 Line 26 MMJump scroll down region [1..24] size 24 Line 27 MMJump scroll down region [1..24] size 24 Line 28 MMJump scroll down region [1..24] size 24 Line 29 MMPush  Origin mode test. This line should be at the bottom of the screen.This line should be the one above the bottom of the screen. Push [?6lOrigin mode test. This line should be at the bottom of the screen.This line should be at the top of the screen. Push Graphic rendition test pattern:vanillaboldunderlinebold underlineblinkbold blinkunderline blinkbold underline blinknegativebold negativeunderline negativebold underline negativeblink negativebold blink negativeunderline blink negativebold underline blink negative[?5lDark background. Push ================================================ FILE: test/test_files/vttest/2-14 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push [?5l[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.Push [?6h[?4hSoft scroll up region [12..13] size 2 Line 1 Soft scroll up region [12..13] size 2 Line 2 Soft scroll up region [12..13] size 2 Line 3 Soft scroll up region [12..13] size 2 Line 4 Soft scroll up region [12..13] size 2 Line 5 Soft scroll up region [12..13] size 2 Line 6 Soft scroll up region [12..13] size 2 Line 7 Soft scroll up region [12..13] size 2 Line 8 Soft scroll up region [12..13] size 2 Line 9 Soft scroll up region [12..13] size 2 Line 10 Soft scroll up region [12..13] size 2 Line 11 Soft scroll up region [12..13] size 2 Line 12 Soft scroll up region [12..13] size 2 Line 13 Soft scroll up region [12..13] size 2 Line 14 Soft scroll up region [12..13] size 2 Line 15 Soft scroll up region [12..13] size 2 Line 16 Soft scroll up region [12..13] size 2 Line 17 Soft scroll up region [12..13] size 2 Line 18 Soft scroll up region [12..13] size 2 Line 19 Soft scroll up region [12..13] size 2 Line 20 Soft scroll up region [12..13] size 2 Line 21 Soft scroll up region [12..13] size 2 Line 22 Soft scroll up region [12..13] size 2 Line 23 Soft scroll up region [12..13] size 2 Line 24 Soft scroll up region [12..13] size 2 Line 25 Soft scroll up region [12..13] size 2 Line 26 Soft scroll up region [12..13] size 2 Line 27 Soft scroll up region [12..13] size 2 Line 28 Soft scroll up region [12..13] size 2 Line 29 Soft scroll down region [12..13] size 2 Line 1 MMSoft scroll down region [12..13] size 2 Line 2 MMSoft scroll down region [12..13] size 2 Line 3 MMSoft scroll down region [12..13] size 2 Line 4 MMSoft scroll down region [12..13] size 2 Line 5 MMSoft scroll down region [12..13] size 2 Line 6 MMSoft scroll down region [12..13] size 2 Line 7 MMSoft scroll down region [12..13] size 2 Line 8 MMSoft scroll down region [12..13] size 2 Line 9 MMSoft scroll down region [12..13] size 2 Line 10 MMSoft scroll down region [12..13] size 2 Line 11 MMSoft scroll down region [12..13] size 2 Line 12 MMSoft scroll down region [12..13] size 2 Line 13 MMSoft scroll down region [12..13] size 2 Line 14 MMSoft scroll down region [12..13] size 2 Line 15 MMSoft scroll down region [12..13] size 2 Line 16 MMSoft scroll down region [12..13] size 2 Line 17 MMSoft scroll down region [12..13] size 2 Line 18 MMSoft scroll down region [12..13] size 2 Line 19 MMSoft scroll down region [12..13] size 2 Line 20 MMSoft scroll down region [12..13] size 2 Line 21 MMSoft scroll down region [12..13] size 2 Line 22 MMSoft scroll down region [12..13] size 2 Line 23 MMSoft scroll down region [12..13] size 2 Line 24 MMSoft scroll down region [12..13] size 2 Line 25 MMSoft scroll down region [12..13] size 2 Line 26 MMSoft scroll down region [12..13] size 2 Line 27 MMSoft scroll down region [12..13] size 2 Line 28 MMSoft scroll down region [12..13] size 2 Line 29 MMPush Soft scroll up region [1..24] size 24 Line 1 Soft scroll up region [1..24] size 24 Line 2 Soft scroll up region [1..24] size 24 Line 3 Soft scroll up region [1..24] size 24 Line 4 Soft scroll up region [1..24] size 24 Line 5 Soft scroll up region [1..24] size 24 Line 6 Soft scroll up region [1..24] size 24 Line 7 Soft scroll up region [1..24] size 24 Line 8 Soft scroll up region [1..24] size 24 Line 9 Soft scroll up region [1..24] size 24 Line 10 Soft scroll up region [1..24] size 24 Line 11 Soft scroll up region [1..24] size 24 Line 12 Soft scroll up region [1..24] size 24 Line 13 Soft scroll up region [1..24] size 24 Line 14 Soft scroll up region [1..24] size 24 Line 15 Soft scroll up region [1..24] size 24 Line 16 Soft scroll up region [1..24] size 24 Line 17 Soft scroll up region [1..24] size 24 Line 18 Soft scroll up region [1..24] size 24 Line 19 Soft scroll up region [1..24] size 24 Line 20 Soft scroll up region [1..24] size 24 Line 21 Soft scroll up region [1..24] size 24 Line 22 Soft scroll up region [1..24] size 24 Line 23 Soft scroll up region [1..24] size 24 Line 24 Soft scroll up region [1..24] size 24 Line 25 Soft scroll up region [1..24] size 24 Line 26 Soft scroll up region [1..24] size 24 Line 27 Soft scroll up region [1..24] size 24 Line 28 Soft scroll up region [1..24] size 24 Line 29 Soft scroll down region [1..24] size 24 Line 1 MMSoft scroll down region [1..24] size 24 Line 2 MMSoft scroll down region [1..24] size 24 Line 3 MMSoft scroll down region [1..24] size 24 Line 4 MMSoft scroll down region [1..24] size 24 Line 5 MMSoft scroll down region [1..24] size 24 Line 6 MMSoft scroll down region [1..24] size 24 Line 7 MMSoft scroll down region [1..24] size 24 Line 8 MMSoft scroll down region [1..24] size 24 Line 9 MMSoft scroll down region [1..24] size 24 Line 10 MMSoft scroll down region [1..24] size 24 Line 11 MMSoft scroll down region [1..24] size 24 Line 12 MMSoft scroll down region [1..24] size 24 Line 13 MMSoft scroll down region [1..24] size 24 Line 14 MMSoft scroll down region [1..24] size 24 Line 15 MMSoft scroll down region [1..24] size 24 Line 16 MMSoft scroll down region [1..24] size 24 Line 17 MMSoft scroll down region [1..24] size 24 Line 18 MMSoft scroll down region [1..24] size 24 Line 19 MMSoft scroll down region [1..24] size 24 Line 20 MMSoft scroll down region [1..24] size 24 Line 21 MMSoft scroll down region [1..24] size 24 Line 22 MMSoft scroll down region [1..24] size 24 Line 23 MMSoft scroll down region [1..24] size 24 Line 24 MMSoft scroll down region [1..24] size 24 Line 25 MMSoft scroll down region [1..24] size 24 Line 26 MMSoft scroll down region [1..24] size 24 Line 27 MMSoft scroll down region [1..24] size 24 Line 28 MMSoft scroll down region [1..24] size 24 Line 29 MMPush [?4lJump scroll up region [12..13] size 2 Line 1 Jump scroll up region [12..13] size 2 Line 2 Jump scroll up region [12..13] size 2 Line 3 Jump scroll up region [12..13] size 2 Line 4 Jump scroll up region [12..13] size 2 Line 5 Jump scroll up region [12..13] size 2 Line 6 Jump scroll up region [12..13] size 2 Line 7 Jump scroll up region [12..13] size 2 Line 8 Jump scroll up region [12..13] size 2 Line 9 Jump scroll up region [12..13] size 2 Line 10 Jump scroll up region [12..13] size 2 Line 11 Jump scroll up region [12..13] size 2 Line 12 Jump scroll up region [12..13] size 2 Line 13 Jump scroll up region [12..13] size 2 Line 14 Jump scroll up region [12..13] size 2 Line 15 Jump scroll up region [12..13] size 2 Line 16 Jump scroll up region [12..13] size 2 Line 17 Jump scroll up region [12..13] size 2 Line 18 Jump scroll up region [12..13] size 2 Line 19 Jump scroll up region [12..13] size 2 Line 20 Jump scroll up region [12..13] size 2 Line 21 Jump scroll up region [12..13] size 2 Line 22 Jump scroll up region [12..13] size 2 Line 23 Jump scroll up region [12..13] size 2 Line 24 Jump scroll up region [12..13] size 2 Line 25 Jump scroll up region [12..13] size 2 Line 26 Jump scroll up region [12..13] size 2 Line 27 Jump scroll up region [12..13] size 2 Line 28 Jump scroll up region [12..13] size 2 Line 29 Jump scroll down region [12..13] size 2 Line 1 MMJump scroll down region [12..13] size 2 Line 2 MMJump scroll down region [12..13] size 2 Line 3 MMJump scroll down region [12..13] size 2 Line 4 MMJump scroll down region [12..13] size 2 Line 5 MMJump scroll down region [12..13] size 2 Line 6 MMJump scroll down region [12..13] size 2 Line 7 MMJump scroll down region [12..13] size 2 Line 8 MMJump scroll down region [12..13] size 2 Line 9 MMJump scroll down region [12..13] size 2 Line 10 MMJump scroll down region [12..13] size 2 Line 11 MMJump scroll down region [12..13] size 2 Line 12 MMJump scroll down region [12..13] size 2 Line 13 MMJump scroll down region [12..13] size 2 Line 14 MMJump scroll down region [12..13] size 2 Line 15 MMJump scroll down region [12..13] size 2 Line 16 MMJump scroll down region [12..13] size 2 Line 17 MMJump scroll down region [12..13] size 2 Line 18 MMJump scroll down region [12..13] size 2 Line 19 MMJump scroll down region [12..13] size 2 Line 20 MMJump scroll down region [12..13] size 2 Line 21 MMJump scroll down region [12..13] size 2 Line 22 MMJump scroll down region [12..13] size 2 Line 23 MMJump scroll down region [12..13] size 2 Line 24 MMJump scroll down region [12..13] size 2 Line 25 MMJump scroll down region [12..13] size 2 Line 26 MMJump scroll down region [12..13] size 2 Line 27 MMJump scroll down region [12..13] size 2 Line 28 MMJump scroll down region [12..13] size 2 Line 29 MMPush Jump scroll up region [1..24] size 24 Line 1 Jump scroll up region [1..24] size 24 Line 2 Jump scroll up region [1..24] size 24 Line 3 Jump scroll up region [1..24] size 24 Line 4 Jump scroll up region [1..24] size 24 Line 5 Jump scroll up region [1..24] size 24 Line 6 Jump scroll up region [1..24] size 24 Line 7 Jump scroll up region [1..24] size 24 Line 8 Jump scroll up region [1..24] size 24 Line 9 Jump scroll up region [1..24] size 24 Line 10 Jump scroll up region [1..24] size 24 Line 11 Jump scroll up region [1..24] size 24 Line 12 Jump scroll up region [1..24] size 24 Line 13 Jump scroll up region [1..24] size 24 Line 14 Jump scroll up region [1..24] size 24 Line 15 Jump scroll up region [1..24] size 24 Line 16 Jump scroll up region [1..24] size 24 Line 17 Jump scroll up region [1..24] size 24 Line 18 Jump scroll up region [1..24] size 24 Line 19 Jump scroll up region [1..24] size 24 Line 20 Jump scroll up region [1..24] size 24 Line 21 Jump scroll up region [1..24] size 24 Line 22 Jump scroll up region [1..24] size 24 Line 23 Jump scroll up region [1..24] size 24 Line 24 Jump scroll up region [1..24] size 24 Line 25 Jump scroll up region [1..24] size 24 Line 26 Jump scroll up region [1..24] size 24 Line 27 Jump scroll up region [1..24] size 24 Line 28 Jump scroll up region [1..24] size 24 Line 29 Jump scroll down region [1..24] size 24 Line 1 MMJump scroll down region [1..24] size 24 Line 2 MMJump scroll down region [1..24] size 24 Line 3 MMJump scroll down region [1..24] size 24 Line 4 MMJump scroll down region [1..24] size 24 Line 5 MMJump scroll down region [1..24] size 24 Line 6 MMJump scroll down region [1..24] size 24 Line 7 MMJump scroll down region [1..24] size 24 Line 8 MMJump scroll down region [1..24] size 24 Line 9 MMJump scroll down region [1..24] size 24 Line 10 MMJump scroll down region [1..24] size 24 Line 11 MMJump scroll down region [1..24] size 24 Line 12 MMJump scroll down region [1..24] size 24 Line 13 MMJump scroll down region [1..24] size 24 Line 14 MMJump scroll down region [1..24] size 24 Line 15 MMJump scroll down region [1..24] size 24 Line 16 MMJump scroll down region [1..24] size 24 Line 17 MMJump scroll down region [1..24] size 24 Line 18 MMJump scroll down region [1..24] size 24 Line 19 MMJump scroll down region [1..24] size 24 Line 20 MMJump scroll down region [1..24] size 24 Line 21 MMJump scroll down region [1..24] size 24 Line 22 MMJump scroll down region [1..24] size 24 Line 23 MMJump scroll down region [1..24] size 24 Line 24 MMJump scroll down region [1..24] size 24 Line 25 MMJump scroll down region [1..24] size 24 Line 26 MMJump scroll down region [1..24] size 24 Line 27 MMJump scroll down region [1..24] size 24 Line 28 MMJump scroll down region [1..24] size 24 Line 29 MMPush  Origin mode test. This line should be at the bottom of the screen.This line should be the one above the bottom of the screen. Push [?6lOrigin mode test. This line should be at the bottom of the screen.This line should be at the top of the screen. Push Graphic rendition test pattern:vanillaboldunderlinebold underlineblinkbold blinkunderline blinkbold underline blinknegativebold negativeunderline negativebold underline negativeblink negativebold blink negativeunderline blink negativebold underline blink negative[?5lDark background. Push [?5hLight background. Push ================================================ FILE: test/test_files/vttest/2-15 ================================================ [?5lnormalboldunderscoredblinkingreversedstars:line:x'es:diamonds:(B)B*****7(B)BA8*****(B)B*****7(B)BA8*****(B)B*****7(B)BA8*****(B)B*****7(B)BA8*****(B)B*****7(B)BA8*****(0)Bqqqqq7(B)BA8qqqqq(0)Bqqqqq7(B)BA8qqqqq(0)Bqqqqq7(B)BA8qqqqq(0)Bqqqqq7(B)BA8qqqqq(0)Bqqqqq7(B)BA8qqqqq(B)Bxxxxx7(B)BA8xxxxx(B)Bxxxxx7(B)BA8xxxxx(B)Bxxxxx7(B)BA8xxxxx(B)Bxxxxx7(B)BA8xxxxx(B)Bxxxxx7(B)BA8xxxxx(0)B`````7(B)BA8`````(0)B`````7(B)BA8`````(0)B`````7(B)BA8`````(0)B`````7(B)BA8`````(0)B`````7(B)BA8`````(B)BTest of the SAVE/RESTORE CURSOR feature. There should be ten characters of each flavour, and a rectangle of 5 x 4 A's filling the top left of the screen. Push ================================================ FILE: test/test_files/vttest/2-2 ================================================ HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push ================================================ FILE: test/test_files/vttest/2-3 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push ================================================ FILE: test/test_files/vttest/2-4 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push ================================================ FILE: test/test_files/vttest/2-5 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push [?5l[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.Push ================================================ FILE: test/test_files/vttest/2-6 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push [?5l[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.Push ================================================ FILE: test/test_files/vttest/2-7 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push [?5l[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.Push [?6h[?4hSoft scroll up region [12..13] size 2 Line 1 Soft scroll up region [12..13] size 2 Line 2 Soft scroll up region [12..13] size 2 Line 3 Soft scroll up region [12..13] size 2 Line 4 Soft scroll up region [12..13] size 2 Line 5 Soft scroll up region [12..13] size 2 Line 6 Soft scroll up region [12..13] size 2 Line 7 Soft scroll up region [12..13] size 2 Line 8 Soft scroll up region [12..13] size 2 Line 9 Soft scroll up region [12..13] size 2 Line 10 Soft scroll up region [12..13] size 2 Line 11 Soft scroll up region [12..13] size 2 Line 12 Soft scroll up region [12..13] size 2 Line 13 Soft scroll up region [12..13] size 2 Line 14 Soft scroll up region [12..13] size 2 Line 15 Soft scroll up region [12..13] size 2 Line 16 Soft scroll up region [12..13] size 2 Line 17 Soft scroll up region [12..13] size 2 Line 18 Soft scroll up region [12..13] size 2 Line 19 Soft scroll up region [12..13] size 2 Line 20 Soft scroll up region [12..13] size 2 Line 21 Soft scroll up region [12..13] size 2 Line 22 Soft scroll up region [12..13] size 2 Line 23 Soft scroll up region [12..13] size 2 Line 24 Soft scroll up region [12..13] size 2 Line 25 Soft scroll up region [12..13] size 2 Line 26 Soft scroll up region [12..13] size 2 Line 27 Soft scroll up region [12..13] size 2 Line 28 Soft scroll up region [12..13] size 2 Line 29 Soft scroll down region [12..13] size 2 Line 1 MMSoft scroll down region [12..13] size 2 Line 2 MMSoft scroll down region [12..13] size 2 Line 3 MMSoft scroll down region [12..13] size 2 Line 4 MMSoft scroll down region [12..13] size 2 Line 5 MMSoft scroll down region [12..13] size 2 Line 6 MMSoft scroll down region [12..13] size 2 Line 7 MMSoft scroll down region [12..13] size 2 Line 8 MMSoft scroll down region [12..13] size 2 Line 9 MMSoft scroll down region [12..13] size 2 Line 10 MMSoft scroll down region [12..13] size 2 Line 11 MMSoft scroll down region [12..13] size 2 Line 12 MMSoft scroll down region [12..13] size 2 Line 13 MMSoft scroll down region [12..13] size 2 Line 14 MMSoft scroll down region [12..13] size 2 Line 15 MMSoft scroll down region [12..13] size 2 Line 16 MMSoft scroll down region [12..13] size 2 Line 17 MMSoft scroll down region [12..13] size 2 Line 18 MMSoft scroll down region [12..13] size 2 Line 19 MMSoft scroll down region [12..13] size 2 Line 20 MMSoft scroll down region [12..13] size 2 Line 21 MMSoft scroll down region [12..13] size 2 Line 22 MMSoft scroll down region [12..13] size 2 Line 23 MMSoft scroll down region [12..13] size 2 Line 24 MMSoft scroll down region [12..13] size 2 Line 25 MMSoft scroll down region [12..13] size 2 Line 26 MMSoft scroll down region [12..13] size 2 Line 27 MMSoft scroll down region [12..13] size 2 Line 28 MMSoft scroll down region [12..13] size 2 Line 29 MMPush ================================================ FILE: test/test_files/vttest/2-8 ================================================ Soft scroll up region [1..24] size 24 Line 1 Soft scroll up region [1..24] size 24 Line 2 Soft scroll up region [1..24] size 24 Line 3 Soft scroll up region [1..24] size 24 Line 4 Soft scroll up region [1..24] size 24 Line 5 Soft scroll up region [1..24] size 24 Line 6 Soft scroll up region [1..24] size 24 Line 7 Soft scroll up region [1..24] size 24 Line 8 Soft scroll up region [1..24] size 24 Line 9 Soft scroll up region [1..24] size 24 Line 10 Soft scroll up region [1..24] size 24 Line 11 Soft scroll up region [1..24] size 24 Line 12 Soft scroll up region [1..24] size 24 Line 13 Soft scroll up region [1..24] size 24 Line 14 Soft scroll up region [1..24] size 24 Line 15 Soft scroll up region [1..24] size 24 Line 16 Soft scroll up region [1..24] size 24 Line 17 Soft scroll up region [1..24] size 24 Line 18 Soft scroll up region [1..24] size 24 Line 19 Soft scroll up region [1..24] size 24 Line 20 Soft scroll up region [1..24] size 24 Line 21 Soft scroll up region [1..24] size 24 Line 22 Soft scroll up region [1..24] size 24 Line 23 Soft scroll up region [1..24] size 24 Line 24 Soft scroll up region [1..24] size 24 Line 25 Soft scroll up region [1..24] size 24 Line 26 Soft scroll up region [1..24] size 24 Line 27 Soft scroll up region [1..24] size 24 Line 28 Soft scroll up region [1..24] size 24 Line 29 Soft scroll down region [1..24] size 24 Line 1 MMSoft scroll down region [1..24] size 24 Line 2 MMSoft scroll down region [1..24] size 24 Line 3 MMSoft scroll down region [1..24] size 24 Line 4 MMSoft scroll down region [1..24] size 24 Line 5 MMSoft scroll down region [1..24] size 24 Line 6 MMSoft scroll down region [1..24] size 24 Line 7 MMSoft scroll down region [1..24] size 24 Line 8 MMSoft scroll down region [1..24] size 24 Line 9 MMSoft scroll down region [1..24] size 24 Line 10 MMSoft scroll down region [1..24] size 24 Line 11 MMSoft scroll down region [1..24] size 24 Line 12 MMSoft scroll down region [1..24] size 24 Line 13 MMSoft scroll down region [1..24] size 24 Line 14 MMSoft scroll down region [1..24] size 24 Line 15 MMSoft scroll down region [1..24] size 24 Line 16 MMSoft scroll down region [1..24] size 24 Line 17 MMSoft scroll down region [1..24] size 24 Line 18 MMSoft scroll down region [1..24] size 24 Line 19 MMSoft scroll down region [1..24] size 24 Line 20 MMSoft scroll down region [1..24] size 24 Line 21 MMSoft scroll down region [1..24] size 24 Line 22 MMSoft scroll down region [1..24] size 24 Line 23 MMSoft scroll down region [1..24] size 24 Line 24 MMSoft scroll down region [1..24] size 24 Line 25 MMSoft scroll down region [1..24] size 24 Line 26 MMSoft scroll down region [1..24] size 24 Line 27 MMSoft scroll down region [1..24] size 24 Line 28 MMSoft scroll down region [1..24] size 24 Line 29 MMPush ================================================ FILE: test/test_files/vttest/2-9 ================================================ [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Line speed 38400bd Choose test type:  0. Exit 1. Test of cursor movements 2. Test of screen features 3. Test of character sets 4. Test of double-sized characters 5. Test of keyboard 6. Test of terminal reports 7. Test of VT52 mode 8. Test of VT102 features (Insert/Delete Char/Line) 9. Test of known bugs 10. Test of reset and self-test 11. Test non-VT100 (e.g., VT220, XTERM) terminals 12. Modify test-parameters Enter choice number (0 - 12): 2 [?7h****************************************************************************************************************************************************************[?7l****************************************************************************************************************************************************************[?7hThis should be three identical lines of *'s completely filling the top of the screen without any empty lines between. (Test of WRAP AROUND mode setting.) Push HHHHHHHHHHHHHHHHHHHHHHHHHH * * * * * * * * * * * * * * * * * * * * * * * * * *Test of TAB setting/resetting. These two lines should look the same. Push [?5h[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.This is 132 column mode, light background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.This is 80 column mode, light background.Push [?5l[?3hHHHHHHHHHHHHHHHHH12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.This is 132 column mode, dark background.Push [?3l1234567890123456789012345678901234567890123456789012345678901234567890123456789This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.This is 80 column mode, dark background.Push [?6h[?4hSoft scroll up region [12..13] size 2 Line 1 Soft scroll up region [12..13] size 2 Line 2 Soft scroll up region [12..13] size 2 Line 3 Soft scroll up region [12..13] size 2 Line 4 Soft scroll up region [12..13] size 2 Line 5 Soft scroll up region [12..13] size 2 Line 6 Soft scroll up region [12..13] size 2 Line 7 Soft scroll up region [12..13] size 2 Line 8 Soft scroll up region [12..13] size 2 Line 9 Soft scroll up region [12..13] size 2 Line 10 Soft scroll up region [12..13] size 2 Line 11 Soft scroll up region [12..13] size 2 Line 12 Soft scroll up region [12..13] size 2 Line 13 Soft scroll up region [12..13] size 2 Line 14 Soft scroll up region [12..13] size 2 Line 15 Soft scroll up region [12..13] size 2 Line 16 Soft scroll up region [12..13] size 2 Line 17 Soft scroll up region [12..13] size 2 Line 18 Soft scroll up region [12..13] size 2 Line 19 Soft scroll up region [12..13] size 2 Line 20 Soft scroll up region [12..13] size 2 Line 21 Soft scroll up region [12..13] size 2 Line 22 Soft scroll up region [12..13] size 2 Line 23 Soft scroll up region [12..13] size 2 Line 24 Soft scroll up region [12..13] size 2 Line 25 Soft scroll up region [12..13] size 2 Line 26 Soft scroll up region [12..13] size 2 Line 27 Soft scroll up region [12..13] size 2 Line 28 Soft scroll up region [12..13] size 2 Line 29 Soft scroll down region [12..13] size 2 Line 1 MMSoft scroll down region [12..13] size 2 Line 2 MMSoft scroll down region [12..13] size 2 Line 3 MMSoft scroll down region [12..13] size 2 Line 4 MMSoft scroll down region [12..13] size 2 Line 5 MMSoft scroll down region [12..13] size 2 Line 6 MMSoft scroll down region [12..13] size 2 Line 7 MMSoft scroll down region [12..13] size 2 Line 8 MMSoft scroll down region [12..13] size 2 Line 9 MMSoft scroll down region [12..13] size 2 Line 10 MMSoft scroll down region [12..13] size 2 Line 11 MMSoft scroll down region [12..13] size 2 Line 12 MMSoft scroll down region [12..13] size 2 Line 13 MMSoft scroll down region [12..13] size 2 Line 14 MMSoft scroll down region [12..13] size 2 Line 15 MMSoft scroll down region [12..13] size 2 Line 16 MMSoft scroll down region [12..13] size 2 Line 17 MMSoft scroll down region [12..13] size 2 Line 18 MMSoft scroll down region [12..13] size 2 Line 19 MMSoft scroll down region [12..13] size 2 Line 20 MMSoft scroll down region [12..13] size 2 Line 21 MMSoft scroll down region [12..13] size 2 Line 22 MMSoft scroll down region [12..13] size 2 Line 23 MMSoft scroll down region [12..13] size 2 Line 24 MMSoft scroll down region [12..13] size 2 Line 25 MMSoft scroll down region [12..13] size 2 Line 26 MMSoft scroll down region [12..13] size 2 Line 27 MMSoft scroll down region [12..13] size 2 Line 28 MMSoft scroll down region [12..13] size 2 Line 29 MMPush Soft scroll up region [1..24] size 24 Line 1 Soft scroll up region [1..24] size 24 Line 2 Soft scroll up region [1..24] size 24 Line 3 Soft scroll up region [1..24] size 24 Line 4 Soft scroll up region [1..24] size 24 Line 5 Soft scroll up region [1..24] size 24 Line 6 Soft scroll up region [1..24] size 24 Line 7 Soft scroll up region [1..24] size 24 Line 8 Soft scroll up region [1..24] size 24 Line 9 Soft scroll up region [1..24] size 24 Line 10 Soft scroll up region [1..24] size 24 Line 11 Soft scroll up region [1..24] size 24 Line 12 Soft scroll up region [1..24] size 24 Line 13 Soft scroll up region [1..24] size 24 Line 14 Soft scroll up region [1..24] size 24 Line 15 Soft scroll up region [1..24] size 24 Line 16 Soft scroll up region [1..24] size 24 Line 17 Soft scroll up region [1..24] size 24 Line 18 Soft scroll up region [1..24] size 24 Line 19 Soft scroll up region [1..24] size 24 Line 20 Soft scroll up region [1..24] size 24 Line 21 Soft scroll up region [1..24] size 24 Line 22 Soft scroll up region [1..24] size 24 Line 23 Soft scroll up region [1..24] size 24 Line 24 Soft scroll up region [1..24] size 24 Line 25 Soft scroll up region [1..24] size 24 Line 26 Soft scroll up region [1..24] size 24 Line 27 Soft scroll up region [1..24] size 24 Line 28 Soft scroll up region [1..24] size 24 Line 29 Soft scroll down region [1..24] size 24 Line 1 MMSoft scroll down region [1..24] size 24 Line 2 MMSoft scroll down region [1..24] size 24 Line 3 MMSoft scroll down region [1..24] size 24 Line 4 MMSoft scroll down region [1..24] size 24 Line 5 MMSoft scroll down region [1..24] size 24 Line 6 MMSoft scroll down region [1..24] size 24 Line 7 MMSoft scroll down region [1..24] size 24 Line 8 MMSoft scroll down region [1..24] size 24 Line 9 MMSoft scroll down region [1..24] size 24 Line 10 MMSoft scroll down region [1..24] size 24 Line 11 MMSoft scroll down region [1..24] size 24 Line 12 MMSoft scroll down region [1..24] size 24 Line 13 MMSoft scroll down region [1..24] size 24 Line 14 MMSoft scroll down region [1..24] size 24 Line 15 MMSoft scroll down region [1..24] size 24 Line 16 MMSoft scroll down region [1..24] size 24 Line 17 MMSoft scroll down region [1..24] size 24 Line 18 MMSoft scroll down region [1..24] size 24 Line 19 MMSoft scroll down region [1..24] size 24 Line 20 MMSoft scroll down region [1..24] size 24 Line 21 MMSoft scroll down region [1..24] size 24 Line 22 MMSoft scroll down region [1..24] size 24 Line 23 MMSoft scroll down region [1..24] size 24 Line 24 MMSoft scroll down region [1..24] size 24 Line 25 MMSoft scroll down region [1..24] size 24 Line 26 MMSoft scroll down region [1..24] size 24 Line 27 MMSoft scroll down region [1..24] size 24 Line 28 MMSoft scroll down region [1..24] size 24 Line 29 MMPush [?4lJump scroll up region [12..13] size 2 Line 1 Jump scroll up region [12..13] size 2 Line 2 Jump scroll up region [12..13] size 2 Line 3 Jump scroll up region [12..13] size 2 Line 4 Jump scroll up region [12..13] size 2 Line 5 Jump scroll up region [12..13] size 2 Line 6 Jump scroll up region [12..13] size 2 Line 7 Jump scroll up region [12..13] size 2 Line 8 Jump scroll up region [12..13] size 2 Line 9 Jump scroll up region [12..13] size 2 Line 10 Jump scroll up region [12..13] size 2 Line 11 Jump scroll up region [12..13] size 2 Line 12 Jump scroll up region [12..13] size 2 Line 13 Jump scroll up region [12..13] size 2 Line 14 Jump scroll up region [12..13] size 2 Line 15 Jump scroll up region [12..13] size 2 Line 16 Jump scroll up region [12..13] size 2 Line 17 Jump scroll up region [12..13] size 2 Line 18 Jump scroll up region [12..13] size 2 Line 19 Jump scroll up region [12..13] size 2 Line 20 Jump scroll up region [12..13] size 2 Line 21 Jump scroll up region [12..13] size 2 Line 22 Jump scroll up region [12..13] size 2 Line 23 Jump scroll up region [12..13] size 2 Line 24 Jump scroll up region [12..13] size 2 Line 25 Jump scroll up region [12..13] size 2 Line 26 Jump scroll up region [12..13] size 2 Line 27 Jump scroll up region [12..13] size 2 Line 28 Jump scroll up region [12..13] size 2 Line 29 Jump scroll down region [12..13] size 2 Line 1 MMJump scroll down region [12..13] size 2 Line 2 MMJump scroll down region [12..13] size 2 Line 3 MMJump scroll down region [12..13] size 2 Line 4 MMJump scroll down region [12..13] size 2 Line 5 MMJump scroll down region [12..13] size 2 Line 6 MMJump scroll down region [12..13] size 2 Line 7 MMJump scroll down region [12..13] size 2 Line 8 MMJump scroll down region [12..13] size 2 Line 9 MMJump scroll down region [12..13] size 2 Line 10 MMJump scroll down region [12..13] size 2 Line 11 MMJump scroll down region [12..13] size 2 Line 12 MMJump scroll down region [12..13] size 2 Line 13 MMJump scroll down region [12..13] size 2 Line 14 MMJump scroll down region [12..13] size 2 Line 15 MMJump scroll down region [12..13] size 2 Line 16 MMJump scroll down region [12..13] size 2 Line 17 MMJump scroll down region [12..13] size 2 Line 18 MMJump scroll down region [12..13] size 2 Line 19 MMJump scroll down region [12..13] size 2 Line 20 MMJump scroll down region [12..13] size 2 Line 21 MMJump scroll down region [12..13] size 2 Line 22 MMJump scroll down region [12..13] size 2 Line 23 MMJump scroll down region [12..13] size 2 Line 24 MMJump scroll down region [12..13] size 2 Line 25 MMJump scroll down region [12..13] size 2 Line 26 MMJump scroll down region [12..13] size 2 Line 27 MMJump scroll down region [12..13] size 2 Line 28 MMJump scroll down region [12..13] size 2 Line 29 MMPush ================================================ FILE: test/utils/ManPages_spec.ts ================================================ import "mocha"; import {expect} from "chai"; import { combineManPageLines, preprocessManPage, extractManPageSections, extractManPageSectionParagraphs, suggestionFromFlagParagraph, } from "../../src/utils/ManPageParsingUtils"; describe("man page line combiner", () => { it("combines lines with correct spacing", () => { expect(combineManPageLines([ " first line ", " second line ", ])).to.eql("first line second line"); }); it("correctly handles words split across lines", () => { expect(combineManPageLines([ "this is com-", "bined", ])).to.eql("this is combined"); }); }); describe("man page preprocessor", () => { it("strips whitespace and applies backspace literals", () => { expect(preprocessManPage(" ab\x08 ")).to.eql("a"); }); }); describe("man page section extractor", () => { it("extracts sections", () => { expect(extractManPageSections("DESCRIPTION\n desc\n\nNAME\n name")).to.eql({ DESCRIPTION: [" desc", ""], NAME: [" name"], }); }); }); describe("man page paragraph extraction", () => { it("extracts paragraphs", () => { expect(extractManPageSectionParagraphs([ "p1", "p1", "", "p2", "p2", ])).to.eql([ ["p1", "p1"], ["p2", "p2"], ]); }); it("doesn't output empty paragraphs", () => { expect(extractManPageSectionParagraphs([ "p1", "p1", "", "", "", "", "", "p2", "p2", ])).to.eql([ ["p1", "p1"], ["p2", "p2"], ]); }); it("can handle flag descriptions that have blank lines in the middle", () => { expect(extractManPageSectionParagraphs([ " -f1 line one", " line two", "", " line three", "", " -f2 line one", ])).to.eql([ [ " -f1 line one", " line two", " line three", ], [" -f2 line one"], ]); }); it("can handle flag descriptions that have indentation like df's -T option", () => { expect(extractManPageSectionParagraphs([ " -f line one", " line two", "", " indented", "", " line three", "", " -g line one", ])).to.eql([ [ " -f line one", " line two", " indented", " line three", ], [" -g line one"], ]); }); }); describe("suggestion parser", () => { it("can handle short flags without arguments", () => { expect(suggestionFromFlagParagraph([ " -f flag with", " description", ])).to.eql({ label: "-f", detail: "flag with description", }); }); it("can handle short flags with arguments", () => { expect(suggestionFromFlagParagraph([ " -f arg", " flag with", " description", ])).to.eql({ label: "-f", detail: "flag with description", }); }); // DESCRIPTION section can contain things other than flags it("returns undefined if attempting to parse paragraph with no flag", () => { expect(suggestionFromFlagParagraph([ " no flag", ])).to.eql(undefined); }); }); ================================================ FILE: test/utils/common_spec.ts ================================================ import "mocha"; import {expect} from "chai"; import {commonPrefix, fuzzyMatch, normalizeProcessInput} from "../../src/utils/Common"; interface SimulatedKeyboardEvent { ctrlKey?: boolean; altKey?: boolean; shiftKey?: boolean; keyCode: number; key: string; } function simulateKeyboardEvent(event: SimulatedKeyboardEvent) { return event as KeyboardEvent; } describe("common utils", () => { describe("commonPrefix", () => { it("returns the whole string for the same strings", () => { expect(commonPrefix("abc", "abc")).to.eql("abc"); }); }); describe("fuzzyMatch", () => { it("matches beginning of string", () => { expect(fuzzyMatch("com", "commit")).to.eql(true); }); it("matches beginning of token", () => { expect(fuzzyMatch("nam", "file_name")).to.eql(true); }); }); describe("normalizeProcessInput", () => { it("handles Ctrl+[", () => { const event = simulateKeyboardEvent({ctrlKey: true, keyCode: 219, key: "["}); const escape = String.fromCharCode(27); expect(normalizeProcessInput(event, false)).to.eql(escape); }); it("handles Ctrl+J", () => { const event = simulateKeyboardEvent({ctrlKey: true, keyCode: 219, key: "["}); const escape = String.fromCharCode(27); expect(normalizeProcessInput(event, false)).to.eql(escape); }); }); }); ================================================ FILE: test/utils/history_trie_spec.ts ================================================ import "mocha"; import {expect} from "chai"; import {HistoryTrie} from "../../src/utils/HistoryTrie"; function getSuggestions(history: string[], input: string): string[] { const trie = new HistoryTrie(); history.forEach(string => trie.add(string)); return trie.getContinuationsFor(input).map(prefix => prefix.value + (prefix.space ? " " : "")); } describe("HistoryTrie", () => { it("finds next common prefixes", () => { const history = [ "git commit", "git checkout", ]; const input = "git "; const suggestions = [ "commit", "checkout", ]; expect(getSuggestions(history, input)).to.eql(suggestions); }); it("finds first word", () => { const history = [ "git commit", "git checkout", ]; const input = "gi"; const suggestions = [ "git ", ]; expect(getSuggestions(history, input)).to.eql(suggestions); }); it("finds single continuation", () => { const history = [ "git commit", "git checkout", ]; const input = "git co"; const suggestions = [ "commit", ]; expect(getSuggestions(history, input)).to.eql(suggestions); }); it("finds next word", () => { const history = [ "git commit", "git checkout master", ]; const input = "git c"; const suggestions = [ "commit", "checkout ", ]; expect(getSuggestions(history, input)).to.eql(suggestions); }); it("finds longest prefix", () => { const history = [ "git commit", "git checkout master --option", ]; const input = "git ch"; const suggestions = [ "checkout ", "checkout master --option", ]; expect(getSuggestions(history, input)).to.eql(suggestions); }); it("is ordered by frequency", () => { const history = [ "git status", "git pull", "git pull", ]; const input = "git "; const suggestions = [ "pull", "status", ]; expect(getSuggestions(history, input)).to.eql(suggestions); }); it("is fuzzy matches", () => { const history = [ "git cherry-pick", ]; const input = "git pi"; const suggestions = [ "cherry-pick", ]; expect(getSuggestions(history, input)).to.eql(suggestions); }); it("considers a string literal a single token", () => { const history = [ "git commit -m 'first message'", "git commit -m 'second message'", ]; const input = "git commit -m "; const suggestions = [ "'first message'", "'second message'", ]; expect(getSuggestions(history, input)).to.eql(suggestions); }); it("gives no results for empty input", () => { const history = [ "git log", ]; const input = ""; const suggestions: string[] = []; expect(getSuggestions(history, input)).to.eql(suggestions); }); }); ================================================ FILE: test/utils/ordered_set_spec.ts ================================================ import "mocha"; import {expect} from "chai"; import {OrderedSet} from "../../src/utils/OrderedSet"; describe("ordered set", () => { describe("prepend", () => { it("doesn't keep two elements with the same values", () => { const set = new OrderedSet(); set.prepend("foo"); set.prepend("foo"); expect(set.size).to.eq(1); }); it("moves an element to the beginning if it already exists", () => { const set = new OrderedSet(); set.prepend("foo"); set.prepend("bar"); set.prepend("foo"); expect(set.at(0)).to.eq("foo"); }); }); }); ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ES6", "module": "commonjs", "removeComments": true, "preserveConstEnums": true, "moduleResolution": "node", "experimentalDecorators": true, "noImplicitAny": true, "noEmitOnError": true, "jsx": "react", "outDir": "compiled/src", "strictNullChecks": true, "noImplicitThis": true, "inlineSourceMap": true, "lib": ["es2015", "es2017", "dom"] }, "exclude": [ "node_modules", "dist", "typings", "test" ] } ================================================ FILE: tslint.json ================================================ { "rules": { "align": [ true, "parameters", "arguments", "statements" ], "ban": [ true, ["Object", "assign", "Use spread syntax ({...a, ...b}) instead."] ], "class-name": true, "comment-format": [ true, "check-space" ], "eofline": true, "forin": true, "indent": [ true, "spaces" ], "interface-name": false, "jsdoc-format": true, "label-position": true, "max-line-length": [ true, 200 ], "member-access": false, "member-ordering": [ true, "public-before-private", "static-before-instance", "variables-before-functions" ], "no-any": false, "no-arg": true, "no-bitwise": false, "no-conditional-assignment": true, "no-consecutive-blank-lines": false, "no-console": [ true, "debug", "info", "time", "timeEnd", "trace" ], "no-construct": true, "no-parameter-properties": false, "no-debugger": true, "no-duplicate-variable": true, "no-empty": true, "no-eval": true, "no-inferrable-types": false, "no-internal-module": true, "no-null-keyword": false, "no-require-imports": false, "no-shadowed-variable": false, "no-string-literal": true, "no-switch-case-fall-through": true, "no-trailing-whitespace": true, "no-unused-expression": true, "no-use-before-declare": false, "no-var-keyword": true, "no-var-requires": false, "object-literal-sort-keys": false, "one-line": [ true, "check-open-brace", "check-catch", "check-else", "check-whitespace" ], "quotemark": [ true, "double", "avoid-escape" ], "radix": true, "semicolon": true, "switch-default": true, "trailing-comma": [ true, { "multiline": "always", "singleline": "never" } ], "triple-equals": [ true, "allow-null-check" ], "typedef-whitespace": [ true, { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace" } ], "variable-name": false, "whitespace": [ true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type" ] } } ================================================ FILE: typings/Interfaces.d.ts ================================================ interface Size { height: number; width: number; } interface Dimensions { columns: number; rows: number; } interface Advancement { vertical?: number; horizontal?: number; } interface RowColumn { columnIndex: number; rowIndex: number; } type GitState = { kind: "repository", branch: string, status: "dirty" | "clean"; } | { kind: "not-repository"; }; interface Margins { top: number; bottom?: number; left: number; right?: number; } interface Dictionary { [index: string]: T; } interface ProcessEnvironment extends Dictionary { PWD: string; } type EscapedShellWord = string & {__isEscapedShellToken: any}; type FullPath = string & { __isFullPath: boolean }; type ExistingAlias = string & { __isExistingAlias: boolean }; type OneBasedPosition = number; ================================================ FILE: typings/Overrides.d.ts ================================================ interface IntersectionObserverEntry { readonly time: number; readonly rootBounds: ClientRect | DOMRect; readonly boundingClientRect: ClientRect | DOMRect; readonly intersectionRect: ClientRect | DOMRect; readonly intersectionRatio: number; readonly target: Element; } interface IntersectionObserverInit { // The root to use for intersection. If not provided, use the top-level document’s viewport. root?: Element | null; // Same as margin, can be 1, 2, 3 or 4 components, possibly negative lengths. If an explicit // root element is specified, components may be percentages of the root element size. If no // explicit root element is specified, using a percentage here is an error. // "5px" // "10% 20%" // "-10px 5px 5px" // "-10px -10px 5px 5px" rootMargin?: string; // Threshold(s) at which to trigger callback, specified as a ratio, or list of ratios, // of (visible area / total area) of the observed element (hence all entries must be // in the range [0, 1]). Callback will be invoked when the visible ratio of the observed // element crosses a threshold in the list. threshold?: number | number[]; } interface Window { DEBUG: boolean; search: any; } declare class AnsiParser { constructor(callbacks: Dictionary) parse(data: string): any; } interface Array { includes(value: T): boolean; } interface NodeBuffer extends Uint8Array { fill(value: number, offset?: number, end?: number): this; } interface ObjectConstructor { assign(a: A, b: B, c: C, d: D, e: E, f: F): A & B & C & D & E & F; } ================================================ FILE: typings/child-process-promise.d.ts ================================================ declare module "child-process-promise" { function execFile(file: string, args: string[], options: {}): Promise<{stdout: string, stderr: string}> } ================================================ FILE: typings/dirStat.d.ts ================================================ declare module "dirStat" { function dirStat(path: string, cb: (err: any, results: any) => void): void } ================================================ FILE: typings/mode-to-permissions.d.ts ================================================ interface PermittedGroups { owner: boolean; group: boolean; others: boolean; } interface Permissions { read: PermittedGroups; write: PermittedGroups; execute: PermittedGroups; } declare module "mode-to-permissions" { function modeToPermissions(mode: number): Permissions; namespace modeToPermissions {} export = modeToPermissions; } ================================================ FILE: typings/uuid.d.ts ================================================ declare module "uuid" { function v4(): string }