Refactor: Merge Buffer into Window
Think it'll make scrolling a lot simpler.
This commit is contained in:
parent
e76e5a9785
commit
24a2492b8e
5 changed files with 214 additions and 234 deletions
|
|
@ -1,65 +0,0 @@
|
|||
import sys
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
class Buffer:
|
||||
MAX_CX = sys.maxsize
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
lines: Optional[Sequence[str]] = None,
|
||||
cx: int = 0,
|
||||
cy: int = 0,
|
||||
):
|
||||
self._lines = lines or []
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
|
||||
self._cx_hint = cx
|
||||
|
||||
def up(self) -> "Buffer":
|
||||
if self.cy > 0:
|
||||
self.cy -= 1
|
||||
self._set_cx_after_vertical_movement()
|
||||
return self
|
||||
|
||||
def down(self) -> "Buffer":
|
||||
if self.cy < len(self._lines) - 1:
|
||||
self.cy += 1
|
||||
self._set_cx_after_vertical_movement()
|
||||
return self
|
||||
|
||||
def _set_cx_after_vertical_movement(self) -> None:
|
||||
if self.cx > self._max_cx:
|
||||
# Cursor exceeded the line
|
||||
if self.cx > self._cx_hint:
|
||||
self._cx_hint = self.cx
|
||||
self.cx = max(self._max_cx, 0)
|
||||
else:
|
||||
self.cx = min(self._cx_hint, self._max_cx)
|
||||
|
||||
def left(self) -> "Buffer":
|
||||
if self.cx > 0:
|
||||
self.cx -= 1
|
||||
self._cx_hint = self.cx
|
||||
return self
|
||||
|
||||
def right(self) -> "Buffer":
|
||||
if self.cx < self._max_cx:
|
||||
self.cx += 1
|
||||
self._cx_hint = self.cx
|
||||
return self
|
||||
|
||||
def home(self) -> "Buffer":
|
||||
self.cx = self._cx_hint = 0
|
||||
return self
|
||||
|
||||
def end(self) -> "Buffer":
|
||||
self._cx_hint = self.MAX_CX
|
||||
self.cx = min(self._cx_hint, self._max_cx)
|
||||
return self
|
||||
|
||||
@property
|
||||
def _max_cx(self) -> int:
|
||||
return len(self._lines[self.cy]) - 1
|
||||
|
|
@ -3,7 +3,6 @@ import curses
|
|||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from .buf import Buffer
|
||||
from .window import Window
|
||||
|
||||
|
||||
|
|
@ -19,30 +18,30 @@ def c_main(stdscr: "curses._CursesWindow", filename: str) -> int:
|
|||
with open(filename) as f:
|
||||
lines = f.read().split("\n")
|
||||
|
||||
buf = Buffer(lines)
|
||||
window = Window(buf, curses.COLS, curses.LINES)
|
||||
win = Window(lines, curses.COLS, curses.LINES)
|
||||
|
||||
while True:
|
||||
# Update screen
|
||||
for y, line in enumerate(window.lines):
|
||||
for y, line in enumerate(lines[: curses.LINES]):
|
||||
stdscr.addstr(y, 0, line)
|
||||
stdscr.move(window.cy, window.cx)
|
||||
cx, cy = win.screen_cursor()
|
||||
stdscr.move(cy, cx)
|
||||
|
||||
# Handle keypresses
|
||||
c = stdscr.getkey()
|
||||
if c == "q":
|
||||
break
|
||||
elif c == "k":
|
||||
buf.up()
|
||||
win.up()
|
||||
elif c == "j":
|
||||
buf.down()
|
||||
win.down()
|
||||
elif c == "h":
|
||||
buf.left()
|
||||
win.left()
|
||||
elif c == "l":
|
||||
buf.right()
|
||||
win.right()
|
||||
elif c == "0":
|
||||
buf.home()
|
||||
win.home()
|
||||
elif c == "$":
|
||||
buf.end()
|
||||
win.end()
|
||||
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -1,31 +1,80 @@
|
|||
import sys
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from .buf import Buffer
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Window:
|
||||
MAX_CX = sys.maxsize
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
buf: Buffer,
|
||||
width: int,
|
||||
height: int,
|
||||
lines: Optional[Sequence[str]] = None,
|
||||
width: int = 0,
|
||||
height: int = 0,
|
||||
cx: int = 0,
|
||||
cy: int = 0,
|
||||
bx: int = 0,
|
||||
by: int = 0,
|
||||
):
|
||||
self._buf = buf
|
||||
self._lines = lines or []
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
self.bx = bx
|
||||
self.by = by
|
||||
|
||||
@property
|
||||
def cx(self) -> int:
|
||||
return self.bx + self._buf.cx
|
||||
self._cx_hint = cx
|
||||
|
||||
def up(self) -> "Window":
|
||||
if self.cy > 0:
|
||||
self.cy -= 1
|
||||
self._set_cx_after_vertical_movement()
|
||||
return self
|
||||
|
||||
def down(self) -> "Window":
|
||||
if self.cy < len(self._lines) - 1:
|
||||
self.cy += 1
|
||||
self._set_cx_after_vertical_movement()
|
||||
return self
|
||||
|
||||
def _set_cx_after_vertical_movement(self) -> None:
|
||||
if self.cx > self._max_cx:
|
||||
# Cursor exceeded the line
|
||||
if self.cx > self._cx_hint:
|
||||
self._cx_hint = self.cx
|
||||
self.cx = max(self._max_cx, 0)
|
||||
else:
|
||||
self.cx = min(self._cx_hint, self._max_cx)
|
||||
|
||||
def left(self) -> "Window":
|
||||
if self.cx > 0:
|
||||
self.cx -= 1
|
||||
self._cx_hint = self.cx
|
||||
return self
|
||||
|
||||
def right(self) -> "Window":
|
||||
if self.cx < self._max_cx:
|
||||
self.cx += 1
|
||||
self._cx_hint = self.cx
|
||||
return self
|
||||
|
||||
def home(self) -> "Window":
|
||||
self.cx = self._cx_hint = 0
|
||||
return self
|
||||
|
||||
def end(self) -> "Window":
|
||||
self._cx_hint = self.MAX_CX
|
||||
self.cx = min(self._cx_hint, self._max_cx)
|
||||
return self
|
||||
|
||||
@property
|
||||
def cy(self) -> int:
|
||||
return self.by + self._buf.cy
|
||||
def _max_cx(self) -> int:
|
||||
return len(self._lines[self.cy]) - 1
|
||||
|
||||
@property
|
||||
def lines(self) -> Sequence[str]:
|
||||
return self._buf._lines[-self.by : (self.height - self.by)]
|
||||
def screen_cursor(self) -> Tuple[int, int]:
|
||||
return (self.bx + self.cx, self.by + self.cy)
|
||||
|
||||
def screen_lines(self) -> Sequence[str]:
|
||||
return self._lines[-self.by : (self.height - self.by)]
|
||||
|
|
|
|||
|
|
@ -1,130 +0,0 @@
|
|||
from editor.main import Buffer
|
||||
|
||||
|
||||
def test_buffer_up():
|
||||
assert Buffer(["foo", "bar"], cy=1).up().cy == 0
|
||||
|
||||
|
||||
def test_buffer_up_at_first_line():
|
||||
assert Buffer(["foo"]).up().cy == 0
|
||||
|
||||
|
||||
def test_buffer_up_passed_shorter_line():
|
||||
# It correctly resets to a shorter and then longer line
|
||||
buf = Buffer(["longer line", "short", "long line"], cx=8, cy=2)
|
||||
buf.up()
|
||||
assert buf.cx == 4
|
||||
assert buf.cy == 1
|
||||
buf.up()
|
||||
assert buf.cx == 8
|
||||
assert buf.cy == 0
|
||||
|
||||
# It correctly resets to a shorter and then medium line
|
||||
buf = Buffer(["long line", "short", "longer line"], cx=10, cy=2)
|
||||
buf.up()
|
||||
assert buf.cx == 4
|
||||
assert buf.cy == 1
|
||||
buf.up()
|
||||
assert buf.cx == 8
|
||||
assert buf.cy == 0
|
||||
|
||||
# It correctly resets cx on empty lines
|
||||
buf = Buffer(["", "foo"], cx=4, cy=1)
|
||||
buf.up()
|
||||
assert buf.cx == 0
|
||||
assert buf.cy == 0
|
||||
|
||||
# It correctly resets cx on lines of the same length
|
||||
buf = Buffer(["short", "short"], cx=4, cy=1)
|
||||
buf.up()
|
||||
assert buf.cx == 4
|
||||
assert buf.cy == 0
|
||||
|
||||
# It correctly resets cx hint after horizontal movement
|
||||
buf = Buffer(["foo", "", "bar"], cy=2)
|
||||
buf.right().up().up()
|
||||
assert buf.cx == 1
|
||||
assert buf.cy == 0
|
||||
buf.left().down().down()
|
||||
assert buf.cx == 0
|
||||
assert buf.cy == 2
|
||||
|
||||
|
||||
def test_buffer_down():
|
||||
assert Buffer(["foo", "bar"]).down().cy == 1
|
||||
|
||||
|
||||
def test_buffer_down_passed_shorter_line():
|
||||
# It correctly resets to a shorter and then longer line
|
||||
buf = Buffer(["long line", "short", "longer line"], cx=8)
|
||||
buf.down()
|
||||
assert buf.cx == 4
|
||||
assert buf.cy == 1
|
||||
buf.down()
|
||||
assert buf.cx == 8
|
||||
assert buf.cy == 2
|
||||
|
||||
# It correctly resets to a shorter and then medium line
|
||||
buf = Buffer(["longer line", "short", "long line"], cx=10)
|
||||
buf.down()
|
||||
assert buf.cx == 4
|
||||
assert buf.cy == 1
|
||||
buf.down()
|
||||
assert buf.cx == 8
|
||||
assert buf.cy == 2
|
||||
|
||||
# It correctly resets cx on empty lines
|
||||
buf = Buffer(["foo", ""], cx=4)
|
||||
buf.down()
|
||||
assert buf.cx == 0
|
||||
assert buf.cy == 1
|
||||
|
||||
# It correctly resets cx on lines of the same length
|
||||
buf = Buffer(["short", "short"], cx=4)
|
||||
buf.down()
|
||||
assert buf.cx == 4
|
||||
assert buf.cy == 1
|
||||
|
||||
# It correctly resets cx hint after horizontal movement
|
||||
buf = Buffer(["foo", "", "bar"])
|
||||
buf.right().down().down()
|
||||
assert buf.cx == 1
|
||||
assert buf.cy == 2
|
||||
buf.left().up().up()
|
||||
assert buf.cx == 0
|
||||
assert buf.cy == 0
|
||||
|
||||
|
||||
def test_buffer_down_at_last_line():
|
||||
assert Buffer(["foo"]).down().cy == 0
|
||||
|
||||
|
||||
def test_buffer_left():
|
||||
assert Buffer(cx=1).left().cx == 0
|
||||
|
||||
|
||||
def test_buffer_left_at_first_char():
|
||||
assert Buffer().left().cx == 0
|
||||
|
||||
|
||||
def test_buffer_right():
|
||||
assert Buffer(["foo"]).right().cx == 1
|
||||
|
||||
|
||||
def test_buffer_right_at_last_char():
|
||||
assert Buffer(["foo"], cx=2).right().cx == 2
|
||||
|
||||
|
||||
def test_buffer_home():
|
||||
assert Buffer(["foo"], cx=2).home().cx == 0
|
||||
|
||||
|
||||
def test_buffer_end():
|
||||
assert Buffer(["foo"]).end().cx == 2
|
||||
|
||||
|
||||
def test_buffer_end_makes_vertical_movement_always_move_to_last_char():
|
||||
buf = Buffer(["short", "longer line", "long line"])
|
||||
assert buf.end().cx == 4
|
||||
assert buf.down().cx == 10
|
||||
assert buf.down().cx == 8
|
||||
|
|
@ -1,21 +1,148 @@
|
|||
from editor.buf import Buffer
|
||||
from editor.window import Window
|
||||
|
||||
|
||||
def test_window_cx_cy():
|
||||
buf = Buffer(["foo", "bar", "baz"], cx=2, cy=2)
|
||||
window = Window(buf, width=3, height=3, bx=-1, by=-1)
|
||||
assert window.cx == 1
|
||||
assert window.cy == 1
|
||||
def test_window_up():
|
||||
assert Window(["foo", "bar"], cy=1).up().cy == 0
|
||||
|
||||
|
||||
def test_window_lines_vertical_scroll():
|
||||
buf = Buffer(["foo", "bar", "baz"], cx=2, cy=2)
|
||||
window = Window(buf, width=3, height=3, by=-1)
|
||||
assert window.lines == ["bar", "baz"]
|
||||
def test_window_up_at_first_line():
|
||||
assert Window(["foo"]).up().cy == 0
|
||||
|
||||
|
||||
def test_window_lines_horizontal_scroll():
|
||||
buf = Buffer(["foo", "bar", "baz"], cx=2, cy=2)
|
||||
window = Window(buf, width=3, height=3, bx=-1)
|
||||
assert window.lines == ["ar", "az"]
|
||||
def test_window_up_passed_shorter_line():
|
||||
# It correctly resets to a shorter and then longer line
|
||||
win = Window(["longer line", "short", "long line"], cx=8, cy=2)
|
||||
win.up()
|
||||
assert win.cx == 4
|
||||
assert win.cy == 1
|
||||
win.up()
|
||||
assert win.cx == 8
|
||||
assert win.cy == 0
|
||||
|
||||
# It correctly resets to a shorter and then medium line
|
||||
win = Window(["long line", "short", "longer line"], cx=10, cy=2)
|
||||
win.up()
|
||||
assert win.cx == 4
|
||||
assert win.cy == 1
|
||||
win.up()
|
||||
assert win.cx == 8
|
||||
assert win.cy == 0
|
||||
|
||||
# It correctly resets cx on empty lines
|
||||
win = Window(["", "foo"], cx=4, cy=1)
|
||||
win.up()
|
||||
assert win.cx == 0
|
||||
assert win.cy == 0
|
||||
|
||||
# It correctly resets cx on lines of the same length
|
||||
win = Window(["short", "short"], cx=4, cy=1)
|
||||
win.up()
|
||||
assert win.cx == 4
|
||||
assert win.cy == 0
|
||||
|
||||
# It correctly resets cx hint after horizontal movement
|
||||
win = Window(["foo", "", "bar"], cy=2)
|
||||
win.right().up().up()
|
||||
assert win.cx == 1
|
||||
assert win.cy == 0
|
||||
win.left().down().down()
|
||||
assert win.cx == 0
|
||||
assert win.cy == 2
|
||||
|
||||
|
||||
def test_window_down():
|
||||
assert Window(["foo", "bar"]).down().cy == 1
|
||||
|
||||
|
||||
def test_window_down_passed_shorter_line():
|
||||
# It correctly resets to a shorter and then longer line
|
||||
win = Window(["long line", "short", "longer line"], cx=8)
|
||||
win.down()
|
||||
assert win.cx == 4
|
||||
assert win.cy == 1
|
||||
win.down()
|
||||
assert win.cx == 8
|
||||
assert win.cy == 2
|
||||
|
||||
# It correctly resets to a shorter and then medium line
|
||||
win = Window(["longer line", "short", "long line"], cx=10)
|
||||
win.down()
|
||||
assert win.cx == 4
|
||||
assert win.cy == 1
|
||||
win.down()
|
||||
assert win.cx == 8
|
||||
assert win.cy == 2
|
||||
|
||||
# It correctly resets cx on empty lines
|
||||
win = Window(["foo", ""], cx=4)
|
||||
win.down()
|
||||
assert win.cx == 0
|
||||
assert win.cy == 1
|
||||
|
||||
# It correctly resets cx on lines of the same length
|
||||
win = Window(["short", "short"], cx=4)
|
||||
win.down()
|
||||
assert win.cx == 4
|
||||
assert win.cy == 1
|
||||
|
||||
# It correctly resets cx hint after horizontal movement
|
||||
win = Window(["foo", "", "bar"])
|
||||
win.right().down().down()
|
||||
assert win.cx == 1
|
||||
assert win.cy == 2
|
||||
win.left().up().up()
|
||||
assert win.cx == 0
|
||||
assert win.cy == 0
|
||||
|
||||
|
||||
def test_window_down_at_last_line():
|
||||
assert Window(["foo"]).down().cy == 0
|
||||
|
||||
|
||||
def test_window_left():
|
||||
assert Window(cx=1).left().cx == 0
|
||||
|
||||
|
||||
def test_window_left_at_first_char():
|
||||
assert Window().left().cx == 0
|
||||
|
||||
|
||||
def test_window_right():
|
||||
assert Window(["foo"]).right().cx == 1
|
||||
|
||||
|
||||
def test_window_right_at_last_char():
|
||||
assert Window(["foo"], cx=2).right().cx == 2
|
||||
|
||||
|
||||
def test_window_home():
|
||||
assert Window(["foo"], cx=2).home().cx == 0
|
||||
|
||||
|
||||
def test_window_end():
|
||||
assert Window(["foo"]).end().cx == 2
|
||||
|
||||
|
||||
def test_window_end_makes_vertical_movement_always_move_to_last_char():
|
||||
win = Window(["short", "longer line", "long line"])
|
||||
assert win.end().cx == 4
|
||||
assert win.down().cx == 10
|
||||
assert win.down().cx == 8
|
||||
|
||||
|
||||
def test_window_screen_cursor():
|
||||
lines = ["foo", "bar", "baz"]
|
||||
window = Window(lines, bx=-1, by=-1, cx=2, cy=2)
|
||||
assert window.screen_cursor() == (1, 1)
|
||||
|
||||
|
||||
def test_window_screen_lines_vertical_scroll():
|
||||
lines = ["foo", "bar", "baz"]
|
||||
window = Window(lines, width=3, height=3, by=-1, cx=2, cy=2)
|
||||
assert window.screen_lines() == ["bar", "baz"]
|
||||
|
||||
|
||||
# def test_window_screen_lines_horizontal_scroll():
|
||||
# lines = ["foo", "bar", "baz"]
|
||||
# window = Window(lines, width=3, height=3, bx=-1, cx=2, cy=2)
|
||||
# assert window.screen_lines() == ["oo", "ar", "az"]
|
||||
|
|
|
|||
Loading…
Reference in a new issue