diff --git a/livesort_hssp/__init__.py b/livesort_hssp/__init__.py index 8d51c2b..bc01457 100644 --- a/livesort_hssp/__init__.py +++ b/livesort_hssp/__init__.py @@ -2,4 +2,4 @@ from hissp.reader import transpile transpile(__package__, "livesort", "test_livesort") del transpile -from livesort_hssp.livesort import livesort, render +from livesort_hssp.livesort import Livesort diff --git a/livesort_hssp/livesort.lissp b/livesort_hssp/livesort.lissp index b06c6b4..1ff147e 100644 --- a/livesort_hssp/livesort.lissp +++ b/livesort_hssp/livesort.lissp @@ -1,44 +1,72 @@ (hissp.basic.._macro_.prelude) -(defmacro setq (symbol value) - "lol this isn't even my lisp" - `(setitem (locals) (quote ,symbol) ,value)) -(define livesort - (lambda (next-line ; iterable function - print ; function to write output - height ; window/screen height in lines - width ; window/screen width in characters - ) - (let (lines (list) ; sorted lines - cursor 0 ; line number to keep on screen and highlight - quitting? False ; set flag to exit loop - ) - ;; the cursor is thought to exist between line `cursor` and `cursor - 1` - (any-for line next-line - (let (lineno (bisect..bisect lines line)) - (unless (gt lineno cursor) - (setq cursor (add cursor 1))) - (.insert lines lineno line) - (print (.format "c{}" (render lines cursor height width)))) - quitting?)))) +(deftype Livesort () + __init__ + (lambda (self + next-line ; iterable function yielding lines of text + print ; function to write output + height ; window/screen height in lines + width ; window/screen width in characters + ) + (attach self next-line print height width : + lines (list) ; sorted input lines + screen (list (mul height '(None)))) + None) ; __init__ required to return `None` -(define render - (lambda (lines ; sequence of sorted lines - cursor ; line to keep on-screen - height ; window height - width ; and width - ) - (.join "\n" - (map (lambda (line) (getitem line (slice 0 width))) - (getitem lines - (let (r (ifloordiv height 2)) - (slice (max 0 (sub cursor r)) - (min (len lines) (add cursor r))))))))) + sortline + (lambda (self line) + "Insort a line and update the cursor position" + (when (.strip line) + (let (lineno (bisect..bisect self.lines line)) + ;; Normally would use `..insort` but there's going to be more + (.insert self.lines lineno line)))) + render + (lambda (self) + "Produce a list of the lines which should be visible, cropped to `width`" + (list (map (lambda (line) (getitem line (slice None self.width))) + (getitem self.lines (slice None self.height))))) + + display + (lambda (self) + "Compare `(self.render)` to `self.screen` and return a string that +will update the display." + (let (output (list) + lines (self.render) + changed? (list)) + (.append output "") ; Home cursor + (any-for lineno (range self.height) + (let (line (if-else (lt lineno (len lines)) + (getitem lines lineno) + "")) + (unless (eq line (getitem self.screen lineno)) + (setitem self.screen lineno line) + (.append changed? line) + ;; ^[[K to erase any characters that follow on this line + (.append output (.format "{}" + (.rstrip line (chr 0x0a))))) + (.append output (chr 0x0a))) ; Move to beginning of next line + False) + ;; If something changed, don't bother outputting the last cursor + ;; movement code. If nothing changed, don't bother outputting + ;; anything. + (if-else changed? + (.join "" (getitem output (slice None -1))) + ""))) + + livesort + (lambda (self) + "Process every input line, updating screen each line" + (any-for line self.next-line + (.sortline self line) + (.print self (.display self)) + False))) (when (eq __name__ '__main__) - (livesort sys..stdin - sys..stdout.write - (int (|| (os..getenv 'LINES) "24")) - (int (|| (os..getenv 'COLUMNS) "80")))) + (.livesort (Livesort sys..stdin + (lambda (data) + (sys..stdout.write data) + (sys..stdout.flush)) + (int (|| (os..getenv 'LINES) "24")) + (int (|| (os..getenv 'COLUMNS) "80"))))) diff --git a/livesort_hssp/test_livesort.lissp b/livesort_hssp/test_livesort.lissp index e88375d..1cdd8e8 100644 --- a/livesort_hssp/test_livesort.lissp +++ b/livesort_hssp/test_livesort.lissp @@ -1,8 +1,63 @@ (hissp.basic.._macro_.prelude) (deftype TestLivesort (unittest..TestCase) + setUp + (lambda (self) + (attach self : + input-buf (list) + output-buf (list) + ls (livesort_hssp.livesort..Livesort self.input-buf + self.output-buf.append + 4 + 8))) + + tearDown + (lambda (self) + (delattr self 'ls) + (delattr self 'output-buf) + (delattr self 'input-buf)) + + test_Livesort + (lambda (self) + (cascade self + (.assertEqual self.ls.next-line self.input-buf) + (.assertEqual self.ls.print self.output-buf.append) + (.assertEqual self.ls.height 4) + (.assertEqual self.ls.width 8) + (.assertFalse self.ls.lines) + (.assertTrue self.ls.screen)) + (any-for line self.ls.screen + (not (self.assertEqual None line)))) + + test_sortline + (lambda (self) + (self.assertFalse self.ls.lines) + (self.ls.sortline "line 2") + (self.assertEqual self.ls.lines (list '(.#"line 2"))) + (self.ls.sortline "line 1") + (self.assertEqual self.ls.lines (list '(.#"line 1" .#"line 2")))) + test_render (lambda (self) - (.assertEqual self "----" (livesort_hssp..render '(.#"-----") 0 4 4)))) + (self.ls.sortline "line 1") + (self.assertEqual (self.ls.render) + (list '(.#"line 1")))) -(when (eq __name__ '__main__) (hissp.reader..transpile __package__ 'test_livesort)) + test_display + (lambda (self) + ;; I got fed up trying to write this sensibly, will go away after refacor + (self.assertEqual " + + +" (self.ls.display)) + (self.ls.sortline "line 1") + (self.assertEqual "line 1 + + +" (self.ls.display))) + + test_livesort ; note (unfortunate) capitalization + (lambda (self) + (self.input-buf.append "line 1") + (self.ls.livesort) + _#(self.assertEqual self.output-buf (list '(.#"line 1")))))