-
Notifications
You must be signed in to change notification settings - Fork 49
Expand file tree
/
Copy pathterminal.clj
More file actions
275 lines (213 loc) · 8.77 KB
/
terminal.clj
File metadata and controls
275 lines (213 loc) · 8.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
(ns lanterna.terminal
(:import com.googlecode.lanterna.TerminalFacade
com.googlecode.lanterna.terminal.Terminal
com.googlecode.lanterna.terminal.swing.SwingTerminal
com.googlecode.lanterna.terminal.swing.TerminalAppearance
com.googlecode.lanterna.terminal.swing.TerminalPalette
java.awt.GraphicsEnvironment
java.awt.Font)
(:use [lanterna.common :only [parse-key block-on]])
(:require [lanterna.constants :as c]))
(defn add-resize-listener
"Create a listener that will call the supplied fn when the terminal is resized.
The function must take two arguments: the new number of columns and the new
number of rows.
The listener itself will be returned. You don't need to do anything with it,
but you can use it to remove it later with remove-resize-listener.
"
[^Terminal terminal listener-fn]
(let [listener (reify com.googlecode.lanterna.terminal.Terminal$ResizeListener
(onResized [this newSize]
(listener-fn (.getColumns newSize)
(.getRows newSize))))]
(.addResizeListener terminal listener)
listener))
(defn remove-resize-listener
"Remove a resize listener from the given terminal."
[^Terminal terminal listener]
(.removeResizeListener terminal listener))
(defn get-available-fonts []
(set (.getAvailableFontFamilyNames
(GraphicsEnvironment/getLocalGraphicsEnvironment))))
(defn- get-font-name [font]
(let [fonts (if (coll? font) font [font])
fonts (concat fonts ["Monospaced"])
available (get-available-fonts)]
(first (filter available fonts))))
(defn- get-swing-terminal [cols rows
{:as opts
:keys [font font-size palette]
:or {font ["Menlo" "Consolas" "Monospaced"]
font-size 14
palette :mac-os-x}}]
(let [font (get-font-name font)
appearance (new TerminalAppearance
(new Font font Font/PLAIN font-size)
(new Font font Font/BOLD font-size)
(c/palettes palette) true)]
(new SwingTerminal appearance cols rows)))
(defn get-terminal
"Get a terminal object.
kind can be one of the following:
:auto - Try to guess the right type of terminal based on OS, whether
there's a windowing environment, etc
:swing - Force a Swing-based terminal.
:text - Force a console (i.e.: non-Swing) terminal. Try to guess the
appropriate kind of console (UNIX/Cygwin) by the OS.
:unix - Force a UNIX console terminal.
:cygwin - Force a Cygwin console terminal.
Options can contain one or more of the following keys:
:cols - Width of the desired terminal in characters (default 80).
:rows - Height of the desired terminal in characters (default 24).
:charset - Charset of the desired terminal. Can be any of
(keys lanterna.constants/charsets) (default :utf-8).
:resize-listener - A function to call when the terminal is resized. This
function should take two parameters: the new number of
columns, and the new number of rows.
:font - Font to use. String or sequence of strings.
Use (lanterna.terminal/get-available-fonts) to see your options.
Will fall back to a basic monospaced font if none of the given
names are available.
:font-size - Font size (default 14).
:palette - Color palette to use. Can be any of
(keys lanterna.constants/palettes) (default :mac-os-x).
NOTE: The options are really just a suggestion!
The console terminal will ignore rows and columns and fonts and colors.
The Swing terminal will start out at this size but can be resized later by the
user, and will ignore the charset entirely.
God only know what Cygwin will do.
"
([] (get-terminal :auto {}))
([kind] (get-terminal kind {}))
([kind {:as opts
:keys [cols rows charset resize-listener]
:or {cols 80
rows 24
charset :utf-8
resize-listener nil}}]
(let [in System/in
out System/out
charset (c/charsets charset)
terminal (case kind
:auto (TerminalFacade/createTerminal charset)
:swing (get-swing-terminal cols rows opts)
:text (TerminalFacade/createTextTerminal in out charset)
:unix (TerminalFacade/createUnixTerminal in out charset)
:cygwin (TerminalFacade/createCygwinTerminal in out charset))]
(when resize-listener
(add-resize-listener terminal resize-listener))
terminal)))
(defn start
"Start the terminal. Consider using in-terminal instead."
[^Terminal terminal]
(.enterPrivateMode terminal))
(defn stop
"Stop the terminal. Consider using in-terminal instead."
[^Terminal terminal]
(.exitPrivateMode terminal))
(defmacro in-terminal
"Start the given terminal, perform the body, and stop the terminal afterward."
[terminal & body]
`(do
(start ~terminal)
(try ~@body
(finally (stop ~terminal)))))
(defn get-size
"Return the current size of the terminal as [cols rows]."
[^Terminal terminal]
(let [size (.getTerminalSize terminal)
cols (.getColumns size)
rows (.getRows size)]
[cols rows]))
(defn move-cursor
"Move the cursor to a specific location on the screen."
[^Terminal terminal x y]
(.moveCursor terminal x y))
(defn put-character
"Draw the character at the current cursor location.
If x and y are given, moves the cursor there first.
Moves the cursor one character to the right, so a sequence of calls will
output next to each other.
"
([^Terminal terminal ch]
(.putCharacter terminal ch))
([^Terminal terminal ch x y]
(move-cursor terminal x y)
(put-character terminal ch)))
(defn put-string
"Draw the string at the current cursor location.
If x and y are given, moves the cursor there first.
The cursor will end up at the position directly after the string.
"
([^Terminal terminal s]
(dorun (map (partial put-character terminal)
s)))
([terminal s x y]
(move-cursor terminal x y)
(put-string terminal s)))
(defn clear
"Clear the terminal.
The cursor will be at 0 0 afterwards.
"
[^Terminal terminal]
(.clearScreen terminal)
(move-cursor terminal 0 0))
(defn set-fg-color [^Terminal terminal color]
(cond (integer? color) (.applyForegroundColor terminal color)
(keyword? color) (.applyForegroundColor terminal (c/colors color))
(vector? color) (let [[r g b] color] (.applyForegroundColor terminal r g b))
:else nil))
(defn set-bg-color [^Terminal terminal color]
(cond (integer? color) (.applyBackgroundColor terminal color)
(keyword? color) (.applyBackgroundColor terminal (c/colors color))
(vector? color) (let [[r g b] color] (.applyBackgroundColor terminal r g b))
:else nil))
; TODO: Fix these.
(defn set-style
"Borked. Don't use this."
[^Terminal terminal style]
(.applySGR terminal (c/enter-styles style)))
(defn remove-style
"Borked. Don't use this."
[^Terminal terminal style]
(.applySGR terminal (c/exit-styles style)))
(defn reset-styles
"Borked. Don't use this."
[^Terminal terminal]
(.applySGR terminal c/reset-style))
(defn get-key
"Get the next keypress from the user, or nil if none are buffered.
If the user has pressed a key, that key will be returned (and popped off the
buffer of input).
If the user has *not* pressed a key, nil will be returned immediately. If you
want to wait for user input, use get-key-blocking instead.
"
[^Terminal terminal]
(parse-key (.readInput terminal)))
(defn get-key-blocking
"Get the next keypress from the user.
If the user has pressed a key, that key will be returned (and popped off the
buffer of input).
If the user has *not* pressed a key, this function will block, checking every
50ms. If you want to return nil immediately, use get-key instead.
Options can include any of the following keys:
:interval - sets the interval between checks
:timeout - sets the maximum amount of time blocking will occur before
returning nil
"
([^Terminal terminal] (get-key-blocking terminal {}))
([^Terminal terminal {:keys [interval timeout] :as opts}]
(block-on get-key [terminal] opts)))
(comment
(def t (get-terminal :swing
{:cols 40 :rows 30
:font ["Menlo"]
:font-size 24
:palette :gnome}))
(start t)
(set-fg-color t :yellow)
(put-string t "Hello, world!")
(get-key-blocking t {:timeout 1000})
(get-key-blocking t {:interval 2000})
(stop t)
)