├── .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 | term logo 3 |
4 | 5 | # Term::Cursor 6 | 7 | ![spec status](https://github.com/crystal-term/cursor/workflows/specs/badge.svg) 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 | --------------------------------------------------------------------------------