Refactor: Merge Buffer into Window

Think it'll make scrolling a lot simpler.
This commit is contained in:
seem 2020-09-06 17:55:58 +02:00
parent e76e5a9785
commit 24a2492b8e
5 changed files with 214 additions and 234 deletions

View file

@ -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

View file

@ -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

View file

@ -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)]

View file

@ -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

View file

@ -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"]