├── .editorconfig
├── .github
└── workflows
│ └── crystal.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── assets
└── term-logo.png
├── shard.yml
├── spec
├── spec_helper.cr
└── unit
│ ├── clear_lines_spec.cr
│ ├── cursor_spec.cr
│ ├── move_spec.cr
│ ├── move_to_spec.cr
│ └── scroll_spec.cr
└── src
├── cursor
└── version.cr
└── term-cursor.cr
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.cr]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 2
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.github/workflows/crystal.yml:
--------------------------------------------------------------------------------
1 | name: specs
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | container:
15 | image: crystallang/crystal
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Install dependencies
20 | run: shards install
21 | - name: Run tests
22 | run: crystal spec
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /docs/
2 | /lib/
3 | /bin/
4 | /.shards/
5 | *.dwarf
6 |
7 | # Libraries don't need dependency lock
8 | # Dependencies will be locked in applications that use them
9 | /shard.lock
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: crystal
2 |
3 | # Uncomment the following if you'd like Travis to run specs and check code formatting
4 | # script:
5 | # - crystal spec
6 | # - crystal tool format --check
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Chris Watson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | # Term::Cursor
6 |
7 | 
8 |
9 | > Terminal cursor positioning, visibility and text manipulation.
10 |
11 | The purpose of this library is to help move the terminal cursor around and manipulate text by using intuitive method calls.
12 |
13 | ## Contents
14 |
15 | - [Term::Cursor](#termcursor)
16 | - [Contents](#contents)
17 | - [Installation](#installation)
18 | - [1. Usage](#1-usage)
19 | - [2. Interface](#2-interface)
20 | - [2.1 Cursor Positioning](#21-cursor-positioning)
21 | - [2.1.1 move_to(x, y)](#211-movetox-y)
22 | - [2.1.2 move(x, y)](#212-movex-y)
23 | - [2.1.3 up(n)](#213-upn)
24 | - [2.1.4 down(n)](#214-downn)
25 | - [2.1.5 forward(n)](#215-forwardn)
26 | - [2.1.6 backward(n)](#216-backwardn)
27 | - [2.1.7 column(n)](#217-columnn)
28 | - [2.1.8 row(n)](#218-rown)
29 | - [2.1.9 next_line](#219-nextline)
30 | - [2.1.10 prev_line](#2110-prevline)
31 | - [2.1.11 save](#2111-save)
32 | - [2.1.12 restore](#2112-restore)
33 | - [2.1.13 current](#2113-current)
34 | - [2.2 Cursor Visibility](#22-cursor-visibility)
35 | - [2.2.1 show](#221-show)
36 | - [2.2.2 hide](#222-hide)
37 | - [2.2.3 invisible(stream)](#223-invisiblestream)
38 | - [2.3 Text Clearing](#23-text-clearing)
39 | - [2.3.1 clear_char(n)](#231-clearcharn)
40 | - [2.3.2 clear_line](#232-clearline)
41 | - [2.3.3 clear_line_before](#233-clearlinebefore)
42 | - [2.3.4 clear_line_after](#234-clearlineafter)
43 | - [2.3.5 clear_lines(n, direction)](#235-clearlinesn-direction)
44 | - [2.3.6 clear_screen](#236-clearscreen)
45 | - [2.3.7 clear_screen_down](#237-clearscreendown)
46 | - [2.3.8 clear_screen_up](#238-clearscreenup)
47 | - [2.4 Scrolling](#24-scrolling)
48 | - [2.4.1 scroll_down](#241-scrolldown)
49 | - [2.4.2 scroll_up](#242-scrollup)
50 | - [Contributing](#contributing)
51 | - [Contributors](#contributors)
52 |
53 | ## Installation
54 |
55 | 1. Add the dependency to your `shard.yml`:
56 |
57 | ```yaml
58 | dependencies:
59 | term-cursor:
60 | github: crystal-term/cursor
61 | ```
62 |
63 | 2. Run `shards install`
64 |
65 | ## 1. Usage
66 |
67 | **Term::Cursor** is just a module hence you can reference it for later like so:
68 |
69 | ```crystal
70 | require "term-cursor"
71 | cursor = Term::Cursor
72 | ```
73 |
74 | and to move the cursor current position by 5 rows up and 2 columns right do:
75 |
76 | ```crystal
77 | print cursor.up(5) + cursor.forward(2)
78 | ```
79 |
80 | or call `move` to move cursor relative to current position:
81 |
82 | ```crystal
83 | print cursor.move(5, 2)
84 | ```
85 |
86 | to remove text from the current line do:
87 |
88 | ```crystal
89 | print cursor.clear_line
90 | ```
91 |
92 | ## 2. Interface
93 |
94 | ### 2.1 Cursor Positioning
95 |
96 | All methods in this section allow to position the cursor around the terminal viewport.
97 |
98 | Cursor movement will be bounded by the current viewport into the buffer. Scrolling (if available) will not occur.
99 |
100 | #### 2.1.1 move_to(x, y)
101 |
102 | Set the cursor absolute position to `x` and `y` coordinate, where `x` is the column of the `y` line.
103 |
104 | If no row/column parameters are provided, the cursor will move to the home position, at the upper left of the screen:
105 |
106 | ```crystal
107 | cursor.move_to
108 | ```
109 |
110 | #### 2.1.2 move(x, y)
111 |
112 | Move cursor by x columns and y rows relative to its current position.
113 |
114 | #### 2.1.3 up(n)
115 |
116 | Move the cursor up by `n` rows; the default n is `1`.
117 |
118 | #### 2.1.4 down(n)
119 |
120 | Move the cursor down by `n` rows; the default n is `1`.
121 |
122 | #### 2.1.5 forward(n)
123 |
124 | Move the cursor forward by `n` columns; the default n is `1`.
125 |
126 | #### 2.1.6 backward(n)
127 |
128 | Move the cursor backward by `n` columns; the default n is `1`.
129 |
130 | #### 2.1.7 column(n)
131 |
132 | Cursor moves to ``th position horizontally in the current line.
133 |
134 | #### 2.1.8 row(n)
135 |
136 | Cursor moves to the ``th position vertically in the current column.
137 |
138 | #### 2.1.9 next_line
139 |
140 | Move the cursor down to the beginning of the next line.
141 |
142 | #### 2.1.10 prev_line
143 |
144 | Move the cursor up to the beginning of the previous line.
145 |
146 | #### 2.1.11 save
147 |
148 | Save current cursor position.
149 |
150 | #### 2.1.12 restore
151 |
152 | Restore cursor position after a save cursor was called.
153 |
154 | #### 2.1.13 current
155 |
156 | Query current cursor position
157 |
158 | ### 2.2 Cursor Visibility
159 |
160 | The following methods control the visibility of the cursor.
161 |
162 | #### 2.2.1 show
163 |
164 | Show the cursor.
165 |
166 | #### 2.2.2 hide
167 |
168 | Hide the cursor.
169 |
170 | #### 2.2.3 invisible(stream)
171 |
172 | To hide the cursor for the duration of the block do:
173 |
174 | ```crystal
175 | cursor.invisible { ... }
176 | ```
177 |
178 | By default standard output will be used but you can change that by passing a different stream that responds to `print` call:
179 |
180 | ```crystal
181 | cursor.invisible(STDERR) { .... }
182 | ```
183 |
184 | ### 2.3 Text Clearing
185 |
186 | All methods in this section provide APIs to modify text buffer contents.
187 |
188 | #### 2.3.1 clear_char(n)
189 |
190 | Erase `` characters from the current cursor position by overwriting them with space character.
191 |
192 | #### 2.3.2 clear_line
193 |
194 | Erase the entire current line and return cursor to beginning of the line.
195 |
196 | #### 2.3.3 clear_line_before
197 |
198 | Erase from the beginning of the line up to and including the current position.
199 |
200 | #### 2.3.4 clear_line_after
201 |
202 | Erase from the current position (inclusive) to the end of the line/display.
203 |
204 | #### 2.3.5 clear_lines(n, direction)
205 |
206 | Erase `n` rows in given direction; the default direction is `:up`.
207 |
208 | ```crystal
209 | cursor.clear_lines(5, :down)
210 | ```
211 |
212 | #### 2.3.6 clear_screen
213 |
214 | Erase the screen with the background colour and moves the cursor to home.
215 |
216 | #### 2.3.7 clear_screen_down
217 |
218 | Erase the screen from the current line down to the bottom of the screen.
219 |
220 | #### 2.3.8 clear_screen_up
221 |
222 | Erase the screen from the current line up to the top of the screen.
223 |
224 | ### 2.4 Scrolling
225 |
226 | #### 2.4.1 scroll_down
227 |
228 | Scroll display down one line.
229 |
230 | ### 2.4.2 scroll_up
231 |
232 | Scroll display up one line.
233 |
234 | ## Contributing
235 |
236 | 1. Fork it ()
237 | 2. Create your feature branch (`git checkout -b my-new-feature`)
238 | 3. Commit your changes (`git commit -am 'Add some feature'`)
239 | 4. Push to the branch (`git push origin my-new-feature`)
240 | 5. Create a new Pull Request
241 |
242 | ## Contributors
243 |
244 | - [Chris Watson](https://github.com/watzon) - creator and maintainer
245 |
--------------------------------------------------------------------------------
/assets/term-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crystal-term/cursor/6bbe0a27a423bc4e0239a051afca05fd36b47e0a/assets/term-logo.png
--------------------------------------------------------------------------------
/shard.yml:
--------------------------------------------------------------------------------
1 | name: term-cursor
2 | version: 0.1.0
3 |
4 | authors:
5 | - Chris Watson
6 |
7 | development_dependencies:
8 | spectator:
9 | gitlab: arctic-fox/spectator
10 | branch: master
11 |
12 | crystal: 0.33.0
13 |
14 | license: MIT
15 |
--------------------------------------------------------------------------------
/spec/spec_helper.cr:
--------------------------------------------------------------------------------
1 | require "../src/term-cursor"
2 | require "spectator"
3 |
--------------------------------------------------------------------------------
/spec/unit/clear_lines_spec.cr:
--------------------------------------------------------------------------------
1 | require "../spec_helper"
2 |
3 | Spectator.describe Term::Cursor do
4 | describe "#clear_lines" do
5 | let(:cursor) { described_class }
6 |
7 | it "clears character" do
8 | expect(cursor.clear_char).to eq("\e[X")
9 | end
10 |
11 | it "clears few characters" do
12 | expect(cursor.clear_char(5)).to eq("\e[5X")
13 | end
14 |
15 | it "clears line" do
16 | expect(cursor.clear_line).to eq("\e[2K\e[1G")
17 | end
18 |
19 | it "clears the line before the cursor" do
20 | expect(cursor.clear_line_before).to eq("\e[1K")
21 | end
22 |
23 | it "clears the line after the cursor" do
24 | expect(cursor.clear_line_after).to eq("\e[0K")
25 | end
26 |
27 | it "clears 5 lines up" do
28 | expect(cursor.clear_lines(5)).to eq([
29 | "\e[2K\e[1G\e[1A",
30 | "\e[2K\e[1G\e[1A",
31 | "\e[2K\e[1G\e[1A",
32 | "\e[2K\e[1G\e[1A",
33 | "\e[2K\e[1G"
34 | ].join)
35 | end
36 |
37 | it "clears 5 lines down" do
38 | expect(cursor.clear_lines(5, :down)).to eq([
39 | "\e[2K\e[1G\e[1B",
40 | "\e[2K\e[1G\e[1B",
41 | "\e[2K\e[1G\e[1B",
42 | "\e[2K\e[1G\e[1B",
43 | "\e[2K\e[1G"
44 | ].join)
45 | end
46 |
47 | it "clears screen down" do
48 | expect(cursor.clear_screen_down).to eq("\e[J")
49 | end
50 |
51 | it "clears screen up" do
52 | expect(cursor.clear_screen_up).to eq("\e[1J")
53 | end
54 |
55 | it "clears entire screen" do
56 | expect(cursor.clear_screen).to eq("\e[2J")
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/spec/unit/cursor_spec.cr:
--------------------------------------------------------------------------------
1 | require "../spec_helper"
2 |
3 | Spectator.describe Term::Cursor do
4 | let(:cursor) { described_class }
5 |
6 | it "shows cursor" do
7 | expect(cursor.show).to eq("\e[?25h")
8 | end
9 |
10 | it "hides cursor" do
11 | expect(cursor.hide).to eq("\e[?25l")
12 | end
13 |
14 | it "saves cursor position" do
15 | # TODO: Skip on Windows
16 |
17 | expect(cursor.save).to eq("\e7")
18 | end
19 |
20 | it "saves cursor position on Windows" do
21 | # TODO: Only run on windows
22 |
23 | # expect(cursor.save).to eq("\e[s")
24 | end
25 |
26 | it "restores cursor position" do
27 | # TODO: Skip on Windows
28 |
29 | expect(cursor.restore).to eq("\e8")
30 | end
31 |
32 | it "restores cursor position on Windows" do
33 | # TODO: Only run on windows
34 |
35 | # expect(cursor.restore).to eq("\e[u")
36 | end
37 |
38 | it "gets current cursor position" do
39 | expect(cursor.current).to eq("\e[6n")
40 | end
41 |
42 | it "moves cursor up default by 1 line" do
43 | expect(cursor.up).to eq("\e[1A")
44 | end
45 |
46 | it "moves cursor up by 5 lines" do
47 | expect(cursor.up(5)).to eq("\e[5A")
48 | end
49 |
50 | it "moves cursor down default by 1 line" do
51 | expect(cursor.down).to eq("\e[1B")
52 | end
53 |
54 | it "moves cursor down by 5 lines" do
55 | expect(cursor.down(5)).to eq("\e[5B")
56 | end
57 |
58 | it "moves cursorleft by 1 line default" do
59 | expect(cursor.backward).to eq("\e[1D")
60 | end
61 |
62 | it "moves cursor left by 5" do
63 | expect(cursor.backward(5)).to eq("\e[5D")
64 | end
65 |
66 | it "moves cursor right by 1 line default" do
67 | expect(cursor.forward).to eq("\e[1C")
68 | end
69 |
70 | it "moves cursor right by 5 lines" do
71 | expect(cursor.forward(5)).to eq("\e[5C")
72 | end
73 |
74 | it "moves cursor horizontal to start" do
75 | expect(cursor.column).to eq("\e[1G")
76 | end
77 |
78 | it "moves cursor horizontally to 66th position" do
79 | expect(cursor.column(66)).to eq("\e[66G")
80 | end
81 |
82 | it "moves cursor vertically to start" do
83 | expect(cursor.row).to eq("\e[1d")
84 | end
85 |
86 | it "moves cursor vertically to 50th row" do
87 | expect(cursor.row(50)).to eq("\e[50d")
88 | end
89 |
90 | it "moves cursor to next line" do
91 | expect(cursor.next_line).to eq("\e[E\e[1G")
92 | end
93 |
94 | it "moves cursor to previous line" do
95 | expect(cursor.prev_line).to eq("\e[A\e[1G")
96 | end
97 |
98 | it "hides cursor for the duration of block call" do
99 | stream = IO::Memory.new
100 | cursor.invisible(stream) { }
101 | expect(stream.rewind.gets_to_end).to eq("\e[?25l\e[?25h")
102 | end
103 | end
104 |
--------------------------------------------------------------------------------
/spec/unit/move_spec.cr:
--------------------------------------------------------------------------------
1 | require "../spec_helper"
2 |
3 | Spectator.describe Term::Cursor do
4 | describe "#move" do
5 | let(:cursor) { described_class }
6 |
7 | it "doesn't move for point (0, 0)" do
8 | expect(cursor.move(0, 0)).to eq("")
9 | end
10 |
11 | it "moves only to the right" do
12 | expect(cursor.move(2, 0)).to eq("\e[2C")
13 | end
14 |
15 | it "moves right and up" do
16 | expect(cursor.move(2, 3)).to eq("\e[2C\e[3A")
17 | end
18 |
19 | it "moves right and down" do
20 | expect(cursor.move(2, -3)).to eq("\e[2C\e[3B")
21 | end
22 |
23 | it "moves left and up" do
24 | expect(cursor.move(-2, 3)).to eq("\e[2D\e[3A")
25 | end
26 |
27 | it "moves left and down" do
28 | expect(cursor.move(-2, -3)).to eq("\e[2D\e[3B")
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/unit/move_to_spec.cr:
--------------------------------------------------------------------------------
1 | require "../spec_helper"
2 |
3 | Spectator.describe Term::Cursor do
4 | describe "#move_to" do
5 | let(:cursor) { described_class }
6 |
7 | it "moves to home" do
8 | expect(cursor.move_to). to eq("\e[H")
9 | end
10 |
11 | it "moves to row and column" do
12 | expect(cursor.move_to(2, 3)).to eq("\e[4;3H")
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/unit/scroll_spec.cr:
--------------------------------------------------------------------------------
1 | require "../spec_helper"
2 |
3 | Spectator.describe Term::Cursor do
4 | let(:cursor) { described_class }
5 |
6 | it "scrolls down by one line" do
7 | expect(cursor.scroll_down).to eq("\eD")
8 | end
9 |
10 | it "scrolls up by one line" do
11 | expect(cursor.scroll_up).to eq("\eM")
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/src/cursor/version.cr:
--------------------------------------------------------------------------------
1 | module Term
2 | module Cursor
3 | VERSION = "0.1.0"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/src/term-cursor.cr:
--------------------------------------------------------------------------------
1 | module Term
2 | module Cursor
3 | extend self
4 |
5 | ESC = "\e"
6 | CSI = "\e["
7 | DEC_RST = "l"
8 | DEC_SET = "h"
9 | DEC_TCEM = "?25"
10 |
11 | # Make cursor visible
12 | def show
13 | CSI + DEC_TCEM + DEC_SET
14 | end
15 |
16 | # Hide cursor
17 | def hide
18 | CSI + DEC_TCEM + DEC_RST
19 | end
20 |
21 | # Switch off cursor for the block
22 | def invisible(stream = STDOUT, &block)
23 | stream.print(hide)
24 | yield
25 | ensure
26 | stream.print(show)
27 | end
28 |
29 | # Save current position
30 | def save
31 | {% if flag?(:windows) %}
32 | CSI + "s"
33 | {% else %}
34 | ESC + "7"
35 | {% end %}
36 | end
37 |
38 | # Restore cursor position
39 | def restore
40 | {% if flag?(:windows) %}
41 | CSI + "u"
42 | {% else %}
43 | ESC + "8"
44 | {% end %}
45 | end
46 |
47 | # Query cursor current position
48 | def current
49 | CSI + "6n"
50 | end
51 |
52 | # Set the cursor absolute position
53 | def move_to(row : Int32? = nil, column : Int32? = nil) : String
54 | return CSI + "H" if row.nil? && column.nil?
55 | row = row.try(&.abs) || 0
56 | column = column.try(&.abs) || 0
57 | CSI + "#{column + 1};#{row + 1}H"
58 | end
59 |
60 | # Move cursor relative to its current position
61 | def move(x, y)
62 | (x < 0 ? backward(-x) : (x > 0 ? forward(x) : "")) +
63 | (y < 0 ? down(-y) : (y > 0 ? up(y) : ""))
64 | end
65 |
66 | # Move cursor up by n
67 | def up(n : Int32? = nil)
68 | CSI + "#{(n || 1)}A"
69 | end
70 |
71 | # ditto
72 | def cursor_up(n)
73 | up(n)
74 | end
75 |
76 | # Move the cursor down by n
77 | def down(n : Int32? = nil)
78 | CSI + "#{(n || 1)}B"
79 | end
80 |
81 | # ditto
82 | def cursor_down(n)
83 | down(n)
84 | end
85 |
86 | # Move the cursor backward by n
87 | def backward(n : Int32? = nil)
88 | CSI + "#{n || 1}D"
89 | end
90 |
91 | # ditto
92 | def cursor_backward(n)
93 | backward(n)
94 | end
95 |
96 | # Move the cursor forward by n
97 | def forward(n : Int32? = nil)
98 | CSI + "#{n || 1}C"
99 | end
100 |
101 | # ditto
102 | def cursor_forward(n)
103 | forward(n)
104 | end
105 |
106 | # Cursor moves to nth position horizontally in the current line
107 | def column(n : Int32? = nil)
108 | CSI + "#{n || 1}G"
109 | end
110 |
111 | # Cursor moves to the nth position vertically in the current column
112 | def row(n : Int32? = nil)
113 | CSI + "#{n || 1}d"
114 | end
115 |
116 | # Move cursor down to beginning of next line
117 | def next_line
118 | CSI + 'E' + column(1)
119 | end
120 |
121 | # Move cursor up to beginning of previous line
122 | def prev_line
123 | CSI + 'A' + column(1)
124 | end
125 |
126 | # Erase n characters from the current cursor position
127 | def clear_char(n : Int32? = nil)
128 | CSI + "#{n}X"
129 | end
130 |
131 | # Erase the entire current line and return to beginning of the line
132 | def clear_line
133 | CSI + "2K" + column(1)
134 | end
135 |
136 | # Erase from the beginning of the line up to and including
137 | # the current cursor position.
138 | def clear_line_before
139 | CSI + "1K"
140 | end
141 |
142 | # Erase from the current position (inclusive) to
143 | # the end of the line
144 | def clear_line_after
145 | CSI + "0K"
146 | end
147 |
148 | # Clear a number of lines
149 | def clear_lines(n, direction = :up)
150 | n.times.reduce([] of String) do |acc, i|
151 | dir = direction == :up ? up : down
152 | acc << clear_line + ((i == n - 1) ? "" : dir)
153 | end.join
154 | end
155 |
156 | # ditto
157 | def clear_rows(n, direction = :up)
158 | clear_lines(n, direction)
159 | end
160 |
161 | # Clear screen down from current position
162 | def clear_screen_down
163 | CSI + "J"
164 | end
165 |
166 | # Clear screen up from current position
167 | def clear_screen_up
168 | CSI + "1J"
169 | end
170 |
171 | # Clear the screen with the background colour and moves the cursor to home
172 | def clear_screen
173 | CSI + "2J"
174 | end
175 |
176 | # Scroll display up one line
177 | def scroll_up
178 | ESC + "M"
179 | end
180 |
181 | # Scroll display down one line
182 | def scroll_down
183 | ESC + "D"
184 | end
185 |
186 | # Get the current cursor position
187 | # Returns a tuple with {row, column}
188 | def get_position(input = STDIN, output = STDOUT) : {Int32, Int32}
189 | output.print current
190 | output.flush
191 | response = input.raw &.read_line
192 | if response =~ /\e\[(\d+);(\d+)R/
193 | {$1.to_i - 1, $2.to_i - 1}
194 | else
195 | {0, 0}
196 | end
197 | end
198 | end
199 | end
200 |
--------------------------------------------------------------------------------