diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b3dfee7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..c512856 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,21 @@ +name: check + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Setup latest deno version + uses: denolib/setup-deno@v2 + with: + deno-version: v1.x + + - name: Run deno fmt + run: deno fmt --check + + - name: Run deno lint + run: deno lint --unstable diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..aff1c96 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +name: release + +on: + push: + tags: + - "*.*.*" +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + draft: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d114020 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# OS files +.DS_Store +.cache + +# IDE +.vscode diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd029c9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-present the denosaurs team + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c652fc1 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# tty + +[![Tags](https://img.shields.io/github/release/denosaurs/tty)](https://github.com/denosaurs/tty/releases) +[![CI Status](https://img.shields.io/github/workflow/status/denosaurs/tty/check)](https://github.com/denosaurs/tty/actions) +[![License](https://img.shields.io/github/license/denosaurs/tty)](https://github.com/denosaurs/tty/blob/master/LICENSE) + +```typescript +import * as tty from "https://deno.land/x/tty/mod.ts"; + +await tty.hideCursor(); + +setInterval(() => { + tty.clearScreenSync(); + tty.goHomeSync(); + console.log("HELLO WORLD"); +}, 200); + +await tty.showCursor(); +``` + +## other + +### contribution + +Pull request, issues and feedback are very welcome. Code style is formatted with deno fmt and commit messages are done following Conventional Commits spec. + +### licence + +Copyright 2020-present, the denosaurs team. All rights reserved. MIT license. diff --git a/deps.ts b/deps.ts new file mode 100644 index 0000000..36d3bec --- /dev/null +++ b/deps.ts @@ -0,0 +1 @@ +export { encode, decode } from "https://deno.land/std@0.66.0/encoding/utf8.ts"; diff --git a/examples/hello.ts b/examples/hello.ts new file mode 100644 index 0000000..bf62d6f --- /dev/null +++ b/examples/hello.ts @@ -0,0 +1,11 @@ +import * as tty from "../mod.ts"; + +await tty.hideCursor(); + +setInterval(() => { + tty.clearScreenSync(); + tty.goHomeSync(); + console.log("HELLO WORLD"); +}, 200); + +await tty.showCursor(); diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..02eba69 --- /dev/null +++ b/mod.ts @@ -0,0 +1,41 @@ +import { showCursorSync } from "./tty_sync.ts"; + +const mac = + (await Deno.permissions.query({ name: "env" })).state === "granted" + ? Deno.env.get("TERM_PROGRAM") === "Apple_Terminal" + : false; + +export const ESC = "\u001B["; + +export const SAVE = mac ? "\u001B7" : ESC + "s"; +export const RESTORE = mac ? "\u001B8" : ESC + "u"; +export const POSITION = "6n"; +export const HIDE = "?25l"; +export const SHOW = "?25h"; +export const SCROLL_UP = "T"; +export const SCROLL_DOWN = "S"; + +export const UP = "A"; +export const DOWN = "B"; +export const RIGHT = "C"; +export const LEFT = "D"; + +export const CLEAR_RIGHT = "0K"; +export const CLEAR_LEFT = "1K"; +export const CLEAR_LINE = "2K"; + +export const CLEAR_DOWN = "0J"; +export const CLEAR_UP = "1J"; +export const CLEAR_SCREEN = "2J"; +export const CLEAR = "\u001Bc"; + +export const NEXT_LINE = "1E"; +export const PREV_LINE = "1F"; +export const COLUMN = "1G"; // left? +export const HOME = "H"; + +export type SyncStream = Deno.WriterSync; +export type AsyncStream = Deno.Writer; + +export * from "./tty_async.ts"; +export * from "./tty_sync.ts"; diff --git a/tty_async.ts b/tty_async.ts new file mode 100644 index 0000000..5007b1f --- /dev/null +++ b/tty_async.ts @@ -0,0 +1,160 @@ +import { encode } from "./deps.ts"; + +import { + AsyncStream, + RESTORE, + ESC, + POSITION, + HIDE, + SHOW, + SCROLL_UP, + SCROLL_DOWN, + CLEAR_UP, + CLEAR_DOWN, + CLEAR_LEFT, + CLEAR_RIGHT, + CLEAR_LINE, + CLEAR_SCREEN, + NEXT_LINE, + PREV_LINE, + HOME, + UP, + DOWN, + LEFT, + RIGHT, +} from "./mod.ts"; + +export async function write(str: string, writer: AsyncStream): Promise { + await writer.write(encode(str)); +} + +export async function restore( + writer: AsyncStream = Deno.stdout +): Promise { + await write(RESTORE, writer); +} + +export async function cursor( + action: string, + writer: AsyncStream = Deno.stdout +): Promise { + await write(ESC + action, writer); +} + +export async function position( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(POSITION, writer); +} + +export async function hideCursor( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(HIDE, writer); +} + +export async function showCursor( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(SHOW, writer); +} + +export async function scrollUp( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(SCROLL_UP, writer); +} + +export async function scrollDown( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(SCROLL_DOWN, writer); +} + +export async function clearUp( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(CLEAR_UP, writer); +} + +export async function clearDown( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(CLEAR_DOWN, writer); +} + +export async function clearLeft( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(CLEAR_LEFT, writer); +} + +export async function clearRight( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(CLEAR_RIGHT, writer); +} + +export async function clearLine( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(CLEAR_LINE, writer); +} + +export async function clearScreen( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(CLEAR_SCREEN, writer); +} + +export async function nextLine( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(NEXT_LINE, writer); +} + +export async function prevLine( + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(PREV_LINE, writer); +} + +export async function goHome(writer: AsyncStream = Deno.stdout): Promise { + await cursor(HOME, writer); +} + +export async function goUp( + y = 1, + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(y + UP, writer); +} + +export async function goDown( + y = 1, + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(y + DOWN, writer); +} + +export async function goLeft( + x = 1, + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(x + LEFT, writer); +} + +export async function goRight( + x = 1, + writer: AsyncStream = Deno.stdout +): Promise { + await cursor(x + RIGHT, writer); +} + +export async function goTo( + x: number, + y: number, + writer: AsyncStream = Deno.stdout +): Promise { + await write(ESC + y + ";" + x + HOME, writer); +} diff --git a/tty_sync.ts b/tty_sync.ts new file mode 100644 index 0000000..ee7b077 --- /dev/null +++ b/tty_sync.ts @@ -0,0 +1,120 @@ +import { encode } from "./deps.ts"; + +import { + SyncStream, + RESTORE, + ESC, + POSITION, + HIDE, + SHOW, + SCROLL_UP, + SCROLL_DOWN, + CLEAR_UP, + CLEAR_DOWN, + CLEAR_LEFT, + CLEAR_RIGHT, + CLEAR_LINE, + CLEAR_SCREEN, + NEXT_LINE, + PREV_LINE, + HOME, + UP, + DOWN, + LEFT, + RIGHT, +} from "./mod.ts"; + +export function writeSync(str: string, writer: SyncStream): void { + writer.writeSync(encode(str)); +} + +export function restoreSync(writer: SyncStream = Deno.stdout): void { + writeSync(RESTORE, writer); +} + +export function cursorSync( + action: string, + writer: SyncStream = Deno.stdout +): void { + writeSync(ESC + action, writer); +} + +export function positionSync(writer: SyncStream = Deno.stdout): void { + cursorSync(POSITION, writer); +} + +export function hideCursorSync(writer: SyncStream = Deno.stdout): void { + cursorSync(HIDE, writer); +} + +export function showCursorSync(writer: SyncStream = Deno.stdout): void { + cursorSync(SHOW, writer); +} + +export function scrollUpSync(writer: SyncStream = Deno.stdout): void { + cursorSync(SCROLL_UP, writer); +} + +export function scrollDownSync(writer: SyncStream = Deno.stdout): void { + cursorSync(SCROLL_DOWN, writer); +} + +export function clearUpSync(writer: SyncStream = Deno.stdout): void { + cursorSync(CLEAR_UP, writer); +} + +export function clearDownSync(writer: SyncStream = Deno.stdout): void { + cursorSync(CLEAR_DOWN, writer); +} + +export function clearLeftSync(writer: SyncStream = Deno.stdout): void { + cursorSync(CLEAR_LEFT, writer); +} + +export function clearRightSync(writer: SyncStream = Deno.stdout): void { + cursorSync(CLEAR_RIGHT, writer); +} + +export function clearLineSync(writer: SyncStream = Deno.stdout): void { + cursorSync(CLEAR_LINE, writer); +} + +export function clearScreenSync(writer: SyncStream = Deno.stdout): void { + cursorSync(CLEAR_SCREEN, writer); +} + +export function nextLineSync(writer: SyncStream = Deno.stdout): void { + cursorSync(NEXT_LINE, writer); +} + +export function prevLineSync(writer: SyncStream = Deno.stdout): void { + cursorSync(PREV_LINE, writer); +} + +export function goHomeSync(writer: SyncStream = Deno.stdout): void { + cursorSync(HOME, writer); +} + +export function goUpSync(y = 1, writer: SyncStream = Deno.stdout): void { + cursorSync(y + UP, writer); +} + +export function goDownSync(y = 1, writer: SyncStream = Deno.stdout): void { + cursorSync(y + DOWN, writer); +} + +export function goLeftSync(x = 1, writer: SyncStream = Deno.stdout): void { + cursorSync(x + LEFT, writer); +} + +export function goRightSync(x = 1, writer: SyncStream = Deno.stdout): void { + cursorSync(`${x}${RIGHT}`, writer); +} + +export function goToSync( + x: number, + y: number, + writer: SyncStream = Deno.stdout +): void { + writeSync(ESC + y + ";" + x + HOME, writer); +}