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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
|
#
#
# Nimrod's Runtime Library
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module contains a few procedures to control the *terminal*
## (also called *console*). On UNIX, the implementation simply uses ANSI escape
## sequences and does not depend on any other module, on Windows it uses the
## Windows API.
## Changing the style is permanent even after program termination! Use the
## code ``system.addQuitProc(resetAttributes)`` to restore the defaults.
when defined(windows):
import windows, os
var
conHandle: THandle
# = createFile("CONOUT$", GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0)
block:
var hTemp = GetStdHandle(STD_OUTPUT_HANDLE)
if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(),
addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
OSError()
proc getCursorPos(): tuple [x,y: int] =
var c: TCONSOLE_SCREEN_BUFFER_INFO
if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError()
return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y))
proc getAttributes(): int16 =
var c: TCONSOLE_SCREEN_BUFFER_INFO
# workaround Windows bugs: try several times
if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0:
return c.wAttributes
else:
OSError()
return 0x70'i16 # ERROR: return white background, black text
var
oldAttr = getAttributes()
proc setCursorPos*(x, y: int) =
## sets the terminal's cursor to the (x,y) position. (0,0) is the
## upper left of the screen.
when defined(windows):
var c: TCoord
c.x = int16(x)
c.y = int16(y)
if SetConsoleCursorPosition(conHandle, c) == 0: OSError()
else:
stdout.write("\e[" & $y & ';' & $x & 'f')
proc setCursorXPos*(x: int) =
## sets the terminal's cursor to the x position. The y position is
## not changed.
when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var origin = scrbuf.dwCursorPosition
origin.x = int16(x)
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError()
else:
stdout.write("\e[" & $x & 'G')
when defined(windows):
proc setCursorYPos*(y: int) =
## sets the terminal's cursor to the y position. The x position is
## not changed. **Warning**: This is not supported on UNIX!
when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var origin = scrbuf.dwCursorPosition
origin.y = int16(y)
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError()
else:
nil
proc CursorUp*(count=1) =
## Moves the cursor up by `count` rows.
when defined(windows):
var p = getCursorPos()
dec(p.y, count)
setCursorPos(p.x, p.y)
else:
stdout.write("\e[" & $count & 'A')
proc CursorDown*(count=1) =
## Moves the cursor down by `count` rows.
when defined(windows):
var p = getCursorPos()
inc(p.y, count)
setCursorPos(p.x, p.y)
else:
stdout.write("\e[" & $count & 'B')
proc CursorForward*(count=1) =
## Moves the cursor forward by `count` columns.
when defined(windows):
var p = getCursorPos()
inc(p.x, count)
setCursorPos(p.x, p.y)
else:
stdout.write("\e[" & $count & 'C')
proc CursorBackward*(count=1) =
## Moves the cursor backward by `count` columns.
when defined(windows):
var p = getCursorPos()
dec(p.x, count)
setCursorPos(p.x, p.y)
else:
stdout.write("\e[" & $count & 'D')
when true:
nil
else:
proc EraseLineEnd* =
## Erases from the current cursor position to the end of the current line.
when defined(windows):
nil
else:
stdout.write("\e[K")
proc EraseLineStart* =
## Erases from the current cursor position to the start of the current line.
when defined(windows):
nil
else:
stdout.write("\e[1K")
proc EraseDown* =
## Erases the screen from the current line down to the bottom of the screen.
when defined(windows):
nil
else:
stdout.write("\e[J")
proc EraseUp* =
## Erases the screen from the current line up to the top of the screen.
when defined(windows):
nil
else:
stdout.write("\e[1J")
proc EraseLine* =
## Erases the entire current line.
when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var numwrote: DWORD
var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var origin = scrbuf.dwCursorPosition
origin.x = 0'i16
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError()
var ht = scrbuf.dwSize.Y - origin.Y
var wt = scrbuf.dwSize.X - origin.X
if FillConsoleOutputCharacter(hStdout,' ', ht*wt,
origin, addr(numwrote)) == 0:
OSError()
if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt,
scrbuf.dwCursorPosition, addr(numwrote)) == 0:
OSError()
else:
stdout.write("\e[2K")
setCursorXPos(0)
proc EraseScreen* =
## Erases the screen with the background colour and moves the cursor to home.
when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var numwrote: DWORD
var origin: TCoord # is inititalized to 0, 0
var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y,
origin, addr(numwrote)) == 0:
OSError()
if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes,
scrbuf.dwSize.X * scrbuf.dwSize.Y,
origin, addr(numwrote)) == 0:
OSError()
setCursorXPos(0)
else:
stdout.write("\e[2J")
proc ResetAttributes* {.noconv.} =
## resets all attributes; it is advisable to register this as a quit proc
## with ``system.addQuitProc(resetAttributes)``.
when defined(windows):
discard SetConsoleTextAttribute(conHandle, oldAttr)
else:
stdout.write("\e[0m")
type
TStyle* = enum ## different styles for text output
styleBright = 1, ## bright text
styleDim, ## dim text
styleUnknown, ## unknown
styleUnderscore = 4, ## underscored text
styleBlink, ## blinking/bold text
styleReverse, ## unknown
styleHidden ## hidden text
when not defined(windows):
var
gFG = 0
gBG = 0
proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) =
## writes the text `txt` in a given `style`.
when defined(windows):
var a = 0'i16
if styleBright in style: a = a or int16(FOREGROUND_INTENSITY)
if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY)
if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO
if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE
var old = getAttributes()
discard SetConsoleTextAttribute(conHandle, old or a)
stdout.write(txt)
discard SetConsoleTextAttribute(conHandle, old)
else:
for s in items(style):
stdout.write("\e[" & $ord(s) & 'm')
stdout.write(txt)
resetAttributes()
if gFG != 0:
stdout.write("\e[" & $ord(gFG) & 'm')
if gBG != 0:
stdout.write("\e[" & $ord(gBG) & 'm')
type
TForegroundColor* = enum ## terminal's foreground colors
fgBlack = 30, ## black
fgRed, ## red
fgGreen, ## green
fgYellow, ## yellow
fgBlue, ## blue
fgMagenta, ## magenta
fgCyan, ## cyan
fgWhite ## white
TBackgroundColor* = enum ## terminal's background colors
bgBlack = 40, ## black
bgRed, ## red
bgGreen, ## green
bgYellow, ## yellow
bgBlue, ## blue
bgMagenta, ## magenta
bgCyan, ## cyan
bgWhite ## white
proc setForegroundColor*(fg: TForegroundColor, bright=false) =
## sets the terminal's foreground color
when defined(windows):
var old = getAttributes() and not 0x0007
if bright:
old = old or FOREGROUND_INTENSITY
const lookup: array [TForegroundColor, int] = [
0,
(FOREGROUND_RED),
(FOREGROUND_GREEN),
(FOREGROUND_RED or FOREGROUND_GREEN),
(FOREGROUND_BLUE),
(FOREGROUND_RED or FOREGROUND_BLUE),
(FOREGROUND_BLUE or FOREGROUND_GREEN),
(FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)]
discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[fg]))
else:
gFG = ord(fg)
if bright: inc(gFG, 60)
stdout.write("\e[" & $gFG & 'm')
proc setBackgroundColor*(bg: TBackgroundColor, bright=false) =
## sets the terminal's background color
when defined(windows):
var old = getAttributes() and not 0x0070
if bright:
old = old or BACKGROUND_INTENSITY
const lookup: array [TBackgroundColor, int] = [
0,
(BACKGROUND_RED),
(BACKGROUND_GREEN),
(BACKGROUND_RED or BACKGROUND_GREEN),
(BACKGROUND_BLUE),
(BACKGROUND_RED or BACKGROUND_BLUE),
(BACKGROUND_BLUE or BACKGROUND_GREEN),
(BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)]
discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[bg]))
else:
gBG = ord(bg)
if bright: inc(gBG, 60)
stdout.write("\e[" & $gBG & 'm')
when isMainModule:
system.addQuitProc(resetAttributes)
write(stdout, "never mind")
eraseLine()
#setCursorPos(2, 2)
writeStyled("styled text ", {styleBright, styleBlink, styleUnderscore})
setBackGroundColor(bgCyan, true)
setForeGroundColor(fgBlue)
writeln(stdout, "ordinary text")
|