feat(stacks): aksi stack klikable (u/s/R/p/D) + konfirmasi turunkan — plus fix zona tombol & emulator DECSTBM/IL/DL
CI / test (push) Successful in 26s
CI / test (push) Successful in 26s
This commit is contained in:
1 parent
f57d1cc29c
commit
1b9a406aa7
6 files changed
+410
-24
No files matched your search
+120
-8
@@ -27,6 +27,8 @@ type screen struct {
|
||||
savR, savC int
|
||||
bg bool // status SGR latar saat ini
|
||||
|
||||
mTop, mBot int // scroll region (DECSTBM), 0-based inklusif
|
||||
|
||||
st int // 0 normal · 1 esc · 2 esc-intern · 3 csi · 4 osc · 5 osc-esc
|
||||
seq []byte
|
||||
|
||||
@@ -50,7 +52,7 @@ func newScreen(rows, cols int) *screen {
|
||||
g[i][j] = cell{r: ' '}
|
||||
}
|
||||
}
|
||||
return &screen{rows: rows, cols: cols, grid: g}
|
||||
return &screen{rows: rows, cols: cols, grid: g, mBot: rows - 1}
|
||||
}
|
||||
|
||||
func (s *screen) write(b []byte) {
|
||||
@@ -63,9 +65,12 @@ func (s *screen) write(b []byte) {
|
||||
case c == '\r':
|
||||
s.curC = 0
|
||||
case c == '\n':
|
||||
s.curR++
|
||||
if s.curR >= s.mBot {
|
||||
s.scrollUp(1) // menggulir dalam scroll region (seperti terminal asli)
|
||||
} else {
|
||||
s.curR++
|
||||
}
|
||||
s.curC = 0
|
||||
s.clampCursor()
|
||||
case c == '\b':
|
||||
if s.curC > 0 {
|
||||
s.curC--
|
||||
@@ -139,9 +144,12 @@ func (s *screen) applySimple(c rune) {
|
||||
case 'D': // index (turun 1 baris)
|
||||
s.curR++
|
||||
s.clampCursor()
|
||||
case 'M': // reverse index (naik 1 baris)
|
||||
s.curR--
|
||||
s.clampCursor()
|
||||
case 'M': // reverse index (naik 1 baris; di batas atas region → scroll turun)
|
||||
if s.curR <= s.mTop {
|
||||
s.scrollDown(1)
|
||||
} else {
|
||||
s.curR--
|
||||
}
|
||||
case 'E': // baris berikutnya, kolom 0
|
||||
s.curR++
|
||||
s.curC = 0
|
||||
@@ -210,16 +218,52 @@ func (s *screen) applyCSI(final rune, params string) {
|
||||
}
|
||||
case 'K':
|
||||
s.eraseLine(at(nums, 0, 0))
|
||||
case 'X': // erase n karakter (kursor tidak pindah)
|
||||
case 'X': // erase n karakter (kursor tidak pindah) — ikut latar aktif (BCE)
|
||||
n := at(nums, 0, 1)
|
||||
for i := 0; i < n && s.curC+i < s.cols; i++ {
|
||||
s.grid[s.curR][s.curC+i] = cell{r: ' '}
|
||||
s.grid[s.curR][s.curC+i] = cell{r: ' ', bg: s.bg}
|
||||
}
|
||||
case 'L': // insert n baris (dipakai renderer saat insertAbove); bukan urutan mouse
|
||||
if priv == 0 && s.curR >= s.mTop && s.curR <= s.mBot {
|
||||
s.insertLines(at(nums, 0, 1))
|
||||
}
|
||||
case 'M': // delete n baris; ESC[<…M adalah KLIK MOUSE (SGR) — jangan tertukar
|
||||
if priv == 0 && s.curR >= s.mTop && s.curR <= s.mBot {
|
||||
s.deleteLines(at(nums, 0, 1))
|
||||
}
|
||||
case 'S': // scroll layar naik n baris
|
||||
if priv == 0 {
|
||||
s.scrollUp(at(nums, 0, 1))
|
||||
}
|
||||
case 'T': // scroll layar turun n baris
|
||||
if priv == 0 {
|
||||
s.scrollDown(at(nums, 0, 1))
|
||||
}
|
||||
case 'r': // DECSTBM — set scroll region (dipakai renderer saat menggeser layar)
|
||||
if priv == 0 {
|
||||
top := at(nums, 0, 1) - 1
|
||||
bot := at(nums, 1, s.rows) - 1
|
||||
if top < 0 {
|
||||
top = 0
|
||||
}
|
||||
if bot >= s.rows {
|
||||
bot = s.rows - 1
|
||||
}
|
||||
if bot > top {
|
||||
s.mTop, s.mBot = top, bot
|
||||
} else {
|
||||
s.mTop, s.mBot = 0, s.rows-1
|
||||
}
|
||||
s.curR, s.curC = 0, 0 // spesifikasi: kursor ke home setelah set margin
|
||||
}
|
||||
case 'h':
|
||||
if priv == '?' && at(nums, 0, 0) == 1049 { // masuk alt-screen
|
||||
s.clearAll()
|
||||
}
|
||||
case 'm':
|
||||
if priv == '<' {
|
||||
return // ESC[<…m = release klik mouse (SGR), bukan SGR gaya
|
||||
}
|
||||
// Lacak SGR latar: 48 (set bg) · 0/49 (reset). Dipakai sel & Highlighted().
|
||||
if len(nums) == 0 {
|
||||
s.bg = false
|
||||
@@ -280,6 +324,74 @@ func (s *screen) clearAll() {
|
||||
}
|
||||
}
|
||||
|
||||
// insertLines — sisipkan n baris kosong di baris kursor DALAM scroll region;
|
||||
// baris di bawahnya bergeser turun (kelebihan di batas bawah terbuang).
|
||||
func (s *screen) insertLines(n int) {
|
||||
if n <= 0 || n > s.mBot-s.curR+1 {
|
||||
n = s.mBot - s.curR + 1
|
||||
}
|
||||
for r := s.mBot; r >= s.curR+n; r-- {
|
||||
copy(s.grid[r], s.grid[r-n])
|
||||
}
|
||||
for r := s.curR; r < s.curR+n && r <= s.mBot; r++ {
|
||||
for c := range s.grid[r] {
|
||||
s.grid[r][c] = cell{r: ' '}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteLines — hapus n baris mulai baris kursor DALAM scroll region.
|
||||
func (s *screen) deleteLines(n int) {
|
||||
if n <= 0 || n > s.mBot-s.curR+1 {
|
||||
n = s.mBot - s.curR + 1
|
||||
}
|
||||
for r := s.curR; r <= s.mBot; r++ {
|
||||
if r+n <= s.mBot {
|
||||
copy(s.grid[r], s.grid[r+n])
|
||||
} else {
|
||||
for c := range s.grid[r] {
|
||||
s.grid[r][c] = cell{r: ' '}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scrollUp — gulir scroll region naik n baris (baris bawah region jadi kosong).
|
||||
func (s *screen) scrollUp(n int) {
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
if n > s.mBot-s.mTop+1 {
|
||||
n = s.mBot - s.mTop + 1
|
||||
}
|
||||
for r := s.mTop; r <= s.mBot-n; r++ {
|
||||
copy(s.grid[r], s.grid[r+n])
|
||||
}
|
||||
for r := s.mBot - n + 1; r <= s.mBot; r++ {
|
||||
for c := range s.grid[r] {
|
||||
s.grid[r][c] = cell{r: ' '}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scrollDown — gulir scroll region turun n baris (baris atas region jadi kosong).
|
||||
func (s *screen) scrollDown(n int) {
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
if n > s.mBot-s.mTop+1 {
|
||||
n = s.mBot - s.mTop + 1
|
||||
}
|
||||
for r := s.mBot; r >= s.mTop+n; r-- {
|
||||
copy(s.grid[r], s.grid[r-n])
|
||||
}
|
||||
for r := s.mTop; r < s.mTop+n; r++ {
|
||||
for c := range s.grid[r] {
|
||||
s.grid[r][c] = cell{r: ' '}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String — snapshot layar (spasi kanan dipangkas per baris).
|
||||
func (s *screen) String() string {
|
||||
lines := make([]string, s.rows)
|
||||
|
||||
Reference in new issue
Block a user