|
1 | 1 | package cursor |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "fmt" |
5 | 4 | "os" |
6 | | - "runtime" |
7 | 5 | "strings" |
8 | 6 | ) |
9 | 7 |
|
10 | 8 | // Area displays content which can be updated on the fly. |
11 | 9 | // You can use this to create live output, charts, dropdowns, etc. |
12 | 10 | type Area struct { |
13 | | - height int |
14 | | - writer Writer |
| 11 | + height int |
| 12 | + writer Writer |
| 13 | + cursor *Cursor |
| 14 | + cursorPosY int |
15 | 15 | } |
16 | 16 |
|
17 | 17 | // NewArea returns a new Area. |
18 | 18 | func NewArea() Area { |
19 | 19 | return Area{ |
20 | | - writer: os.Stdout, |
21 | | - height: 0, |
| 20 | + height: 0, |
| 21 | + writer: os.Stdout, |
| 22 | + cursor: cursor, |
| 23 | + cursorPosY: 0, |
22 | 24 | } |
23 | 25 | } |
24 | 26 |
|
25 | | -// WithWriter sets a custom writer for the Area. |
| 27 | +// WithWriter sets the custom writer. |
26 | 28 | func (area Area) WithWriter(writer Writer) Area { |
27 | 29 | area.writer = writer |
| 30 | + area.cursor = area.cursor.WithWriter(writer) |
28 | 31 |
|
29 | 32 | return area |
30 | 33 | } |
31 | 34 |
|
32 | 35 | // Clear clears the content of the Area. |
33 | 36 | func (area *Area) Clear() { |
34 | | - Bottom() |
| 37 | + // Initialize writer if not done yet |
| 38 | + if area.writer == nil { |
| 39 | + area.writer = os.Stdout |
| 40 | + } |
35 | 41 |
|
36 | 42 | if area.height > 0 { |
37 | | - ClearLinesUp(area.height) |
| 43 | + area.Bottom() |
| 44 | + area.ClearLinesUp(area.height) |
| 45 | + area.StartOfLine() |
| 46 | + } else { |
| 47 | + area.StartOfLine() |
| 48 | + area.cursor.ClearLine() |
38 | 49 | } |
39 | 50 | } |
40 | 51 |
|
41 | | -// Update overwrites the content of the Area. |
| 52 | +// Update overwrites the content of the Area and adjusts its height based on content. |
42 | 53 | func (area *Area) Update(content string) { |
43 | | - oldWriter := target |
44 | | - |
45 | | - SetTarget(area.writer) // Temporary set the target to the Area's writer so we can use the cursor functions |
46 | 54 | area.Clear() |
| 55 | + area.writeArea(content) |
| 56 | + area.cursorPosY = 0 |
| 57 | + area.height = strings.Count(content, "\n") |
| 58 | +} |
47 | 59 |
|
48 | | - lines := strings.Split(content, "\n") |
49 | | - fmt.Fprintln(area.writer, strings.Repeat("\n", len(lines)-1)) // This appends space if the terminal is at the bottom |
50 | | - Up(len(lines)) |
51 | | - SetTarget(oldWriter) // Reset the target to the old writer |
52 | | - |
53 | | - // Workaround for buggy behavior on Windows |
54 | | - if runtime.GOOS == "windows" { |
55 | | - for _, line := range lines { |
56 | | - fmt.Fprint(area.writer, line) |
57 | | - StartOfLineDown(1) |
| 60 | +// Up moves the cursor of the area up one line. |
| 61 | +func (area *Area) Up(n int) { |
| 62 | + if n > 0 { |
| 63 | + if area.cursorPosY+n > area.height { |
| 64 | + n = area.height - area.cursorPosY |
58 | 65 | } |
59 | | - } else { |
60 | | - for _, line := range lines { |
61 | | - fmt.Fprintln(area.writer, line) |
| 66 | + |
| 67 | + area.cursor.Up(n) |
| 68 | + area.cursorPosY += n |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +// Down moves the cursor of the area down one line. |
| 73 | +func (area *Area) Down(n int) { |
| 74 | + if n > 0 { |
| 75 | + if area.cursorPosY-n < 0 { |
| 76 | + n = area.height - area.cursorPosY |
62 | 77 | } |
| 78 | + |
| 79 | + area.cursor.Down(n) |
| 80 | + area.cursorPosY -= n |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +// Bottom moves the cursor to the bottom of the terminal. |
| 85 | +// This is done by calculating how many lines were moved by Up and Down. |
| 86 | +func (area *Area) Bottom() { |
| 87 | + if area.cursorPosY > 0 { |
| 88 | + area.Down(area.cursorPosY) |
| 89 | + area.cursorPosY = 0 |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +// Top moves the cursor to the top of the area. |
| 94 | +// This is done by calculating how many lines were moved by Up and Down. |
| 95 | +func (area *Area) Top() { |
| 96 | + if area.cursorPosY < area.height { |
| 97 | + area.Up(area.height - area.cursorPosY) |
| 98 | + area.cursorPosY = area.height |
63 | 99 | } |
| 100 | +} |
| 101 | + |
| 102 | +// StartOfLine moves the cursor to the start of the current line. |
| 103 | +func (area *Area) StartOfLine() { |
| 104 | + area.cursor.HorizontalAbsolute(0) |
| 105 | +} |
64 | 106 |
|
65 | | - height = 0 |
66 | | - area.height = len(strings.Split(content, "\n")) |
| 107 | +// StartOfLineDown moves the cursor down by n lines, then moves to cursor to the start of the line. |
| 108 | +func (area *Area) StartOfLineDown(n int) { |
| 109 | + area.Down(n) |
| 110 | + area.StartOfLine() |
| 111 | +} |
| 112 | + |
| 113 | +// StartOfLineUp moves the cursor up by n lines, then moves to cursor to the start of the line. |
| 114 | +func (area *Area) StartOfLineUp(n int) { |
| 115 | + area.Up(n) |
| 116 | + area.StartOfLine() |
| 117 | +} |
| 118 | + |
| 119 | +// UpAndClear moves the cursor up by n lines, then clears the line. |
| 120 | +func (area *Area) UpAndClear(n int) { |
| 121 | + area.Up(n) |
| 122 | + area.cursor.ClearLine() |
| 123 | +} |
| 124 | + |
| 125 | +// DownAndClear moves the cursor down by n lines, then clears the line. |
| 126 | +func (area *Area) DownAndClear(n int) { |
| 127 | + area.Down(n) |
| 128 | + area.cursor.ClearLine() |
| 129 | +} |
| 130 | + |
| 131 | +// Move moves the cursor relative by x and y. |
| 132 | +func (area *Area) Move(x, y int) { |
| 133 | + if x > 0 { |
| 134 | + area.cursor.Right(x) |
| 135 | + } else if x < 0 { |
| 136 | + area.cursor.Left(-x) |
| 137 | + } |
| 138 | + |
| 139 | + if y > 0 { |
| 140 | + area.Up(y) |
| 141 | + } else if y < 0 { |
| 142 | + area.Down(-y) |
| 143 | + } |
| 144 | +} |
| 145 | + |
| 146 | +// ClearLinesUp clears n lines upwards from the current position and moves the cursor. |
| 147 | +func (area *Area) ClearLinesUp(n int) { |
| 148 | + area.StartOfLine() |
| 149 | + area.cursor.ClearLine() |
| 150 | + |
| 151 | + for i := 0; i < n; i++ { |
| 152 | + area.UpAndClear(1) |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +// ClearLinesDown clears n lines downwards from the current position and moves the cursor. |
| 157 | +func (area *Area) ClearLinesDown(n int) { |
| 158 | + area.StartOfLine() |
| 159 | + area.cursor.ClearLine() |
| 160 | + |
| 161 | + for i := 0; i < n; i++ { |
| 162 | + area.DownAndClear(1) |
| 163 | + } |
67 | 164 | } |
0 commit comments