diff --git a/CHANGELOG.md b/CHANGELOG.md index 19dc726..ba02b98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,22 +3,7 @@ All notable changes to this project will be documented in this file. This change ## [Unreleased] ### Changed -- Add a new arity to `make-widget-async` to provide a different widget shape. +- Document public API [#28](https://github.com/phronmophobic/membrane.term/issues/28) -## [0.1.1] - 2021-10-21 -### Changed -- Documentation on how to make the widgets. - -### Removed -- `make-widget-sync` - we're all async, all the time. - -### Fixed -- Fixed widget maker to keep working when daylight savings switches over. - -## 0.1.0 - 2021-10-21 -### Added -- Files from the new template. -- Widget maker public API - `make-widget-sync`. - -[Unreleased]: https://github.com/com.phronemophobic.membrane/term/compare/0.1.1...HEAD -[0.1.1]: https://github.com/com.phronemophobic.membrane/term/compare/0.1.0...0.1.1 +## [0.9.0] - 2021-11-30 +- Initial release to clojars diff --git a/README.md b/README.md index 05890fe..ef3e743 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # membrane.term +[![cljdoc](https://cljdoc.org/badge/com.phronemophobic/membrane.term)](https://cljdoc.org/d/com.phronemophobic/membrane.term) +[![clojurians slack](https://img.shields.io/badge/slack-join_chat-brightgreen.svg)](https://clojurians.slack.com/archives/C02KE09HMHV) +[![clojars](https://img.shields.io/clojars/v/com.phronemophobic/membrane.term.svg)](https://clojars.org/com.phronemophobic/membrane.term) + A simple terminal emulator in clojure. ## Rationale @@ -12,6 +16,11 @@ Some reasons to use **membrane.term**: - If you'd like to embed a terminal somewhere. There's not really a guide for embedding, but if you file an issue, I can provide some tips - If you'd like to capture terminal screenshots for documentation +## Status + +Pre 1.0 release. +APIs and behavior are subject to change. + ## Installation Membrane.term works on macOS and Linux operating systems. diff --git a/doc/cljdoc.edn b/doc/cljdoc.edn new file mode 100644 index 0000000..0138519 --- /dev/null +++ b/doc/cljdoc.edn @@ -0,0 +1,3 @@ +{:cljdoc.doc/tree [["Readme" {:file "README.md"}] + ["Changelog" {:file "CHANGELOG.md"}] + ["Developer Notes" {:file "doc/dev.md"}]]} diff --git a/src/com/phronemophobic/membrane/term.clj b/src/com/phronemophobic/membrane/term.clj index 13f5a9c..135ad95 100644 --- a/src/com/phronemophobic/membrane/term.clj +++ b/src/com/phronemophobic/membrane/term.clj @@ -7,18 +7,18 @@ (:import [com.pty4j PtyProcess WinSize])) -(defn start-pty [] +(defn- start-pty [] (let [cmd (into-array String ["/bin/bash" "-l"]) pty (PtyProcess/exec ^"[Ljava.lang.String;" cmd ^java.util.Map (merge (into {} (System/getenv)) {"TERM" "xterm-256color"}))] pty)) -(def blank-cell [32 {}]) -(defn blank-cell? [cell] +(def ^:private blank-cell [32 {}]) +(defn- blank-cell? [cell] (= cell blank-cell)) -(defn vt-color->term-color +(defn- vt-color->term-color [color-scheme vt-color] (if (vector? vt-color) (let [[r g b] vt-color] @@ -101,7 +101,7 @@ :italic :upright))))) -(defn term-line [color-scheme {:keys [:membrane.term/cell-width :membrane.term/cell-height] :as font} line] +(defn- term-line [color-scheme {:keys [:membrane.term/cell-width :membrane.term/cell-height] :as font} line] (into [] (comp (map-indexed vector) @@ -125,10 +125,10 @@ foreground)))))) line)) -(def term-line-memo (memoize term-line)) -(def window-padding-height 8) +(def ^:private term-line-memo (memoize term-line)) +(def ^:private window-padding-height 8) -(defn term-view [color-scheme {:keys [:membrane.term/cell-width :membrane.term/cell-height] :as font} vt] +(defn- term-view [color-scheme {:keys [:membrane.term/cell-width :membrane.term/cell-height] :as font} vt] (let [screen (:screen vt) cursor (let [{:keys [x y visible]} (:cursor screen)] (when visible @@ -153,14 +153,14 @@ (-> vt :screen :lines)) cursor)))) -(defn writec-bytes [out bytes] +(defn- writec-bytes [out bytes] (.write ^java.io.OutputStream out (byte-array bytes))) -(defn send-input [pty s] +(defn- send-input [pty s] (let [out (.getOutputStream ^PtyProcess pty)] (writec-bytes out (.getBytes ^String s)))) -(def meta-shift-map +(def ^:private meta-shift-map { \` \~ \1 \! @@ -187,7 +187,7 @@ \. \> \/ \?}) -(defn term-events [pty view] +(defn- term-events [pty view] (let [out (.getOutputStream ^PtyProcess pty)] (ui/on :key-event @@ -278,7 +278,7 @@ nil) view))) -(defn run-pty-process [width height term-state] +(defn- run-pty-process [width height term-state] (let [^PtyProcess pty (doto ^PtyProcess (start-pty) (.setWinSize (WinSize. width height)))] @@ -313,12 +313,11 @@ :cell-height (tk/font-line-height toolkit term-font) :descent-gap (- descent-offset baseline-offset)}))))) -(defn load-default-toolkit [] +(defn- load-default-toolkit [] @(requiring-resolve 'membrane.java2d/toolkit)) (def default-color-scheme - "Colors are specified a per membrane convention: - vectors of [red green blue] or [red green blue alpha] with values from 0 - 1 inclusive" + "Default color-scheme used in [[default-run-term-opts]] and [[default-screenshot-opts]]" {:white [1 1 1] :black [0 0 0] :red [0.76 0.21 0.13] @@ -340,67 +339,135 @@ :background [1 1 1] :foreground [0 0 0]}) -(def default-common-opts {:width 90 - :height 30 - :font-family :monospace - :font-size 12 - :toolkit nil - :color-scheme default-color-scheme}) +(def ^:private default-common-opts {:width 90 + :height 30 + :font-family :monospace + :font-size 12 + :toolkit nil + :color-scheme default-color-scheme}) + +(def default-run-term-opts "Default options used for [[run-term]]" default-common-opts) (defn run-term + "Launch an interactive membrane.term terminal. Terminal exits when explicitly closed by user. + + Accepts optional `opts` map: + - `:width` Window width in characters (default: `90`) + - `:height` Window height in characters (default: `30`) + - `:color-scheme` Map for terminal colors (defaults to an internal scheme) + Colors are specified per membrane convention, vectors of `[red green blue]` or + `[red green blue alpha]` with values from `0` - `1` inclusive. Example: `[0.14 0.74 0.14 0.50]`. + A color value must be specified for all of: + - ANSI colors + - `:white` `:black` `:red` `:green` `:yellow` `:blue` `:magenta` `cyan` + - `:bright-white` `:bright-black` `:bright-red` `:bright-green` `:bright-yellow` `:bright-blue` `:bright-magenta` `:bright-cyan` + - `:cursor` - Background color for cursor + - `:cursor-text` - Foreground color for cursor text + - `:background` - Default background color + - `:foreground` - Default text color + - `:font-family` OS installed font family name. Example: `\"Courier New\"`. + Use `:monospace` for default monospace (default: `:monospace`) + - `:font-size` Font point size (default: `12`) + - `:toolkit` Graphics toolkit (default: `membrane.toolkit/java2d`) + - An object that must satisfy the following + [`membrane.toolkit`](https://github.com/phronmophobic/membrane/blob/master/src/membrane/toolkit.clj) interfaces: + - `IToolkit` + - `IToolkitLogicalFontFontFamily` + - `IToolkitFontExists` + - `IToolkitFontMetrics` + - `IToolkitFontAdvanceX` + - `IToolkitFontLineHeight` + - `IToolkitRunSync` + - Usable examples from membrane library: `membrane.java2d/toolkit`, `membrane.skia/toolkit`" ([] (run-term {})) - ([opts] - (let [opts (merge default-common-opts opts) + ([{:keys [width height color-scheme font-family font-size toolkit] :as opts}] + (let [opts (merge default-run-term-opts opts) {:keys [width height color-scheme font-family font-size toolkit]} opts term-state (atom {:vt (vt/make-vt width height)}) toolkit (if toolkit toolkit (load-default-toolkit)) font (load-terminal-font toolkit font-family font-size)] - (swap! term-state assoc - :pty (run-pty-process width height term-state)) - (tk/run-sync - toolkit - (fn [] - (let [{:keys [pty vt]} @term-state] - (term-events pty - (term-view color-scheme font vt)))) - {:window-title "membrane.term" - :window-start-width (* width (:membrane.term/cell-width font)) - :window-start-height (+ window-padding-height (* height (:membrane.term/cell-height font)))}) - - (let [^PtyProcess pty (:pty @term-state)] - (.close (.getInputStream pty)) - (.close (.getOutputStream pty)))))) - -(defn screenshot - ([opts] - (let [opts (merge default-common-opts - {:line-delay 1e3 - :final-delay 10e3 - :out "terminal.png"} - opts) - {:keys [play width height out line-delay final-delay color-scheme font-family font-size toolkit]} opts - term-state (atom {:vt (vt/make-vt width height)}) - toolkit (if toolkit - toolkit - (load-default-toolkit)) - font (load-terminal-font toolkit font-family font-size)] (swap! term-state assoc :pty (run-pty-process width height term-state)) - (doseq [line (string/split-lines (slurp play))] - (send-input (:pty @term-state) line) - (send-input (:pty @term-state) "\n") - (Thread/sleep line-delay)) - - (Thread/sleep final-delay) - (tk/save-image toolkit - out - (ui/fill-bordered (:background color-scheme) 5 - (term-view color-scheme font (:vt @term-state)))) - (println (str "Wrote screenshot to " out ".")) + (tk/run-sync + toolkit + (fn [] + (let [{:keys [pty vt]} @term-state] + (term-events pty + (term-view color-scheme font vt)))) + {:window-title "membrane.term" + :window-start-width (* width (:membrane.term/cell-width font)) + :window-start-height (+ window-padding-height (* height (:membrane.term/cell-height font)))}) (let [^PtyProcess pty (:pty @term-state)] (.close (.getInputStream pty)) (.close (.getOutputStream pty)))))) + +(def default-screenshot-opts "Default options used for [[screenshot]]" (merge default-common-opts {:line-delay 1e3 + :final-delay 10e3 + :out "terminal.png"})) + +(defn screenshot + "Take a screenshot after playing a script line by line in a membrane.term terminal. + Terminal is not displayed and automatically exits after screenshot is written. + + Requires `opts` map: + - `:play` Path to script to play in terminal (**required**) + - `:out` Filename for screenshot image (default: `\"terminal.png\"`) + - `:line-delay` Delay in milliseconds to wait after each line in `:play` script is sent to terminal (default: `1000`) + - `:final-delay` Delay in milliseconds to wait after all lines in `:play` script are sent to terminal (default: `10000`) + - `:width` Window width in characters (default: `90`) + - `:height` Window height in characters (default: `30`) + - `:color-scheme` Map for terminal colors (defaults to an internal scheme) + Colors are specified per membrane convention, vectors of `[red green blue]` or + `[red green blue alpha]` with values from `0` - `1` inclusive. Example: `[0.14 0.74 0.14 0.50]`. + A color value must be specified for all of: + - ANSI colors + - `:white` `:black` `:red` `:green` `:yellow` `:blue` `:magenta` `cyan` + - `:bright-white` `:bright-black` `:bright-red` `:bright-green` `:bright-yellow` `:bright-blue` `:bright-magenta` `:bright-cyan` + - `:cursor` - Background color for cursor + - `:cursor-text` - Foreground color for cursor text + - `:background` - Default background color + - `:foreground` - Default text color + - `:font-family` OS installed font family name. Example: `\"Courier New\"`. + Use `:monospace` for default monospace (default: `:monospace`) + - `:font-size` Font point size (default: `12`) + - `:toolkit` Graphics toolkit (default: `membrane.toolkit/java2d`) + - An object that must satisfy the following + [`membrane.toolkit`](https://github.com/phronmophobic/membrane/blob/master/src/membrane/toolkit.clj) interfaces: + - `IToolkit` + - `IToolkitLogicalFontFontFamily` + - `IToolkitFontExists` + - `IToolkitFontMetrics` + - `IToolkitFontAdvanceX` + - `IToolkitFontLineHeight` + - `IToolkitRunSync` + - `IToolkitSaveImage` + - Usable examples from membrane library: `membrane.java2d/toolkit`, `membrane.skia/toolkit`" + [{:keys [play out line-delay final-delay width height color-scheme font-family font-size toolkit] :as opts}] + (let [opts (merge default-screenshot-opts opts) + {:keys [play width height out line-delay final-delay color-scheme font-family font-size toolkit]} opts + term-state (atom {:vt (vt/make-vt width height)}) + toolkit (if toolkit + toolkit + (load-default-toolkit)) + font (load-terminal-font toolkit font-family font-size)] + (swap! term-state assoc + :pty (run-pty-process width height term-state)) + (doseq [line (string/split-lines (slurp play))] + (send-input (:pty @term-state) line) + (send-input (:pty @term-state) "\n") + (Thread/sleep line-delay)) + + (Thread/sleep final-delay) + (tk/save-image toolkit + out + (ui/fill-bordered (:background color-scheme) 5 + (term-view color-scheme font (:vt @term-state)))) + (println (str "Wrote screenshot to " out ".")) + + (let [^PtyProcess pty (:pty @term-state)] + (.close (.getInputStream pty)) + (.close (.getOutputStream pty))))) diff --git a/src/com/phronemophobic/membrane/term/color_scheme.clj b/src/com/phronemophobic/membrane/term/color_scheme.clj index d505e38..175980d 100644 --- a/src/com/phronemophobic/membrane/term/color_scheme.clj +++ b/src/com/phronemophobic/membrane/term/color_scheme.clj @@ -1,4 +1,4 @@ -(ns com.phronemophobic.membrane.term.color-scheme +(ns ^:no-doc com.phronemophobic.membrane.term.color-scheme (:require [clojure.data.xml :as xml] [clojure.data.zip.xml :as zxml] [clojure.set :as cset] diff --git a/src/com/phronemophobic/membrane/term/main.clj b/src/com/phronemophobic/membrane/term/main.clj index 9358779..7ff4a01 100644 --- a/src/com/phronemophobic/membrane/term/main.clj +++ b/src/com/phronemophobic/membrane/term/main.clj @@ -1,4 +1,4 @@ -(ns com.phronemophobic.membrane.term.main +(ns ^:no-doc com.phronemophobic.membrane.term.main (:require [clojure.java.io :as io] [clojure.string :as string] [com.phronemophobic.membrane.term :as term] @@ -37,9 +37,6 @@ Replace membrane.term with your appropriate Clojure tools CLI launch sequence. F | clojure -M:membrane.term run-term -w 133 -h 60 |") -(defn- parse-string [v] - (str v)) - (defn parse-font-family [v] (if (= "monospace" v) :monospace