From 61ac3a765dea399fcb422aea9212ad8055040b9b Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 20:33:41 -0500 Subject: [PATCH 001/152] Rename Size to Tuple --- README.markdown | 2 +- examples/from_readme/readme.rb | 2 +- examples/menu/menu.rb | 2 +- lib/remedy/console.rb | 2 +- lib/remedy/partial.rb | 2 +- lib/remedy/{size.rb => tuple.rb} | 38 ++++++++++++++++---------------- lib/remedy/viewport.rb | 4 ++-- 7 files changed, 26 insertions(+), 26 deletions(-) rename lib/remedy/{size.rb => tuple.rb} (66%) diff --git a/README.markdown b/README.markdown index 3eea5c6..5063c57 100644 --- a/README.markdown +++ b/README.markdown @@ -85,7 +85,7 @@ Content in `Remedy::Partial`s will be truncated as needed to accommodate the hea disclaimer << "According to a survey they were funny. I didn't make them." screen = Viewport.new - screen.draw jokes, Size.new(0,0), title, disclaimer + screen.draw jokes, Tuple.zero, title, disclaimer ``` ### Console diff --git a/examples/from_readme/readme.rb b/examples/from_readme/readme.rb index 70aca02..dc4cf5b 100644 --- a/examples/from_readme/readme.rb +++ b/examples/from_readme/readme.rb @@ -34,7 +34,7 @@ disclaimer = Partial.new disclaimer << "According to a survey they were funny. I didn't make them." -screen.draw jokes, Size.new(0,0), title, disclaimer +screen.draw jokes, Tuple.zero, title, disclaimer user_input.get_key diff --git a/examples/menu/menu.rb b/examples/menu/menu.rb index 8412691..7fed8c8 100644 --- a/examples/menu/menu.rb +++ b/examples/menu/menu.rb @@ -34,7 +34,7 @@ def listen # this tells the Viewport to draw to the screen def draw - @viewport.draw content, Size.zero, header, footer + @viewport.draw content, Tuple.zero, header, footer end # this is the body of our menu, it will be squished if the terminal is too small diff --git a/lib/remedy/console.rb b/lib/remedy/console.rb index 25cae6a..4470da4 100644 --- a/lib/remedy/console.rb +++ b/lib/remedy/console.rb @@ -54,7 +54,7 @@ def rows def size str = [0, 0, 0, 0].pack('SSSS') if input.ioctl(TIOCGWINSZ, str) >= 0 then - Size.new str.unpack('SSSS').first 2 + Tuple.new str.unpack('SSSS').first 2 else raise UnknownConsoleSize, "Unable to get console size" end diff --git a/lib/remedy/partial.rb b/lib/remedy/partial.rb index 5e6e0ff..4230ccd 100644 --- a/lib/remedy/partial.rb +++ b/lib/remedy/partial.rb @@ -37,7 +37,7 @@ def width end def size - Size.new height, width + Tuple.new height, width end def to_a diff --git a/lib/remedy/size.rb b/lib/remedy/tuple.rb similarity index 66% rename from lib/remedy/size.rb rename to lib/remedy/tuple.rb index fc2fc32..e2fa4ca 100644 --- a/lib/remedy/size.rb +++ b/lib/remedy/tuple.rb @@ -1,5 +1,5 @@ module Remedy - class Size + class Tuple def initialize *new_dimensions new_dimensions.flatten! if new_dimensions.first.is_a? Range then @@ -15,11 +15,11 @@ def self.zero self.new([0,0]) end - def - other_size - if other_size.respond_to? :length then - self.class.new subtract(other_size) + def - other_tuple + if other_tuple.respond_to? :length then + self.class.new subtract(other_tuple) else - self.class.new deduct(other_size) + self.class.new deduct(other_tuple) end end @@ -34,10 +34,10 @@ def << value end - def fits_into? size_to_fit_into - other_size = Size(size_to_fit_into) + def fits_into? tuple_to_fit_into + other_tuple = Tuple(tuple_to_fit_into) length.times.each do |index| - return false if self[index] > other_size[index] + return false if self[index] > other_tuple[index] end true end @@ -85,25 +85,25 @@ def deduct amount end end - def subtract other_size - sizesame? other_size + def subtract other_tuple + tuplesame? other_tuple - length.times.inject Size.new do |difference, index| - difference << self[index] - other_size[index] + length.times.inject Tuple.new do |difference, index| + difference << self[index] - other_tuple[index] end end - def sizesame? other_size - raise "Different numbers of dimensions!" unless length == other_size.length + def tuplesame? other_tuple + raise "Different numbers of dimensions!" unless length == other_tuple.length end end end -def Size *sizeable - sizeable.flatten! - if sizeable.first.is_a? Remedy::Size then - sizeable +def Tuple *tupleable + tupleable.flatten! + if tupleable.first.is_a? Remedy::Tuple then + tupleable else - Remedy::Size.new sizeable + Remedy::Tuple.new tupleable end end diff --git a/lib/remedy/viewport.rb b/lib/remedy/viewport.rb index 4ef2832..db514a1 100644 --- a/lib/remedy/viewport.rb +++ b/lib/remedy/viewport.rb @@ -5,7 +5,7 @@ module Remedy class Viewport - def draw content, scroll = Size.zero, header = Partial.new, footer = Partial.new + def draw content, scroll = Tuple.zero, header = Partial.new, footer = Partial.new range = range_find content, scroll, available_space(header,footer) viewable_content = content.excerpt *range @@ -31,7 +31,7 @@ def range_find partial, scroll, available_heightwidth # This determines the maximum amount of room left available for Content # after taking into consideration the height of the Header and Footer def available_space header, footer - trim = Size [header.height + footer.height, 0] + trim = Tuple [header.height + footer.height, 0] size - trim end From 2a1ff85113a8971f6bb7e57d2c313e69f225a049 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 20:40:17 -0500 Subject: [PATCH 002/152] Document and clean up the Tuple class --- lib/remedy/tuple.rb | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index e2fa4ca..f840937 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -1,5 +1,9 @@ module Remedy class Tuple + # Formerly known as "Remedy::Size", with related concepts in my other projects + # called things like "Coordinate", "Pair", or similar + # Used primarily to contain dimensional numeric values such as the sizes of screen areas, + # offsets in two or more dimensions, etc def initialize *new_dimensions new_dimensions.flatten! if new_dimensions.first.is_a? Range then @@ -15,6 +19,8 @@ def self.zero self.new([0,0]) end + # OPERATIONS + def - other_tuple if other_tuple.respond_to? :length then self.class.new subtract(other_tuple) @@ -33,6 +39,11 @@ def << value self.dimensions << value end + # COMPARISON + + def bijective? other_tuple + length == other_tuple.length + end def fits_into? tuple_to_fit_into other_tuple = Tuple(tuple_to_fit_into) @@ -42,24 +53,33 @@ def fits_into? tuple_to_fit_into true end - def rows + # ACCESSORS + + def x dimensions[0] end - alias_method :height, :rows + alias_method :height, :x - def cols + def y dimensions[1] end - alias_method :columns, :cols - alias_method :width, :cols + alias_method :width, :y + + def z + dimensions[2] + end + alias_method :depth, :z def [] index dimensions[index] end - def length + def cardinality dimensions.length end + alias_method :length, :cardinality + + # CONVERSIONS def to_a dimensions.dup @@ -86,24 +106,21 @@ def deduct amount end def subtract other_tuple - tuplesame? other_tuple + raise "Different numbers of dimensions!" unless bijective? other_tuple length.times.inject Tuple.new do |difference, index| difference << self[index] - other_tuple[index] end end - - def tuplesame? other_tuple - raise "Different numbers of dimensions!" unless length == other_tuple.length - end end end def Tuple *tupleable + klass = ::Remedy::Tuple tupleable.flatten! - if tupleable.first.is_a? Remedy::Tuple then + if tupleable.first.is_a? klass then tupleable else - Remedy::Tuple.new tupleable + klass.new tupleable end end From fa8e1d42147c766df8d65c67329bc4d7bd02154b Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 20:41:57 -0500 Subject: [PATCH 003/152] Use a new reference instead of mutating the original object --- lib/remedy/tuple.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index f840937..a7f6aac 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -5,13 +5,13 @@ class Tuple # Used primarily to contain dimensional numeric values such as the sizes of screen areas, # offsets in two or more dimensions, etc def initialize *new_dimensions - new_dimensions.flatten! - if new_dimensions.first.is_a? Range then - new_dimensions.map! do |range| - range.to_a.length + dims = new_dimensions.flatten + if dims.first.is_a? Range then + dims.map! do |range| + range.end end end - @dimensions = new_dimensions + @dimensions = dims end attr_accessor :dimensions From 0aaddeca81eab666b2071868c7c09bb18d3db04f Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 20:45:58 -0500 Subject: [PATCH 004/152] Clean up uses of length to better express what it is doing --- lib/remedy/tuple.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index a7f6aac..9399694 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -41,13 +41,16 @@ def << value # COMPARISON + # Determines if the two tuples have the same number of dimensions + # uses `length` on the other object so it can be used in comparison with more types def bijective? other_tuple - length == other_tuple.length + cardinality == other_tuple.length end + alias_method :sizesame?, :bijective? def fits_into? tuple_to_fit_into other_tuple = Tuple(tuple_to_fit_into) - length.times.each do |index| + cardinality.times.each do |index| return false if self[index] > other_tuple[index] end true @@ -108,7 +111,7 @@ def deduct amount def subtract other_tuple raise "Different numbers of dimensions!" unless bijective? other_tuple - length.times.inject Tuple.new do |difference, index| + cardinality.times.inject Tuple.new do |difference, index| difference << self[index] - other_tuple[index] end end From 3e8b2fe14758889f4f4c63a625298aaae1f701ff Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 20:51:32 -0500 Subject: [PATCH 005/152] Tuple clean up --- lib/remedy/tuple.rb | 22 +++++++++++----------- lib/remedy/viewport.rb | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index 9399694..805fcbd 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -1,9 +1,9 @@ module Remedy - class Tuple # Formerly known as "Remedy::Size", with related concepts in my other projects # called things like "Coordinate", "Pair", or similar # Used primarily to contain dimensional numeric values such as the sizes of screen areas, # offsets in two or more dimensions, etc + class Tuple def initialize *new_dimensions dims = new_dimensions.flatten if dims.first.is_a? Range then @@ -41,21 +41,21 @@ def << value # COMPARISON - # Determines if the two tuples have the same number of dimensions - # uses `length` on the other object so it can be used in comparison with more types - def bijective? other_tuple - cardinality == other_tuple.length - end - alias_method :sizesame?, :bijective? - - def fits_into? tuple_to_fit_into - other_tuple = Tuple(tuple_to_fit_into) + def fits_into? size_to_fit_into + other_tuple = Tuple(size_to_fit_into) cardinality.times.each do |index| return false if self[index] > other_tuple[index] end true end + # Determines if the two tuples have the same number of dimensions + # uses `length` on the other object so it can be used in comparison with more types + def bijective? other_tuple + cardinality == other_tuple.length + end + alias_method :sizesame?, :bijective? + # ACCESSORS def x @@ -111,7 +111,7 @@ def deduct amount def subtract other_tuple raise "Different numbers of dimensions!" unless bijective? other_tuple - cardinality.times.inject Tuple.new do |difference, index| + cardinality.times.inject self.class.new do |difference, index| difference << self[index] - other_tuple[index] end end diff --git a/lib/remedy/viewport.rb b/lib/remedy/viewport.rb index db514a1..22735fe 100644 --- a/lib/remedy/viewport.rb +++ b/lib/remedy/viewport.rb @@ -1,5 +1,5 @@ require 'remedy/view' -require 'remedy/size' +require 'remedy/tuple' require 'remedy/console' require 'remedy/ansi' From a50f24abc788b66c089cf0cdb2f0f09c024bc3b0 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 21:20:07 -0500 Subject: [PATCH 006/152] Splitting Viewport into Pane --- examples/menu/menu.rb | 6 +++- lib/remedy/pane.rb | 81 ++++++++++++++++++++++++++++++++++++++++++ lib/remedy/viewport.rb | 62 ++++++++++++-------------------- 3 files changed, 108 insertions(+), 41 deletions(-) create mode 100644 lib/remedy/pane.rb diff --git a/examples/menu/menu.rb b/examples/menu/menu.rb index 7fed8c8..a64c461 100644 --- a/examples/menu/menu.rb +++ b/examples/menu/menu.rb @@ -7,6 +7,7 @@ class Menu def initialize @viewport = Viewport.new + @pane = Pane.new viewport: @viewport end # will do basic setup and then loop over user input @@ -34,7 +35,10 @@ def listen # this tells the Viewport to draw to the screen def draw - @viewport.draw content, Tuple.zero, header, footer + @viewport.content = content + @viewport.header = header + @viewport.footer = footer + @viewport.draw end # this is the body of our menu, it will be squished if the terminal is too small diff --git a/lib/remedy/pane.rb b/lib/remedy/pane.rb new file mode 100644 index 0000000..4edac2a --- /dev/null +++ b/lib/remedy/pane.rb @@ -0,0 +1,81 @@ +require 'remedy/view' +require 'remedy/tuple' +require 'remedy/console' +require 'remedy/ansi' + +module Remedy + + # By default a Pane will fill all available area + # If a Pane is constrained to a specific size, then it will only take up that space + # Any content that wouldn't fit to the constrained size will be truncated + class Pane + def initialize size: Tuple.zero, content: Partial.new, viewport: Viewport.new + @size = size + @content = content + @viewport = viewport + end + + def draw content, scroll = Tuple.zero + range = range_find @content, scroll, @viewport.size + + viewable_content = @content.excerpt *range + + @viewport.draw viewable_content + end + + def range_find partial, scroll, available_heightwidth + avail_height, avail_width = available_heightwidth + partial_height, partial_width = partial.size + + center_row, center_col = scroll + + row_range = visible_range center_row, partial_height, avail_height + col_range = visible_range center_col, partial_width, avail_width + + [row_range, col_range] + end + + # This is the target size of this pane, but may still be truncated if there is not enough room + def size + Tuple(height, width) + end + + def height + if @size.height > 0 then + @size.height + else + content_height + end + end + + def width + if @size.width > 0 then + @size.width + else + content_width + end + end + + def visible_range offset, actual, available + # if the actual content can fit into the available space, then we're done + return (0...actual) if actual <= available + + # otherwise start looking at the scrolling offset, if any + + # clamp the offset within the possible range of the actual content + if offset < 0 then + range_start = 0 + elsif offset > actual then + range_start = actual + else + range_start = offset + end + + # determine the subset of content that can be displayed + range_end = range_start + (available - offset) + + (range_start...range_end) + end + + end +end diff --git a/lib/remedy/viewport.rb b/lib/remedy/viewport.rb index 22735fe..8d64f67 100644 --- a/lib/remedy/viewport.rb +++ b/lib/remedy/viewport.rb @@ -2,63 +2,45 @@ require 'remedy/tuple' require 'remedy/console' require 'remedy/ansi' +require 'remedy/pane' module Remedy - class Viewport - def draw content, scroll = Tuple.zero, header = Partial.new, footer = Partial.new - range = range_find content, scroll, available_space(header,footer) + class Viewport < Pane + def initialize content: Partial.new, header: Partial.new, footer: Partial.new + @content = content + @header = header + @footer = footer + end + attr_accessor :content, :header, :footer + + def draw + range = range_find @content, Tuple.zero, available_space(@header, @footer) - viewable_content = content.excerpt *range + viewable_content = @content.excerpt *range - view = View.new viewable_content, header, footer + view = View.new viewable_content, @header, @footer ANSI.screen.safe_reset! Console.output << view end - def range_find partial, scroll, available_heightwidth - avail_height, avail_width = available_heightwidth - partial_height, partial_width = partial.size - - center_row, center_col = scroll + def size + Console.size + end - row_range = get_range center_row, partial_height, avail_height - col_range = get_range center_col, partial_width, avail_width + def height + @size.height + end - [row_range, col_range] + def width + @size.width end # This determines the maximum amount of room left available for Content # after taking into consideration the height of the Header and Footer def available_space header, footer - trim = Tuple [header.height + footer.height, 0] + trim = Tuple [@header.height + @footer.height, 0] size - trim end - - def size - Console.size - end - - def get_range offset, actual, available - # if the actual content can fit into the available space, then we're done - return (0...actual) if actual <= available - - # otherwise start looking at the scrolling offset, if any - - # clamp the offset within the possible range of the actual content - if offset < 0 then - range_start = 0 - elsif offset > actual then - range_start = actual - else - range_start = offset - end - - # determine the subset of content that can be displayed - range_end = range_start + (available - offset) - - (range_start...range_end) - end - end end From b1e61ca91be2552c9696db029be2ac5bbc8d5bbf Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 21:34:22 -0500 Subject: [PATCH 007/152] Return 0 instead of nil when there are no lines --- lib/remedy/partial.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remedy/partial.rb b/lib/remedy/partial.rb index 4230ccd..7bd6416 100644 --- a/lib/remedy/partial.rb +++ b/lib/remedy/partial.rb @@ -33,7 +33,7 @@ def height alias_method :length, :height def width - lines.map{|line| line.length }.max + lines.map{|line| line.length }.max || 0 end def size From 1a7324f9e39cbc8e9a663ae43153db4a3538c957 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 21:58:39 -0500 Subject: [PATCH 008/152] Improvements to readme examples --- README.markdown | 12 +++++++----- examples/from_readme/readme.rb | 26 ++++++++++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/README.markdown b/README.markdown index 5063c57..725517c 100644 --- a/README.markdown +++ b/README.markdown @@ -45,7 +45,7 @@ There are objects for input as well as output, including low level console keyst The `Interaction` object wraps raw keyboard reads and streamlines some aspects of accepting keyboard input. -For instance to get a keypress from the terminal and display it: +For instance to get a keypress from the terminal and display what it is called: ```ruby include Remedy @@ -75,7 +75,7 @@ Content in `Remedy::Partial`s will be truncated as needed to accommodate the hea ```ruby include Remedy title = Partial.new - title << "Someone Said These Were Good" + title << "Two Longer Jokes" jokes = Content.new jokes << %q{1. A woman gets on a bus with her baby. The bus driver says: 'Ugh, that's the ugliest baby I've ever seen!' The woman walks to the rear of the bus and sits down, fuming. She says to a man next to her: 'The driver just insulted me!' The man says: 'You go up there and tell him off. Go on, I'll hold your monkey for you.'} @@ -85,7 +85,9 @@ Content in `Remedy::Partial`s will be truncated as needed to accommodate the hea disclaimer << "According to a survey they were funny. I didn't make them." screen = Viewport.new - screen.draw jokes, Tuple.zero, title, disclaimer + screen.header = title + screen.footer = disclaimer + screen.draw jokes ``` ### Console @@ -101,8 +103,8 @@ The most interesting function in my opinion is the callback that gets triggered Console.set_console_resized_hook! do |size| notice = Partial.new - notice << "You just resized your screen!\n\nNew size:" - notice << size + notice << "You just resized your screen!" + notice << "New size: #{size}" screen.draw notice end ``` diff --git a/examples/from_readme/readme.rb b/examples/from_readme/readme.rb index dc4cf5b..d59b649 100644 --- a/examples/from_readme/readme.rb +++ b/examples/from_readme/readme.rb @@ -4,12 +4,22 @@ include Remedy +def cleanup + Console.cooked! + ANSI.cursor.show! +end + +trap "SIGINT" do + cleanup + exit 130 +end + screen = Viewport.new Console.set_console_resized_hook! do |size| notice = Partial.new - notice << "You just resized your screen!\n\nNew size:" - notice << size + notice << "You just resized your screen!" + notice << "New size: #{size}" screen.draw notice end @@ -25,7 +35,7 @@ user_input.get_key title = Partial.new -title << "Someone Said These Were Good" +title << "Two Longer Jokes" jokes = Partial.new jokes << %q{1. A woman gets on a bus with her baby. The bus driver says: 'Ugh, that's the ugliest baby I've ever seen!' The woman walks to the rear of the bus and sits down, fuming. She says to a man next to her: 'The driver just insulted me!' The man says: 'You go up there and tell him off. Go on, I'll hold your monkey for you.'} @@ -34,10 +44,15 @@ disclaimer = Partial.new disclaimer << "According to a survey they were funny. I didn't make them." -screen.draw jokes, Tuple.zero, title, disclaimer +screen.header = title +screen.footer = disclaimer +screen.draw jokes user_input.get_key +screen.header = Partial.new %w{Monitoring\ Keypresses...} +screen.footer = Partial.new + ANSI.cursor.next_line! keys = Partial.new loop_demo = Interaction.new "press q to exit, or any other key to display that key's name\n" @@ -48,5 +63,4 @@ break if key == ?q end -Console.cooked! -ANSI.cursor.show! +cleanup From f43a03271b85a7efccd3dd11bc931819dfe52004 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 22:03:30 -0500 Subject: [PATCH 009/152] Enable viewport body override --- lib/remedy/viewport.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/remedy/viewport.rb b/lib/remedy/viewport.rb index 8d64f67..7d1dd61 100644 --- a/lib/remedy/viewport.rb +++ b/lib/remedy/viewport.rb @@ -13,10 +13,11 @@ def initialize content: Partial.new, header: Partial.new, footer: Partial.new end attr_accessor :content, :header, :footer - def draw - range = range_find @content, Tuple.zero, available_space(@header, @footer) + def draw override = nil + body = override || @content + range = range_find body, Tuple.zero, available_space(@header, @footer) - viewable_content = @content.excerpt *range + viewable_content = body.excerpt *range view = View.new viewable_content, @header, @footer From ffd1c7d943d395a8d86237c5715c831a09f11b7a Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 4 Oct 2023 22:04:09 -0500 Subject: [PATCH 010/152] Move range finding code down into a protected section --- lib/remedy/pane.rb | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/remedy/pane.rb b/lib/remedy/pane.rb index 4edac2a..814f463 100644 --- a/lib/remedy/pane.rb +++ b/lib/remedy/pane.rb @@ -23,18 +23,6 @@ def draw content, scroll = Tuple.zero @viewport.draw viewable_content end - def range_find partial, scroll, available_heightwidth - avail_height, avail_width = available_heightwidth - partial_height, partial_width = partial.size - - center_row, center_col = scroll - - row_range = visible_range center_row, partial_height, avail_height - col_range = visible_range center_col, partial_width, avail_width - - [row_range, col_range] - end - # This is the target size of this pane, but may still be truncated if there is not enough room def size Tuple(height, width) @@ -56,6 +44,20 @@ def width end end + protected + + def range_find partial, scroll, available_heightwidth + avail_height, avail_width = available_heightwidth + partial_height, partial_width = partial.size + + center_row, center_col = scroll + + row_range = visible_range center_row, partial_height, avail_height + col_range = visible_range center_col, partial_width, avail_width + + [row_range, col_range] + end + def visible_range offset, actual, available # if the actual content can fit into the available space, then we're done return (0...actual) if actual <= available From fd305d1782e5d4da27c21bdda90b064b9fdbe082 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 06:26:31 -0500 Subject: [PATCH 011/152] Add Yard configuration file --- .yardopts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .yardopts diff --git a/.yardopts b/.yardopts new file mode 100644 index 0000000..8ca5ab7 --- /dev/null +++ b/.yardopts @@ -0,0 +1,3 @@ +--no-private +--markup markdown +lib/**/*.rb From 5c5f5c4a5842ff1d8899f1a2a427dfcbb401222e Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 06:51:32 -0500 Subject: [PATCH 012/152] Add Screenbuffer class --- lib/remedy/screenbuffer.rb | 44 ++++++++++++++++++++++++++++++++++++++ spec/screenbuffer_spec.rb | 25 ++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 lib/remedy/screenbuffer.rb create mode 100644 spec/screenbuffer_spec.rb diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb new file mode 100644 index 0000000..a8ae912 --- /dev/null +++ b/lib/remedy/screenbuffer.rb @@ -0,0 +1,44 @@ +require 'remedy/tuple' + +module Remedy + # A screenbuffer is an in-memory representation of the terminal display + # Even most modern terminals do not allow direct access to the character display array + # So we create our own, like a DOM, to do our work on before rendering it to the screen + class Screenbuffer + def initialize size, fill: " ", nl: ?\n + @size = size + @fill = fill[0] + @nl = nl + @buf = new_buf + end + attr_accessor :size, :fill, :nl, :buf + + def []= *params + value = params.pop + coords = params.flatten + + if coords.first.is_a? ::Remedy::Tuple then + coords = coords.first + end + + row = coords[0] + col = coords[1] + + buf[row][col] = value + end + + def buf + @buf ||= new_buf + end + + def new_buf + Array.new(size.height) do + fill * size.width + end + end + + def to_s + buf.join nl + end + end +end diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb new file mode 100644 index 0000000..24c671d --- /dev/null +++ b/spec/screenbuffer_spec.rb @@ -0,0 +1,25 @@ +require_relative 'spec_helper' +require 'remedy/screenbuffer' + +describe Remedy::Screenbuffer do + subject(:sb){ described_class.new size, fill: "." } + let(:size){ Tuple 2, 2 } + + describe "#to_s" do + it "dumps the screenbuffer as a single string" do + expected = "..\n.." + actual = sb.to_s + expect(actual).to eq expected + end + end + + describe "#[]=" do + it "sets the value at a particular location for a single character" do + value = "x" + expected = "..\n.#{value}" + sb[1,1] = value + actual = sb.to_s + expect(actual).to eq expected + end + end +end From fc73329f58acd92cca9e622785dcc542263acf30 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 07:00:32 -0500 Subject: [PATCH 013/152] Allow longer replacements in the screenbuffer --- lib/remedy/screenbuffer.rb | 8 ++++---- spec/screenbuffer_spec.rb | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index a8ae912..c7abbab 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -1,9 +1,9 @@ require 'remedy/tuple' module Remedy - # A screenbuffer is an in-memory representation of the terminal display - # Even most modern terminals do not allow direct access to the character display array - # So we create our own, like a DOM, to do our work on before rendering it to the screen + # A screenbuffer is an in-memory representation of the terminal display. + # Even most modern terminals do not allow direct access to the character display array. + # So we create our own, like a DOM, to do our work on before rendering it to the screen. class Screenbuffer def initialize size, fill: " ", nl: ?\n @size = size @@ -24,7 +24,7 @@ def []= *params row = coords[0] col = coords[1] - buf[row][col] = value + buf[row][col,value.length] = value end def buf diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index 24c671d..b67d727 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -21,5 +21,13 @@ actual = sb.to_s expect(actual).to eq expected end + + it "sets the value at a particular location for sequential characters" do + value = "xy" + expected = "..\n#{value}" + sb[1,0] = value + actual = sb.to_s + expect(actual).to eq expected + end end end From ebd8fa048be6817f8f27b4ebf8c2912652cb2e69 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 07:06:53 -0500 Subject: [PATCH 014/152] Refactor screenbuffer to get ready to handle multiple lines --- lib/remedy/screenbuffer.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index c7abbab..e9ba6da 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -15,16 +15,9 @@ def initialize size, fill: " ", nl: ?\n def []= *params value = params.pop - coords = params.flatten + coords = Tuple params.flatten - if coords.first.is_a? ::Remedy::Tuple then - coords = coords.first - end - - row = coords[0] - col = coords[1] - - buf[row][col,value.length] = value + replace_inline coords, value end def buf @@ -40,5 +33,11 @@ def new_buf def to_s buf.join nl end + + private + + def replace_inline coords, value + buf[coords.row][coords.col,value.length] = value + end end end From 1a2c4f23f08a8fb80fde09de95d435b19ed6b6a2 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 07:24:45 -0500 Subject: [PATCH 015/152] Make Tuples from other Tuples --- lib/remedy/tuple.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index 805fcbd..d349845 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -6,7 +6,9 @@ module Remedy class Tuple def initialize *new_dimensions dims = new_dimensions.flatten - if dims.first.is_a? Range then + if dims.first.is_a? self.class then + dims = dims.first.dimensions.dup + elsif dims.first.is_a? Range then dims.map! do |range| range.end end From 663c4fc97e9b6d9d91a33e102f033049d67ca5bb Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 07:25:33 -0500 Subject: [PATCH 016/152] Add Tuples to scalars and other Tuplable types --- lib/remedy/tuple.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index d349845..c881296 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -23,6 +23,14 @@ def self.zero # OPERATIONS + def + other_tuple + if other_tuple.respond_to? :[] then + combine other_tuple + elsif other_tuple.respond_to? :+ then + add other_tuple + end + end + def - other_tuple if other_tuple.respond_to? :length then self.class.new subtract(other_tuple) @@ -117,6 +125,22 @@ def subtract other_tuple difference << self[index] - other_tuple[index] end end + + def add amount + dimensions.map do |dimension| + dimension + amount + end + end + + def combine other_tuple + raise "Different numbers of dimensions!" unless bijective? other_tuple + + result = cardinality.times.inject self.class.new do |sum, index| + sum << self[index] + other_tuple[index] + end + + Tuple result + end end end From 04f268f9762bdc0f90e29529bf327e71bf662d4a Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 07:26:08 -0500 Subject: [PATCH 017/152] Move tuplify functionality into class itself --- lib/remedy/tuple.rb | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index c881296..a5f0e65 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -17,8 +17,20 @@ def initialize *new_dimensions end attr_accessor :dimensions - def self.zero - self.new([0,0]) + class << self + def zero + self.new(0,0) + end + + def tuplify *tupleable + klass = self + tupleable.flatten! + if tupleable.first.is_a? klass then + tupleable + else + klass.new tupleable + end + end end # OPERATIONS @@ -139,17 +151,11 @@ def combine other_tuple sum << self[index] + other_tuple[index] end - Tuple result + self.class.tuplify result end end end def Tuple *tupleable - klass = ::Remedy::Tuple - tupleable.flatten! - if tupleable.first.is_a? klass then - tupleable - else - klass.new tupleable - end + ::Remedy::Tuple.tuplify tupleable end From fd62d04cbb66c08b2c47766b19e86d80c23fb961 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 07:33:54 -0500 Subject: [PATCH 018/152] Clean up Tuple math operations --- lib/remedy/tuple.rb | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index a5f0e65..0db07d8 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -37,17 +37,17 @@ def tuplify *tupleable def + other_tuple if other_tuple.respond_to? :[] then - combine other_tuple + matrix_addition other_tuple elsif other_tuple.respond_to? :+ then - add other_tuple + scalar_addition other_tuple end end def - other_tuple if other_tuple.respond_to? :length then - self.class.new subtract(other_tuple) + matrix_subtract other_tuple else - self.class.new deduct(other_tuple) + scalar_subtract other_tuple end end @@ -124,27 +124,37 @@ def inspect protected - def deduct amount + def scalar_subtract! amount dimensions.map do |dimension| dimension - amount end end - def subtract other_tuple + def scalar_subtract amount + dup scalar_subtract! amount + end + + def matrix_subtract other_tuple raise "Different numbers of dimensions!" unless bijective? other_tuple - cardinality.times.inject self.class.new do |difference, index| + result = cardinality.times.inject self.class.new do |difference, index| difference << self[index] - other_tuple[index] end + + self.class.tuplify result end - def add amount + def scalar_addition! amount dimensions.map do |dimension| dimension + amount end end - def combine other_tuple + def scalar_addition amount + dup.add! amount + end + + def matrix_addition other_tuple raise "Different numbers of dimensions!" unless bijective? other_tuple result = cardinality.times.inject self.class.new do |sum, index| From 902b1fb9e1e941be06afd30ace7af139bcb90642 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 07:36:17 -0500 Subject: [PATCH 019/152] Screenbuffer handles multiple line insertion --- lib/remedy/screenbuffer.rb | 10 +++++++++- spec/screenbuffer_spec.rb | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index e9ba6da..1cba18a 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -17,7 +17,15 @@ def []= *params value = params.pop coords = Tuple params.flatten - replace_inline coords, value + if value.respond_to? :each + lines = value + else + lines = split value + end + + lines.each.with_index do |line, index| + replace_inline(coords + Tuple(index,0), line) + end end def buf diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index b67d727..5d3e154 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -29,5 +29,13 @@ actual = sb.to_s expect(actual).to eq expected end + + it "sets the value at a particular location for multiple lines" do + value = %w{a b} + expected = "a.\nb." + sb[0,0] = value + actual = sb.to_s + expect(actual).to eq expected + end end end From 863633f6fa204067938132bd7dac5288dfc827e4 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 07:38:43 -0500 Subject: [PATCH 020/152] Screenbuffer handles newlines in simple strings --- lib/remedy/screenbuffer.rb | 4 ++++ spec/screenbuffer_spec.rb | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 1cba18a..242a1a6 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -47,5 +47,9 @@ def to_s def replace_inline coords, value buf[coords.row][coords.col,value.length] = value end + + def split line + Array line.split(/\r\n|\n\r|\n|\r/) + end end end diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index 5d3e154..ac41fba 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -37,5 +37,13 @@ actual = sb.to_s expect(actual).to eq expected end + + it "handles embedded newlines gracefully" do + value = "a\nb" + expected = ".a\n.b" + sb[0,1] = value + actual = sb.to_s + expect(actual).to eq expected + end end end From 730a391322a21654cbb5d5295eeb9d82370462e6 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 07:47:51 -0500 Subject: [PATCH 021/152] Screenbuffer handles newlines in arrays of lines --- lib/remedy/screenbuffer.rb | 22 ++++++++++++---------- spec/screenbuffer_spec.rb | 12 ++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 242a1a6..4dcd76d 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -17,15 +17,7 @@ def []= *params value = params.pop coords = Tuple params.flatten - if value.respond_to? :each - lines = value - else - lines = split value - end - - lines.each.with_index do |line, index| - replace_inline(coords + Tuple(index,0), line) - end + replace_perline coords, value end def buf @@ -44,12 +36,22 @@ def to_s private + def replace_perline coords, value + lines = Array(value).map do |line| + split line + end.flatten + + lines.each.with_index do |line, index| + replace_inline(coords + Tuple(index,0), line) + end + end + def replace_inline coords, value buf[coords.row][coords.col,value.length] = value end def split line - Array line.split(/\r\n|\n\r|\n|\r/) + line.split(/\r\n|\n\r|\n|\r/) end end end diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index ac41fba..0058040 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -45,5 +45,17 @@ actual = sb.to_s expect(actual).to eq expected end + + context "larger size" do + let(:size){ Tuple 4, 4 } + + it "handles embedded newlines gracefully for multiple lines" do + value = ["a\nb", "c"] + expected = "....\n...a\n...b\n...c" + sb[1,3] = value + actual = sb.to_s + expect(actual).to eq expected + end + end end end From c791b9b29ca07185512d8a8addb9d5b66dd45c08 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 08:07:36 -0500 Subject: [PATCH 022/152] Document Screenbuffer class --- lib/remedy/screenbuffer.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 4dcd76d..723bcec 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -5,6 +5,12 @@ module Remedy # Even most modern terminals do not allow direct access to the character display array. # So we create our own, like a DOM, to do our work on before rendering it to the screen. class Screenbuffer + + # Create a new screenbuffer. + # + # @param size [Remedy::Tuple] the number of rows and columns to allocate + # @param fill: [String] a character to pre-fill the buffer with + # @param nl: [String] the sequence used to separate lines when converted to a string def initialize size, fill: " ", nl: ?\n @size = size @fill = fill[0] @@ -13,6 +19,26 @@ def initialize size, fill: " ", nl: ?\n end attr_accessor :size, :fill, :nl, :buf + # Replace the contents of the buffer at a given coordinate (from top left). + # + # Usage with scalar coordinates and simple string: + # ```ruby + # row = 1 + # col = 2 + # screenbuffer[row, col] = "foo" + # ``` + # Usage with Tuple coordinates and line array: + # ```ruby + # coords = Tuple 3, 4 + # lines = ["foo\nbar", "baz"] + # screenbuffer[coords] = lines + # ``` + # + # @param coords [Remedy::Tuple] the coordinates to begin replacing from + # @param row [Numeric] the row to start replacing from, 0 indexed + # @param col [Numeric] the column to start replacing from, 0 indexed + # @param value [String or Enumerable] the content to place into the screenbuffer + # (always the last argument) def []= *params value = params.pop coords = Tuple params.flatten @@ -20,6 +46,7 @@ def []= *params replace_perline coords, value end + # The raw screenbuffer array itself. def buf @buf ||= new_buf end @@ -30,6 +57,8 @@ def new_buf end end + # Convert screenbuffer to single string. + # Concatenates the contents of the buffer with the `nl` attribute. def to_s buf.join nl end From 9e3e00a876cf43240aefd6327ee2c5b347bbc4be Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 08:10:31 -0500 Subject: [PATCH 023/152] Make new buffer method private --- lib/remedy/screenbuffer.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 723bcec..523b362 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -51,12 +51,6 @@ def buf @buf ||= new_buf end - def new_buf - Array.new(size.height) do - fill * size.width - end - end - # Convert screenbuffer to single string. # Concatenates the contents of the buffer with the `nl` attribute. def to_s @@ -65,6 +59,12 @@ def to_s private + def new_buf + Array.new(size.height) do + fill * size.width + end + end + def replace_perline coords, value lines = Array(value).map do |line| split line From e68e38fad832e1f9c24a45ef3637ce509933c8eb Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 08:19:09 -0500 Subject: [PATCH 024/152] Additional accessors for Tuple --- lib/remedy/tuple.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index 0db07d8..8eda814 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -84,16 +84,26 @@ def x dimensions[0] end alias_method :height, :x + alias_method :row, :x + alias_method :first, :x def y dimensions[1] end alias_method :width, :y + alias_method :col, :y + alias_method :second, :y def z dimensions[2] end alias_method :depth, :z + alias_method :layer, :z + alias_method :third, :z + + def last + dimensions.last + end def [] index dimensions[index] From 792d7268bb6f890200c121c77eae73d6f7a3a7cb Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 08:46:39 -0500 Subject: [PATCH 025/152] Screenbuffer handles overflowing lines and ellipsis --- lib/remedy/screenbuffer.rb | 16 +++++++++++----- spec/screenbuffer_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 523b362..b11fce3 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -11,13 +11,17 @@ class Screenbuffer # @param size [Remedy::Tuple] the number of rows and columns to allocate # @param fill: [String] a character to pre-fill the buffer with # @param nl: [String] the sequence used to separate lines when converted to a string - def initialize size, fill: " ", nl: ?\n + # @param elipsis: [String] the character used to indicate truncated lines, + # if set to `nil` then content will extend to the edge of the screen + def initialize size, fill: " ", nl: ?\n, ellipsis: "…" + @charwidth = 1 # in case we need to support multiple widths in the future @size = size - @fill = fill[0] + @fill = fill[0, charwidth] @nl = nl + @ellipsis = ellipsis @buf = new_buf end - attr_accessor :size, :fill, :nl, :buf + attr_accessor :size, :fill, :nl, :buf, :ellipsis, :charwidth # Replace the contents of the buffer at a given coordinate (from top left). # @@ -61,7 +65,7 @@ def to_s def new_buf Array.new(size.height) do - fill * size.width + fill * size.width * charwidth end end @@ -76,7 +80,9 @@ def replace_perline coords, value end def replace_inline coords, value - buf[coords.row][coords.col,value.length] = value + fit = value[0,size.width - coords.col] + fit[-1] = ellipsis[0,charwidth] if ellipsis && fit.length < value.length + buf[coords.row][coords.col,fit.length] = fit end def split line diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index 0058040..104d42e 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -57,5 +57,25 @@ expect(actual).to eq expected end end + + it "truncates horizontal overflows" do + value = "1234" + expected = "1…\n.." + sb[0,0] = value + actual = sb.to_s + expect(actual).to eq expected + end + + context "without ellipsis" do + subject(:sb){ described_class.new size, fill: ".", ellipsis: nil } + + it "truncates horizontal overflows" do + value = "1234" + expected = "12\n.." + sb[0,0] = value + actual = sb.to_s + expect(actual).to eq expected + end + end end end From ac37c8d55d048a42bc885094eb56d4815da842e0 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 08:55:01 -0500 Subject: [PATCH 026/152] Screenbuffer handles overflowing vertical content --- lib/remedy/screenbuffer.rb | 4 +++- spec/screenbuffer_spec.rb | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index b11fce3..203d9f5 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -75,7 +75,9 @@ def replace_perline coords, value end.flatten lines.each.with_index do |line, index| - replace_inline(coords + Tuple(index,0), line) + new_coords = coords + Tuple(index,0) + return if new_coords.height >= size.height + replace_inline(new_coords, line) end end diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index 104d42e..84e1acd 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -66,6 +66,14 @@ expect(actual).to eq expected end + it "truncates vertical overflows" do + value = %w(1 2 3) + expected = "..\n1." + sb[1,0] = value + actual = sb.to_s + expect(actual).to eq expected + end + context "without ellipsis" do subject(:sb){ described_class.new size, fill: ".", ellipsis: nil } From 57b59a66e513b3ed009fc7ccfd321a707535f2d8 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 09:24:53 -0500 Subject: [PATCH 027/152] Screenbuffer can be resized and output ansi new lines --- lib/remedy/screenbuffer.rb | 18 +++++++++++++++++- spec/screenbuffer_spec.rb | 13 +++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 203d9f5..85754df 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -1,4 +1,5 @@ -require 'remedy/tuple' +require "remedy/tuple" +require "remedy/ansi" module Remedy # A screenbuffer is an in-memory representation of the terminal display. @@ -55,12 +56,27 @@ def buf @buf ||= new_buf end + def size= new_size + raise ArgumentError unless new_size.is_a? Tuple + + if size != new_size then + @size = new_size + @buf = new_buf + end + end + # Convert screenbuffer to single string. # Concatenates the contents of the buffer with the `nl` attribute. def to_s buf.join nl end + # Convert screenbuffer to single string for output to a display using ANSI line motions. + # Standard newlines at screen edges cause many terminals to display extraneous empty lines. + def to_ansi + buf.join ANSI.cursor.next_line + end + private def new_buf diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index 84e1acd..16ca1e6 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -85,5 +85,18 @@ expect(actual).to eq expected end end + + it "can be resized" do + expected = ".....\n....." + sb.size = Tuple 2, 5 + actual = sb.to_s + expect(actual).to eq expected + end + + it "generates terminal safe output strings" do + expected = "..\e[1B\e[0G.." + actual = sb.to_ansi + expect(actual).to eq expected + end end end From 566e9e82e3680f515c29ce7cf55799e54aced279 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 09:44:55 -0500 Subject: [PATCH 028/152] Create simple Screen object - the would-be replacement for Viewport --- lib/remedy/screen.rb | 53 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 lib/remedy/screen.rb diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb new file mode 100644 index 0000000..646c822 --- /dev/null +++ b/lib/remedy/screen.rb @@ -0,0 +1,53 @@ +require "remedy/tuple" +require "remedy/console" +require "remedy/ansi" +require "remedy/screenbuffer" + +module Remedy + class Screen + # Create a new Screen object. + # + # Only one Screen object should be in use at a time + # because they talk directly to the raw terminal. + # But feel free to have multiple available and swap between them, + # Might be good for having multiple workspaces. + # + # @param auto_resize [Boolean] can be disabled if you are setting up your own console resize hook + # @see #resized + # @see Console.set_console_resized_hook! + def initialize auto_resize: true + @buffer = Screenbuffer.new Console.size, fill: "." + + Console.set_console_resized_hook! do |new_size| + resized new_size + end if auto_resize + end + attr_accessor :buffer + + # Draw the buffer to the console using raw output. + def draw + ANSI.screen.safe_reset! + Console.output << buffer.to_ansi + end + + # This sets the new screen size and rebuilds the buffer before redrawing it. + # + # Called automatically unless `auto_resize` was set to `false`, + # or if the console resized hook was changed to something else. + # + # If setting up your own `Console.set_console_resized_hook!` callback + # then you can use this as a starting point: + # + # ```ruby + # Console.set_console_resized_hook! do |new_size| + # my_screen.resized new_size + # end + # ``` + # + # @param [Remedy::Tuple] the new size of the terminal + def resized new_size + buffer.size = new_size + draw + end + end +end From 3ff3180ee9cacdb88cc997f17eb41b6c580adf40 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 10:15:28 -0500 Subject: [PATCH 029/152] Improve documentation for Screenbuffer --- lib/remedy/screen.rb | 4 ++- lib/remedy/screenbuffer.rb | 65 +++++++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 646c822..eb0c09a 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -25,6 +25,7 @@ def initialize auto_resize: true attr_accessor :buffer # Draw the buffer to the console using raw output. + # @return [void] def draw ANSI.screen.safe_reset! Console.output << buffer.to_ansi @@ -44,7 +45,8 @@ def draw # end # ``` # - # @param [Remedy::Tuple] the new size of the terminal + # @param new_size [Remedy::Tuple] the new size of the terminal + # @return [void] def resized new_size buffer.size = new_size draw diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 85754df..d8cebdf 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -10,40 +10,43 @@ class Screenbuffer # Create a new screenbuffer. # # @param size [Remedy::Tuple] the number of rows and columns to allocate - # @param fill: [String] a character to pre-fill the buffer with - # @param nl: [String] the sequence used to separate lines when converted to a string - # @param elipsis: [String] the character used to indicate truncated lines, + # @param fill [String] a character to pre-fill the buffer with + # @param nl [String] the sequence used to separate lines when converted to a string + # @param ellipsis [String] the character used to indicate truncated lines, # if set to `nil` then content will extend to the edge of the screen - def initialize size, fill: " ", nl: ?\n, ellipsis: "…" - @charwidth = 1 # in case we need to support multiple widths in the future + # @param charwidth [Numeric] in case we are able to support multiple character widths in the future + def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1 + @charwidth = charwidth @size = size @fill = fill[0, charwidth] @nl = nl @ellipsis = ellipsis @buf = new_buf end - attr_accessor :size, :fill, :nl, :buf, :ellipsis, :charwidth + attr_accessor :fill, :nl, :ellipsis, :charwidth - # Replace the contents of the buffer at a given coordinate (from top left). + # Replace the contents of the buffer at a given coordinate. # - # Usage with scalar coordinates and simple string: - # ```ruby - # row = 1 - # col = 2 - # screenbuffer[row, col] = "foo" - # ``` - # Usage with Tuple coordinates and line array: - # ```ruby - # coords = Tuple 3, 4 - # lines = ["foo\nbar", "baz"] - # screenbuffer[coords] = lines - # ``` + # @overload []= coords, value + # @param coords [Remedy::Tuple] the coordinates to begin replacing from + # @param value [String, Enumerable] the content to place into the screenbuffer + # Usage with Tuple coordinates and line array: + # ```ruby + # coords = Tuple 3, 4 + # lines = ["foo\nbar", "baz"] + # screenbuffer[coords] = lines + # ``` + # @overload []= row, col, value + # @param row [Numeric] the row to start replacing from, 0 indexed + # @param col [Numeric] the column to start replacing from, 0 indexed + # @param value [String, Enumerable] the content to place into the screenbuffer # - # @param coords [Remedy::Tuple] the coordinates to begin replacing from - # @param row [Numeric] the row to start replacing from, 0 indexed - # @param col [Numeric] the column to start replacing from, 0 indexed - # @param value [String or Enumerable] the content to place into the screenbuffer - # (always the last argument) + # Usage with scalar coordinates and simple string: + # ```ruby + # row = 1 + # col = 2 + # screenbuffer[row, col] = "foo" + # ``` def []= *params value = params.pop coords = Tuple params.flatten @@ -51,11 +54,23 @@ def []= *params replace_perline coords, value end - # The raw screenbuffer array itself. + # @return [Array] the raw screenbuffer array def buf @buf ||= new_buf end + # @return [Remedy::Tuple] the size of the buffer in rows and columns + def size + @size + end + + # Set a new size for the screenbuffer. + # @note This will destroy the contents of the current buffer! + # + # @param new_size [Remedy::Tuple] the new size, + # as typically received from `Console.size` or + # `Console.set_console_resized_hook!` + # @raise [ArgumentError] if passed anything other than a Remedy::Tuple def size= new_size raise ArgumentError unless new_size.is_a? Tuple From ad18b8a73302c03e3eae8a5cc064e9d84226fecd Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 11:05:31 -0500 Subject: [PATCH 030/152] Make Console more testable with some overrides --- lib/remedy/console.rb | 16 +++++++++++++++- spec/screen_spec.rb | 37 +++++++++++++++++++++++++++++++++++++ spec/viewport_spec.rb | 33 ++++++++++++++++++++++++--------- 3 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 spec/screen_spec.rb diff --git a/lib/remedy/console.rb b/lib/remedy/console.rb index 4470da4..335d18f 100644 --- a/lib/remedy/console.rb +++ b/lib/remedy/console.rb @@ -23,6 +23,14 @@ def output @output ||= $stdout end + def input= new_input + @input = new_input + end + + def output= new_output + @output = new_output + end + def raw raw! result = yield @@ -52,14 +60,20 @@ def rows alias_method :height, :rows def size + return @size_override if @size_override + str = [0, 0, 0, 0].pack('SSSS') - if input.ioctl(TIOCGWINSZ, str) >= 0 then + if input.respond_to?(:ioctl) && input.ioctl(TIOCGWINSZ, str) >= 0 then Tuple.new str.unpack('SSSS').first 2 else raise UnknownConsoleSize, "Unable to get console size" end end + def size_override= new_size + @size_override = new_size + end + def interactive? input.isatty end diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb new file mode 100644 index 0000000..2610bba --- /dev/null +++ b/spec/screen_spec.rb @@ -0,0 +1,37 @@ +require_relative "spec_helper" +require "remedy/screen" +require "stringio" + +describe Remedy::Screen do + subject(:s){ described_class.new auto_resize: false } + let(:console){ ::Remedy::Console } + let(:size){ Tuple 20, 40 } + let(:size_override){ Tuple 20, 40 } + let(:stringio){ StringIO.new.tap{|sio| def sio.ioctl(magic, str); str = [20, 40, 0, 0].pack('SSSS'); 1; end } } + + before(:each) do + console.input = stringio + console.output = stringio + console.size_override = size_override + s.resized size + end + + after(:each) do + console.input = $stdin + console.output = $stdout + console.size_override = nil + end + + describe "#draw" do + let(:size){ Tuple 2, 2 } + + it "writes the buffer to the output" do + expected = "\e[H\e[J..\e[1B\e[0G..\e[H\e[J..\e[1B\e[0G..".inspect[1..-2] + + s.draw + + actual = stringio.string.inspect[1..-2] + expect(actual).to eq expected + end + end +end diff --git a/spec/viewport_spec.rb b/spec/viewport_spec.rb index 576d561..8e1f70d 100644 --- a/spec/viewport_spec.rb +++ b/spec/viewport_spec.rb @@ -1,19 +1,34 @@ -require_relative 'spec_helper' -require 'remedy/viewport' +require_relative "spec_helper" +require "remedy/viewport" describe Remedy::Viewport do + let(:console){ ::Remedy::Console } + let(:size_override){ Tuple 20, 40 } + let(:stringio){ StringIO.new } + + before(:each) do + console.input = stringio + console.output = stringio + console.size_override = size_override + end + + after(:each) do + console.input = $stdin + console.output = $stdout + console.size_override = nil + end + it 'should be able to execute the example code from the readme' do - @stdout = $stdout + expected = "\\e[H\\e[JQ: What's the difference between a duck?\\e[1B\\e[0GA: Purple, because ice cream has no bone\\e[1B\\e[0G" + joke = ::Remedy::Partial.new joke << "Q: What's the difference between a duck?" joke << "A: Purple, because ice cream has no bones!" screen = ::Remedy::Viewport.new - sio = StringIO.new - $stdout = sio - screen.draw joke unless ENV['CI'] - expect(sio.string).to include("ice cream") - ensure - $stdout = @stdout + screen.draw joke + + actual = stringio.string.inspect[1..-2] + expect(actual).to eq expected end end From 68118565ed389687fda25a3c082f213c5fbba0f9 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 11:47:59 -0500 Subject: [PATCH 031/152] Add test for using Tuples as buffer coordinates --- spec/screenbuffer_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index 16ca1e6..7b962fc 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -14,6 +14,15 @@ end describe "#[]=" do + it "accepts Tuples as coordinates" do + value = "x" + expected = "..\n.#{value}" + coords = ::Remedy::Tuple.tuplify 1, 1 + sb[coords] = value + actual = sb.to_s + expect(actual).to eq expected + end + it "sets the value at a particular location for a single character" do value = "x" expected = "..\n.#{value}" From 9da72c62340e61c28cda1db01acd621b66a7ed85 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 12:02:02 -0500 Subject: [PATCH 032/152] Read characters from buffer --- lib/remedy/screenbuffer.rb | 26 ++++++++++++++++++++++++++ spec/screenbuffer_spec.rb | 14 ++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index d8cebdf..4a26037 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -25,6 +25,21 @@ def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1 end attr_accessor :fill, :nl, :ellipsis, :charwidth + # Get the contents of the buffer at a given coordinate. + # + # @overload [] coords + # @param coords [Remedy::Tuple] the coordinates to read from + # + # @overload [] row, col + # @param row [Numeric] the row to read from, 0 indexed + # @param col [Numeric] the column to read from, 0 indexed + # + # @todo get more than single characters + def [] *params + coords = Tuple params.flatten + buf[coords.row][coords.col] + end + # Replace the contents of the buffer at a given coordinate. # # @overload []= coords, value @@ -59,6 +74,17 @@ def buf @buf ||= new_buf end + # Replace the contents of the internal buffer. + # + # Primarily useful for testing. + # Could also be used for double/triple buffering implementation. + # + # @param override_buf [Array] the new replacement buffer contents + def buf= override_buf + self.size = Tuple override_buf.length, (override_buf.map{|l|l.length}.max || 0) + @buf = override_buf + end + # @return [Remedy::Tuple] the size of the buffer in rows and columns def size @size diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index 7b962fc..f81c0e5 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -13,6 +13,20 @@ end end + describe "#[]" do + it "returns the character at a particular location" do + expected = "x" + coords = ::Remedy::Tuple.tuplify 1, 1 + sb.buf = [ + "ab", + "yx" + ] + + actual = sb[coords] + expect(actual).to eq expected + end + end + describe "#[]=" do it "accepts Tuples as coordinates" do value = "x" From a5ce4ddf915f14fb4cf436a933833a1d6d3138fe Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 12:09:07 -0500 Subject: [PATCH 033/152] Test that screen draw generates ANSI --- spec/screen_spec.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 2610bba..30599f7 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -23,15 +23,17 @@ end describe "#draw" do - let(:size){ Tuple 2, 2 } + context "tiny screen" do + let(:size){ Tuple 2, 2 } - it "writes the buffer to the output" do - expected = "\e[H\e[J..\e[1B\e[0G..\e[H\e[J..\e[1B\e[0G..".inspect[1..-2] + it "writes the buffer to the output" do + expected = "\e[H\e[J..\e[1B\e[0G..\e[H\e[J..\e[1B\e[0G..".inspect[1..-2] - s.draw + s.draw - actual = stringio.string.inspect[1..-2] - expect(actual).to eq expected + actual = stringio.string.inspect[1..-2] + expect(actual).to eq expected + end end end end From fa90bd5e26a134234c393d57a9f2fb814a6b8ca0 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 12:17:52 -0500 Subject: [PATCH 034/152] Screen can draw simple objects directly, fix state issue in tests --- lib/remedy/screen.rb | 6 +++++- spec/screen_spec.rb | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index eb0c09a..95e0005 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -2,6 +2,7 @@ require "remedy/console" require "remedy/ansi" require "remedy/screenbuffer" +require "remedy/align" module Remedy class Screen @@ -26,7 +27,10 @@ def initialize auto_resize: true # Draw the buffer to the console using raw output. # @return [void] - def draw + def draw override = nil + if override then + Align.buffer_center override, buffer + end ANSI.screen.safe_reset! Console.output << buffer.to_ansi end diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 30599f7..9570fd9 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -14,6 +14,7 @@ console.output = stringio console.size_override = size_override s.resized size + stringio.string = "" end after(:each) do @@ -23,11 +24,11 @@ end describe "#draw" do - context "tiny screen" do + context "tiny 2x2 screen" do let(:size){ Tuple 2, 2 } it "writes the buffer to the output" do - expected = "\e[H\e[J..\e[1B\e[0G..\e[H\e[J..\e[1B\e[0G..".inspect[1..-2] + expected = "\e[H\e[J..\e[1B\e[0G..".inspect[1..-2] s.draw @@ -35,5 +36,21 @@ expect(actual).to eq expected end end + + context "small 3x20 screen" do + let(:size){ Tuple 3, 20 } + + it "can display single objects with the override parameter" do + expected = "\\e[H\\e[J....................\\e[1B\\e[0G....hello, world!...\\e[1B\\e[0G...................." + value = "hello, world!" + + s.draw ::Remedy::Partial.new [value] + + actual = stringio.string.inspect[1..-2] + + expect(actual).to match value + expect(actual).to eq expected + end + end end end From b07de3f5b4498b361fdad5b66ce30672c0fc5dbd Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 20:28:32 -0500 Subject: [PATCH 035/152] Test that Remedy objects work with Screenbuffer, make Views work --- lib/remedy/screenbuffer.rb | 1 + lib/remedy/view.rb | 4 ++++ spec/screenbuffer_spec.rb | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 4a26037..df6c8ca 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -127,6 +127,7 @@ def new_buf end def replace_perline coords, value + # Array() checks for `.to_a` on whatever is passed to it lines = Array(value).map do |line| split line end.flatten diff --git a/lib/remedy/view.rb b/lib/remedy/view.rb index 1fbc971..cf36bec 100644 --- a/lib/remedy/view.rb +++ b/lib/remedy/view.rb @@ -14,6 +14,10 @@ def to_s force_recompile = false end end + def to_a + merged + end + protected def compile! diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index f81c0e5..2a2fa21 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -69,6 +69,22 @@ expect(actual).to eq expected end + it "accepts partials" do + value = Remedy::Partial.new ["a\nb"] + expected = ".a\n.b" + sb[0,1] = value + actual = sb.to_s + expect(actual).to eq expected + end + + it "accepts views" do + value = Remedy::View.new Remedy::Partial.new ["a\nb"] + expected = ".a\n.b" + sb[0,1] = value + actual = sb.to_s + expect(actual).to eq expected + end + context "larger size" do let(:size){ Tuple 4, 4 } From ac4662f9e5a0bb300dee770fb30a7634654e5c3d Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 21:05:07 -0500 Subject: [PATCH 036/152] Beginning of Frame class to manage layout --- lib/remedy/frame.rb | 54 +++++++++++++++++++++++++++++++++++++++++++++ spec/frame_spec.rb | 22 ++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 lib/remedy/frame.rb create mode 100644 spec/frame_spec.rb diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb new file mode 100644 index 0000000..abaa11e --- /dev/null +++ b/lib/remedy/frame.rb @@ -0,0 +1,54 @@ +require "remedy/tuple" + +module Remedy + # Frames contain Panes and Panes contain Partials + # Frames can be nested within other Frames or Panes + class Frame + def initialize + # origin is where the inner pane will be attached to + # :left, :right, :top, :bottom, :center + @origin = :center + + # offset is what the offset from that origin the pane should be placed + @offset = Tuple.zero + + # depth is the z index or layer, higher numbers cover lower numbers + # if two panes have the same layer but would overlap, then the one added most recently should come out on top + @depth = 0 + + # arrangement, if this frame contains multiple panes, then they will be arranged according to this + # :stacked, :columnar, :tabbed(?) + @arragement = :stacked + + # the maximum size that this frame wants to be + # zero means fill + @max_size = Tuple.zero + + # empty list of panes + @panes = Array.new + + # background fill + @fill = " " + + # newline character + @nl = ?\n + end + attr_accessor :panes, :nl, :fill + + def to_a + compile_panes + end + + def to_s + compile_panes.join nl + end + + def to_ansi + compile_panes.join ANSI.cursor.next_line + end + + def compile_panes + panes + end + end +end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb new file mode 100644 index 0000000..3b67b51 --- /dev/null +++ b/spec/frame_spec.rb @@ -0,0 +1,22 @@ +require_relative "spec_helper" +require "remedy/frame" + +describe Remedy::Frame do + subject(:f){ described_class.new } + + describe "#to_s" do + it "returns a string" do + expected = String + actual = f.to_s.class + expect(actual).to eq expected + end + end + + describe "#to_a" do + it "returns an array" do + expected = Array + actual = f.to_a.class + expect(actual).to eq expected + end + end +end From 0b4dc40fe8f69c929e7d883882b1fb7308b290da Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 21:18:37 -0500 Subject: [PATCH 037/152] Demonstrate basic Frame content compilation --- lib/remedy/frame.rb | 24 ++++++++++++------------ spec/frame_spec.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index abaa11e..675184c 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -5,18 +5,18 @@ module Remedy # Frames can be nested within other Frames or Panes class Frame def initialize - # origin is where the inner pane will be attached to + # origin is where the frame will be attached to # :left, :right, :top, :bottom, :center @origin = :center - # offset is what the offset from that origin the pane should be placed + # offset is what the offset from that origin the frame should be placed @offset = Tuple.zero # depth is the z index or layer, higher numbers cover lower numbers - # if two panes have the same layer but would overlap, then the one added most recently should come out on top + # if two frames have the same layer but would overlap, then the one added most recently should come out on top @depth = 0 - # arrangement, if this frame contains multiple panes, then they will be arranged according to this + # arrangement, if this frame contains multiple content items, then they will be arranged according to this # :stacked, :columnar, :tabbed(?) @arragement = :stacked @@ -24,8 +24,8 @@ def initialize # zero means fill @max_size = Tuple.zero - # empty list of panes - @panes = Array.new + # empty list of contents + @contents = Array.new # background fill @fill = " " @@ -33,22 +33,22 @@ def initialize # newline character @nl = ?\n end - attr_accessor :panes, :nl, :fill + attr_accessor :contents, :nl, :fill def to_a - compile_panes + compile_contents end def to_s - compile_panes.join nl + compile_contents.join nl end def to_ansi - compile_panes.join ANSI.cursor.next_line + compile_contents.join ANSI.cursor.next_line end - def compile_panes - panes + def compile_contents + contents.map{|c| Array c}.flatten end end end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 3b67b51..42c506a 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -1,5 +1,6 @@ require_relative "spec_helper" require "remedy/frame" +require "remedy/partial" describe Remedy::Frame do subject(:f){ described_class.new } @@ -19,4 +20,31 @@ expect(actual).to eq expected end end + + describe "#compile_contents" do + it "compiles the contents of a single string" do + expected = "foo" + content = "foo" + f.contents << content + actual = f.to_s + expect(actual).to eq expected + end + + it "compiles the contents of multiple strings" do + expected = "foo\nbar\nbaz" + f.contents << "foo" + f.contents << "bar" + f.contents << "baz" + actual = f.to_s + expect(actual).to eq expected + end + + it "compiles the contents of partials" do + expected = "foo\nbar" + f.contents << "foo" + f.contents << ::Remedy::Partial.new(["bar"]) + actual = f.to_s + expect(actual).to eq expected + end + end end From 9848d80eddef8d27af1cc759b749449cc874e838 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 21:30:34 -0500 Subject: [PATCH 038/152] Screenbuffer can clear its contents --- lib/remedy/screenbuffer.rb | 5 +++++ spec/screenbuffer_spec.rb | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index df6c8ca..fb13e0f 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -118,6 +118,11 @@ def to_ansi buf.join ANSI.cursor.next_line end + # Reset contents of buffer to the empty state using the @fill character. + def reset + @buf = new_buf + end + private def new_buf diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index 2a2fa21..d7dd44f 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -138,4 +138,18 @@ expect(actual).to eq expected end end + + describe "#reset" do + it "resets the contents of the buffer" do + expected = "..\n.." + + sb[0,0] = ["ab", "cd"] + expect(sb.to_s).to eq "ab\ncd" + + sb.reset + actual = sb.to_s + + expect(actual).to eq expected + end + end end From bd33979b20daa07fe6de68adabf83b7d08d4f07a Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 21:38:16 -0500 Subject: [PATCH 039/152] Move STDIO capturing tests to their own context --- spec/screen_spec.rb | 62 +++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 9570fd9..3716129 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -7,49 +7,55 @@ let(:console){ ::Remedy::Console } let(:size){ Tuple 20, 40 } let(:size_override){ Tuple 20, 40 } - let(:stringio){ StringIO.new.tap{|sio| def sio.ioctl(magic, str); str = [20, 40, 0, 0].pack('SSSS'); 1; end } } before(:each) do - console.input = stringio - console.output = stringio console.size_override = size_override - s.resized size - stringio.string = "" end - after(:each) do - console.input = $stdin - console.output = $stdout - console.size_override = nil - end + context "captured STDIO" do + let(:stringio){ StringIO.new.tap{|sio| def sio.ioctl(magic, str); str = [20, 40, 0, 0].pack('SSSS'); 1; end } } + + before(:each) do + console.input = stringio + console.output = stringio + s.resized size + stringio.string = "" + end - describe "#draw" do - context "tiny 2x2 screen" do - let(:size){ Tuple 2, 2 } + after(:each) do + console.input = $stdin + console.output = $stdout + console.size_override = nil + end + + describe "#draw" do + context "tiny 2x2 screen" do + let(:size){ Tuple 2, 2 } - it "writes the buffer to the output" do - expected = "\e[H\e[J..\e[1B\e[0G..".inspect[1..-2] + it "writes the buffer to the output" do + expected = "\e[H\e[J..\e[1B\e[0G..".inspect[1..-2] - s.draw + s.draw - actual = stringio.string.inspect[1..-2] - expect(actual).to eq expected + actual = stringio.string.inspect[1..-2] + expect(actual).to eq expected + end end - end - context "small 3x20 screen" do - let(:size){ Tuple 3, 20 } + context "small 3x20 screen" do + let(:size){ Tuple 3, 20 } - it "can display single objects with the override parameter" do - expected = "\\e[H\\e[J....................\\e[1B\\e[0G....hello, world!...\\e[1B\\e[0G...................." - value = "hello, world!" + it "can display single objects with the override parameter" do + expected = "\\e[H\\e[J....................\\e[1B\\e[0G....hello, world!...\\e[1B\\e[0G...................." + value = "hello, world!" - s.draw ::Remedy::Partial.new [value] + s.draw ::Remedy::Partial.new [value] - actual = stringio.string.inspect[1..-2] + actual = stringio.string.inspect[1..-2] - expect(actual).to match value - expect(actual).to eq expected + expect(actual).to match value + expect(actual).to eq expected + end end end end From 48dbba78a016465096a8ba5013478fdf9757ef56 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 21:39:09 -0500 Subject: [PATCH 040/152] Basic Frame incorporation --- lib/remedy/screen.rb | 32 ++++++++++++++++++++++++++++++++ spec/screen_spec.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 95e0005..4074317 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -30,11 +30,17 @@ def initialize auto_resize: true def draw override = nil if override then Align.buffer_center override, buffer + else + refresh_buffer end ANSI.screen.safe_reset! Console.output << buffer.to_ansi end + def frames + @frames ||= Array.new + end + # This sets the new screen size and rebuilds the buffer before redrawing it. # # Called automatically unless `auto_resize` was set to `false`, @@ -55,5 +61,31 @@ def resized new_size buffer.size = new_size draw end + + def to_a + refresh_buffer + buffer.to_a + end + + def to_s + refresh_buffer + buffer.to_s + end + + def to_ansi + refresh_buffer + buffer.to_ansi + end + + def refresh_buffer + buffer.reset + populate_buffer + end + + def populate_buffer + frames.sort(&:depth).each do |frame| + buffer[0,0] = frame + end + end end end diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 3716129..54e0bae 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -1,5 +1,6 @@ require_relative "spec_helper" require "remedy/screen" +require "remedy/frame" require "stringio" describe Remedy::Screen do @@ -59,4 +60,32 @@ end end end + + describe "#frames" do + let(:size){ Tuple 3, 5 } + let(:size_override){ size } + + let(:frame) do + f = ::Remedy::Frame.new + f.contents << "foo" + f.contents << "bar\nbaz" + f + end + + it "gets the list of frames" do + expected = [frame] + + s.frames << frame + + actual = s.frames + expect(actual).to eq expected + end + + it "can add a single frame to the screen" do + expected = "foo..\nbar..\nbaz.." + s.frames << frame + actual = s.to_s + expect(actual).to eq expected + end + end end From 9a40e807b8f9d62ec37f23d1512369c11bf4ef3b Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 22:08:34 -0500 Subject: [PATCH 041/152] Horizontal alignment of Frame contents --- lib/remedy/frame.rb | 40 ++++++++++++++++++++++++++++++++++------ lib/remedy/screen.rb | 1 + spec/frame_spec.rb | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 675184c..5156c8b 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -12,6 +12,14 @@ def initialize # offset is what the offset from that origin the frame should be placed @offset = Tuple.zero + # horizontal alignment of contents + # :left, :right, :center + @halign = :left + + # vertical alignment of contents + # :top, :bottom, :center + @halign = :top + # depth is the z index or layer, higher numbers cover lower numbers # if two frames have the same layer but would overlap, then the one added most recently should come out on top @depth = 0 @@ -20,9 +28,20 @@ def initialize # :stacked, :columnar, :tabbed(?) @arragement = :stacked - # the maximum size that this frame wants to be - # zero means fill - @max_size = Tuple.zero + # spacing as to how multiple content items should be spaced + # :none, :evenly + @spacing = :none + + # the maximum size that this frame wants to be, the actual size may be smaller + # :auto - the frame has no size of its own, it conforms to the size of its content + # :fill - the frame tries to fill as much space as possible + # Tuple - specify a Tuple to constrain it to a specific size + # Tuple.zero is same as :auto + @max_size = :auto + + # this size is used when max_size is set to :fill + # typically set by a screen object after resize + @available_size = Tuple.zero # empty list of contents @contents = Array.new @@ -33,7 +52,7 @@ def initialize # newline character @nl = ?\n end - attr_accessor :contents, :nl, :fill + attr_accessor :contents, :nl, :fill, :available_size, :max_size, :halign def to_a compile_contents @@ -47,8 +66,17 @@ def to_ansi compile_contents.join ANSI.cursor.next_line end - def compile_contents - contents.map{|c| Array c}.flatten + def compile_contents size = available_size + contents.map{|c| Array c}.flatten.map do |line| + space = size.width - line.length + if max_size == :fill && halign == :left && space > 0 then + line + (fill * space) + elsif max_size == :fill && halign == :right && space > 0 then + (fill * space) + line + else + line + end + end end end end diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 4074317..4aa0d76 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -84,6 +84,7 @@ def refresh_buffer def populate_buffer frames.sort(&:depth).each do |frame| + frame.available_size = buffer.size buffer[0,0] = frame end end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 42c506a..7d22b06 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -46,5 +46,42 @@ actual = f.to_s expect(actual).to eq expected end + + context "max_size = :fill" do + before do + f.max_size = :fill + f.available_size = Tuple 6, 6 + + f.contents << "foo" + f.contents << "bar" + f.contents << "baz" + end + + context "halign = :left" do + before do + f.halign = :left + end + + it "fills available space" do + expected = "foo \nbar \nbaz " + + actual = f.to_s + expect(actual).to eq expected + end + end + + context "halign = :right" do + before do + f.halign = :right + end + + it "fills and aligns contents to the right when max_size = :fill and halign = :right" do + expected = " foo\n bar\n baz" + + actual = f.to_s + expect(actual).to eq expected + end + end + end end end From 3776c88b34c75d0f5873ca842ffe78b8c11e861f Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 22:54:23 -0500 Subject: [PATCH 042/152] Align module to reuse alignment code, center Frames --- lib/remedy/align.rb | 54 +++++++++++++++++++++++++++++++++++++++++++++ lib/remedy/frame.rb | 2 ++ spec/align_spec.rb | 16 ++++++++++++++ spec/frame_spec.rb | 13 +++++++++++ spec/screen_spec.rb | 2 +- 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 lib/remedy/align.rb create mode 100644 spec/align_spec.rb diff --git a/lib/remedy/align.rb b/lib/remedy/align.rb new file mode 100644 index 0000000..0f2b909 --- /dev/null +++ b/lib/remedy/align.rb @@ -0,0 +1,54 @@ +module Remedy + module Align + + module_function + + # Center content with a single line by padding out the left and right sides. + # + # @param content [String] the line to be centered + # @param size [Remedy::Tuple] a Tuple with a width - it controls the centering + def h_center_pad content, size + head, tail = middle_spacing content.length, size.width + (" " * head) + content + (" " * tail) + end + + # Center content in the middle of a buffer, both vertically and horizontally. + # + # @param content [Remedy::Partial] any object that responds to `height`, `width`, and a `to_a` that returns an array of strings + # @return [content] whatever was passed in as the `content` param will be returned + def buffer_center content, screenbuffer + voffset = middle content.height, screenbuffer.size.height + hoffset = middle content.width, screenbuffer.size.width + screenbuffer[voffset,hoffset] = content + end + + # Given the actual space something takes up, + # determine what the offset to get it centered in the available space. + # + # @param actual [Numeric] the space already taken + # @param available [Numeric] the available space + # @return [Integer] the offset from the end of the availabe space to center the actual content + def middle actual, available + return 0 unless actual < available + + offset = ((available - actual) / 2.0).floor + end + + # Given the actual space something takes up, + # determine what the offset to get it centered in the available space, + # including the trailing space remaining. + # + # @param actual [Numeric] the space already taken + # @param available [Numeric] the available space + # @return [Array] two element Array with the remaining space on each side + # (since contents may be shifted to one side or the other by 1 space) + def middle_spacing actual, available + return [0,0] unless actual < available + + head = middle actual, available + tail = available - (head + actual) + + [head, tail] + end + end +end diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 5156c8b..ae18015 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -73,6 +73,8 @@ def compile_contents size = available_size line + (fill * space) elsif max_size == :fill && halign == :right && space > 0 then (fill * space) + line + elsif max_size == :fill && halign == :center && space > 0 then + Align.h_center_pad line, size else line end diff --git a/spec/align_spec.rb b/spec/align_spec.rb new file mode 100644 index 0000000..96baf55 --- /dev/null +++ b/spec/align_spec.rb @@ -0,0 +1,16 @@ +require_relative "spec_helper" +require "remedy/align" + +describe Remedy::Align do + subject(:a){ described_class } + + describe ".h_center_pad" do + it "centers a single string horizontally with padding" do + expected = " foo " + actual = a.h_center_pad "foo", Tuple(10,5) + + expect(actual).to eq expected + end + end + +end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 7d22b06..30b7ddf 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -82,6 +82,19 @@ expect(actual).to eq expected end end + + context "halign = :center" do + before do + f.halign = :center + end + + it "fills and aligns contents to the right when max_size = :fill and halign = :right" do + expected = " foo \n bar \n baz " + + actual = f.to_s + expect(actual).to eq expected + end + end end end end diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 54e0bae..9e475c7 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -47,7 +47,7 @@ let(:size){ Tuple 3, 20 } it "can display single objects with the override parameter" do - expected = "\\e[H\\e[J....................\\e[1B\\e[0G....hello, world!...\\e[1B\\e[0G...................." + expected = "\\e[H\\e[J....................\\e[1B\\e[0G...hello, world!....\\e[1B\\e[0G...................." value = "hello, world!" s.draw ::Remedy::Partial.new [value] From c5c842a5119c0dff628623500870262a883cebeb Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 23:01:57 -0500 Subject: [PATCH 043/152] Align lines right and left --- lib/remedy/align.rb | 19 +++++++++++++++++-- lib/remedy/frame.rb | 6 +++--- spec/align_spec.rb | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/remedy/align.rb b/lib/remedy/align.rb index 0f2b909..6003d67 100644 --- a/lib/remedy/align.rb +++ b/lib/remedy/align.rb @@ -7,9 +7,24 @@ module Align # # @param content [String] the line to be centered # @param size [Remedy::Tuple] a Tuple with a width - it controls the centering - def h_center_pad content, size + # @param fill [String] the string to fill the space with + def h_center_p content, size, fill: " " head, tail = middle_spacing content.length, size.width - (" " * head) + content + (" " * tail) + (fill * head) + content + (fill * tail) + end + + # Left align by padding the right side to fill out the total width. + def left_p content, size, fill: " " + space = size.width - content.length + return content if space < 0 + content + (fill * space) + end + + # Right align by padding the left side to fill out the total width. + def right_p content, size, fill: " " + space = size.width - content.length + return content if space < 0 + (fill * space) + content end # Center content in the middle of a buffer, both vertically and horizontally. diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index ae18015..b28b9d1 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -70,11 +70,11 @@ def compile_contents size = available_size contents.map{|c| Array c}.flatten.map do |line| space = size.width - line.length if max_size == :fill && halign == :left && space > 0 then - line + (fill * space) + Align.left_p line, size, fill: fill elsif max_size == :fill && halign == :right && space > 0 then - (fill * space) + line + Align.right_p line, size, fill: fill elsif max_size == :fill && halign == :center && space > 0 then - Align.h_center_pad line, size + Align.h_center_p line, size, fill: fill else line end diff --git a/spec/align_spec.rb b/spec/align_spec.rb index 96baf55..6200ad0 100644 --- a/spec/align_spec.rb +++ b/spec/align_spec.rb @@ -7,7 +7,7 @@ describe ".h_center_pad" do it "centers a single string horizontally with padding" do expected = " foo " - actual = a.h_center_pad "foo", Tuple(10,5) + actual = a.h_center_p "foo", Tuple(10,5) expect(actual).to eq expected end From a06a17fb47dedff35516d6fe65edcfbad38bc5ab Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 23:03:09 -0500 Subject: [PATCH 044/152] Remove space check from Frame, Align should handle it --- lib/remedy/frame.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index b28b9d1..6b21055 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -68,12 +68,11 @@ def to_ansi def compile_contents size = available_size contents.map{|c| Array c}.flatten.map do |line| - space = size.width - line.length - if max_size == :fill && halign == :left && space > 0 then + if max_size == :fill && halign == :left then Align.left_p line, size, fill: fill - elsif max_size == :fill && halign == :right && space > 0 then + elsif max_size == :fill && halign == :right then Align.right_p line, size, fill: fill - elsif max_size == :fill && halign == :center && space > 0 then + elsif max_size == :fill && halign == :center then Align.h_center_p line, size, fill: fill else line From 1bdd4b8f7a463a20d496cd237b6f2ce97e1009f3 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 23:10:06 -0500 Subject: [PATCH 045/152] Error when alignment unknown --- lib/remedy/frame.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 6b21055..b1729a2 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -68,14 +68,16 @@ def to_ansi def compile_contents size = available_size contents.map{|c| Array c}.flatten.map do |line| - if max_size == :fill && halign == :left then + if max_size == :auto then + line + elsif max_size == :fill && halign == :left then Align.left_p line, size, fill: fill elsif max_size == :fill && halign == :right then Align.right_p line, size, fill: fill elsif max_size == :fill && halign == :center then Align.h_center_p line, size, fill: fill else - line + raise "Unknown alignment - halign:#{} max_size:#{max_size}" end end end From 0771246ededb023360f1ad7c81a49e59de7e63c7 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 23:13:25 -0500 Subject: [PATCH 046/152] Change default Frame max_size to none This more closely matched the behavior of dumping raw strings to output. --- lib/remedy/frame.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index b1729a2..8b3b5c4 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -33,11 +33,12 @@ def initialize @spacing = :none # the maximum size that this frame wants to be, the actual size may be smaller - # :auto - the frame has no size of its own, it conforms to the size of its content + # :none - frame has no size, contents are not aligned + # :auto - frame conforms to the size of its largest content and alignts to it # :fill - the frame tries to fill as much space as possible # Tuple - specify a Tuple to constrain it to a specific size # Tuple.zero is same as :auto - @max_size = :auto + @max_size = :none # this size is used when max_size is set to :fill # typically set by a screen object after resize @@ -68,7 +69,7 @@ def to_ansi def compile_contents size = available_size contents.map{|c| Array c}.flatten.map do |line| - if max_size == :auto then + if max_size == :none then line elsif max_size == :fill && halign == :left then Align.left_p line, size, fill: fill From 67aa604b68dce25e850b7dd2f710a125ce6db404 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 23:40:30 -0500 Subject: [PATCH 047/152] Frame content alignment sized to the longest line --- lib/remedy/frame.rb | 22 +++++++++++++----- spec/frame_spec.rb | 56 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 8b3b5c4..ddea7cc 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -67,15 +67,25 @@ def to_ansi compile_contents.join ANSI.cursor.next_line end - def compile_contents size = available_size - contents.map{|c| Array c}.flatten.map do |line| + def compile_contents + flat_contents = contents.map{|c| Array c}.flatten + + flat_contents.map do |line| if max_size == :none then - line - elsif max_size == :fill && halign == :left then + next line + elsif max_size == :fill then + size = available_size + elsif max_size == :auto then + rows = flat_contents.length + cols = flat_contents.map(&:length).max || 0 + size = Tuple(rows, cols) + end + + if halign == :left then Align.left_p line, size, fill: fill - elsif max_size == :fill && halign == :right then + elsif halign == :right then Align.right_p line, size, fill: fill - elsif max_size == :fill && halign == :center then + elsif halign == :center then Align.h_center_p line, size, fill: fill else raise "Unknown alignment - halign:#{} max_size:#{max_size}" diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 30b7ddf..2c231d2 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -62,7 +62,7 @@ f.halign = :left end - it "fills available space" do + it "fills and aligns contents to the left" do expected = "foo \nbar \nbaz " actual = f.to_s @@ -75,7 +75,7 @@ f.halign = :right end - it "fills and aligns contents to the right when max_size = :fill and halign = :right" do + it "fills and aligns contents to the right" do expected = " foo\n bar\n baz" actual = f.to_s @@ -88,7 +88,7 @@ f.halign = :center end - it "fills and aligns contents to the right when max_size = :fill and halign = :right" do + it "fills and aligns contents to the center" do expected = " foo \n bar \n baz " actual = f.to_s @@ -96,5 +96,55 @@ end end end + + context "max_size = :auto" do + before do + f.max_size = :auto + f.available_size = Tuple 6, 6 + + f.contents << "foo" + f.contents << "bar" + f.contents << "bazyx" + end + + context "halign = :left" do + before do + f.halign = :left + end + + it "aligns contents to the left" do + expected = "foo \nbar \nbazyx" + + actual = f.to_s + expect(actual).to eq expected + end + end + + context "halign = :right" do + before do + f.halign = :right + end + + it "aligns contents to the right" do + expected = " foo\n bar\nbazyx" + + actual = f.to_s + expect(actual).to eq expected + end + end + + context "halign = :center" do + before do + f.halign = :center + end + + it "aligns contents to the center" do + expected = " foo \n bar \nbazyx" + + actual = f.to_s + expect(actual).to eq expected + end + end + end end end From dca6a1d499cc000ec8639d13d8e4457931afa6a5 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Thu, 5 Oct 2023 23:51:21 -0500 Subject: [PATCH 048/152] Frame handles explicit sizes --- lib/remedy/frame.rb | 8 ++++++-- spec/frame_spec.rb | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index ddea7cc..7131095 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -53,7 +53,7 @@ def initialize # newline character @nl = ?\n end - attr_accessor :contents, :nl, :fill, :available_size, :max_size, :halign + attr_accessor :contents, :nl, :fill, :available_size, :max_size, :halign, :valign, :origin def to_a compile_contents @@ -79,6 +79,10 @@ def compile_contents rows = flat_contents.length cols = flat_contents.map(&:length).max || 0 size = Tuple(rows, cols) + elsif Tuple === max_size then + size = max_size + else + raise "Unknown max_size:#{max_size}" end if halign == :left then @@ -88,7 +92,7 @@ def compile_contents elsif halign == :center then Align.h_center_p line, size, fill: fill else - raise "Unknown alignment - halign:#{} max_size:#{max_size}" + raise "Unknown halign:#{halign}" end end end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 2c231d2..20ff3a4 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -47,6 +47,30 @@ expect(actual).to eq expected end + context "max_size = Tuple" do + before do + f.max_size = Tuple 5, 5 + f.available_size = Tuple 7, 7 + + f.contents << "foo" + f.contents << "bar" + f.contents << "baz" + end + + context "halign = :left" do + before do + f.halign = :left + end + + it "fills and aligns contents to the left" do + expected = "foo \nbar \nbaz " + + actual = f.to_s + expect(actual).to eq expected + end + end + end + context "max_size = :fill" do before do f.max_size = :fill From 10549e7c445b181d9ff3f2c4a54f3386a125b8bc Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:08:09 -0500 Subject: [PATCH 049/152] Add equality check to Tuple --- lib/remedy/frame.rb | 22 +++++++++++++++++----- lib/remedy/tuple.rb | 10 ++++++++++ spec/tuple_spec.rb | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 spec/tuple_spec.rb diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 7131095..ef5096d 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -67,18 +67,24 @@ def to_ansi compile_contents.join ANSI.cursor.next_line end + def content_size + sizeof merge_contents + end + + def merge_contents + contents.map{|c| Array c}.flatten + end + def compile_contents - flat_contents = contents.map{|c| Array c}.flatten + merged = merge_contents - flat_contents.map do |line| + merged.map do |line| if max_size == :none then next line elsif max_size == :fill then size = available_size elsif max_size == :auto then - rows = flat_contents.length - cols = flat_contents.map(&:length).max || 0 - size = Tuple(rows, cols) + size = sizeof merged elsif Tuple === max_size then size = max_size else @@ -96,5 +102,11 @@ def compile_contents end end end + + def sizeof content + height = content.length + width = content.map(&:length).max || 0 + Tuple height, width + end end end diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index 8eda814..248d679 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -63,6 +63,16 @@ def << value # COMPARISON + def == other_tuple + return false unless bijective? other_tuple + + self.dimensions.each.with_index do |d, i| + return false unless d == other_tuple[i] + end + + true + end + def fits_into? size_to_fit_into other_tuple = Tuple(size_to_fit_into) cardinality.times.each do |index| diff --git a/spec/tuple_spec.rb b/spec/tuple_spec.rb new file mode 100644 index 0000000..591477f --- /dev/null +++ b/spec/tuple_spec.rb @@ -0,0 +1,40 @@ +require_relative "spec_helper" +require "remedy/tuple" + +describe Remedy::Tuple do + subject(:t){ described_class.new 1, 1 } + + describe "#==" do + context "when other Tuple is the same" do + let(:other){ described_class.new 1, 1 } + + it "can tell when another Tuple has the same dimensional coordinates" do + expect(t == other).to be true + end + end + + context "when other is an Array" do + let(:other){ [1,1] } + + it "can tell that they are the same" do + expect(t == other).to be true + end + end + + context "when other Tuple has a different y" do + let(:other){ described_class.new 1, 2 } + + it "can tell that another Tuple is different" do + expect(t == other).to be false + end + end + + context "when other Tuple has a different cardinality" do + let(:other){ described_class.new 1, 1, 1 } + + it "can tell that another Tuple is different" do + expect(t == other).to be false + end + end + end +end From 810e51119942c01a2a534c7626124d3bd139312a Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:09:25 -0500 Subject: [PATCH 050/152] Frame can determine the size of its contents independent from itself --- spec/frame_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 20ff3a4..1f73e5d 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -5,6 +5,17 @@ describe Remedy::Frame do subject(:f){ described_class.new } + describe "#content_size" do + it "returns a Tuple of the contents dimensions" do + expected = Tuple 2, 4 + f.contents << "1234" + f.contents << "567" + + actual = f.content_size + expect(actual).to eq expected + end + end + describe "#to_s" do it "returns a string" do expected = String From 3256604a8f2484e357de437196ce0a2204d2ea75 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:12:19 -0500 Subject: [PATCH 051/152] Rename Frame max_size to size --- lib/remedy/frame.rb | 28 ++++++++++++++-------------- spec/frame_spec.rb | 12 ++++++------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index ef5096d..69e1472 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -37,8 +37,8 @@ def initialize # :auto - frame conforms to the size of its largest content and alignts to it # :fill - the frame tries to fill as much space as possible # Tuple - specify a Tuple to constrain it to a specific size - # Tuple.zero is same as :auto - @max_size = :none + # Tuple.zero is same as :none + @size = :none # this size is used when max_size is set to :fill # typically set by a screen object after resize @@ -53,7 +53,7 @@ def initialize # newline character @nl = ?\n end - attr_accessor :contents, :nl, :fill, :available_size, :max_size, :halign, :valign, :origin + attr_accessor :contents, :nl, :fill, :available_size, :size, :halign, :valign, :origin def to_a compile_contents @@ -79,24 +79,24 @@ def compile_contents merged = merge_contents merged.map do |line| - if max_size == :none then + if size == :none then next line - elsif max_size == :fill then - size = available_size - elsif max_size == :auto then - size = sizeof merged - elsif Tuple === max_size then - size = max_size + elsif size == :fill then + compiled_size = available_size + elsif size == :auto then + compiled_size = sizeof merged + elsif Tuple === size then + compiled_size = size else - raise "Unknown max_size:#{max_size}" + raise "Unknown max_size:#{size}" end if halign == :left then - Align.left_p line, size, fill: fill + Align.left_p line, compiled_size, fill: fill elsif halign == :right then - Align.right_p line, size, fill: fill + Align.right_p line, compiled_size, fill: fill elsif halign == :center then - Align.h_center_p line, size, fill: fill + Align.h_center_p line, compiled_size, fill: fill else raise "Unknown halign:#{halign}" end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 1f73e5d..a1b4ad6 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -58,9 +58,9 @@ expect(actual).to eq expected end - context "max_size = Tuple" do + context "size = Tuple" do before do - f.max_size = Tuple 5, 5 + f.size = Tuple 5, 5 f.available_size = Tuple 7, 7 f.contents << "foo" @@ -82,9 +82,9 @@ end end - context "max_size = :fill" do + context "size = :fill" do before do - f.max_size = :fill + f.size = :fill f.available_size = Tuple 6, 6 f.contents << "foo" @@ -132,9 +132,9 @@ end end - context "max_size = :auto" do + context "size = :auto" do before do - f.max_size = :auto + f.size = :auto f.available_size = Tuple 6, 6 f.contents << "foo" From 6620a593f52c916a70178f6589dc8074d5208e05 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:32:23 -0500 Subject: [PATCH 052/152] Fixing typo in valign --- lib/remedy/frame.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 69e1472..fc208ac 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -18,7 +18,7 @@ def initialize # vertical alignment of contents # :top, :bottom, :center - @halign = :top + @valign = :top # depth is the z index or layer, higher numbers cover lower numbers # if two frames have the same layer but would overlap, then the one added most recently should come out on top From e932c9e1e6168d565c3706e2df675e5b5f50ea96 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:33:03 -0500 Subject: [PATCH 053/152] Only calculate the compiled_size once --- lib/remedy/frame.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index fc208ac..76befb2 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -78,19 +78,19 @@ def merge_contents def compile_contents merged = merge_contents - merged.map do |line| - if size == :none then - next line - elsif size == :fill then - compiled_size = available_size - elsif size == :auto then - compiled_size = sizeof merged - elsif Tuple === size then - compiled_size = size - else - raise "Unknown max_size:#{size}" - end + if size == :none then + return merged + elsif size == :fill then + compiled_size = available_size + elsif size == :auto then + compiled_size = sizeof merged + elsif Tuple === size then + compiled_size = size + else + raise "Unknown max_size:#{size}" + end + haligned = merged.map do |line| if halign == :left then Align.left_p line, compiled_size, fill: fill elsif halign == :right then From 29590007cfe9cf673e07801791dc6f25a313bafa Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:34:05 -0500 Subject: [PATCH 054/152] Size now sets the actual resulting size of the output, including empty space --- lib/remedy/frame.rb | 4 ++++ spec/frame_spec.rb | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 76befb2..c3117ef 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -101,6 +101,10 @@ def compile_contents raise "Unknown halign:#{halign}" end end + + buf = Screenbuffer.new compiled_size, fill: fill, nl: nl + buf[0,0] = haligned + buf.to_a end def sizeof content diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index a1b4ad6..d4c5609 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -74,7 +74,7 @@ end it "fills and aligns contents to the left" do - expected = "foo \nbar \nbaz " + expected = "foo \nbar \nbaz \n \n " actual = f.to_s expect(actual).to eq expected @@ -98,7 +98,7 @@ end it "fills and aligns contents to the left" do - expected = "foo \nbar \nbaz " + expected = "foo \nbar \nbaz \n \n \n " actual = f.to_s expect(actual).to eq expected @@ -111,7 +111,7 @@ end it "fills and aligns contents to the right" do - expected = " foo\n bar\n baz" + expected = " foo\n bar\n baz\n \n \n " actual = f.to_s expect(actual).to eq expected @@ -124,7 +124,7 @@ end it "fills and aligns contents to the center" do - expected = " foo \n bar \n baz " + expected = " foo \n bar \n baz \n \n \n " actual = f.to_s expect(actual).to eq expected From 208b60482efb3c458b5b8b24f09326392d3a99ac Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:50:20 -0500 Subject: [PATCH 055/152] Fix typo in Tuple, improve error messages --- lib/remedy/frame.rb | 14 +++++++++++++- lib/remedy/tuple.rb | 6 +++--- spec/frame_spec.rb | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index c3117ef..e8e4550 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -90,6 +90,7 @@ def compile_contents raise "Unknown max_size:#{size}" end + # TODO: this could probably be replaced with direct buffer insertions haligned = merged.map do |line| if halign == :left then Align.left_p line, compiled_size, fill: fill @@ -103,7 +104,18 @@ def compile_contents end buf = Screenbuffer.new compiled_size, fill: fill, nl: nl - buf[0,0] = haligned + case valign + when :top + voffset = 0 + when :bottom + voffset = compiled_size - merged.length + when :center + voffset = Align.mido merged.length, compiled_size.height + else + raise "Unknown valign:#{valign}" + end + + buf[voffset,0] = haligned buf.to_a end diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index 248d679..2e41e93 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -151,11 +151,11 @@ def scalar_subtract! amount end def scalar_subtract amount - dup scalar_subtract! amount + dup.scalar_subtract! amount end def matrix_subtract other_tuple - raise "Different numbers of dimensions!" unless bijective? other_tuple + raise "Different numbers of dimensions! (#{cardinality} vs #{other_tuple.cardinality})" unless bijective? other_tuple result = cardinality.times.inject self.class.new do |difference, index| difference << self[index] - other_tuple[index] @@ -175,7 +175,7 @@ def scalar_addition amount end def matrix_addition other_tuple - raise "Different numbers of dimensions!" unless bijective? other_tuple + raise "Different numbers of dimensions! (#{cardinality} vs #{other_tuple.cardinality})" unless bijective? other_tuple result = cardinality.times.inject self.class.new do |sum, index| sum << self[index] + other_tuple[index] diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index d4c5609..4299032 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -182,4 +182,29 @@ end end end + + describe "modal dialog style" do + before do + f.origin = :center + f.halign = :center + f.valign = :center + f.size = Tuple(5,7) + f.available_size = Tuple(11,11) + + f.contents << "lol" + end + + it "content appears centered" do + expected = [ + " ", + " ", + " lol ", + " ", + " " + ].join ?\n + + actual = f.to_s + expect(actual).to eq expected + end + end end From 3985b6e6ad5575bd5a86580d9cb74c5ffe571152 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:51:43 -0500 Subject: [PATCH 056/152] Method name tweaks and improve documentation --- lib/remedy/align.rb | 12 ++++++------ lib/remedy/frame.rb | 2 ++ lib/remedy/screen.rb | 2 +- lib/remedy/screenbuffer.rb | 7 ++++++- spec/frame_spec.rb | 3 +-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/remedy/align.rb b/lib/remedy/align.rb index 6003d67..527561a 100644 --- a/lib/remedy/align.rb +++ b/lib/remedy/align.rb @@ -31,10 +31,10 @@ def right_p content, size, fill: " " # # @param content [Remedy::Partial] any object that responds to `height`, `width`, and a `to_a` that returns an array of strings # @return [content] whatever was passed in as the `content` param will be returned - def buffer_center content, screenbuffer - voffset = middle content.height, screenbuffer.size.height - hoffset = middle content.width, screenbuffer.size.width - screenbuffer[voffset,hoffset] = content + def hv_center content, buffer + voffset = mido content.height, buffer.size.height + hoffset = mido content.width, buffer.size.width + buffer[voffset,hoffset] = content end # Given the actual space something takes up, @@ -43,7 +43,7 @@ def buffer_center content, screenbuffer # @param actual [Numeric] the space already taken # @param available [Numeric] the available space # @return [Integer] the offset from the end of the availabe space to center the actual content - def middle actual, available + def mido actual, available return 0 unless actual < available offset = ((available - actual) / 2.0).floor @@ -60,7 +60,7 @@ def middle actual, available def middle_spacing actual, available return [0,0] unless actual < available - head = middle actual, available + head = mido actual, available tail = available - (head + actual) [head, tail] diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index e8e4550..cc9cfeb 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -1,4 +1,6 @@ require "remedy/tuple" +require "remedy/align" +require "remedy/screenbuffer" module Remedy # Frames contain Panes and Panes contain Partials diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 4aa0d76..fae6c20 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -29,7 +29,7 @@ def initialize auto_resize: true # @return [void] def draw override = nil if override then - Align.buffer_center override, buffer + Align.hv_center override, buffer else refresh_buffer end diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index fb13e0f..72b6df3 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -106,7 +106,12 @@ def size= new_size end end - # Convert screenbuffer to single string. + # @return [Array] the contents of the buffer as an array of strings + def to_a + buf + end + + # Convert screenbuffer to single String. # Concatenates the contents of the buffer with the `nl` attribute. def to_s buf.join nl diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 4299032..3976f90 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -183,9 +183,8 @@ end end - describe "modal dialog style" do + describe "centered alignment" do before do - f.origin = :center f.halign = :center f.valign = :center f.size = Tuple(5,7) From 4da002eadedbf061b5416a80c597dde49babb687 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:52:15 -0500 Subject: [PATCH 057/152] Frame can bottom align text --- lib/remedy/frame.rb | 2 +- spec/frame_spec.rb | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index cc9cfeb..0bd3b92 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -110,7 +110,7 @@ def compile_contents when :top voffset = 0 when :bottom - voffset = compiled_size - merged.length + voffset = compiled_size.height - merged.length when :center voffset = Align.mido merged.length, compiled_size.height else diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 3976f90..0d25a6a 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -206,4 +206,28 @@ expect(actual).to eq expected end end + + describe "bottom alignment" do + before do + f.halign = :center + f.valign = :bottom + f.size = Tuple(5,7) + f.available_size = Tuple(11,11) + + f.contents << "lol" + end + + it "content appears centered in the bottom" do + expected = [ + " ", + " ", + " ", + " ", + " lol " + ].join ?\n + + actual = f.to_s + expect(actual).to eq expected + end + end end From 01d9acb8679702d878fb47262aca4d242023011b Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 00:59:20 -0500 Subject: [PATCH 058/152] Add Editorconfig file --- .editorconfig | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..95c370a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig is awesome: http://EditorConfig.org + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true From 6950aa8ece0fd946dbd2c4b2b60099742cbee595 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 01:32:58 -0500 Subject: [PATCH 059/152] Frames can be oriented in the screen according to their origin --- lib/remedy/frame.rb | 46 +++++++++++++++++++++++++++++--------------- lib/remedy/screen.rb | 26 ++++++++++++++++++++++++- spec/screen_spec.rb | 30 +++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 16 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 0bd3b92..4124f57 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -7,9 +7,13 @@ module Remedy # Frames can be nested within other Frames or Panes class Frame def initialize - # origin is where the frame will be attached to - # :left, :right, :top, :bottom, :center - @origin = :center + # vorigin is where the frame will be attached vertically + # :top, :bottom, :center + @vorigin = :top + + # horigin is where the frame will be attached horizontally + # :left, :right, :center + @horigin = :left # offset is what the offset from that origin the frame should be placed @offset = Tuple.zero @@ -55,7 +59,7 @@ def initialize # newline character @nl = ?\n end - attr_accessor :contents, :nl, :fill, :available_size, :size, :halign, :valign, :origin + attr_accessor :contents, :nl, :fill, :available_size, :size, :halign, :valign, :horigin, :vorigin def to_a compile_contents @@ -77,17 +81,21 @@ def merge_contents contents.map{|c| Array c}.flatten end + def compiled_size + sizeof compile_contents + end + def compile_contents merged = merge_contents if size == :none then return merged elsif size == :fill then - compiled_size = available_size + compile_size = available_size elsif size == :auto then - compiled_size = sizeof merged + compile_size = sizeof merged elsif Tuple === size then - compiled_size = size + compile_size = size else raise "Unknown max_size:#{size}" end @@ -95,24 +103,24 @@ def compile_contents # TODO: this could probably be replaced with direct buffer insertions haligned = merged.map do |line| if halign == :left then - Align.left_p line, compiled_size, fill: fill + Align.left_p line, compile_size, fill: fill elsif halign == :right then - Align.right_p line, compiled_size, fill: fill + Align.right_p line, compile_size, fill: fill elsif halign == :center then - Align.h_center_p line, compiled_size, fill: fill + Align.h_center_p line, compile_size, fill: fill else raise "Unknown halign:#{halign}" end end - buf = Screenbuffer.new compiled_size, fill: fill, nl: nl + buf = Screenbuffer.new compile_size, fill: fill, nl: nl case valign when :top voffset = 0 when :bottom - voffset = compiled_size.height - merged.length + voffset = compile_size.height - merged.length when :center - voffset = Align.mido merged.length, compiled_size.height + voffset = Align.mido merged.length, compile_size.height else raise "Unknown valign:#{valign}" end @@ -122,9 +130,17 @@ def compile_contents end def sizeof content - height = content.length - width = content.map(&:length).max || 0 + lines = Array(content).map do |line| + split line + end.flatten + + height = lines.length + width = lines.map(&:length).max || 0 Tuple height, width end + + def split line + line.split(/\r\n|\n\r|\n|\r/) + end end end diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index fae6c20..17385c3 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -85,7 +85,31 @@ def refresh_buffer def populate_buffer frames.sort(&:depth).each do |frame| frame.available_size = buffer.size - buffer[0,0] = frame + fsize = frame.compiled_size + + case frame.vorigin + when :top + voffset = 0 + when :center + voffset = Align.mido fsize.height, buffer.size.height + when :bottom + voffset = buffer.size.height - compiled_size.height + else + raise "Unknown vorigin:#{frame.vorigin}" + end + + case frame.horigin + when :left + hoffset = 0 + when :center + hoffset = Align.mido fsize.width, buffer.size.width + when :right + hoffset = buffer.size.width - compiled_size.width + else + raise "Unknown horigin:#{frame.horigin}" + end + + buffer[voffset,hoffset] = frame end end end diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 9e475c7..eb89fec 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -88,4 +88,34 @@ expect(actual).to eq expected end end + + describe "#to_s" do + let(:size_override){ Tuple 5, 11 } + + let(:frame) do + f = ::Remedy::Frame.new + f.contents << "foo" + f.contents << "bar\nbaz" + f.vorigin = :center + f.horigin = :center + f.size = :none + f + end + + it "attaches frames where they specify" do + expected = [ + " ", + " foo ", + " bar ", + " baz ", + " " + ].join ?\n + + s.buffer.fill = " " + s.frames << frame + actual = s.to_s + + expect(actual).to eq expected + end + end end From bab29e0e2022b2021f2df16e50137745f6cfc04c Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Fri, 6 Oct 2023 22:37:55 -0500 Subject: [PATCH 060/152] Sortable frame depth fix --- lib/remedy/frame.rb | 2 +- lib/remedy/screen.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 4124f57..4e409bc 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -59,7 +59,7 @@ def initialize # newline character @nl = ?\n end - attr_accessor :contents, :nl, :fill, :available_size, :size, :halign, :valign, :horigin, :vorigin + attr_accessor :contents, :nl, :fill, :available_size, :size, :halign, :valign, :horigin, :vorigin, :depth def to_a compile_contents diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 17385c3..5ef10a7 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -83,7 +83,7 @@ def refresh_buffer end def populate_buffer - frames.sort(&:depth).each do |frame| + frames.sort_by(&:depth).each do |frame| frame.available_size = buffer.size fsize = frame.compiled_size From 6766c74566ea5dd78e241058a09c37e31a428a95 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 07:21:35 -0500 Subject: [PATCH 061/152] Reorganize frame test for better output --- spec/frame_spec.rb | 83 ++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 0d25a6a..733ca42 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -57,6 +57,9 @@ actual = f.to_s expect(actual).to eq expected end + end + + describe "size and alignment" do context "size = Tuple" do before do @@ -181,53 +184,53 @@ end end end - end - describe "centered alignment" do - before do - f.halign = :center - f.valign = :center - f.size = Tuple(5,7) - f.available_size = Tuple(11,11) - - f.contents << "lol" - end + describe "centered alignment" do + before do + f.halign = :center + f.valign = :center + f.size = Tuple(5,7) + f.available_size = Tuple(11,11) - it "content appears centered" do - expected = [ - " ", - " ", - " lol ", - " ", - " " - ].join ?\n + f.contents << "lol" + end - actual = f.to_s - expect(actual).to eq expected + it "content appears centered" do + expected = [ + " ", + " ", + " lol ", + " ", + " " + ].join ?\n + + actual = f.to_s + expect(actual).to eq expected + end end - end - - describe "bottom alignment" do - before do - f.halign = :center - f.valign = :bottom - f.size = Tuple(5,7) - f.available_size = Tuple(11,11) - f.contents << "lol" - end + describe "bottom alignment" do + before do + f.halign = :center + f.valign = :bottom + f.size = Tuple(5,7) + f.available_size = Tuple(11,11) - it "content appears centered in the bottom" do - expected = [ - " ", - " ", - " ", - " ", - " lol " - ].join ?\n + f.contents << "lol" + end - actual = f.to_s - expect(actual).to eq expected + it "content appears centered in the bottom" do + expected = [ + " ", + " ", + " ", + " ", + " lol " + ].join ?\n + + actual = f.to_s + expect(actual).to eq expected + end end end end From 0b2e8ce54b2421a46d5cd10ecd495309b2e87981 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 07:22:51 -0500 Subject: [PATCH 062/152] Frames with 0 dimension sizes act as fill for that dimension --- lib/remedy/frame.rb | 8 +++++++- lib/remedy/tuple.rb | 4 ++++ spec/frame_spec.rb | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 4e409bc..4406f8f 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -95,7 +95,13 @@ def compile_contents elsif size == :auto then compile_size = sizeof merged elsif Tuple === size then - compile_size = size + compile_size = size.dup + if size.height == 0 then + compile_size[0] = available_size.height + end + if size.width == 0 then + compile_size[1] = available_size.width + end else raise "Unknown max_size:#{size}" end diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index 2e41e93..a38cba2 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -119,6 +119,10 @@ def [] index dimensions[index] end + def []= index, value + dimensions[index] = value + end + def cardinality dimensions.length end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 733ca42..fc1544c 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -232,5 +232,46 @@ expect(actual).to eq expected end end + + describe "0 height Tuple" do + before do + f.halign = :center + f.valign = :bottom + f.size = Tuple(0,7) + f.available_size = Tuple(3,11) + + f.contents << "lol" + end + it "stretches to the vertical bounds of the container" do + expected = [ + " ", + " ", + " lol " + ].join ?\n + + actual = f.to_s + expect(actual).to eq expected + end + end + + describe "0 width tuple" do + before do + f.halign = :center + f.valign = :bottom + f.size = Tuple(1,0) + f.available_size = Tuple(3,11) + + f.contents << "lol" + end + + it "stretches to the horizontal bounds of the container" do + expected = [ + " lol " + ].join ?\n + + actual = f.to_s + expect(actual).to eq expected + end + end end end From bc6f91a18aa1ca3ae8a3b02ca750add7e3f76914 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 07:25:52 -0500 Subject: [PATCH 063/152] Fix lingering old name from a rename --- lib/remedy/screen.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 5ef10a7..ca354d2 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -93,7 +93,7 @@ def populate_buffer when :center voffset = Align.mido fsize.height, buffer.size.height when :bottom - voffset = buffer.size.height - compiled_size.height + voffset = buffer.size.height - fsize.height else raise "Unknown vorigin:#{frame.vorigin}" end @@ -104,7 +104,7 @@ def populate_buffer when :center hoffset = Align.mido fsize.width, buffer.size.width when :right - hoffset = buffer.size.width - compiled_size.width + hoffset = buffer.size.width - fsize.width else raise "Unknown horigin:#{frame.horigin}" end From fee0344f7ae56698d7a7ab99b6cf488e31a5abc4 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 07:46:40 -0500 Subject: [PATCH 064/152] Frames with fractional sizes take up that portion of the container --- lib/remedy/frame.rb | 6 ++++++ spec/frame_spec.rb | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 4406f8f..da2bbea 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -96,11 +96,17 @@ def compile_contents compile_size = sizeof merged elsif Tuple === size then compile_size = size.dup + if size.height == 0 then compile_size[0] = available_size.height + elsif size.height < 1 then + compile_size[0] = (available_size.height * size.height).floor end + if size.width == 0 then compile_size[1] = available_size.width + elsif size.width < 1 then + compile_size[1] = (available_size.width * size.width).floor end else raise "Unknown max_size:#{size}" diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index fc1544c..5bfae4b 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -273,5 +273,48 @@ expect(actual).to eq expected end end + + describe "fractional width size" do + before do + f.halign = :center + f.valign = :bottom + f.size = Tuple(0,0.5) + f.available_size = Tuple(3,11) + + f.contents << "lol" + end + + it "stretches to half the horizontal bounds of the container" do + expected = [ + " ", + " ", + " lol " + ].join ?\n + + actual = f.to_s + expect(actual).to eq expected + end + end + + describe "fractional height size" do + before do + f.halign = :center + f.valign = :bottom + f.size = Tuple(0.5,0) + f.available_size = Tuple(4,11) + + f.contents << "lol" + end + + it "stretches to half the vertical bounds of the container" do + expected = [ + " ", + " lol " + ].join ?\n + + actual = f.to_s + expect(actual).to eq expected + end + end end end From 9dcdef711f0242426e6c6657a32a633d73bcbef7 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 08:33:04 -0500 Subject: [PATCH 065/152] Frames are named, good for debugging --- lib/remedy/frame.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index da2bbea..01ae4a3 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -6,7 +6,9 @@ module Remedy # Frames contain Panes and Panes contain Partials # Frames can be nested within other Frames or Panes class Frame - def initialize + def initialize name: self.object_id + @name = name + # vorigin is where the frame will be attached vertically # :top, :bottom, :center @vorigin = :top @@ -59,7 +61,7 @@ def initialize # newline character @nl = ?\n end - attr_accessor :contents, :nl, :fill, :available_size, :size, :halign, :valign, :horigin, :vorigin, :depth + attr_accessor :name, :contents, :nl, :fill, :available_size, :size, :halign, :valign, :horigin, :vorigin, :depth def to_a compile_contents From 31b3dcb36622b64243c6946b41155b754fdb2be3 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 08:33:42 -0500 Subject: [PATCH 066/152] Screen redraw can be disabled when resize is called This makes testing easier, but probably will help with multiple screen objects being used at once, and other situations. --- lib/remedy/screen.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index ca354d2..3defff6 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -57,9 +57,9 @@ def frames # # @param new_size [Remedy::Tuple] the new size of the terminal # @return [void] - def resized new_size + def resized new_size, redraw: true buffer.size = new_size - draw + draw if redraw end def to_a From 99ba7253b4ad0733600433848462bc2b9c894597 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 08:35:03 -0500 Subject: [PATCH 067/152] Screen test uses Partial --- spec/screen_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index eb89fec..55e538a 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -1,6 +1,7 @@ require_relative "spec_helper" require "remedy/screen" require "remedy/frame" +require "remedy/partial" require "stringio" describe Remedy::Screen do From 9484c7d15d56b6d86e665e1254e553cf3b843267 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 19:56:14 -0500 Subject: [PATCH 068/152] Document buffer param in Align method --- lib/remedy/align.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/remedy/align.rb b/lib/remedy/align.rb index 527561a..5b8d9b0 100644 --- a/lib/remedy/align.rb +++ b/lib/remedy/align.rb @@ -30,6 +30,7 @@ def right_p content, size, fill: " " # Center content in the middle of a buffer, both vertically and horizontally. # # @param content [Remedy::Partial] any object that responds to `height`, `width`, and a `to_a` that returns an array of strings + # @param buffer [Remedy::Screenbuffer] a screenbuffer to write to # @return [content] whatever was passed in as the `content` param will be returned def hv_center content, buffer voffset = mido content.height, buffer.size.height From b2feb0f255996aa7507c49b231fbe6664ee0d14e Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 19:57:16 -0500 Subject: [PATCH 069/152] Ensure that newlines are split in merged contents Also refactors Frame drawing to specify the location of any horizontal offsets directly in the buffer write. But there is currently no way to space out the lines without processing them individually and adding string padding. This seems weird, but may be better than using a bunch of different writes to screenbuffer? May move to individual screenbuffer writes in the future if there is a layering or transparency concern. --- lib/remedy/frame.rb | 41 +++++++++++++++++++++++++---------------- spec/frame_spec.rb | 26 +++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 01ae4a3..2f8a105 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -80,7 +80,9 @@ def content_size end def merge_contents - contents.map{|c| Array c}.flatten + contents.map{|c| Array c}.flatten.map do |line| + split line + end.flatten end def compiled_size @@ -89,13 +91,15 @@ def compiled_size def compile_contents merged = merge_contents + merged_size = sizeof merged + compile_size = nil # will be overwritten lower down if size == :none then return merged elsif size == :fill then compile_size = available_size elsif size == :auto then - compile_size = sizeof merged + compile_size = merged_size elsif Tuple === size then compile_size = size.dup @@ -114,32 +118,37 @@ def compile_contents raise "Unknown max_size:#{size}" end - # TODO: this could probably be replaced with direct buffer insertions - haligned = merged.map do |line| - if halign == :left then - Align.left_p line, compile_size, fill: fill - elsif halign == :right then - Align.right_p line, compile_size, fill: fill - elsif halign == :center then - Align.h_center_p line, compile_size, fill: fill - else - raise "Unknown halign:#{halign}" + buf = Screenbuffer.new compile_size, fill: fill, nl: nl + + case halign + when :left + hoffset = 0 + when :right + hoffset = compile_size.width - merged_size.width + merged.map! do |line| + line = Align.right_p line, merged_size, fill: fill end + when :center + hoffset = Align.mido merged_size.width, compile_size.width + merged.map! do |line| + line = Align.h_center_p line, merged_size, fill: fill + end + else + raise "Unknown halign:#{halign}" end - buf = Screenbuffer.new compile_size, fill: fill, nl: nl case valign when :top voffset = 0 when :bottom - voffset = compile_size.height - merged.length + voffset = compile_size.height - merged_size.height when :center - voffset = Align.mido merged.length, compile_size.height + voffset = Align.mido merged_size.height, compile_size.height else raise "Unknown valign:#{valign}" end - buf[voffset,0] = haligned + buf[voffset,hoffset] = merged buf.to_a end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 5bfae4b..77809c7 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -185,7 +185,7 @@ end end - describe "centered alignment" do + context "size = Tuple halign = :center" do before do f.halign = :center f.valign = :center @@ -207,6 +207,30 @@ actual = f.to_s expect(actual).to eq expected end + + it "maintains centering when there are newlines in contents" do + # I was getting this weird output: + # ...... + # ...a.. + # ...b.. + # ..c... + # d..... + # This was due to the starting calculations being based on + # the length of unsplit lines. + + expected = [ + " a ", + " b ", + " c ", + " d ", + " " + ].join ?\n + + f.contents = ["a", "b", "c\nd"] + + actual = f.to_s + expect(actual).to eq expected + end end describe "bottom alignment" do From bb537b4a69865694ec87ab930469a09ae376b375 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 20:36:09 -0500 Subject: [PATCH 070/152] Dup a Tuple and get a new dimension Array too This fixes a bug where a dupped Tuple being changed was inadvertantly changing both the parent and the child. This meant, that, for example, frames would maintain their initial size as the fraction was getting overriden with a whole integer value. --- lib/remedy/tuple.rb | 6 ++++++ spec/tuple_spec.rb | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index a38cba2..ef3eeb6 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -146,6 +146,12 @@ def inspect "#<#{self.class}:#{to_s}>" end + def dup + new_tuple = super + new_tuple.dimensions = dimensions.dup + new_tuple + end + protected def scalar_subtract! amount diff --git a/spec/tuple_spec.rb b/spec/tuple_spec.rb index 591477f..245829c 100644 --- a/spec/tuple_spec.rb +++ b/spec/tuple_spec.rb @@ -37,4 +37,14 @@ end end end + + describe "#dup" do + let(:other){ t.dup } + + it "creates a new dimension array" do + expect(other[1] == t[1]).to be true + t[1] = 88 + expect(other[1] == t[1]).to be false + end + end end From 9c47e8a34dfdb90e040cfc72d6b9ca5732f9306c Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 20:45:58 -0500 Subject: [PATCH 071/152] Reorganize Screen spec, update test to make the inner frame more visible --- spec/screen_spec.rb | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 55e538a..399faac 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -9,6 +9,17 @@ let(:console){ ::Remedy::Console } let(:size){ Tuple 20, 40 } let(:size_override){ Tuple 20, 40 } + let(:frame) do + f = ::Remedy::Frame.new + f.contents << "foo" + f.contents << "bar\nbaz" + f.vorigin = :center + f.horigin = :center + f.valign = :center + f.halign = :center + f.size = :none + f + end before(:each) do console.size_override = size_override @@ -90,30 +101,28 @@ end end - describe "#to_s" do - let(:size_override){ Tuple 5, 11 } + describe "#resized" do + let(:new_size_override){ Tuple 5, 9 } - let(:frame) do - f = ::Remedy::Frame.new - f.contents << "foo" - f.contents << "bar\nbaz" - f.vorigin = :center - f.horigin = :center - f.size = :none - f + before do + frame.size = Tuple(0, 0.5) + frame.fill = "." end - it "attaches frames where they specify" do + it "resizes internal frames" do + # This is a very surface level test which did not detect a bug caused by + # Tuple#dup, but still demonstrates the basics expected = [ - " ", - " foo ", - " bar ", - " baz ", - " " + " .... ", + " foo. ", + " bar. ", + " baz. ", + " .... " ].join ?\n s.buffer.fill = " " s.frames << frame + s.resized new_size_override, redraw: false actual = s.to_s expect(actual).to eq expected From 464869fcf10db8100160e4b717324e5b91dac953 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 20:46:42 -0500 Subject: [PATCH 072/152] Add test to ensure that Screenbuffers can have contents placed outside of their bounds --- spec/screenbuffer_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index d7dd44f..a62b140 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -113,6 +113,17 @@ expect(actual).to eq expected end + it "truncates content when passed a negative index" do + # this enabled resizing the terminal smaller than fixed content sizes + # and moving windows partially off screen + + value = %w(1 2 3) + expected = "2.\n3." + sb[-1,0] = value + actual = sb.to_s + expect(actual).to eq expected + end + context "without ellipsis" do subject(:sb){ described_class.new size, fill: ".", ellipsis: nil } From 471fccb7263c2d9c4bd2094ee5a9558413554320 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 21:27:34 -0500 Subject: [PATCH 073/152] Frames can now have a columnar internal arrangement for their contents --- lib/remedy/frame.rb | 32 +++++++++++++++-- spec/frame_spec.rb | 85 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 2f8a105..3363b4b 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -34,7 +34,7 @@ def initialize name: self.object_id # arrangement, if this frame contains multiple content items, then they will be arranged according to this # :stacked, :columnar, :tabbed(?) - @arragement = :stacked + @arrangement = :stacked # spacing as to how multiple content items should be spaced # :none, :evenly @@ -61,7 +61,7 @@ def initialize name: self.object_id # newline character @nl = ?\n end - attr_accessor :name, :contents, :nl, :fill, :available_size, :size, :halign, :valign, :horigin, :vorigin, :depth + attr_accessor :name, :contents, :nl, :fill, :available_size, :size, :halign, :valign, :horigin, :vorigin, :depth, :arrangement def to_a compile_contents @@ -80,9 +80,35 @@ def content_size end def merge_contents - contents.map{|c| Array c}.flatten.map do |line| + merged = contents.map do |c| + content = Array c + + content.map! do |line| split line end.flatten + end + + case arrangement + when :stacked + # TODO: insert padding? + merged.flatten + when :columnar + msize = sizeof merged.flatten + result = Array.new + msize.width.times do |index| + fullline = "" + merged.each do |content| + line = content[index] + if line then + fullline << line + end + end + result << fullline + end + result.flatten + else + raise "unknown arrangement: #{arrangement}" + end end def compiled_size diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 77809c7..2a420f3 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -341,4 +341,89 @@ end end end + + describe "arrangement" do + + context "with strings" + before do + f.contents << "a" + f.contents << "b" + f.contents << "c" + end + + context "arrangement = stacked" do + before do + f.arrangement = :stacked + end + + it "arranges contents on top of each other" do + expected = "a\nb\nc" + actual = f.to_s + expect(actual).to eq expected + end + end + + context "arrangement = columnar" do + before do + f.arrangement = :columnar + end + + it "arranges contents next to each other" do + expected = "abc" + actual = f.to_s + expect(actual).to eq expected + end + end + end + + context "with nested frames" do + let(:f1) do + f1 = described_class.new + f1.contents << "a" + f1 + end + let(:f2) do + f2 = described_class.new + f2.contents << "b" + f2 + end + let(:f3) do + f3 = described_class.new + f3.contents << "c" + f3.size = Tuple 3, 3 + f3.valign = :center + f3.halign = :center + f3 + end + + before do + f.contents << f1 + f.contents << f2 + f.contents << f3 + end + + context "arrangement = stacked" do + before do + f.arrangement = :stacked + end + + it "arranges contents on top of each other" do + expected = "a\nb\n \n c \n " + actual = f.to_s + expect(actual).to eq expected + end + end + + context "arrangement = columnar" do + before do + f.arrangement = :columnar + end + + it "arranges contents next to each other" do + expected = "ab \n c \n " + actual = f.to_s + expect(actual).to eq expected + end + end + end end From 2a86dc447c3b76eb09e37af8b13eac71ed30e67c Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 21:56:22 -0500 Subject: [PATCH 074/152] Reorganize Frame accessors, add offset Offset doesn't do anything yet, it is only exposed for later use. --- lib/remedy/frame.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 3363b4b..ead9ea8 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -61,7 +61,11 @@ def initialize name: self.object_id # newline character @nl = ?\n end - attr_accessor :name, :contents, :nl, :fill, :available_size, :size, :halign, :valign, :horigin, :vorigin, :depth, :arrangement + + attr_accessor :vorigin, :horigin, :offset, :depth + attr_accessor :name, :size, :available_size + attr_accessor :nl, :fill, :halign, :valign + attr_accessor :contents, :arrangement def to_a compile_contents From 3c4fff3d378e6d8e3bf498299bd958b66abfbe9a Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 21:57:37 -0500 Subject: [PATCH 075/152] Stringify anything passed to Frame#split Lots of things are passed to Frame#split, and errors are hard to track down without know the context anyway. So hopefully this helps rather than hinders. --- lib/remedy/frame.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index ead9ea8..7c275e4 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -193,7 +193,7 @@ def sizeof content end def split line - line.split(/\r\n|\n\r|\n|\r/) + line.to_s.split(/\r\n|\n\r|\n|\r/) end end end From 06097bfc89bc1a2a9ffe2aca99eebbf3c0d8a27d Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 22:14:39 -0500 Subject: [PATCH 076/152] Frames in a Screen can now be offset by arbitrary values from point of origin --- lib/remedy/frame.rb | 8 +++++++- lib/remedy/screen.rb | 3 +++ spec/screen_spec.rb | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 7c275e4..467e779 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -62,11 +62,17 @@ def initialize name: self.object_id @nl = ?\n end - attr_accessor :vorigin, :horigin, :offset, :depth + attr_accessor :vorigin, :horigin, :depth attr_accessor :name, :size, :available_size attr_accessor :nl, :fill, :halign, :valign attr_accessor :contents, :arrangement + # Sets the offset from the origin point. + # @note Positive offsets always move the frame right and down, + # so negative values are more useful when `horigin = :right` and/or `vorigin = :bottom`. + # @return [Remedy::Tuple] the vertical and horizontal offset to apply + attr_accessor :offset + def to_a compile_contents end diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 3defff6..356558a 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -109,6 +109,9 @@ def populate_buffer raise "Unknown horigin:#{frame.horigin}" end + voffset += frame.offset.height + hoffset += frame.offset.width + buffer[voffset,hoffset] = frame end end diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 399faac..044775e 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -128,4 +128,36 @@ expect(actual).to eq expected end end + + describe "offset frames" do + let(:new_size_override){ Tuple 5, 9 } + + before do + frame.vorigin = :bottom + frame.horigin = :right + frame.halign = :center + frame.valign = :center + frame.offset = Tuple -1, -2 + frame.size = Tuple(4, 0.5) + frame.fill = "." + end + + it "moves the frame away from the point of origin" do + + expected = [ + " foo. ", + " bar. ", + " baz. ", + " .... ", + " " + ].join ?\n + + s.buffer.fill = " " + s.frames << frame + s.resized new_size_override, redraw: false + actual = s.to_s + + expect(actual).to eq expected + end + end end From 4f0e8114405e511d6f09810523b2295e098c32d7 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 22:24:09 -0500 Subject: [PATCH 077/152] Move methods up to make room for breaking down the big compile method --- lib/remedy/frame.rb | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 467e779..1eaecdf 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -89,6 +89,21 @@ def content_size sizeof merge_contents end + def sizeof content + lines = Array(content).map do |line| + split line + end.flatten + + height = lines.length + width = lines.map(&:length).max || 0 + Tuple height, width + end + + # @todo move this to a helper module or something + def split line + line.to_s.split(/\r\n|\n\r|\n|\r/) + end + def merge_contents merged = contents.map do |c| content = Array c @@ -187,19 +202,5 @@ def compile_contents buf[voffset,hoffset] = merged buf.to_a end - - def sizeof content - lines = Array(content).map do |line| - split line - end.flatten - - height = lines.length - width = lines.map(&:length).max || 0 - Tuple height, width - end - - def split line - line.to_s.split(/\r\n|\n\r|\n|\r/) - end end end From 8260d5eaf121bc3b765338678d286356ae299c8e Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 22:43:16 -0500 Subject: [PATCH 078/152] Refactor Frame compilation to make each section more clear --- lib/remedy/frame.rb | 107 +++++++++++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 36 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 1eaecdf..4f8b1be 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -113,27 +113,7 @@ def merge_contents end.flatten end - case arrangement - when :stacked - # TODO: insert padding? - merged.flatten - when :columnar - msize = sizeof merged.flatten - result = Array.new - msize.width.times do |index| - fullline = "" - merged.each do |content| - line = content[index] - if line then - fullline << line - end - end - result << fullline - end - result.flatten - else - raise "unknown arrangement: #{arrangement}" - end + arrange_contents merged end def compiled_size @@ -143,10 +123,24 @@ def compiled_size def compile_contents merged = merge_contents merged_size = sizeof merged - compile_size = nil # will be overwritten lower down + compile_size = compute_actual_size(merged_size) or return merged + + buf = Screenbuffer.new compile_size, fill: fill, nl: nl + + hoffset = compute_horizontal_offset merged_size, compile_size + voffset = compute_vertical_offset merged_size, compile_size + + align_contents! merged, merged_size + + buf[voffset,hoffset] = merged + buf.to_a + end + def compute_actual_size merged_size if size == :none then - return merged + # size none indicates that no further formatting should take place + # when we return nil from here, it will just return the merged array of content + return nil elsif size == :fill then compile_size = available_size elsif size == :auto then @@ -169,38 +163,79 @@ def compile_contents raise "Unknown max_size:#{size}" end - buf = Screenbuffer.new compile_size, fill: fill, nl: nl + compile_size + end + def compute_horizontal_offset original_size, actual_size case halign when :left hoffset = 0 when :right - hoffset = compile_size.width - merged_size.width - merged.map! do |line| - line = Align.right_p line, merged_size, fill: fill - end + hoffset = actual_size.width - original_size.width when :center - hoffset = Align.mido merged_size.width, compile_size.width - merged.map! do |line| - line = Align.h_center_p line, merged_size, fill: fill - end + hoffset = Align.mido original_size.width, actual_size.width else raise "Unknown halign:#{halign}" end + hoffset + end + + def compute_vertical_offset original_size, actual_size case valign when :top voffset = 0 when :bottom - voffset = compile_size.height - merged_size.height + voffset = actual_size.height - original_size.height when :center - voffset = Align.mido merged_size.height, compile_size.height + voffset = Align.mido original_size.height, actual_size.height else raise "Unknown valign:#{valign}" end - buf[voffset,hoffset] = merged - buf.to_a + voffset + end + + def arrange_contents content_to_arrange + case arrangement + when :stacked + # TODO: insert padding? + content_to_arrange.flatten + when :columnar + msize = sizeof content_to_arrange.flatten + result = Array.new + msize.width.times do |index| + fullline = "" + content_to_arrange.each do |content| + line = content[index] + if line then + fullline << line + end + end + result << fullline + end + result.flatten + else + raise "unknown arrangement: #{arrangement}" + end + end + + def align_contents! content_to_align, original_size + case halign + when :left + # noop + when :right + content_to_align.map! do |line| + line = Align.right_p line, original_size, fill: fill + end + when :center + content_to_align.map! do |line| + line = Align.h_center_p line, original_size, fill: fill + end + else + raise "Unknown halign:#{halign}" + end + content_to_align end end end From 553a70a71022802a9120a86187182e62b41516c1 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 22:44:35 -0500 Subject: [PATCH 079/152] Rename variable for clarity Inspired by browsers showing the computed size of elements, I use the same terminology here. --- lib/remedy/frame.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 4f8b1be..1050981 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -123,12 +123,12 @@ def compiled_size def compile_contents merged = merge_contents merged_size = sizeof merged - compile_size = compute_actual_size(merged_size) or return merged + computed_size = compute_actual_size(merged_size) or return merged - buf = Screenbuffer.new compile_size, fill: fill, nl: nl + buf = Screenbuffer.new computed_size, fill: fill, nl: nl - hoffset = compute_horizontal_offset merged_size, compile_size - voffset = compute_vertical_offset merged_size, compile_size + hoffset = compute_horizontal_offset merged_size, computed_size + voffset = compute_vertical_offset merged_size, computed_size align_contents! merged, merged_size From 5d518c454f32b5d7573b5df85b51f569e652b47e Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 23:03:01 -0500 Subject: [PATCH 080/152] Memoize Frame's computed size and buffer for reuse Now those values do not need to be recomputed constantly and can be inspected after the fact. Also renamed Screenbuffer#reset to Screenbuffer#reset! as it is a destructive self-changing action. --- lib/remedy/frame.rb | 22 ++++++++++++++++++---- lib/remedy/screen.rb | 2 +- lib/remedy/screenbuffer.rb | 2 +- spec/screenbuffer_spec.rb | 2 +- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 1050981..463e6ea 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -73,6 +73,16 @@ def initialize name: self.object_id # @return [Remedy::Tuple] the vertical and horizontal offset to apply attr_accessor :offset + # The computed size is the actual size of the Frame after taking into account all of the different factors. + # @note This value is invalid until after the contents have been compiled. + # @return [Remedy::Tuple,nil] the size Tuple or `nil` if the Frame has not yet been compiled + attr_reader :computed_size + + # The cached Frame buffer from the last compilation. + # @note This value is invalid until after the contents have been compiled. + # @return [Remedy::Screenbuffer,nil] the buffer or `nil` if the Frame has not yet been compiled + attr_accessor :buffer + def to_a compile_contents end @@ -123,17 +133,21 @@ def compiled_size def compile_contents merged = merge_contents merged_size = sizeof merged - computed_size = compute_actual_size(merged_size) or return merged + @computed_size = compute_actual_size(merged_size) or return merged - buf = Screenbuffer.new computed_size, fill: fill, nl: nl + if buffer then + buffer.reset! + else + @buffer = Screenbuffer.new computed_size, fill: fill, nl: nl + end hoffset = compute_horizontal_offset merged_size, computed_size voffset = compute_vertical_offset merged_size, computed_size align_contents! merged, merged_size - buf[voffset,hoffset] = merged - buf.to_a + buffer[voffset,hoffset] = merged + buffer.to_a end def compute_actual_size merged_size diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 356558a..57b96ce 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -78,7 +78,7 @@ def to_ansi end def refresh_buffer - buffer.reset + buffer.reset! populate_buffer end diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 72b6df3..091232b 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -124,7 +124,7 @@ def to_ansi end # Reset contents of buffer to the empty state using the @fill character. - def reset + def reset! @buf = new_buf end diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index a62b140..ee49317 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -157,7 +157,7 @@ sb[0,0] = ["ab", "cd"] expect(sb.to_s).to eq "ab\ncd" - sb.reset + sb.reset! actual = sb.to_s expect(actual).to eq expected From 3f6cecb0b94630c0874bba03152ed633d9e90059 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 23:33:39 -0500 Subject: [PATCH 081/152] Simplify Screen spec slightly --- spec/screen_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 044775e..af0af17 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -7,10 +7,11 @@ describe Remedy::Screen do subject(:s){ described_class.new auto_resize: false } let(:console){ ::Remedy::Console } + let(:fclass) { ::Remedy::Frame } let(:size){ Tuple 20, 40 } let(:size_override){ Tuple 20, 40 } let(:frame) do - f = ::Remedy::Frame.new + f = fclass.new f.contents << "foo" f.contents << "bar\nbaz" f.vorigin = :center @@ -78,7 +79,7 @@ let(:size_override){ size } let(:frame) do - f = ::Remedy::Frame.new + f = fclass.new f.contents << "foo" f.contents << "bar\nbaz" f @@ -130,7 +131,7 @@ end describe "offset frames" do - let(:new_size_override){ Tuple 5, 9 } + let(:size_override){ Tuple 5, 9 } before do frame.vorigin = :bottom @@ -154,7 +155,6 @@ s.buffer.fill = " " s.frames << frame - s.resized new_size_override, redraw: false actual = s.to_s expect(actual).to eq expected From 96b761d9b3cfee01843f799b13f7f4deda03bcc1 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 23:34:35 -0500 Subject: [PATCH 082/152] Test that Screen properly layers Frames --- spec/screen_spec.rb | 78 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index af0af17..260f1e6 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -160,4 +160,82 @@ expect(actual).to eq expected end end + + describe "layering" do + let(:size_override){ Tuple 7, 7 } + let(:f1) do + f1 = fclass.new + f1.valign = :center + f1.halign = :center + f1.contents << "a" + f1.size = Tuple 3, 3 + f1.fill = ":" + f1 + end + let(:f2) do + f2 = fclass.new + f2.valign = :center + f2.halign = :center + f2.contents << "b" + f2.size = Tuple 3, 3 + f2.offset = Tuple 2, 2 + f2.fill = "*" + f2.depth = 1 + f2 + end + let(:f3) do + f3 = fclass.new + f3.valign = :center + f3.halign = :center + f3.contents << "c" + f3.size = Tuple 3, 3 + f3.offset = Tuple 4, 4 + f3.fill = "#" + f3.depth = 2 + f3 + end + + before do + s.frames.clear + s.frames << f1 + s.frames << f2 + s.frames << f3 + end + + it "places frames on top of each other according to their depth and order" do + expected = [ + ":::....", + ":a:....", + "::***..", + "..*b*..", + "..**###", + "....#c#", + "....###" + ].join ?\n + + actual = s.to_s + expect(actual).to eq expected + end + + context "f2.depth = 3" do + before do + f2.depth = 3 + end + + it "places frames on top of each other according to their depth and order" do + expected = [ + ":::....", + ":a:....", + "::***..", + "..*b*..", + "..***##", + "....#c#", + "....###" + ].join ?\n + + actual = s.to_s + expect(actual).to eq expected + end + end + end end From ce49f6cf2a561b5477cdfdf071447352e0eb77f9 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sat, 7 Oct 2023 23:36:08 -0500 Subject: [PATCH 083/152] Use memoized Frame compilation instead of regenerating --- lib/remedy/screen.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 57b96ce..91bb7bf 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -85,7 +85,8 @@ def refresh_buffer def populate_buffer frames.sort_by(&:depth).each do |frame| frame.available_size = buffer.size - fsize = frame.compiled_size + content = frame.compile_contents + fsize = frame.computed_size case frame.vorigin when :top @@ -112,7 +113,7 @@ def populate_buffer voffset += frame.offset.height hoffset += frame.offset.width - buffer[voffset,hoffset] = frame + buffer[voffset,hoffset] = content end end end From c82dabb6081935f726e92df9b8f473597ca15382 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sun, 8 Oct 2023 08:55:50 -0500 Subject: [PATCH 084/152] WIP: Working on abitrary arrangement --- lib/remedy/frame.rb | 102 +++++++++++++++++++++++++++++++++------ spec/frame_spec.rb | 113 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 15 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 463e6ea..0b8922f 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -1,12 +1,14 @@ require "remedy/tuple" require "remedy/align" require "remedy/screenbuffer" +require "remedy" +require "pry" module Remedy # Frames contain Panes and Panes contain Partials # Frames can be nested within other Frames or Panes class Frame - def initialize name: self.object_id + def initialize name: self.object_id, content: nil @name = name # vorigin is where the frame will be attached vertically @@ -33,10 +35,11 @@ def initialize name: self.object_id @depth = 0 # arrangement, if this frame contains multiple content items, then they will be arranged according to this - # :stacked, :columnar, :tabbed(?) + # :stacked, :columnar, :arbitrary, :tabbed(?) @arrangement = :stacked - # spacing as to how multiple content items should be spaced + # spacing as to how multiple content items should be spaced in an arrangement + # has no effect when `arrangement = :arbitrary` # :none, :evenly @spacing = :none @@ -54,6 +57,9 @@ def initialize name: self.object_id # empty list of contents @contents = Array.new + if content then + @contents << content + end # background fill @fill = " " @@ -65,7 +71,18 @@ def initialize name: self.object_id attr_accessor :vorigin, :horigin, :depth attr_accessor :name, :size, :available_size attr_accessor :nl, :fill, :halign, :valign - attr_accessor :contents, :arrangement + attr_accessor :contents + + # Determines how contents are arranged when compiling. + # + # Possible values are: + # + # - `:stacked` + # - `:columnar` + # - `:arbitrary` + # + # @return [Symbol] one of the preset arragements + attr_accessor :arrangement # Sets the offset from the origin point. # @note Positive offsets always move the frame right and down, @@ -101,7 +118,7 @@ def content_size def sizeof content lines = Array(content).map do |line| - split line + nlsplit line end.flatten height = lines.length @@ -110,20 +127,27 @@ def sizeof content end # @todo move this to a helper module or something - def split line - line.to_s.split(/\r\n|\n\r|\n|\r/) + def nlsplit line + line.split(/\r\n|\n\r|\n|\r/) + end end def merge_contents - merged = contents.map do |c| - content = Array c + if arrangement == :arbitrary then + contents_to_arrange = contents.map do |c| + c.available_size = available_size + c.to_s + end + else + contents_to_arrange = contents.map do |c| + content = Array c + content.map! do |line| + nlsplit line + end.flatten + end - content.map! do |line| - split line - end.flatten + arrange_contents contents_to_arrange end - - arrange_contents merged end def compiled_size @@ -218,7 +242,7 @@ def arrange_contents content_to_arrange when :columnar msize = sizeof content_to_arrange.flatten result = Array.new - msize.width.times do |index| + msize.col.times do |index| fullline = "" content_to_arrange.each do |content| line = content[index] @@ -229,11 +253,59 @@ def arrange_contents content_to_arrange result << fullline end result.flatten + when :arbitrary + arrange_arbitrary content_to_arrange else raise "unknown arrangement: #{arrangement}" end end + def arrange_arbitrary content_to_arrange + arrange_buffer = Screenbuffer.new available_size, fill: fill + + # wrap all items in a Frame + # this has the side effect of setting all arbitrary content to layer 0 + arranger_content = content_to_arrange.map do |frame| + frame = Frame.new(content: frame) unless frame.is_a? Frame + frame + end + + arranger_content.sort_by(&:depth).each do |frame| + frame.available_size = arrange_buffer.size + content = frame.compile_contents + fsize = frame.computed_size + + p size, fsize + + case frame.vorigin + when :top + voffset = 0 + when :center + voffset = Align.mido fsize.height, arrange_buffer.size.height + when :bottom + voffset = arrange_buffer.size.height - fsize.height + else + raise "Unknown vorigin:#{frame.vorigin}" + end + + case frame.horigin + when :left + hoffset = 0 + when :center + hoffset = Align.mido fsize.width, arrange_buffer.size.width + when :right + hoffset = arrange_buffer.size.width - fsize.width + else + raise "Unknown horigin:#{frame.horigin}" + end + + voffset += frame.offset.height + hoffset += frame.offset.width + + arrange_buffer[voffset,hoffset] = content + end + end + def align_contents! content_to_align, original_size case halign when :left diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 2a420f3..a7fed41 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -397,6 +397,7 @@ end before do + f.contents.clear f.contents << f1 f.contents << f2 f.contents << f3 @@ -425,5 +426,117 @@ expect(actual).to eq expected end end + + context "arrangement = arbitrary" do + before do + f.arrangement = :arbitrary + f1.depth = 3 + f1.fill = ":" + f1.size = Tuple 1,2 + f2.fill = "*" + f2.size = Tuple 2,1 + f3.fill = "#" + end + + it "contents are not relative to others" do + # f1e = "a:" + # f2e = "b\n*" # completely covered + # f3e = "###\n#c#\n###" + + expected = "a:#\n#c#\n###" + + actual = f.to_s + expect(actual).to eq expected + end + end + + context "vorigin = :bottom" do + let(:f1) do + f1 = described_class.new + f1.contents << "a" + f1.size = Tuple 3, 3 + f1.fill = "." + f1 + end + + before do + f.size = Tuple 5, 5 + f.contents = [] + f.contents << f1 + end + + it "puts the nested frame in the correct location" do + expected = " \n \n ... \n .a. \n ... " + actual = f.to_s + expect(actual).to eq expected + end + end + end + + describe "layering" do + let(:size_override){ Tuple 7, 7 } + let(:f1) do + f1 = described_class.new + f1.contents << "a" + f1.size = Tuple 3, 3 + f1.fill = ":" + f1.valign = :center + f1.halign = :center + f1 + end + let(:f2) do + f2 = described_class.new + f2.contents << "b" + f2.size = Tuple 3, 3 + f2.offset = Tuple 2, 2 + f2.fill = "*" + f2.valign = :center + f2.halign = :center + f2.depth = 1 + f2 + end + let(:f3) do + f3 = described_class.new + f3.contents << "c" + f3.size = Tuple 3, 3 + f3.offset = Tuple 4, 4 + f3.fill = "#" + f3.valign = :center + f3.halign = :center + f3.depth = 2 + f3 + end + + before do + f.contents.clear + f.contents << f1 + f.contents << f2 + f.contents << f3 + + f.available_size = size_override + f.size = :fill + f.arrangement = :arbitrary + f.fill = "." + end + + it "places frames on top of each other according to their depth and order" do + expected = [ + ":::....", + ":a:....", + "::***..", + "..*b*..", + "..**###", + "....#c#", + "....###" + ].join ?\n + + actual = f.to_s + expect(actual).to eq expected + end + + xit "treats plain strings as layer 0" do + actual = f.to_s + expect(actual).to eq expected + end end end From 563889147fbf7950e6d52f08e7e50048bcfbae1d Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sun, 8 Oct 2023 08:56:40 -0500 Subject: [PATCH 085/152] Adding names to test Frames to make tracing easier --- spec/frame_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index a7fed41..8fd0c06 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -3,7 +3,7 @@ require "remedy/partial" describe Remedy::Frame do - subject(:f){ described_class.new } + subject(:f){ described_class.new name: "subject" } describe "#content_size" do it "returns a Tuple of the contents dimensions" do @@ -378,17 +378,17 @@ context "with nested frames" do let(:f1) do - f1 = described_class.new + f1 = described_class.new name: "f1" f1.contents << "a" f1 end let(:f2) do - f2 = described_class.new + f2 = described_class.new name: "f2" f2.contents << "b" f2 end let(:f3) do - f3 = described_class.new + f3 = described_class.new name: "f3" f3.contents << "c" f3.size = Tuple 3, 3 f3.valign = :center From bf17f0f2456543d63ade7333f43b2c64aee85aba Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sun, 8 Oct 2023 22:43:30 -0500 Subject: [PATCH 086/152] WIP: Removing direct Frame content access in favor of arbitrated Frame#<< --- lib/remedy/frame.rb | 90 ++++++++++++++++++------------------ lib/remedy/screenbuffer.rb | 7 +-- lib/remedy/text_util.rb | 30 ++++++++++++ spec/frame_spec.rb | 93 ++++++++++++++++++++------------------ spec/screen_spec.rb | 14 +++--- 5 files changed, 130 insertions(+), 104 deletions(-) create mode 100644 lib/remedy/text_util.rb diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 0b8922f..32abab3 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -1,6 +1,7 @@ require "remedy/tuple" require "remedy/align" require "remedy/screenbuffer" +require "remedy/text_util" require "remedy" require "pry" @@ -71,7 +72,7 @@ def initialize name: self.object_id, content: nil attr_accessor :vorigin, :horigin, :depth attr_accessor :name, :size, :available_size attr_accessor :nl, :fill, :halign, :valign - attr_accessor :contents + # Determines how contents are arranged when compiling. # @@ -100,6 +101,19 @@ def initialize name: self.object_id, content: nil # @return [Remedy::Screenbuffer,nil] the buffer or `nil` if the Frame has not yet been compiled attr_accessor :buffer + def << new_content + if new_content.is_a? String or new_content.is_a? Array then + conformed_content = TextUtil.nlclean(new_content) + else + conformed_content = new_content + end + @contents << conformed_content + end + + def reset! + @contents.clear + end + def to_a compile_contents end @@ -108,56 +122,41 @@ def to_s compile_contents.join nl end + def to_str + to_s + end + def to_ansi compile_contents.join ANSI.cursor.next_line end def content_size - sizeof merge_contents + sizeof arrange_contents end def sizeof content - lines = Array(content).map do |line| - nlsplit line - end.flatten + lines = TextUtil.nlclean(content, self).flatten(1) height = lines.length width = lines.map(&:length).max || 0 Tuple height, width end - # @todo move this to a helper module or something - def nlsplit line - line.split(/\r\n|\n\r|\n|\r/) - end - end - - def merge_contents - if arrangement == :arbitrary then - contents_to_arrange = contents.map do |c| - c.available_size = available_size - c.to_s - end + def length + if size == :none then + content_size.width + elsif computed_size then + computed_size.width else - contents_to_arrange = contents.map do |c| - content = Array c - content.map! do |line| - nlsplit line - end.flatten - end - - arrange_contents contents_to_arrange + compile_contents + computed_size.width end end - def compiled_size - sizeof compile_contents - end - def compile_contents - merged = merge_contents - merged_size = sizeof merged - @computed_size = compute_actual_size(merged_size) or return merged + c = arrange_contents + csize = sizeof c + @computed_size = compute_actual_size(csize) or return c if buffer then buffer.reset! @@ -165,12 +164,12 @@ def compile_contents @buffer = Screenbuffer.new computed_size, fill: fill, nl: nl end - hoffset = compute_horizontal_offset merged_size, computed_size - voffset = compute_vertical_offset merged_size, computed_size + hoffset = compute_horizontal_offset csize, computed_size + voffset = compute_vertical_offset csize, computed_size - align_contents! merged, merged_size + align_contents! c, csize - buffer[voffset,hoffset] = merged + buffer[voffset,hoffset] = c buffer.to_a end @@ -234,7 +233,9 @@ def compute_vertical_offset original_size, actual_size voffset end - def arrange_contents content_to_arrange + def arrange_contents + content_to_arrange = @contents + case arrangement when :stacked # TODO: insert padding? @@ -263,19 +264,12 @@ def arrange_contents content_to_arrange def arrange_arbitrary content_to_arrange arrange_buffer = Screenbuffer.new available_size, fill: fill - # wrap all items in a Frame - # this has the side effect of setting all arbitrary content to layer 0 - arranger_content = content_to_arrange.map do |frame| - frame = Frame.new(content: frame) unless frame.is_a? Frame - frame - end - - arranger_content.sort_by(&:depth).each do |frame| + result = content_to_arrange.sort_by(&:depth).each do |frame| frame.available_size = arrange_buffer.size content = frame.compile_contents - fsize = frame.computed_size + fsize = frame.computed_size || frame.content_size - p size, fsize + Remedy.log.debug "sizes", size, fsize case frame.vorigin when :top @@ -304,6 +298,8 @@ def arrange_arbitrary content_to_arrange arrange_buffer[voffset,hoffset] = content end + + arrange_buffer.to_a end def align_contents! content_to_align, original_size diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 091232b..3f67711 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -1,5 +1,6 @@ require "remedy/tuple" require "remedy/ansi" +require "remedy/text_util" module Remedy # A screenbuffer is an in-memory representation of the terminal display. @@ -139,7 +140,7 @@ def new_buf def replace_perline coords, value # Array() checks for `.to_a` on whatever is passed to it lines = Array(value).map do |line| - split line + TextUtil.nlclean line end.flatten lines.each.with_index do |line, index| @@ -154,9 +155,5 @@ def replace_inline coords, value fit[-1] = ellipsis[0,charwidth] if ellipsis && fit.length < value.length buf[coords.row][coords.col,fit.length] = fit end - - def split line - line.split(/\r\n|\n\r|\n|\r/) - end end end diff --git a/lib/remedy/text_util.rb b/lib/remedy/text_util.rb new file mode 100644 index 0000000..88a1c54 --- /dev/null +++ b/lib/remedy/text_util.rb @@ -0,0 +1,30 @@ +module Remedy + module TextUtil + module_function + + def nlclean content, context = nil + case content + when String + nlsplit(content) + when Frame + content.to_a + when Partial + content.to_a + when View + content.to_a + when Array + content.map do |l| + nlclean l + end + else + binding.pry + end + end + + def nlsplit line + raise ArgumentError, "Requires a String, got #{line.class} instead!" unless line.is_a? String + line.split(/\r\n|\n\r|\n|\r/) + end + + end +end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 8fd0c06..f2755a0 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -8,8 +8,8 @@ describe "#content_size" do it "returns a Tuple of the contents dimensions" do expected = Tuple 2, 4 - f.contents << "1234" - f.contents << "567" + f << "1234" + f << "567" actual = f.content_size expect(actual).to eq expected @@ -36,24 +36,24 @@ it "compiles the contents of a single string" do expected = "foo" content = "foo" - f.contents << content + f << content actual = f.to_s expect(actual).to eq expected end it "compiles the contents of multiple strings" do expected = "foo\nbar\nbaz" - f.contents << "foo" - f.contents << "bar" - f.contents << "baz" + f << "foo" + f << "bar" + f << "baz" actual = f.to_s expect(actual).to eq expected end it "compiles the contents of partials" do expected = "foo\nbar" - f.contents << "foo" - f.contents << ::Remedy::Partial.new(["bar"]) + f << "foo" + f << ::Remedy::Partial.new(["bar"]) actual = f.to_s expect(actual).to eq expected end @@ -66,9 +66,9 @@ f.size = Tuple 5, 5 f.available_size = Tuple 7, 7 - f.contents << "foo" - f.contents << "bar" - f.contents << "baz" + f << "foo" + f << "bar" + f << "baz" end context "halign = :left" do @@ -90,9 +90,9 @@ f.size = :fill f.available_size = Tuple 6, 6 - f.contents << "foo" - f.contents << "bar" - f.contents << "baz" + f << "foo" + f << "bar" + f << "baz" end context "halign = :left" do @@ -140,9 +140,9 @@ f.size = :auto f.available_size = Tuple 6, 6 - f.contents << "foo" - f.contents << "bar" - f.contents << "bazyx" + f << "foo" + f << "bar" + f << "bazyx" end context "halign = :left" do @@ -192,7 +192,7 @@ f.size = Tuple(5,7) f.available_size = Tuple(11,11) - f.contents << "lol" + f << "lol" end it "content appears centered" do @@ -226,7 +226,10 @@ " " ].join ?\n - f.contents = ["a", "b", "c\nd"] + f.reset! + f << "a" + f << "b" + f << "c\nd" actual = f.to_s expect(actual).to eq expected @@ -240,7 +243,7 @@ f.size = Tuple(5,7) f.available_size = Tuple(11,11) - f.contents << "lol" + f << "lol" end it "content appears centered in the bottom" do @@ -264,7 +267,7 @@ f.size = Tuple(0,7) f.available_size = Tuple(3,11) - f.contents << "lol" + f << "lol" end it "stretches to the vertical bounds of the container" do expected = [ @@ -285,7 +288,7 @@ f.size = Tuple(1,0) f.available_size = Tuple(3,11) - f.contents << "lol" + f << "lol" end it "stretches to the horizontal bounds of the container" do @@ -305,7 +308,7 @@ f.size = Tuple(0,0.5) f.available_size = Tuple(3,11) - f.contents << "lol" + f << "lol" end it "stretches to half the horizontal bounds of the container" do @@ -327,7 +330,7 @@ f.size = Tuple(0.5,0) f.available_size = Tuple(4,11) - f.contents << "lol" + f << "lol" end it "stretches to half the vertical bounds of the container" do @@ -346,9 +349,9 @@ context "with strings" before do - f.contents << "a" - f.contents << "b" - f.contents << "c" + f << "a" + f << "b" + f << "c" end context "arrangement = stacked" do @@ -379,17 +382,17 @@ context "with nested frames" do let(:f1) do f1 = described_class.new name: "f1" - f1.contents << "a" + f1 << "a" f1 end let(:f2) do f2 = described_class.new name: "f2" - f2.contents << "b" + f2 << "b" f2 end let(:f3) do f3 = described_class.new name: "f3" - f3.contents << "c" + f3 << "c" f3.size = Tuple 3, 3 f3.valign = :center f3.halign = :center @@ -397,10 +400,10 @@ end before do - f.contents.clear - f.contents << f1 - f.contents << f2 - f.contents << f3 + f.reset! + f << f1 + f << f2 + f << f3 end context "arrangement = stacked" do @@ -430,7 +433,7 @@ context "arrangement = arbitrary" do before do f.arrangement = :arbitrary - f1.depth = 3 + f1.depth = 3 # no longer respected?? f1.fill = ":" f1.size = Tuple 1,2 f2.fill = "*" @@ -453,7 +456,7 @@ context "vorigin = :bottom" do let(:f1) do f1 = described_class.new - f1.contents << "a" + f1 << "a" f1.size = Tuple 3, 3 f1.fill = "." f1 @@ -461,8 +464,8 @@ before do f.size = Tuple 5, 5 - f.contents = [] - f.contents << f1 + f.reset! + f << f1 end it "puts the nested frame in the correct location" do @@ -477,7 +480,7 @@ let(:size_override){ Tuple 7, 7 } let(:f1) do f1 = described_class.new - f1.contents << "a" + f1 << "a" f1.size = Tuple 3, 3 f1.fill = ":" f1.valign = :center @@ -486,7 +489,7 @@ end let(:f2) do f2 = described_class.new - f2.contents << "b" + f2 << "b" f2.size = Tuple 3, 3 f2.offset = Tuple 2, 2 f2.fill = "*" @@ -497,7 +500,7 @@ end let(:f3) do f3 = described_class.new - f3.contents << "c" + f3 << "c" f3.size = Tuple 3, 3 f3.offset = Tuple 4, 4 f3.fill = "#" @@ -508,10 +511,10 @@ end before do - f.contents.clear - f.contents << f1 - f.contents << f2 - f.contents << f3 + f.reset! + f << f1 + f << f2 + f << f3 f.available_size = size_override f.size = :fill diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 260f1e6..9df6204 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -12,8 +12,8 @@ let(:size_override){ Tuple 20, 40 } let(:frame) do f = fclass.new - f.contents << "foo" - f.contents << "bar\nbaz" + f << "foo" + f << "bar\nbaz" f.vorigin = :center f.horigin = :center f.valign = :center @@ -80,8 +80,8 @@ let(:frame) do f = fclass.new - f.contents << "foo" - f.contents << "bar\nbaz" + f << "foo" + f << "bar\nbaz" f end @@ -167,7 +167,7 @@ f1 = fclass.new f1.valign = :center f1.halign = :center - f1.contents << "a" + f1 << "a" f1.size = Tuple 3, 3 f1.fill = ":" f1 @@ -176,7 +176,7 @@ f2 = fclass.new f2.valign = :center f2.halign = :center - f2.contents << "b" + f2 << "b" f2.size = Tuple 3, 3 f2.offset = Tuple 2, 2 f2.fill = "*" @@ -187,7 +187,7 @@ f3 = fclass.new f3.valign = :center f3.halign = :center - f3.contents << "c" + f3 << "c" f3.size = Tuple 3, 3 f3.offset = Tuple 4, 4 f3.fill = "#" From 7f4554d897a690b7046cf0351b1f61c117a42fb8 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Sun, 8 Oct 2023 23:09:14 -0500 Subject: [PATCH 087/152] Better names for the columnar arrangement code, and extract methods Better names for the columnar arrangement code: - specify that we need the `rows` because we are going to iterate over each of them i neach piece of content to build the columns row by row - use `arranged_line` instead of `fullline` to better express intent Extract methods: - I have the `maxsizeof` sitting around because I was sure I would need it for something, so I was able to remove the code that tried to do the same thing poorly and use that instead. - Implement `Frame#[]` to make seeking into a Frame as easy as an Array. This has the benefit of flattening out Frame interfaces to make it less important to care about their nested items. --- lib/remedy/frame.rb | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 32abab3..7ab4d16 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -110,6 +110,12 @@ def << new_content @contents << conformed_content end + def [] *index + # Can't decide if this should seek into the contents array or into the rendered output + #@contents[*index] + to_a[*index] + end + def reset! @contents.clear end @@ -134,6 +140,16 @@ def content_size sizeof arrange_contents end + def maxsizeof content_list + content_sizes = content_list.map do |content| + sizeof content + end + + height = content_sizes.map(&:height).max || 0 + width = content_sizes.map(&:width).max || 0 + Tuple height, width + end + def sizeof content lines = TextUtil.nlclean(content, self).flatten(1) @@ -154,6 +170,8 @@ def length end def compile_contents + # TODO: insert dirty check and then skip the rest of this if no changes detected, + # also a param which overrides this c = arrange_contents csize = sizeof c @computed_size = compute_actual_size(csize) or return c @@ -241,17 +259,17 @@ def arrange_contents # TODO: insert padding? content_to_arrange.flatten when :columnar - msize = sizeof content_to_arrange.flatten + rows = maxsizeof(content_to_arrange).row result = Array.new - msize.col.times do |index| - fullline = "" + rows.times do |row| + arranged_line = "" content_to_arrange.each do |content| - line = content[index] + line = content[row] if line then - fullline << line + arranged_line << line end end - result << fullline + result << arranged_line end result.flatten when :arbitrary From d266308334194c8558935ffc46fb266c05dc5316 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:24:55 -0500 Subject: [PATCH 088/152] Split out the way new lines are added to buffer for reuse --- lib/remedy/screenbuffer.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 3f67711..39f7c18 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -133,10 +133,14 @@ def reset! def new_buf Array.new(size.height) do - fill * size.width * charwidth + new_buf_line end end + def new_buf_line + fill * size.width * charwidth + end + def replace_perline coords, value # Array() checks for `.to_a` on whatever is passed to it lines = Array(value).map do |line| From d8395b27c01a0c8a2f27cfb0a955d7d56711c84f Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:26:38 -0500 Subject: [PATCH 089/152] Split out size computing of buffers for reuse --- lib/remedy/screenbuffer.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 39f7c18..e165c39 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -82,7 +82,7 @@ def buf # # @param override_buf [Array] the new replacement buffer contents def buf= override_buf - self.size = Tuple override_buf.length, (override_buf.map{|l|l.length}.max || 0) + self.size = compute_actual_size override_buf @buf = override_buf end @@ -129,6 +129,10 @@ def reset! @buf = new_buf end + def compute_actual_size array2d = buf + Tuple array2d.length, (array2d.map{|l|l.length}.max || 0) + end + private def new_buf From 52ee50279eb9d3766bb4dd94bc2f076885c16d7c Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:28:06 -0500 Subject: [PATCH 090/152] Screenbuffer#resize now extends buffers Change behavior of Screenbuffer#size= to extend buffer instead of creating a new one Rename to resize, but alias the old one --- lib/remedy/screenbuffer.rb | 49 +++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index e165c39..5638da7 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -91,22 +91,6 @@ def size @size end - # Set a new size for the screenbuffer. - # @note This will destroy the contents of the current buffer! - # - # @param new_size [Remedy::Tuple] the new size, - # as typically received from `Console.size` or - # `Console.set_console_resized_hook!` - # @raise [ArgumentError] if passed anything other than a Remedy::Tuple - def size= new_size - raise ArgumentError unless new_size.is_a? Tuple - - if size != new_size then - @size = new_size - @buf = new_buf - end - end - # @return [Array] the contents of the buffer as an array of strings def to_a buf @@ -129,6 +113,39 @@ def reset! @buf = new_buf end + # Set a new size for the screenbuffer. + # @todo The buffer is not shrank or otherwise truncated when the size changes. + # + # @param new_size [Remedy::Tuple] the new size, + # as typically received from `Console.size` or + # `Console.set_console_resized_hook!` + # @raise [ArgumentError] if passed anything other than a Remedy::Tuple + def resize new_size + raise ArgumentError unless new_size.is_a? Tuple + # FIXME: @size is getting reset to old versions somehow. + # But if we determine the actual size and use that instead, + # then we work around that behavior. + actual_size = compute_actual_size + + if new_size.height > actual_size.height then + grow_by = new_size.height - actual_size.height + grow_by.times do + @buf << new_buf_line + end + end + if new_size.width > actual_size.width then + grow_by = new_size.width - actual_size.width + @buf.each do |l| + # TODO: handle char width? + l << fill * grow_by + end + end + + Remedy.log.debug "size", size, "actual_size", actual_size, "new_size", new_size, (new_size.height - size.height), "buf", @buf + @size = new_size.dup + end + alias_method :size=, :resize + def compute_actual_size array2d = buf Tuple array2d.length, (array2d.map{|l|l.length}.max || 0) end From 48a65880cca4c4802d596ae45e245ef70ba514f6 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:33:54 -0500 Subject: [PATCH 091/152] Screenbuffer can grow to fit new contents --- lib/remedy/screenbuffer.rb | 30 ++++++++++++++++++++++++------ spec/screenbuffer_spec.rb | 10 ++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 5638da7..6e1b24e 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -16,15 +16,16 @@ class Screenbuffer # @param ellipsis [String] the character used to indicate truncated lines, # if set to `nil` then content will extend to the edge of the screen # @param charwidth [Numeric] in case we are able to support multiple character widths in the future - def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1 + def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1, fit: false @charwidth = charwidth @size = size @fill = fill[0, charwidth] @nl = nl @ellipsis = ellipsis @buf = new_buf + @fit = fit end - attr_accessor :fill, :nl, :ellipsis, :charwidth + attr_accessor :fill, :nl, :ellipsis, :charwidth, :fit # Get the contents of the buffer at a given coordinate. # @@ -170,15 +171,32 @@ def replace_perline coords, value lines.each.with_index do |line, index| new_coords = coords + Tuple(index,0) - return if new_coords.height >= size.height + if new_coords.height >= size.height then + if fit then + grow_size = Tuple (new_coords.height + 1), size.width + resize(grow_size) + size.height = new_coords.height + else + return + end + end replace_inline(new_coords, line) end + + self end def replace_inline coords, value - fit = value[0,size.width - coords.col] - fit[-1] = ellipsis[0,charwidth] if ellipsis && fit.length < value.length - buf[coords.row][coords.col,fit.length] = fit + if fit then + grow_size = Tuple size.height, value.length + resize(grow_size) + truncated_value = value # not truncated + else + truncated_value = value[0,size.width - coords.col] + truncated_value[-1] = ellipsis[0,charwidth] if ellipsis && truncated_value.length < value.length + end + + buf[coords.row][coords.col,truncated_value.length] = truncated_value end end end diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index ee49317..a4c9ad4 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -163,4 +163,14 @@ expect(actual).to eq expected end end + + describe "#resize" do + xit "extends the buffer without destroying the contents" + xit "updates the size field properly" + xit "outputs constrained array after shrinking" + end + + describe "option fit = true" do + xit "automatically grows the buffer when content does not fit" + end end From 0ab941e34d039345d5c028e29acc5f5a43427752 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:36:43 -0500 Subject: [PATCH 092/152] Tuple#zero? to check if all dimensions are 0 --- lib/remedy/tuple.rb | 6 ++++++ spec/tuple_spec.rb | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index ef3eeb6..2be7bd2 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -128,6 +128,12 @@ def cardinality end alias_method :length, :cardinality + def zero? + dimensions.all? do |d| + d == 0 + end + end + # CONVERSIONS def to_a diff --git a/spec/tuple_spec.rb b/spec/tuple_spec.rb index 245829c..2ae1e21 100644 --- a/spec/tuple_spec.rb +++ b/spec/tuple_spec.rb @@ -47,4 +47,13 @@ expect(other[1] == t[1]).to be false end end + + describe "#zero?" do + it "returns true when all dimensions are zero" do + z = described_class.new 0, 0 + expected = true + actual = z.zero? + expect(actual).to eq expected + end + end end From 213e9350a4327cb5cf068008819f3e8791e33a63 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:37:26 -0500 Subject: [PATCH 093/152] Setters for Tuple to match the ergonomic getters --- lib/remedy/tuple.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index 2be7bd2..ffca044 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -111,6 +111,27 @@ def z alias_method :layer, :z alias_method :third, :z + def x= new_value + dimensions[0] = new_value + end + alias_method :height=, :x= + alias_method :row=, :x= + alias_method :first=, :x= + + def y= new_value + dimensions[1] = new_value + end + alias_method :width=, :y= + alias_method :col=, :y= + alias_method :second=, :y= + + def z= new_value + dimensions[2] = new_value + end + alias_method :depth=, :z= + alias_method :layer=, :z= + alias_method :third=, :z= + def last dimensions.last end From a56fc8fc47f0c9b01e150866e9524c17b814118e Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:38:30 -0500 Subject: [PATCH 094/152] Use Tuple for buffer offset instead of discrete values, for homgenity --- lib/remedy/frame.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 7ab4d16..c1b7b9b 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -314,7 +314,9 @@ def arrange_arbitrary content_to_arrange voffset += frame.offset.height hoffset += frame.offset.width - arrange_buffer[voffset,hoffset] = content + offset = Tuple voffset, hoffset + + arrange_buffer[offset] = content end arrange_buffer.to_a From 72c227e0ac43b5514aff33f40faf69c86655029e Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:39:00 -0500 Subject: [PATCH 095/152] Remove debug logging --- lib/remedy/frame.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index c1b7b9b..45ace70 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -287,8 +287,6 @@ def arrange_arbitrary content_to_arrange content = frame.compile_contents fsize = frame.computed_size || frame.content_size - Remedy.log.debug "sizes", size, fsize - case frame.vorigin when :top voffset = 0 From c8b020280b21d35cf988f1c32bb09842e368fef7 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:39:52 -0500 Subject: [PATCH 096/152] Split out depth sort and make it more comprehensive --- lib/remedy/frame.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 45ace70..dcc7825 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -282,7 +282,7 @@ def arrange_contents def arrange_arbitrary content_to_arrange arrange_buffer = Screenbuffer.new available_size, fill: fill - result = content_to_arrange.sort_by(&:depth).each do |frame| + result = depth_sort(content_to_arrange).each do |frame| frame.available_size = arrange_buffer.size content = frame.compile_contents fsize = frame.computed_size || frame.content_size @@ -337,5 +337,17 @@ def align_contents! content_to_align, original_size end content_to_align end + + def depth_sort list_of_content + list_of_content.sort do |a,b| + depthof(a) <=> depthof(b) + end + end + + def depthof content + if content.is_a? Frame then + content.depth + else + 0 end end From e85bc1af26a1e32b22c62991d9654f16fc22bf5d Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:40:31 -0500 Subject: [PATCH 097/152] Auto expand arrange buffer --- lib/remedy/frame.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index dcc7825..05b26e7 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -280,9 +280,11 @@ def arrange_contents end def arrange_arbitrary content_to_arrange - arrange_buffer = Screenbuffer.new available_size, fill: fill + expand_buffer = available_size.zero? + arrange_buffer = Screenbuffer.new available_size, fit: expand_buffer, fill: fill result = depth_sort(content_to_arrange).each do |frame| + # FIXME: what happens when the buffer size is zero? the buffer will grow, right? frame.available_size = arrange_buffer.size content = frame.compile_contents fsize = frame.computed_size || frame.content_size From 609ef9d98d297781a1e94473e1d2f57d8d74b69d Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:41:10 -0500 Subject: [PATCH 098/152] End comments to make it clear what is in the Frame class Also fix some associated whitespace. --- lib/remedy/frame.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 05b26e7..b6d9588 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -351,5 +351,8 @@ def depthof content content.depth else 0 - end -end + end + end + + end # Frame class +end # Remedy module From e4c820f2d9d63688148a9f163cb14b6c38772cde Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 02:51:45 -0500 Subject: [PATCH 099/152] Fix broken test for bottom origin nested Frame --- spec/frame_spec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index f2755a0..4dea0c4 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -459,16 +459,22 @@ f1 << "a" f1.size = Tuple 3, 3 f1.fill = "." + f1.valign = :center + f1.halign = :center + f1.horigin = :center + f1.vorigin = :bottom f1 end before do f.size = Tuple 5, 5 + f.available_size = Tuple 5, 5 + f.arrangement = :arbitrary f.reset! f << f1 end - it "puts the nested frame in the correct location" do + it "puts the nested frame at the bottom" do expected = " \n \n ... \n .a. \n ... " actual = f.to_s expect(actual).to eq expected From 2633aafc65af125e63a800083906b0105287ed71 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 04:11:35 -0500 Subject: [PATCH 100/152] Use Frame#<< when adding content during init --- lib/remedy/frame.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index b6d9588..5b0e5ea 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -59,7 +59,7 @@ def initialize name: self.object_id, content: nil # empty list of contents @contents = Array.new if content then - @contents << content + self << content end # background fill From e7a59a1d33765689a91a7df504d7077cfba52379 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 04:59:48 -0500 Subject: [PATCH 101/152] Started cleaning up Frame tests, discovered invalid test, Columnar broken --- spec/frame_spec.rb | 121 +++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 66 deletions(-) diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 4dea0c4..6110438 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -3,7 +3,41 @@ require "remedy/partial" describe Remedy::Frame do - subject(:f){ described_class.new name: "subject" } + let(:sizeclass) { ::Remedy::Tuple } + let(:console_size) { sizeclass.new 20, 40 } + subject(:f) do + f0 = described_class.new name: "subject" + f0.available_size = console_size + f0 + end + + let(:f1) do + f1 = described_class.new name: "f1" + f1 << "a" + f1.size = Tuple 3, 3 + f1.fill = ":" + f1.valign = :center + f1.halign = :center + f1 + end + let(:f2) do + f2 = described_class.new name: "f2" + f2 << "b" + f2.size = Tuple 3, 3 + f2.fill = "*" + f2.valign = :center + f2.halign = :center + f2 + end + let(:f3) do + f3 = described_class.new name: "f3" + f3 << "c" + f3.size = Tuple 3, 3 + f3.fill = "#" + f3.valign = :center + f3.halign = :center + f3 + end describe "#content_size" do it "returns a Tuple of the contents dimensions" do @@ -380,30 +414,14 @@ end context "with nested frames" do - let(:f1) do - f1 = described_class.new name: "f1" - f1 << "a" - f1 - end - let(:f2) do - f2 = described_class.new name: "f2" - f2 << "b" - f2 - end - let(:f3) do - f3 = described_class.new name: "f3" - f3 << "c" - f3.size = Tuple 3, 3 - f3.valign = :center - f3.halign = :center - f3 - end - before do f.reset! f << f1 f << f2 f << f3 + + f1.size = :none + f2.size = :none end context "arrangement = stacked" do @@ -412,7 +430,7 @@ end it "arranges contents on top of each other" do - expected = "a\nb\n \n c \n " + expected = "a\nb\n###\n#c#\n###" actual = f.to_s expect(actual).to eq expected end @@ -424,7 +442,7 @@ end it "arranges contents next to each other" do - expected = "ab \n c \n " + expected = "ab###\n #c#\n ###" actual = f.to_s expect(actual).to eq expected end @@ -433,6 +451,7 @@ context "arrangement = arbitrary" do before do f.arrangement = :arbitrary + f.available_size = sizeclass.zero f1.depth = 3 # no longer respected?? f1.fill = ":" f1.size = Tuple 1,2 @@ -454,23 +473,20 @@ end context "vorigin = :bottom" do - let(:f1) do - f1 = described_class.new - f1 << "a" + before do + f.size = Tuple 5, 5 + f.available_size = Tuple 5, 5 + f.arrangement = :arbitrary + f.reset! + + f1.size = Tuple 3, 3 f1.fill = "." f1.valign = :center f1.halign = :center + f1.horigin = :center f1.vorigin = :bottom - f1 - end - - before do - f.size = Tuple 5, 5 - f.available_size = Tuple 5, 5 - f.arrangement = :arbitrary - f.reset! f << f1 end @@ -483,38 +499,7 @@ end describe "layering" do - let(:size_override){ Tuple 7, 7 } - let(:f1) do - f1 = described_class.new - f1 << "a" - f1.size = Tuple 3, 3 - f1.fill = ":" - f1.valign = :center - f1.halign = :center - f1 - end - let(:f2) do - f2 = described_class.new - f2 << "b" - f2.size = Tuple 3, 3 - f2.offset = Tuple 2, 2 - f2.fill = "*" - f2.valign = :center - f2.halign = :center - f2.depth = 1 - f2 - end - let(:f3) do - f3 = described_class.new - f3 << "c" - f3.size = Tuple 3, 3 - f3.offset = Tuple 4, 4 - f3.fill = "#" - f3.valign = :center - f3.halign = :center - f3.depth = 2 - f3 - end + let(:console_size){ Tuple 7, 7 } before do f.reset! @@ -522,7 +507,11 @@ f << f2 f << f3 - f.available_size = size_override + f2.depth = 1 + f2.offset = Tuple 2, 2 + f3.depth = 2 + f3.offset = Tuple 4, 4 + f.size = :fill f.arrangement = :arbitrary f.fill = "." From 1ad467846f3e023f4322bde91d0ccb035237e279 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 05:01:24 -0500 Subject: [PATCH 102/152] Expose Frame#contents again for debugging purposes, remove debugging library --- lib/remedy/frame.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 5b0e5ea..3be3909 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -3,7 +3,6 @@ require "remedy/screenbuffer" require "remedy/text_util" require "remedy" -require "pry" module Remedy # Frames contain Panes and Panes contain Partials @@ -72,6 +71,7 @@ def initialize name: self.object_id, content: nil attr_accessor :vorigin, :horigin, :depth attr_accessor :name, :size, :available_size attr_accessor :nl, :fill, :halign, :valign + attr_reader :contents # Determines how contents are arranged when compiling. @@ -111,8 +111,6 @@ def << new_content end def [] *index - # Can't decide if this should seek into the contents array or into the rendered output - #@contents[*index] to_a[*index] end From a7e74e1b6b5af476946c590f98b4f867e96c71ad Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 05:17:23 -0500 Subject: [PATCH 103/152] Add dev deps for debugging and documentation --- remedy.gemspec | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/remedy.gemspec b/remedy.gemspec index 64d1f4a..9d8bddb 100644 --- a/remedy.gemspec +++ b/remedy.gemspec @@ -19,4 +19,8 @@ Gem::Specification.new do |gem| gem.require_paths = ["lib"] gem.add_development_dependency 'rspec' + gem.add_development_dependency 'logsaber' + gem.add_development_dependency 'pry' + gem.add_development_dependency 'yard' + gem.add_development_dependency 'webrick' end From f6ad32232c7b8f7bff480b89705853c105ae3d61 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 05:17:42 -0500 Subject: [PATCH 104/152] Remove extra Array#flatten --- lib/remedy/frame.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 3be3909..3790758 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -269,7 +269,7 @@ def arrange_contents end result << arranged_line end - result.flatten + result when :arbitrary arrange_arbitrary content_to_arrange else From a2f104538bd54f20b89688c702ddd095fa017d9a Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 06:00:58 -0500 Subject: [PATCH 105/152] Fix columnar arrangement - use hoffsets Now it gets the list of nested contents and their size ahead of time and preprocesses it into a single number to use to pad out the gaps between. This only needs to happen with the nested content is not a Frame with a set size other than `:none`. The rest of the time, this code doesn't really do anything. At least it shouldn't! --- lib/remedy/frame.rb | 42 +++++++++++++++++++++++++++++------------- spec/frame_spec.rb | 2 +- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 3790758..1c69bf0 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -257,19 +257,7 @@ def arrange_contents # TODO: insert padding? content_to_arrange.flatten when :columnar - rows = maxsizeof(content_to_arrange).row - result = Array.new - rows.times do |row| - arranged_line = "" - content_to_arrange.each do |content| - line = content[row] - if line then - arranged_line << line - end - end - result << arranged_line - end - result + arrange_columnar content_to_arrange when :arbitrary arrange_arbitrary content_to_arrange else @@ -320,6 +308,34 @@ def arrange_arbitrary content_to_arrange arrange_buffer.to_a end + def arrange_columnar content_to_arrange + content_list = depth_sort(content_to_arrange) + rows = maxsizeof(content_list).row + result = Array.new + + content_sizes = [0] # the first column starts with zero + + content_list.each_with_object(content_sizes).with_index do |(content, cs), i| + content.available_size = available_size if content.respond_to? :available_size + cs << sizeof(content).width + cs[i] + end + + rows.times do |row| + arranged_line = "" + + content_list.each.with_index do |content, index| + line = content[row] + if line then + padding = fill * [content_sizes[index] - arranged_line.length, 0].max + arranged_line << padding + arranged_line << line + end + end + result << arranged_line + end + result + end + def align_contents! content_to_align, original_size case halign when :left diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 6110438..010ad90 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -442,7 +442,7 @@ end it "arranges contents next to each other" do - expected = "ab###\n #c#\n ###" + expected = "ab###\n #c#\n ###" actual = f.to_s expect(actual).to eq expected end From a1e98e13f04e337861398ba3ddb7cd9958eff8a2 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 06:05:30 -0500 Subject: [PATCH 106/152] Move utility methods into protected section --- lib/remedy/frame.rb | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 1c69bf0..587bf9f 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -138,24 +138,6 @@ def content_size sizeof arrange_contents end - def maxsizeof content_list - content_sizes = content_list.map do |content| - sizeof content - end - - height = content_sizes.map(&:height).max || 0 - width = content_sizes.map(&:width).max || 0 - Tuple height, width - end - - def sizeof content - lines = TextUtil.nlclean(content, self).flatten(1) - - height = lines.length - width = lines.map(&:length).max || 0 - Tuple height, width - end - def length if size == :none then content_size.width @@ -354,8 +336,28 @@ def align_contents! content_to_align, original_size content_to_align end - def depth_sort list_of_content - list_of_content.sort do |a,b| + protected + + def maxsizeof content_list + content_sizes = content_list.map do |content| + sizeof content + end + + height = content_sizes.map(&:height).max || 0 + width = content_sizes.map(&:width).max || 0 + Tuple height, width + end + + def sizeof content + lines = TextUtil.nlclean(content, self).flatten(1) + + height = lines.length + width = lines.map(&:length).max || 0 + Tuple height, width + end + + def depth_sort content_list = contents + content_list.sort do |a,b| depthof(a) <=> depthof(b) end end From 678f4f53786abab1057021f099c0356b0bf024f1 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 06:22:30 -0500 Subject: [PATCH 107/152] Remove duplicated test setup and outdated comment --- spec/frame_spec.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 010ad90..da96f85 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -452,12 +452,9 @@ before do f.arrangement = :arbitrary f.available_size = sizeclass.zero - f1.depth = 3 # no longer respected?? - f1.fill = ":" + f1.depth = 3 f1.size = Tuple 1,2 - f2.fill = "*" f2.size = Tuple 2,1 - f3.fill = "#" end it "contents are not relative to others" do From aef2f19113e9311309e7d6b7a034451a23cccc50 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 06:32:37 -0500 Subject: [PATCH 108/152] Stop letting compute_actual_size return nil It meant I could collapse the original calling code to one line, but it was always a silly choice. Now it tries to estimate the actual size, even if set to :none. --- lib/remedy/frame.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 587bf9f..84090bf 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -152,9 +152,13 @@ def length def compile_contents # TODO: insert dirty check and then skip the rest of this if no changes detected, # also a param which overrides this + c = arrange_contents + + return c if size == :none + csize = sizeof c - @computed_size = compute_actual_size(csize) or return c + @computed_size = compute_actual_size csize if buffer then buffer.reset! @@ -173,9 +177,7 @@ def compile_contents def compute_actual_size merged_size if size == :none then - # size none indicates that no further formatting should take place - # when we return nil from here, it will just return the merged array of content - return nil + frame.content_size elsif size == :fill then compile_size = available_size elsif size == :auto then From 6df960623b2c1645b84f5b5efc4ea830fa61076b Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Mon, 9 Oct 2023 07:06:51 -0500 Subject: [PATCH 109/152] Refactor Frame#compute_actual_size and add tests --- lib/remedy/frame.rb | 44 +++++++++++++++++++++++++++++--------------- spec/frame_spec.rb | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 84090bf..f299244 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -175,32 +175,46 @@ def compile_contents buffer.to_a end - def compute_actual_size merged_size - if size == :none then - frame.content_size - elsif size == :fill then - compile_size = available_size - elsif size == :auto then - compile_size = merged_size - elsif Tuple === size then - compile_size = size.dup + # Determine what the actual output size would be based on the size option and contents. + # + # Most of the time the output size can be determined statically based on the available + # size information and the one parameter. + # + # In practice `:none` and `:auto` output the same maximum height and width - despite rendering differences. + # Technically, `:none` will always result in `content_size`, + # while `:auto` could in theory be further modified by the alignment and later processing. + # + # @parram arranged_size [Remedy::Tuple] an externally determined size after preprocessing + # @return [Remedy::Tuple] output size in rows/height and columns/width + def compute_actual_size arranged_size = content_size + case size + when :none + # generally identical to `arranged_size` + # if needed, using that here could save processing + content_size + when :fill + available_size + when :auto + arranged_size + when Tuple + actual_size = size.dup if size.height == 0 then - compile_size[0] = available_size.height + actual_size[0] = available_size.height elsif size.height < 1 then - compile_size[0] = (available_size.height * size.height).floor + actual_size[0] = (available_size.height * size.height).floor end if size.width == 0 then - compile_size[1] = available_size.width + actual_size[1] = available_size.width elsif size.width < 1 then - compile_size[1] = (available_size.width * size.width).floor + actual_size[1] = (available_size.width * size.width).floor end + + actual_size else raise "Unknown max_size:#{size}" end - - compile_size end def compute_horizontal_offset original_size, actual_size diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index da96f85..cb25d42 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -50,6 +50,39 @@ end end + describe "#compute_actual_size" do + it "returns a Tuple of the rendered size" do + f << "1234" + f << "567" + arranged_size = Tuple(5, 5) + + f.size = :none + expected = Tuple 2, 4 + actual = f.compute_actual_size arranged_size + expect(actual).to eq expected + + f.size = :fill + actual = f.compute_actual_size arranged_size + expect(actual).to eq console_size + + f.size = :auto + actual = f.compute_actual_size arranged_size + expect(actual).to eq arranged_size + + f.size = sizeclass.zero + actual = f.compute_actual_size arranged_size + expect(actual).to eq console_size + + f.size = sizeclass.new 2, 2 + actual = f.compute_actual_size arranged_size + expect(actual).to eq sizeclass.new(2, 2) + + f.size = sizeclass.new 0.5, 0.74 + actual = f.compute_actual_size arranged_size + expect(actual).to eq sizeclass.new(10, 29) + end + end + describe "#to_s" do it "returns a string" do expected = String From 2e7fb33601f7af2ac4f05e47b19e7afb32becf65 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Tue, 10 Oct 2023 09:48:55 -0500 Subject: [PATCH 110/152] Add Screenbuffer#parent - does nothing but good for tracking ownership --- lib/remedy/screenbuffer.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 6e1b24e..a0f900a 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -16,7 +16,8 @@ class Screenbuffer # @param ellipsis [String] the character used to indicate truncated lines, # if set to `nil` then content will extend to the edge of the screen # @param charwidth [Numeric] in case we are able to support multiple character widths in the future - def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1, fit: false + def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1, fit: false, parent: nil + raise ArgumentError, "size cannot be `nil'!" if size.nil? @charwidth = charwidth @size = size @fill = fill[0, charwidth] @@ -24,8 +25,9 @@ def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1, fit: fal @ellipsis = ellipsis @buf = new_buf @fit = fit + @parent = parent end - attr_accessor :fill, :nl, :ellipsis, :charwidth, :fit + attr_accessor :fill, :nl, :ellipsis, :charwidth, :fit, :parent # Get the contents of the buffer at a given coordinate. # From 2acc56cd4e8b5862fbb572068d10bb3f8ffa5343 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Tue, 10 Oct 2023 09:49:52 -0500 Subject: [PATCH 111/152] Remove debug logging, remove uneeded parens --- lib/remedy/screenbuffer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index a0f900a..5fa241b 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -144,7 +144,6 @@ def resize new_size end end - Remedy.log.debug "size", size, "actual_size", actual_size, "new_size", new_size, (new_size.height - size.height), "buf", @buf @size = new_size.dup end alias_method :size=, :resize @@ -182,7 +181,8 @@ def replace_perline coords, value return end end - replace_inline(new_coords, line) + + replace_inline new_coords, line end self From 343eee0595a7d96488270910b79d5981082524be Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Tue, 10 Oct 2023 09:56:01 -0500 Subject: [PATCH 112/152] Prefer parent Frame size over available size for nested --- lib/remedy/frame.rb | 9 +++++++-- spec/frame_spec.rb | 12 ++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index f299244..d0a7f2d 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -264,8 +264,13 @@ def arrange_contents end def arrange_arbitrary content_to_arrange - expand_buffer = available_size.zero? - arrange_buffer = Screenbuffer.new available_size, fit: expand_buffer, fill: fill + if size.is_a? Tuple then + buffer_size = size + else + buffer_size = available_size + expand_buffer = available_size.zero? + end + arrange_buffer = Screenbuffer.new buffer_size, fit: expand_buffer, fill: fill result = depth_sort(content_to_arrange).each do |frame| # FIXME: what happens when the buffer size is zero? the buffer will grow, right? diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index cb25d42..3882424 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -525,6 +525,18 @@ actual = f.to_s expect(actual).to eq expected end + + context "available size is zero" do + before do + f.available_size = sizeclass.zero + end + + it "still puts the nested frame at the bottom" do + expected = " \n \n ... \n .a. \n ... " + actual = f.to_s + expect(actual).to eq expected + end + end end end From a13603d3791948767c17336b68bfd142f7e22884 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Tue, 10 Oct 2023 12:46:33 -0500 Subject: [PATCH 113/152] Adds several comparison and math methods to Tuple, plus tests and docs --- lib/remedy/tuple.rb | 117 ++++++++++++++++++++++++- spec/tuple_spec.rb | 209 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+), 4 deletions(-) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index ffca044..fbde3fc 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -64,6 +64,7 @@ def << value # COMPARISON def == other_tuple + return false unless other_tuple.respond_to? :length return false unless bijective? other_tuple self.dimensions.each.with_index do |d, i| @@ -73,21 +74,120 @@ def == other_tuple true end - def fits_into? size_to_fit_into - other_tuple = Tuple(size_to_fit_into) + # Three-way comparison operator AKA the "spaceship operator". + # + # Used for sorting. + # @return [-1,0,1] `-1` if `self > other_tuple`, `1` if `self < other_tuple`, `0` otherwise + def <=> other_tuple + self.aold(other_tuple).magnitude <=> self.aogd(other_tuple).magnitude + end + + # Determines if this Tuple is smaller than the other Tuple in all dimensions. + # @param other_tuple [Remedy::Tuple] + # @return [Boolean] `true` if this Tuple is the same size or smaller, `false` otherwise. + def fits_into? other_tuple + return false if self.cardinality > other_tuple.cardinality cardinality.times.each do |index| return false if self[index] > other_tuple[index] end true end - # Determines if the two tuples have the same number of dimensions - # uses `length` on the other object so it can be used in comparison with more types + # Determines if the two tuples have the same number of dimensions + # uses `length` on the other object so it can be used in comparison with more types than just Tuples. + # @param other_tuple [Remedy::Tuple] + # @return [Boolean] def bijective? other_tuple cardinality == other_tuple.length end alias_method :sizesame?, :bijective? + # Returns a Tuple where the dimensions are the absolute values of the current Tuple. + # @return [Remedy::Tuple] + def abs + self.class.new dimensions.map(&:abs) + end + + # Returns the total magnitude of all dimensions. + # @return [Numeric] + def magnitude + dimensions.map(&:abs).sum + end + + # The area of difference. + # + # Indicates the magnitude of the difference between two Tuples. + # + # @param other_tuple [Remedy::Tuple] + # @return [Remedy::Tuple] + def aod other_tuple + result = [cardinality, other_tuple.cardinality].max.times.with_object(self.class.new) do |index, area| + if self[index].nil? then + area << other_tuple[index].abs + elsif other_tuple[index].nil? then + area << self[index].abs + else + difference = other_tuple[index] - self[index] + + area << difference.abs + end + end + + self.class.tuplify result + end + + # The area of lesser difference. + # + # Indicates where the `other` Tuple is lesser and by how much. + # + # @param other_tuple [Remedy::Tuple] + # @return [Remedy::Tuple] + def aold other_tuple + result = [cardinality, other_tuple.cardinality].max.times.with_object(self.class.new) do |index, area| + if self[index].nil? then + area << 0 + elsif other_tuple[index].nil? then + area << self[index] + else + difference = self[index] - other_tuple[index] + + if difference < 0 then + area << 0 + else + area << difference + end + end + end + + self.class.tuplify result + end + + # The area of greater difference. + # + # Indicates where the `other` Tuple is greater and by how much. + # + # @param other_tuple [Remedy::Tuple] + # @return [Remedy::Tuple] + def aogd other_tuple + result = [cardinality, other_tuple.cardinality].max.times.with_object(self.class.new) do |index, area| + if self[index].nil? then + area << other_tuple[index] + elsif other_tuple[index].nil? then + area << 0 + else + difference = other_tuple[index] - self[index] + + if difference < 0 then + area << 0 + else + area << difference + end + end + end + + self.class.tuplify result + end + # ACCESSORS def x @@ -144,17 +244,26 @@ def []= index, value dimensions[index] = value end + # @return [Integer] The number of dimensions in this Tuple. def cardinality dimensions.length end alias_method :length, :cardinality + # @return [Boolean] `true` if all dimensions are zero, otherwise `false`. def zero? dimensions.all? do |d| d == 0 end end + # @return [Boolean] `true` if any dimension is nonzero, otherwise `false`. + def nonzero? + dimensions.any? do |d| + d != 0 + end + end + # CONVERSIONS def to_a diff --git a/spec/tuple_spec.rb b/spec/tuple_spec.rb index 2ae1e21..159c9db 100644 --- a/spec/tuple_spec.rb +++ b/spec/tuple_spec.rb @@ -48,6 +48,111 @@ end end + describe "#abs" do + it "returns a version of the tuple where all dimensions are positive" do + n = described_class.new -1, -0.5 + expected = described_class.new 1, 0.5 + actual = n.abs + expect(actual).to eq expected + end + end + + describe "#aod" do + context "other has same cardinality" do + let(:other) { described_class.new 0, 2 } + + it "returns a Tuple with the area of difference" do + expected = described_class.new 1, 1 + actual = t.aod other + expect(actual).to eq expected + end + end + + context "other has smaller cardinality" do + let(:other) { described_class.new 3 } + + it "returns a Tuple with the area of difference" do + expected = described_class.new 2, 1 + actual = t.aod other + expect(actual).to eq expected + end + end + + context "other has larger cardinality" do + let(:other) { described_class.new -1, 1, 3 } + + it "returns a Tuple with the area of difference" do + expected = described_class.new 2, 0, 3 + actual = t.aod other + expect(actual).to eq expected + end + end + end + + describe "#aold" do + context "other has same cardinality" do + let(:other) { described_class.new 0, 2 } + + it "returns a Tuple with the area of lesser difference" do + expected = described_class.new 1, 0 + actual = t.aold other + expect(actual).to eq expected + end + end + + context "other has smaller cardinality" do + let(:other) { described_class.new 3 } + + it "returns a Tuple with the area of lesser difference" do + expected = described_class.new 0, 1 + actual = t.aold other + expect(actual).to eq expected + end + end + + context "other has larger cardinality" do + let(:other) { described_class.new -1, 1, 3 } + + it "returns a Tuple with the area of lesser difference" do + expected = described_class.new 2, 0, 0 + actual = t.aold other + expect(actual).to eq expected + end + end + end + + describe "#aogd" do + context "other has same cardinality" do + let(:other) { described_class.new 0, 2 } + + it "returns a Tuple with the area of greater difference" do + expected = described_class.new 0, 1 + actual = t.aogd other + expect(actual).to eq expected + end + end + + context "other has smaller cardinality" do + let(:other) { described_class.new 3 } + + it "returns a Tuple with the area of greater difference" do + expected = described_class.new 2, 0 + actual = t.aogd other + expect(actual).to eq expected + end + end + + context "other has larger cardinality" do + let(:other) { described_class.new -1, 1, 3 } + + it "returns a Tuple with the area of greater difference" do + expected = described_class.new 0, 0, 3 + actual = t.aogd other + expect(actual).to eq expected + end + end + end + describe "#zero?" do it "returns true when all dimensions are zero" do z = described_class.new 0, 0 @@ -56,4 +161,108 @@ expect(actual).to eq expected end end + + describe "#nonzero?" do + it "returns true when any dimension is not zero" do + z = described_class.new 1, 0 + expected = true + actual = z.nonzero? + expect(actual).to eq expected + end + end + + describe "#<=>" do + context "other Tuple is larger" do + let(:other){ described_class.new 2, 2 } + + it "returns -1" do + expected = -1 + actual = t <=> other + expect(actual).to eq expected + end + + it "returns -1 due to magnitude" do + other = described_class.new 0, 3 + expected = -1 + actual = t <=> other + expect(actual).to eq expected + end + end + + context "other Tuple is smaller" do + let(:other){ described_class.new 0, 0 } + + it "returns 1 when the other tuple is lesser" do + expected = 1 + actual = t <=> other + expect(actual).to eq expected + end + end + + context "other Tuple is the same size" do + let(:other){ t.dup } + + it "returns 0" do + expected = 0 + actual = t <=> other + expect(actual).to eq expected + end + end + end + + describe "#fits_into?" do + context "other has same cardinality" do + context "and is bigger in one dimension but smaller in another" do + let(:other) { described_class.new 0, 2 } + + it "does not fit" do + expected = false + actual = t.fits_into? other + expect(actual).to eq expected + end + end + + context "and is bigger in one dimension but equal in another" do + let(:other) { described_class.new 1, 2 } + + it "returns a Tuple with the area of greater difference" do + expected = true + actual = t.fits_into? other + expect(actual).to eq expected + end + end + end + + context "other has smaller cardinality" do + let(:other) { described_class.new 3 } + + it "does not fit" do + expected = false + actual = t.fits_into? other + expect(actual).to eq expected + end + end + + context "other has larger cardinality" do + context "and has a negative dimension" do + let(:other) { described_class.new -1, 1, 3 } + + it "does not fit" do + expected = false + actual = t.fits_into? other + expect(actual).to eq expected + end + end + + context "has more dimensions which are the same size" do + let(:other) { described_class.new 1, 1, 1 } + + it "does fit" do + expected = true + actual = t.fits_into? other + expect(actual).to eq expected + end + end + end + end end From b713df952d6ec84bb149eba51ac02913b9e752bc Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:00:55 -0500 Subject: [PATCH 114/152] Fix bottom origins underflowing buffer --- lib/remedy/align.rb | 16 ++++++++++++++++ lib/remedy/frame.rb | 15 ++++++++++----- spec/frame_spec.rb | 20 ++++++++++++++++---- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/lib/remedy/align.rb b/lib/remedy/align.rb index 5b8d9b0..f5874f7 100644 --- a/lib/remedy/align.rb +++ b/lib/remedy/align.rb @@ -38,6 +38,8 @@ def hv_center content, buffer buffer[voffset,hoffset] = content end + # Middle offset. + # # Given the actual space something takes up, # determine what the offset to get it centered in the available space. # @@ -50,6 +52,20 @@ def mido actual, available offset = ((available - actual) / 2.0).floor end + # Bottom offset. + # + # Given the actual space something takes up, + # determine what the offset to get it at the far edge in the available space. + # + # @param actual [Numeric] the space already taken + # @param available [Numeric] the available space + # @return [Integer] the offset from the end of the availabe space to place the actual content at the end + def boto actual, available + return available unless actual < available + + offset = available - actual + end + # Given the actual space something takes up, # determine what the offset to get it centered in the available space, # including the trailing space remaining. diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index d0a7f2d..8e0c18c 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -274,7 +274,7 @@ def arrange_arbitrary content_to_arrange result = depth_sort(content_to_arrange).each do |frame| # FIXME: what happens when the buffer size is zero? the buffer will grow, right? - frame.available_size = arrange_buffer.size + frame.available_size = buffer_size content = frame.compile_contents fsize = frame.computed_size || frame.content_size @@ -282,20 +282,24 @@ def arrange_arbitrary content_to_arrange when :top voffset = 0 when :center - voffset = Align.mido fsize.height, arrange_buffer.size.height + voffset = Align.mido fsize.height, buffer_size.height when :bottom - voffset = arrange_buffer.size.height - fsize.height + voffset = Align.boto fsize.height, buffer_size.height else raise "Unknown vorigin:#{frame.vorigin}" end + # this line works around an edge case where only :top vorigins would + # be rendered when available_size was zero and the frame size was :none + voffset = 0 if frame.vorigin != :top && buffer_size.height == 0 && voffset < 0 + case frame.horigin when :left hoffset = 0 when :center - hoffset = Align.mido fsize.width, arrange_buffer.size.width + hoffset = Align.mido fsize.width, buffer_size.width when :right - hoffset = arrange_buffer.size.width - fsize.width + voffset = Align.boto fsize.height, buffer_size.height else raise "Unknown horigin:#{frame.horigin}" end @@ -306,6 +310,7 @@ def arrange_arbitrary content_to_arrange offset = Tuple voffset, hoffset arrange_buffer[offset] = content + buffer_size = arrange_buffer.size end arrange_buffer.to_a diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 3882424..3a3272d 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -509,11 +509,8 @@ f.arrangement = :arbitrary f.reset! - f1.size = Tuple 3, 3 f1.fill = "." - f1.valign = :center - f1.halign = :center f1.horigin = :center f1.vorigin = :bottom @@ -526,7 +523,7 @@ expect(actual).to eq expected end - context "available size is zero" do + context "available_size.zero? = true" do before do f.available_size = sizeclass.zero end @@ -536,6 +533,21 @@ actual = f.to_s expect(actual).to eq expected end + + context "size = :none" do + before do + f.size = :none + f1.depth = 2 + f2.size = Tuple 2,1 + f << f2 + end + + it "puts the frame at the bottom of the actual space" do + expected = "b \n...\n.a.\n..." + actual = f.to_s + expect(actual).to eq expected + end + end end end end From 88d72e164cb207fd63e72f91f0b78e8145cea016 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:09:50 -0500 Subject: [PATCH 115/152] Test negative layers, test arbitrary arrangements of non-frame types --- lib/remedy/frame.rb | 7 +++++++ spec/frame_spec.rb | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 8e0c18c..c570b14 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -273,6 +273,13 @@ def arrange_arbitrary content_to_arrange arrange_buffer = Screenbuffer.new buffer_size, fit: expand_buffer, fill: fill result = depth_sort(content_to_arrange).each do |frame| + # special case handling of plain Strings and Arrays + unless frame.is_a? Frame then + arrange_buffer[Tuple.zero] = frame + buffer_size = arrange_buffer.size + next + end + # FIXME: what happens when the buffer size is zero? the buffer will grow, right? frame.available_size = buffer_size content = frame.compile_contents diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 3a3272d..2cf3c84 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -571,8 +571,8 @@ f.fill = "." end - it "places frames on top of each other according to their depth and order" do - expected = [ + let(:expected) do + [ ":::....", ":a:....", "::***..", @@ -581,14 +581,32 @@ "....#c#", "....###" ].join ?\n + end + it "places frames on top of each other according to their depth and order" do actual = f.to_s expect(actual).to eq expected end - xit "treats plain strings as layer 0" do + it "treats plain strings as layer 0" do + f.reset! + f << f2 + f << f3 + f << f1.to_s + actual = f.to_s expect(actual).to eq expected end + + context "negative depth" do + before do + f1.depth = -1 + end + + it "places frames properly" do + actual = f.to_s + expect(actual).to eq expected + end + end end end From 9771a86af93bac4d033d7b0320620f1651d1f5b1 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:10:31 -0500 Subject: [PATCH 116/152] Explain override param --- lib/remedy/screen.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 91bb7bf..ea99164 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -26,6 +26,7 @@ def initialize auto_resize: true attr_accessor :buffer # Draw the buffer to the console using raw output. + # @param override [Remedy::Frame,String] temporarily replace the contents with this instead (until the next redraw!) # @return [void] def draw override = nil if override then From f78817bcb3c221152dcffdc47181c05ac4e256b8 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:11:22 -0500 Subject: [PATCH 117/152] Clarify comment to discuss downsides of workaround --- lib/remedy/screenbuffer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 5fa241b..c5648a0 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -127,7 +127,7 @@ def resize new_size raise ArgumentError unless new_size.is_a? Tuple # FIXME: @size is getting reset to old versions somehow. # But if we determine the actual size and use that instead, - # then we work around that behavior. + # then we work around that behavior at some performance cost. actual_size = compute_actual_size if new_size.height > actual_size.height then From 9b5e4983b4f8d21f45d97e38302ce2e0ed9c669f Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:12:04 -0500 Subject: [PATCH 118/152] Better error messages for when negative indicies are out of range --- lib/remedy/frame.rb | 4 ++-- lib/remedy/screen.rb | 2 +- lib/remedy/screenbuffer.rb | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index c570b14..556ed84 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -163,7 +163,7 @@ def compile_contents if buffer then buffer.reset! else - @buffer = Screenbuffer.new computed_size, fill: fill, nl: nl + @buffer = Screenbuffer.new computed_size, fill: fill, nl: nl, parent: self, name: "compile_contents" end hoffset = compute_horizontal_offset csize, computed_size @@ -270,7 +270,7 @@ def arrange_arbitrary content_to_arrange buffer_size = available_size expand_buffer = available_size.zero? end - arrange_buffer = Screenbuffer.new buffer_size, fit: expand_buffer, fill: fill + arrange_buffer = Screenbuffer.new buffer_size, fit: expand_buffer, fill: fill, parent: self, name: "arrange_arbitrary" result = depth_sort(content_to_arrange).each do |frame| # special case handling of plain Strings and Arrays diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index ea99164..ac6a0bc 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -17,7 +17,7 @@ class Screen # @see #resized # @see Console.set_console_resized_hook! def initialize auto_resize: true - @buffer = Screenbuffer.new Console.size, fill: "." + @buffer = Screenbuffer.new Console.size, fill: ".", parent: self, name: "screen#init" Console.set_console_resized_hook! do |new_size| resized new_size diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index c5648a0..869347f 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -16,7 +16,7 @@ class Screenbuffer # @param ellipsis [String] the character used to indicate truncated lines, # if set to `nil` then content will extend to the edge of the screen # @param charwidth [Numeric] in case we are able to support multiple character widths in the future - def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1, fit: false, parent: nil + def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1, fit: false, parent: nil, name: object_id raise ArgumentError, "size cannot be `nil'!" if size.nil? @charwidth = charwidth @size = size @@ -26,8 +26,9 @@ def initialize size, fill: " ", nl: ?\n, ellipsis: "…", charwidth: 1, fit: fal @buf = new_buf @fit = fit @parent = parent + @name = name end - attr_accessor :fill, :nl, :ellipsis, :charwidth, :fit, :parent + attr_accessor :fill, :nl, :ellipsis, :charwidth, :fit, :parent, :name # Get the contents of the buffer at a given coordinate. # @@ -198,6 +199,8 @@ def replace_inline coords, value truncated_value[-1] = ellipsis[0,charwidth] if ellipsis && truncated_value.length < value.length end + raise RangeError, "Negative range #{coords} extends beyond boundary #{size} by #{size.aogd coords.abs} for #{"#{parent.name}/" if parent && parent.respond_to?(:name)}#{name}" if (-coords.row) > size.height + buf[coords.row][coords.col,truncated_value.length] = truncated_value end end From 88d043423a458aa7d208e18f326f5f49e5328664 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:36:09 -0500 Subject: [PATCH 119/152] Defensive programming: Sizes are prone to accidental alteration --- lib/remedy/screenbuffer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 869347f..74d738c 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -92,7 +92,7 @@ def buf= override_buf # @return [Remedy::Tuple] the size of the buffer in rows and columns def size - @size + @size.dup end # @return [Array] the contents of the buffer as an array of strings From fdb5e5dacb4802f1fefef2b65230470f5e2884e5 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:39:02 -0500 Subject: [PATCH 120/152] Disallow zero height buffers from having width --- lib/remedy/screenbuffer.rb | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index 74d738c..d4e7c5d 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -144,11 +144,32 @@ def resize new_size l << fill * grow_by end end + raise RangeError, "cannot have buffer width without buffer height! new_size: #{new_size}" if new_size.height < 1 and new_size.width > 0 @size = new_size.dup end alias_method :size=, :resize + def fit_height height + grow_height height - @size.height + end + + def fit_width width + grow_width width - @size.width + end + + def grow_height additional_rows + grow Tuple(additional_rows, 0) if additional_rows > 0 + end + + def grow_width additional_cols + grow Tuple(0, additional_cols) if additional_cols > 0 + end + + def grow amount + resize @size + amount + end + def compute_actual_size array2d = buf Tuple array2d.length, (array2d.map{|l|l.length}.max || 0) end @@ -175,8 +196,7 @@ def replace_perline coords, value new_coords = coords + Tuple(index,0) if new_coords.height >= size.height then if fit then - grow_size = Tuple (new_coords.height + 1), size.width - resize(grow_size) + fit_height new_coords.height + 1 size.height = new_coords.height else return @@ -191,8 +211,7 @@ def replace_perline coords, value def replace_inline coords, value if fit then - grow_size = Tuple size.height, value.length - resize(grow_size) + fit_width value.length truncated_value = value # not truncated else truncated_value = value[0,size.width - coords.col] From 10d3184563610839151ef7935378685d0233401d Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:41:51 -0500 Subject: [PATCH 121/152] Change expected behavior based on last commit Previously, the code would grab the last line and then append the bottom Frame to it. That was defensivble bahvior for this edge case. Now, it adds a new line to the end and adds it there. I think this is also defensible. I consider this an edge case that I have spent too much time on already, but ideally I think that it should truncate the top of the nested Frame and its bottom line should be on the bottom line of the parent Frame, instead of the top line of the nested Frame being on the bottom line of the parent Frame. --- spec/frame_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 2cf3c84..21a6b09 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -543,7 +543,7 @@ end it "puts the frame at the bottom of the actual space" do - expected = "b \n...\n.a.\n..." + expected = "b \n* \n...\n.a.\n..." actual = f.to_s expect(actual).to eq expected end From 12dcc66433e01bea557f18df50be0227e6d8579c Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:50:20 -0500 Subject: [PATCH 122/152] Clean up some type tests --- lib/remedy/frame.rb | 3 ++- lib/remedy/text_util.rb | 8 +------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 556ed84..cf3fd1a 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -396,7 +396,8 @@ def depth_sort content_list = contents end def depthof content - if content.is_a? Frame then + case content + when Frame content.depth else 0 diff --git a/lib/remedy/text_util.rb b/lib/remedy/text_util.rb index 88a1c54..6023c75 100644 --- a/lib/remedy/text_util.rb +++ b/lib/remedy/text_util.rb @@ -6,18 +6,12 @@ def nlclean content, context = nil case content when String nlsplit(content) - when Frame - content.to_a - when Partial - content.to_a - when View - content.to_a when Array content.map do |l| nlclean l end else - binding.pry + content.to_a end end From f7e47508cfc812096b853c44011f5ad723bf9988 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:51:32 -0500 Subject: [PATCH 123/152] Save VS Code Workspace --- .vscode/remedy.code-workspace | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .vscode/remedy.code-workspace diff --git a/.vscode/remedy.code-workspace b/.vscode/remedy.code-workspace new file mode 100644 index 0000000..bab1b7f --- /dev/null +++ b/.vscode/remedy.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": ".." + } + ], + "settings": {} +} \ No newline at end of file From cd98ea5fce002237d660fe7528025aa8f8acca05 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:58:04 -0500 Subject: [PATCH 124/152] Add new complex example, add dev tools to Gemfile --- Gemfile | 10 +- examples/from_readme/Gemfile | 2 +- examples/menu/Gemfile | 2 +- examples/new_example/Gemfile | 8 ++ examples/new_example/new_example.rb | 145 ++++++++++++++++++++++++++++ remedy.gemspec | 4 - 6 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 examples/new_example/Gemfile create mode 100644 examples/new_example/new_example.rb diff --git a/Gemfile b/Gemfile index 1a0a42d..92eed56 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,11 @@ source 'https://rubygems.org' gemspec unless ENV['CI'] then - gem 'pry' - gem 'pry-doc' - gem 'pry-theme' + gem "pry" + gem "pry-doc" + gem "pry-coolline" + gem "pry-theme" + gem "logsaber" + gem "yard" + gem "webrick" end diff --git a/examples/from_readme/Gemfile b/examples/from_readme/Gemfile index 07849d7..9db9539 100644 --- a/examples/from_readme/Gemfile +++ b/examples/from_readme/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem "remedy", path: "../.." \ No newline at end of file +gem "remedy", path: "../.." diff --git a/examples/menu/Gemfile b/examples/menu/Gemfile index 07849d7..9db9539 100644 --- a/examples/menu/Gemfile +++ b/examples/menu/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem "remedy", path: "../.." \ No newline at end of file +gem "remedy", path: "../.." diff --git a/examples/new_example/Gemfile b/examples/new_example/Gemfile new file mode 100644 index 0000000..41b1586 --- /dev/null +++ b/examples/new_example/Gemfile @@ -0,0 +1,8 @@ +source "https://rubygems.org" + +gem "remedy", path: "../.." +gem "pry" +gem "pry-doc" +gem "pry-coolline" +gem "pry-theme" +gem "logsaber" diff --git a/examples/new_example/new_example.rb b/examples/new_example/new_example.rb new file mode 100644 index 0000000..1984b1a --- /dev/null +++ b/examples/new_example/new_example.rb @@ -0,0 +1,145 @@ +require "remedy" +require "remedy/screen" +require "remedy/frame" + +class Example + include Remedy + + def self.cleanup + ANSI.screen.safe_reset! + Console.cooked! + ANSI.cursor.show! + end + + def setup + trap "SIGINT" do + Example.cleanup + warn "Caught control-c interupt!" + exit 130 + end + end + + def run + screen = Screen.new + screen.frames << lsidebar + screen.frames << rsidebar + screen.frames << content + screen.frames << window + screen.frames << modal + + screen.draw + + Interaction.new.get_key + ensure + self.class.cleanup + end + + def lsidebar + f = Frame.new name: "lsidebar" + f.horigin = :left + f.vorigin = :center + f.halign = :left + f.valign = :center + f.size = Tuple 0, 0.25 + f.fill = "=" + f + end + + def rsidebar + f = Frame.new name: "rsidebar" + f.horigin = :right + f.vorigin = :center + f.halign = :left + f.valign = :center + f.size = Tuple 0, 0.25 + f.fill = "+" + f + end + + def content + f = Frame.new name: "content" + f.horigin = :center + f.vorigin = :center + f.halign = :left + f.valign = :top + f.size = Tuple 0, 0.5 + f.fill = ":" + f.arrangement = :columnar + f << l_col + f << r_col + f + end + + def l_col + f = Frame.new name: "l_col" + f << "foo" + f.halign = :left + f.valign = :top + f.fill = "-" + f.size = Tuple 5, 5 + f + end + + def r_col + f = Frame.new name: "r_col" + f << "bar" + f.halign = :right + f.valign = :bottom + f.fill = "|" + f.size = Tuple 5, 5 + f + end + + def modal + f = Frame.new name: "modal" + f.horigin = :center + f.vorigin = :center + f.halign = :center + f.valign = :center + f.size = Tuple 3, 15 + f.fill = "#" + + msg = "hello, world!" + f << msg + f + end + + def window + f = Frame.new name: "window" + f.arrangement = :arbitrary + f.offset = Tuple 5, 5 + f.size = Tuple 10, 20 + f.fill = "'" + f << titlebar + f << statusbar + f + end + + def titlebar + f = Frame.new name: "titlebar" + f.vorigin = :top + f.halign = :center + # FIXME: zero sizes don't yet work for nested frames + #f.size = Tuple 1, 0 + f.size = Tuple 1, 20 + f.fill = "━" + f << "┫ title ┣" + f + end + + def statusbar + f = Frame.new name: "statusbar" + f.vorigin = :bottom + f.horigin = :left + f.halign = :center + f.depth = 3 + # FIXME: zero sizes don't yet work for nested frames + #f.size = Tuple 1, 0 + f.size = Tuple 1, 20 + f.fill = "┄" + f << Time.now.to_s + f + end +end + +Example.new.run diff --git a/remedy.gemspec b/remedy.gemspec index 9d8bddb..64d1f4a 100644 --- a/remedy.gemspec +++ b/remedy.gemspec @@ -19,8 +19,4 @@ Gem::Specification.new do |gem| gem.require_paths = ["lib"] gem.add_development_dependency 'rspec' - gem.add_development_dependency 'logsaber' - gem.add_development_dependency 'pry' - gem.add_development_dependency 'yard' - gem.add_development_dependency 'webrick' end From f4f907f5e4c2c055b08e857e520eaaad8d20ace6 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 05:59:55 -0500 Subject: [PATCH 125/152] Ignore log files created by debugging tools --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d87d4be..7cec3b4 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ spec/reports test/tmp test/version_tmp tmp +remedy.log From d6f15aff7793bb7d3eb74042053126473149283a Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 06:07:06 -0500 Subject: [PATCH 126/152] Renamed Screen#resized to Screen#resize, allow disabling auto redrawing while still auto resizing --- lib/remedy/screen.rb | 10 +++++----- spec/screen_spec.rb | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index ac6a0bc..27e5fe5 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -14,13 +14,13 @@ class Screen # Might be good for having multiple workspaces. # # @param auto_resize [Boolean] can be disabled if you are setting up your own console resize hook - # @see #resized + # @see #resize # @see Console.set_console_resized_hook! - def initialize auto_resize: true + def initialize auto_resize: true, auto_redraw: true @buffer = Screenbuffer.new Console.size, fill: ".", parent: self, name: "screen#init" Console.set_console_resized_hook! do |new_size| - resized new_size + resize new_size, redraw: auto_redraw end if auto_resize end attr_accessor :buffer @@ -52,13 +52,13 @@ def frames # # ```ruby # Console.set_console_resized_hook! do |new_size| - # my_screen.resized new_size + # my_screen.resize new_size # end # ``` # # @param new_size [Remedy::Tuple] the new size of the terminal # @return [void] - def resized new_size, redraw: true + def resize new_size, redraw: true buffer.size = new_size draw if redraw end diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 9df6204..e540302 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -32,7 +32,7 @@ before(:each) do console.input = stringio console.output = stringio - s.resized size + s.resize size, redraw: true # why does this need to be set to true?? stringio.string = "" end @@ -102,7 +102,7 @@ end end - describe "#resized" do + describe "#resize" do let(:new_size_override){ Tuple 5, 9 } before do @@ -123,7 +123,7 @@ s.buffer.fill = " " s.frames << frame - s.resized new_size_override, redraw: false + s.resize new_size_override, redraw: false actual = s.to_s expect(actual).to eq expected From 86d4d4aea279a6fa7499c0e3e93e8d48a07a5ab4 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 06:21:03 -0500 Subject: [PATCH 127/152] Add parent attribute to Frame - Easier to debug where the Frame is being used - Can use the parent to pull additional information in nested Frames --- lib/remedy/frame.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index cf3fd1a..8857e16 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -8,7 +8,7 @@ module Remedy # Frames contain Panes and Panes contain Partials # Frames can be nested within other Frames or Panes class Frame - def initialize name: self.object_id, content: nil + def initialize name: self.object_id, content: nil, parent: nil @name = name # vorigin is where the frame will be attached vertically @@ -66,13 +66,15 @@ def initialize name: self.object_id, content: nil # newline character @nl = ?\n + + @parent = parent end attr_accessor :vorigin, :horigin, :depth attr_accessor :name, :size, :available_size attr_accessor :nl, :fill, :halign, :valign attr_reader :contents - + attr_accessor :parent # Determines how contents are arranged when compiling. # From 4a80fed7338bfd05fac8cda297d79c448658b488 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 06:28:29 -0500 Subject: [PATCH 128/152] Depth sort first, set parent in depth_sort --- lib/remedy/frame.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 8857e16..a2bcc0d 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -104,7 +104,8 @@ def initialize name: self.object_id, content: nil, parent: nil attr_accessor :buffer def << new_content - if new_content.is_a? String or new_content.is_a? Array then + case new_content + when String, Array conformed_content = TextUtil.nlclean(new_content) else conformed_content = new_content @@ -250,7 +251,7 @@ def compute_vertical_offset original_size, actual_size end def arrange_contents - content_to_arrange = @contents + content_to_arrange = depth_sort case arrangement when :stacked @@ -274,7 +275,7 @@ def arrange_arbitrary content_to_arrange end arrange_buffer = Screenbuffer.new buffer_size, fit: expand_buffer, fill: fill, parent: self, name: "arrange_arbitrary" - result = depth_sort(content_to_arrange).each do |frame| + result = content_to_arrange.each do |frame| # special case handling of plain Strings and Arrays unless frame.is_a? Frame then arrange_buffer[Tuple.zero] = frame @@ -326,7 +327,7 @@ def arrange_arbitrary content_to_arrange end def arrange_columnar content_to_arrange - content_list = depth_sort(content_to_arrange) + content_list = content_to_arrange rows = maxsizeof(content_list).row result = Array.new @@ -393,6 +394,8 @@ def sizeof content def depth_sort content_list = contents content_list.sort do |a,b| + a.parent = self if a.respond_to? :parent= + b.parent = self if b.respond_to? :parent= depthof(a) <=> depthof(b) end end From 07f71b37b98d9d64f739194499c461d5ac2ce5f1 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 10:31:32 -0500 Subject: [PATCH 129/152] Fix typo in comment --- lib/remedy/frame.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index a2bcc0d..40ccd7e 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -84,7 +84,7 @@ def initialize name: self.object_id, content: nil, parent: nil # - `:columnar` # - `:arbitrary` # - # @return [Symbol] one of the preset arragements + # @return [Symbol] one of the preset arrangements attr_accessor :arrangement # Sets the offset from the origin point. From f853f6136c71d5f4407e2e82f44609d015010bc6 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 10:32:06 -0500 Subject: [PATCH 130/152] Move stacked arrangement code to its own method - Add support for non-Frame types by calling .to_a on each content - Set available_size on contents --- lib/remedy/frame.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 40ccd7e..1c7d260 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -255,8 +255,7 @@ def arrange_contents case arrangement when :stacked - # TODO: insert padding? - content_to_arrange.flatten + arrange_stacked content_to_arrange when :columnar arrange_columnar content_to_arrange when :arbitrary @@ -266,6 +265,14 @@ def arrange_contents end end + def arrange_stacked content_to_arrange + # TODO: insert padding? + content_to_arrange.map do |content| + content.available_size = available_size if content.respond_to? :available_size + content.to_a + end.flatten + end + def arrange_arbitrary content_to_arrange if size.is_a? Tuple then buffer_size = size From 4281823df9b470dd1cceffdcbde64a070a8bcbf9 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 10:36:32 -0500 Subject: [PATCH 131/152] Fix copy/paste error causing hoffsets to be wrong --- lib/remedy/frame.rb | 2 +- spec/frame_spec.rb | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 1c7d260..9186c04 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -316,7 +316,7 @@ def arrange_arbitrary content_to_arrange when :center hoffset = Align.mido fsize.width, buffer_size.width when :right - voffset = Align.boto fsize.height, buffer_size.height + hoffset = Align.boto fsize.width, buffer_size.width else raise "Unknown horigin:#{frame.horigin}" end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 21a6b09..67a1cb0 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -549,6 +549,30 @@ end end end + + context "horigin = :center" do + before do + f.size = Tuple 5, 5 + f.available_size = Tuple 5, 5 + f.arrangement = :arbitrary + f.reset! + + f1.size = Tuple 3, 3 + f1.fill = "." + + f1.horigin = :center + f << f1 + end + + xit "parent frame places it in the middle" do + expected = " ... \n .a. \n ... \n \n " + end + end + + context "horigin = :right" do + xit "parent frame places it to the right" + end + end end From 1083e330e4e7c7c6f7ea13cb18de9425da7bd450 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 10:37:31 -0500 Subject: [PATCH 132/152] Replace bespoke Screen layout code with dedicated Frame --- lib/remedy/screen.rb | 76 +++++++++++++++----------------------------- spec/screen_spec.rb | 9 +++--- 2 files changed, 30 insertions(+), 55 deletions(-) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 27e5fe5..4e9f4ea 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -17,29 +17,39 @@ class Screen # @see #resize # @see Console.set_console_resized_hook! def initialize auto_resize: true, auto_redraw: true - @buffer = Screenbuffer.new Console.size, fill: ".", parent: self, name: "screen#init" + @mainframe = Frame.new name: "screen#init", parent: self + mainframe.fill = "." + mainframe.size = :fill + mainframe.arrangement = :arbitrary Console.set_console_resized_hook! do |new_size| resize new_size, redraw: auto_redraw end if auto_resize end - attr_accessor :buffer + attr_accessor :mainframe # Draw the buffer to the console using raw output. # @param override [Remedy::Frame,String] temporarily replace the contents with this instead (until the next redraw!) # @return [void] def draw override = nil if override then - Align.hv_center override, buffer + f = Frame.new name: "screen#draw/override", parent: self, content: override + f.size = :fill + f.fill = "." + f.available_size = mainframe.available_size + f.halign = :center + f.valign = :center + frame = f else - refresh_buffer + refresh + frame = mainframe end ANSI.screen.safe_reset! - Console.output << buffer.to_ansi + Console.output << frame.to_ansi end def frames - @frames ||= Array.new + mainframe.contents end # This sets the new screen size and rebuilds the buffer before redrawing it. @@ -59,63 +69,27 @@ def frames # @param new_size [Remedy::Tuple] the new size of the terminal # @return [void] def resize new_size, redraw: true - buffer.size = new_size + mainframe.available_size = new_size draw if redraw end def to_a - refresh_buffer - buffer.to_a + refresh + mainframe.to_a end def to_s - refresh_buffer - buffer.to_s + refresh + mainframe.to_s end def to_ansi - refresh_buffer - buffer.to_ansi + refresh + mainframe.to_ansi end - def refresh_buffer - buffer.reset! - populate_buffer - end - - def populate_buffer - frames.sort_by(&:depth).each do |frame| - frame.available_size = buffer.size - content = frame.compile_contents - fsize = frame.computed_size - - case frame.vorigin - when :top - voffset = 0 - when :center - voffset = Align.mido fsize.height, buffer.size.height - when :bottom - voffset = buffer.size.height - fsize.height - else - raise "Unknown vorigin:#{frame.vorigin}" - end - - case frame.horigin - when :left - hoffset = 0 - when :center - hoffset = Align.mido fsize.width, buffer.size.width - when :right - hoffset = buffer.size.width - fsize.width - else - raise "Unknown horigin:#{frame.horigin}" - end - - voffset += frame.offset.height - hoffset += frame.offset.width - - buffer[voffset,hoffset] = content - end + def refresh + mainframe.compile_contents end end end diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index e540302..828f120 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -24,6 +24,7 @@ before(:each) do console.size_override = size_override + s.resize size_override, redraw: false end context "captured STDIO" do @@ -108,6 +109,8 @@ before do frame.size = Tuple(0, 0.5) frame.fill = "." + s.mainframe.fill = " " + s.frames << frame end it "resizes internal frames" do @@ -121,8 +124,6 @@ " .... " ].join ?\n - s.buffer.fill = " " - s.frames << frame s.resize new_size_override, redraw: false actual = s.to_s @@ -141,6 +142,8 @@ frame.offset = Tuple -1, -2 frame.size = Tuple(4, 0.5) frame.fill = "." + s.mainframe.fill = " " + s.frames << frame end it "moves the frame away from the point of origin" do @@ -153,8 +156,6 @@ " " ].join ?\n - s.buffer.fill = " " - s.frames << frame actual = s.to_s expect(actual).to eq expected From eaf6b9c5e4b24cdef57a7cba9875317ba8ac3b38 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 10:38:13 -0500 Subject: [PATCH 133/152] Start to break Frame tests out into their own files and reorganize --- spec/frame_origin_spec.rb | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 spec/frame_origin_spec.rb diff --git a/spec/frame_origin_spec.rb b/spec/frame_origin_spec.rb new file mode 100644 index 0000000..e1b3b09 --- /dev/null +++ b/spec/frame_origin_spec.rb @@ -0,0 +1,69 @@ +require_relative "spec_helper" +require "remedy/frame" + +describe Remedy::Frame do + let(:sizeclass) { ::Remedy::Tuple } + let(:console_size) { sizeclass.new 6, 6 } + subject(:f) do + f0 = described_class.new name: "subject" + f0.available_size = console_size + f0.arrangement = :arbitrary + f0.size = :fill + f0 + end + + let(:f1) do + f1 = described_class.new name: "f1" + f1 << "a" + f1.size = Tuple 3, 3 + f1.fill = ":" + f1.valign = :center + f1.halign = :center + f1 + end + let(:f2) do + f2 = described_class.new name: "f2" + f2 << "b" + f2.size = Tuple 3, 3 + f2.fill = "*" + f2.valign = :center + f2.halign = :center + f2 + end + let(:f3) do + f3 = described_class.new name: "f3" + f3 << "c" + f3.size = Tuple 3, 3 + f3.fill = "#" + f3.valign = :center + f3.halign = :center + f3 + end + + describe "horigin = :center" do + before do + f1.horigin = :center + f << f1 + end + + it "is centered" do + expected = " ::: \n :a: \n ::: \n \n \n " + + actual = f.to_s + expect(actual).to eq expected + end + + context "dynamic nested frame size" do + before do + f1.size = Tuple 0, 0.5 + end + + it "is centered" do + expected = " ::: \n ::: \n :a: \n ::: \n ::: \n ::: " + + actual = f.to_s + expect(actual).to eq expected + end + end + end +end From 6e6c90b9ec7df44be177c2e32c544a02d2473064 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 10:49:32 -0500 Subject: [PATCH 134/152] Move origin tests to dedicated file, begin homogenizing them --- spec/frame_origin_spec.rb | 84 +++++++++++++++++++++++++++++++++++++++ spec/frame_spec.rb | 73 ---------------------------------- 2 files changed, 84 insertions(+), 73 deletions(-) diff --git a/spec/frame_origin_spec.rb b/spec/frame_origin_spec.rb index e1b3b09..f4e3458 100644 --- a/spec/frame_origin_spec.rb +++ b/spec/frame_origin_spec.rb @@ -66,4 +66,88 @@ end end end + + describe "vorigin = :bottom" do + before do + f.arrangement = :arbitrary + f.reset! + + f1.size = Tuple 3, 3 + + f1.horigin = :center + f1.vorigin = :bottom + f << f1 + end + + it "puts the nested frame at the bottom" do + expected = " \n \n \n ::: \n :a: \n ::: " + actual = f.to_s + expect(actual).to eq expected + end + + context "available_size.zero? = true" do + before do + f.available_size = sizeclass.zero + f.size = sizeclass.new 6, 6 + end + + it "still puts the nested frame at the bottom" do + expected = " \n \n \n ::: \n :a: \n ::: " + actual = f.to_s + expect(actual).to eq expected + end + + context "size = :none" do + before do + f.size = :none + f1.depth = 2 + f2.size = Tuple 2,1 + f << f2 + end + + it "puts the frame at the bottom of the actual space" do + expected = "b \n* \n:::\n:a:\n:::" + actual = f.to_s + expect(actual).to eq expected + end + end + end + + context "horigin = :center" do + before do + f.size = Tuple 5, 5 + f.available_size = Tuple 5, 5 + f.arrangement = :arbitrary + f.reset! + + f1.horigin = :center + f << f1 + end + + it "parent frame places it in the middle" do + expected = " \n \n ::: \n :a: \n ::: " + actual = f.to_s + expect(actual).to eq expected + end + end + + context "horigin = :right" do + before do + f.size = Tuple 5, 5 + f.available_size = Tuple 5, 5 + f.arrangement = :arbitrary + f.reset! + + f1.horigin = :right + f << f1 + end + + it "parent frame places it to the right" do + expected = " \n \n :::\n :a:\n :::" + actual = f.to_s + expect(actual).to eq expected + end + end + + end end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 67a1cb0..84854f8 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -501,79 +501,6 @@ expect(actual).to eq expected end end - - context "vorigin = :bottom" do - before do - f.size = Tuple 5, 5 - f.available_size = Tuple 5, 5 - f.arrangement = :arbitrary - f.reset! - - f1.size = Tuple 3, 3 - f1.fill = "." - - f1.horigin = :center - f1.vorigin = :bottom - f << f1 - end - - it "puts the nested frame at the bottom" do - expected = " \n \n ... \n .a. \n ... " - actual = f.to_s - expect(actual).to eq expected - end - - context "available_size.zero? = true" do - before do - f.available_size = sizeclass.zero - end - - it "still puts the nested frame at the bottom" do - expected = " \n \n ... \n .a. \n ... " - actual = f.to_s - expect(actual).to eq expected - end - - context "size = :none" do - before do - f.size = :none - f1.depth = 2 - f2.size = Tuple 2,1 - f << f2 - end - - it "puts the frame at the bottom of the actual space" do - expected = "b \n* \n...\n.a.\n..." - actual = f.to_s - expect(actual).to eq expected - end - end - end - - context "horigin = :center" do - before do - f.size = Tuple 5, 5 - f.available_size = Tuple 5, 5 - f.arrangement = :arbitrary - f.reset! - - f1.size = Tuple 3, 3 - f1.fill = "." - - f1.horigin = :center - f << f1 - end - - xit "parent frame places it in the middle" do - expected = " ... \n .a. \n ... \n \n " - end - end - - context "horigin = :right" do - xit "parent frame places it to the right" - end - - end end describe "layering" do From e11e51c0ff3b368f670bdbf31d5eb08ffed3c258 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 11:12:27 -0500 Subject: [PATCH 135/152] More cleanup of origin tests --- spec/frame_origin_spec.rb | 100 ++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/spec/frame_origin_spec.rb b/spec/frame_origin_spec.rb index f4e3458..fcc0a48 100644 --- a/spec/frame_origin_spec.rb +++ b/spec/frame_origin_spec.rb @@ -40,10 +40,23 @@ f3 end + before do + f << f1 + end + + # HORIZONTAL ORIGIN + + describe "horigin = :top" do + it "is the default" do + expected = :left + actual = described_class.new.horigin + expect(actual).to eq expected + end + end + describe "horigin = :center" do before do f1.horigin = :center - f << f1 end it "is centered" do @@ -67,34 +80,53 @@ end end + # VERTICAL ORIGIN + describe "vorigin = :bottom" do before do - f.arrangement = :arbitrary - f.reset! + f1.vorigin = :bottom + end - f1.size = Tuple 3, 3 + context "horigin = :center" do + before do + f1.horigin = :center + end - f1.horigin = :center - f1.vorigin = :bottom - f << f1 + it "parent frame places it in the bottom middle" do + expected = " \n \n \n ::: \n :a: \n ::: " + actual = f.to_s + expect(actual).to eq expected + end end - it "puts the nested frame at the bottom" do - expected = " \n \n \n ::: \n :a: \n ::: " - actual = f.to_s - expect(actual).to eq expected + context "horigin = :right" do + before do + f1.horigin = :right + end + + it "parent frame places it to the right" do + expected = " \n \n \n :::\n :a:\n :::" + actual = f.to_s + expect(actual).to eq expected + end end context "available_size.zero? = true" do before do f.available_size = sizeclass.zero - f.size = sizeclass.new 6, 6 + f1.horigin = :center end - it "still puts the nested frame at the bottom" do - expected = " \n \n \n ::: \n :a: \n ::: " - actual = f.to_s - expect(actual).to eq expected + context "size is Tuple" do + before do + f.size = console_size + end + + it "still puts the nested frame at the bottom" do + expected = " \n \n \n ::: \n :a: \n ::: " + actual = f.to_s + expect(actual).to eq expected + end end context "size = :none" do @@ -113,41 +145,5 @@ end end - context "horigin = :center" do - before do - f.size = Tuple 5, 5 - f.available_size = Tuple 5, 5 - f.arrangement = :arbitrary - f.reset! - - f1.horigin = :center - f << f1 - end - - it "parent frame places it in the middle" do - expected = " \n \n ::: \n :a: \n ::: " - actual = f.to_s - expect(actual).to eq expected - end - end - - context "horigin = :right" do - before do - f.size = Tuple 5, 5 - f.available_size = Tuple 5, 5 - f.arrangement = :arbitrary - f.reset! - - f1.horigin = :right - f << f1 - end - - it "parent frame places it to the right" do - expected = " \n \n :::\n :a:\n :::" - actual = f.to_s - expect(actual).to eq expected - end - end - end end From a42ab253d926bd87c2d19f95f66577d9a2da8d27 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 11:18:44 -0500 Subject: [PATCH 136/152] Aggressively condense origin tests --- spec/frame_origin_spec.rb | 134 +++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/spec/frame_origin_spec.rb b/spec/frame_origin_spec.rb index fcc0a48..0f9ace4 100644 --- a/spec/frame_origin_spec.rb +++ b/spec/frame_origin_spec.rb @@ -44,106 +44,106 @@ f << f1 end - # HORIZONTAL ORIGIN + it "does all the things" do + topleft = "::: \n:a: \n::: \n \n \n " + topcenter = " ::: \n :a: \n ::: \n \n \n " + topright = " :::\n :a:\n :::\n \n \n " - describe "horigin = :top" do - it "is the default" do - expected = :left - actual = described_class.new.horigin - expect(actual).to eq expected - end + centerleft = " \n::: \n:a: \n::: \n \n " + centercenter = " \n ::: \n :a: \n ::: \n \n " + centerright = " \n :::\n :a:\n :::\n \n " + + bottomleft = " \n \n \n::: \n:a: \n::: " + bottomcenter = " \n \n \n ::: \n :a: \n ::: " + bottomright = " \n \n \n :::\n :a:\n :::" + + actual = f.to_s + expect(actual).to eq topleft + + f1.horigin = :center + actual = f.to_s + expect(actual).to eq topcenter + + f1.horigin = :right + actual = f.to_s + expect(actual).to eq topright + + f1.vorigin = :center + + f1.horigin = :left + actual = f.to_s + expect(actual).to eq centerleft + + f1.horigin = :center + actual = f.to_s + expect(actual).to eq centercenter + + f1.horigin = :right + actual = f.to_s + expect(actual).to eq centerright + + f1.vorigin = :bottom + + f1.horigin = :left + actual = f.to_s + expect(actual).to eq bottomleft + + f1.horigin = :center + actual = f.to_s + expect(actual).to eq bottomcenter + + f1.horigin = :right + actual = f.to_s + expect(actual).to eq bottomright end - describe "horigin = :center" do + context "dynamic nested frame size" do before do f1.horigin = :center + f1.size = Tuple 0, 0.5 end it "is centered" do - expected = " ::: \n :a: \n ::: \n \n \n " + expected = " ::: \n ::: \n :a: \n ::: \n ::: \n ::: " actual = f.to_s expect(actual).to eq expected end - - context "dynamic nested frame size" do - before do - f1.size = Tuple 0, 0.5 - end - - it "is centered" do - expected = " ::: \n ::: \n :a: \n ::: \n ::: \n ::: " - - actual = f.to_s - expect(actual).to eq expected - end - end end - # VERTICAL ORIGIN - - describe "vorigin = :bottom" do + context "available_size.zero? = true" do before do + f.available_size = sizeclass.zero f1.vorigin = :bottom + f1.horigin = :center end - context "horigin = :center" do + context "size is Tuple" do before do - f1.horigin = :center + f.size = console_size end - it "parent frame places it in the bottom middle" do + it "still puts the nested frame at the bottom" do expected = " \n \n \n ::: \n :a: \n ::: " actual = f.to_s expect(actual).to eq expected end end - context "horigin = :right" do + context "size = :none" do before do - f1.horigin = :right + f.size = :none + f1.depth = 2 + f2.size = Tuple 2,1 + f << f2 end - it "parent frame places it to the right" do - expected = " \n \n \n :::\n :a:\n :::" + it "puts the frame at the bottom of the actual space" do + expected = "b \n* \n:::\n:a:\n:::" actual = f.to_s expect(actual).to eq expected end end - - context "available_size.zero? = true" do - before do - f.available_size = sizeclass.zero - f1.horigin = :center - end - - context "size is Tuple" do - before do - f.size = console_size - end - - it "still puts the nested frame at the bottom" do - expected = " \n \n \n ::: \n :a: \n ::: " - actual = f.to_s - expect(actual).to eq expected - end - end - - context "size = :none" do - before do - f.size = :none - f1.depth = 2 - f2.size = Tuple 2,1 - f << f2 - end - - it "puts the frame at the bottom of the actual space" do - expected = "b \n* \n:::\n:a:\n:::" - actual = f.to_s - expect(actual).to eq expected - end - end - end - end + end From 2fb73b29f9094181ccfbf1e371cd4175d25703ce Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 11:32:40 -0500 Subject: [PATCH 137/152] Separate out alignment tests --- spec/frame_align_spec.rb | 101 +++++++++++++++++++++++++++++++++++++++ spec/frame_spec.rb | 75 ----------------------------- 2 files changed, 101 insertions(+), 75 deletions(-) create mode 100644 spec/frame_align_spec.rb diff --git a/spec/frame_align_spec.rb b/spec/frame_align_spec.rb new file mode 100644 index 0000000..7fa93ee --- /dev/null +++ b/spec/frame_align_spec.rb @@ -0,0 +1,101 @@ +require_relative "spec_helper" +require "remedy/frame" + +describe Remedy::Frame do + let(:sizeclass) { ::Remedy::Tuple } + let(:console_size) { sizeclass.new 6, 6 } + subject(:f) do + f0 = described_class.new name: "subject" + f0.available_size = console_size + f0.arrangement = :stacked + f0.size = :fill + f0 + end + + let(:f1) do + f1 = described_class.new name: "f1" + f1 << "a" + f1.size = Tuple 3, 3 + f1.fill = ":" + f1.valign = :center + f1.halign = :center + f1 + end + let(:f2) do + f2 = described_class.new name: "f2" + f2 << "b" + f2.size = Tuple 3, 3 + f2.fill = "*" + f2.valign = :center + f2.halign = :center + f2 + end + let(:f3) do + f3 = described_class.new name: "f3" + f3 << "c" + f3.size = Tuple 3, 3 + f3.fill = "#" + f3.valign = :center + f3.halign = :center + f3 + end + + before do + f << "foo" + f << "bar\nbaz" + end + + it "does all the things" do + topleft = "foo \nbar \nbaz \n \n \n " + topcenter = " foo \n bar \n baz \n \n \n " + topright = " foo\n bar\n baz\n \n \n " + + centerleft = " \nfoo \nbar \nbaz \n \n " + centercenter = " \n foo \n bar \n baz \n \n " + centerright = " \n foo\n bar\n baz\n \n " + + bottomleft = " \n \n \nfoo \nbar \nbaz " + bottomcenter = " \n \n \n foo \n bar \n baz " + bottomright = " \n \n \n foo\n bar\n baz" + + actual = f.to_s + expect(actual).to eq topleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq topcenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq topright + + f.valign = :center + + f.halign = :left + actual = f.to_s + expect(actual).to eq centerleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq centercenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq centerright + + f.valign = :bottom + + f.halign = :left + actual = f.to_s + expect(actual).to eq bottomleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq bottomcenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq bottomright + end + +end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 84854f8..1dd9bdf 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -252,81 +252,6 @@ end end - context "size = Tuple halign = :center" do - before do - f.halign = :center - f.valign = :center - f.size = Tuple(5,7) - f.available_size = Tuple(11,11) - - f << "lol" - end - - it "content appears centered" do - expected = [ - " ", - " ", - " lol ", - " ", - " " - ].join ?\n - - actual = f.to_s - expect(actual).to eq expected - end - - it "maintains centering when there are newlines in contents" do - # I was getting this weird output: - # ...... - # ...a.. - # ...b.. - # ..c... - # d..... - # This was due to the starting calculations being based on - # the length of unsplit lines. - - expected = [ - " a ", - " b ", - " c ", - " d ", - " " - ].join ?\n - - f.reset! - f << "a" - f << "b" - f << "c\nd" - - actual = f.to_s - expect(actual).to eq expected - end - end - - describe "bottom alignment" do - before do - f.halign = :center - f.valign = :bottom - f.size = Tuple(5,7) - f.available_size = Tuple(11,11) - - f << "lol" - end - - it "content appears centered in the bottom" do - expected = [ - " ", - " ", - " ", - " ", - " lol " - ].join ?\n - - actual = f.to_s - expect(actual).to eq expected - end - end - describe "0 height Tuple" do before do f.halign = :center From b2b2e578168232e3555911699b9c55eb97bc8210 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 11:33:12 -0500 Subject: [PATCH 138/152] Fix missing context block --- spec/frame_spec.rb | 47 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index 1dd9bdf..1a6f79d 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -338,35 +338,36 @@ end describe "arrangement" do - - context "with strings" - before do - f << "a" - f << "b" - f << "c" - end - - context "arrangement = stacked" do + context "with strings" do before do - f.arrangement = :stacked + f.reset! + f << "a" + f << "b" + f << "c" end - it "arranges contents on top of each other" do - expected = "a\nb\nc" - actual = f.to_s - expect(actual).to eq expected - end - end + context "arrangement = stacked" do + before do + f.arrangement = :stacked + end - context "arrangement = columnar" do - before do - f.arrangement = :columnar + it "arranges contents on top of each other" do + expected = "a\nb\nc" + actual = f.to_s + expect(actual).to eq expected + end end - it "arranges contents next to each other" do - expected = "abc" - actual = f.to_s - expect(actual).to eq expected + context "arrangement = columnar" do + before do + f.arrangement = :columnar + end + + it "arranges contents next to each other" do + expected = "abc" + actual = f.to_s + expect(actual).to eq expected + end end end end From c02c20ca4a2cf852785d4ca820a7b58308515f74 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 12:35:47 -0500 Subject: [PATCH 139/152] Fix terminal resize issues finally! --- lib/remedy/frame.rb | 1 + lib/remedy/screen.rb | 1 + spec/screen_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 9186c04..fdc9fe6 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -165,6 +165,7 @@ def compile_contents if buffer then buffer.reset! + buffer.resize computed_size else @buffer = Screenbuffer.new computed_size, fill: fill, nl: nl, parent: self, name: "compile_contents" end diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index 4e9f4ea..bfffe39 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -32,6 +32,7 @@ def initialize auto_resize: true, auto_redraw: true # @param override [Remedy::Frame,String] temporarily replace the contents with this instead (until the next redraw!) # @return [void] def draw override = nil + mainframe.available_size = Console.size if override then f = Frame.new name: "screen#draw/override", parent: self, content: override f.size = :fill diff --git a/spec/screen_spec.rb b/spec/screen_spec.rb index 828f120..deb8926 100644 --- a/spec/screen_spec.rb +++ b/spec/screen_spec.rb @@ -45,7 +45,7 @@ describe "#draw" do context "tiny 2x2 screen" do - let(:size){ Tuple 2, 2 } + let(:size_override){ Tuple 2, 2 } it "writes the buffer to the output" do expected = "\e[H\e[J..\e[1B\e[0G..".inspect[1..-2] @@ -58,7 +58,7 @@ end context "small 3x20 screen" do - let(:size){ Tuple 3, 20 } + let(:size_override){ Tuple 3, 20 } it "can display single objects with the override parameter" do expected = "\\e[H\\e[J....................\\e[1B\\e[0G...hello, world!....\\e[1B\\e[0G...................." From 6163526fd2eaf1c062f4093c2f93191e4cbe7f7b Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 12:38:19 -0500 Subject: [PATCH 140/152] Screens can be named too --- lib/remedy/screen.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/remedy/screen.rb b/lib/remedy/screen.rb index bfffe39..1ecf823 100644 --- a/lib/remedy/screen.rb +++ b/lib/remedy/screen.rb @@ -16,7 +16,7 @@ class Screen # @param auto_resize [Boolean] can be disabled if you are setting up your own console resize hook # @see #resize # @see Console.set_console_resized_hook! - def initialize auto_resize: true, auto_redraw: true + def initialize auto_resize: true, auto_redraw: true, name: object_id @mainframe = Frame.new name: "screen#init", parent: self mainframe.fill = "." mainframe.size = :fill @@ -26,7 +26,7 @@ def initialize auto_resize: true, auto_redraw: true resize new_size, redraw: auto_redraw end if auto_resize end - attr_accessor :mainframe + attr_accessor :mainframe, :name # Draw the buffer to the console using raw output. # @param override [Remedy::Frame,String] temporarily replace the contents with this instead (until the next redraw!) From 7b29e103e2a449d97ce631b814738042b967be07 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 12:39:15 -0500 Subject: [PATCH 141/152] Remove unused frames in test, stub out columnar test --- spec/frame_align_spec.rb | 86 +++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/spec/frame_align_spec.rb b/spec/frame_align_spec.rb index 7fa93ee..9092d11 100644 --- a/spec/frame_align_spec.rb +++ b/spec/frame_align_spec.rb @@ -12,34 +12,6 @@ f0 end - let(:f1) do - f1 = described_class.new name: "f1" - f1 << "a" - f1.size = Tuple 3, 3 - f1.fill = ":" - f1.valign = :center - f1.halign = :center - f1 - end - let(:f2) do - f2 = described_class.new name: "f2" - f2 << "b" - f2.size = Tuple 3, 3 - f2.fill = "*" - f2.valign = :center - f2.halign = :center - f2 - end - let(:f3) do - f3 = described_class.new name: "f3" - f3 << "c" - f3.size = Tuple 3, 3 - f3.fill = "#" - f3.valign = :center - f3.halign = :center - f3 - end - before do f << "foo" f << "bar\nbaz" @@ -98,4 +70,62 @@ expect(actual).to eq bottomright end + context "columnar" do + before do + f.arrangement = :columnar + end + + xit "does all the things" do + topleft = "foo \nbar \nbaz \n \n \n " + topcenter = " foo \n bar \n baz \n \n \n " + topright = " foo\n bar\n baz\n \n \n " + + centerleft = " \nfoo \nbar \nbaz \n \n " + centercenter = " \n foo \n bar \n baz \n \n " + centerright = " \n foo\n bar\n baz\n \n " + + bottomleft = " \n \n \nfoo \nbar \nbaz " + bottomcenter = " \n \n \n foo \n bar \n baz " + bottomright = " \n \n \n foo\n bar\n baz" + + actual = f.to_s + expect(actual).to eq topleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq topcenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq topright + + f.valign = :center + + f.halign = :left + actual = f.to_s + expect(actual).to eq centerleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq centercenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq centerright + + f.valign = :bottom + + f.halign = :left + actual = f.to_s + expect(actual).to eq bottomleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq bottomcenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq bottomright + end + end end From 831ee85d2e10c5c2cc38f33704af9a0042d4ed5b Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 12:39:53 -0500 Subject: [PATCH 142/152] Test for that resize bug fixed a few commits ago --- spec/frame_size_spec.rb | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 spec/frame_size_spec.rb diff --git a/spec/frame_size_spec.rb b/spec/frame_size_spec.rb new file mode 100644 index 0000000..1c102e2 --- /dev/null +++ b/spec/frame_size_spec.rb @@ -0,0 +1,41 @@ +require_relative "spec_helper" +require "remedy/frame" + +describe Remedy::Frame do + let(:sizeclass) { ::Remedy::Tuple } + let(:console_size) { sizeclass.new 6, 6 } + subject(:f) do + f0 = described_class.new name: "subject" + f0.available_size = console_size + f0.arrangement = :stacked + f0 + end + + before do + f << "foo" + f << "bar\nbaz" + end + + xit "occupies the size specified" do + f.size = Tuple 5, 5 + actual = f.to_s + expect(actual).to eq "???" + end + + describe "#resize" do + let(:new_size) { sizeclass.new 5, 5 } + + before do + f.size = :fill + end + + it "it resizes the buffer" do + f.compile_contents + expect(f.compute_actual_size).to eq console_size + f.available_size = new_size + expect(f.compute_actual_size).to eq new_size + f.compile_contents # buffer size is not updated until recompile + expect(f.buffer.size).to eq new_size + end + end +end From 8a3b010f082fcddb8dfd3cd7c6fbb646ab893ed7 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 12:42:27 -0500 Subject: [PATCH 143/152] Name the screen in the example --- examples/new_example/new_example.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/new_example/new_example.rb b/examples/new_example/new_example.rb index 1984b1a..7a65700 100644 --- a/examples/new_example/new_example.rb +++ b/examples/new_example/new_example.rb @@ -20,7 +20,7 @@ def setup end def run - screen = Screen.new + screen = Screen.new name: "Main Screen" screen.frames << lsidebar screen.frames << rsidebar screen.frames << content From 2d5fbb6ae4b3b5cf100d02d6d054025bb5035dc0 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 12:45:33 -0500 Subject: [PATCH 144/152] Implement basic size test --- spec/frame_size_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/frame_size_spec.rb b/spec/frame_size_spec.rb index 1c102e2..5c22b02 100644 --- a/spec/frame_size_spec.rb +++ b/spec/frame_size_spec.rb @@ -16,10 +16,11 @@ f << "bar\nbaz" end - xit "occupies the size specified" do + it "occupies the size specified" do + expected = "foo \nbar \nbaz \n \n " f.size = Tuple 5, 5 actual = f.to_s - expect(actual).to eq "???" + expect(actual).to eq expected end describe "#resize" do From 093e04e671f2348393f73869d89f2eb190a025c6 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 13:07:32 -0500 Subject: [PATCH 145/152] Comprehensive alignment tests --- spec/frame_align_spec.rb | 296 ++++++++++++++++++++++++++++----------- 1 file changed, 217 insertions(+), 79 deletions(-) diff --git a/spec/frame_align_spec.rb b/spec/frame_align_spec.rb index 9092d11..80dc252 100644 --- a/spec/frame_align_spec.rb +++ b/spec/frame_align_spec.rb @@ -7,8 +7,6 @@ subject(:f) do f0 = described_class.new name: "subject" f0.available_size = console_size - f0.arrangement = :stacked - f0.size = :fill f0 end @@ -17,115 +15,255 @@ f << "bar\nbaz" end - it "does all the things" do - topleft = "foo \nbar \nbaz \n \n \n " - topcenter = " foo \n bar \n baz \n \n \n " - topright = " foo\n bar\n baz\n \n \n " + context "stacked arrangement" do + before do + f.arrangement = :stacked + end + + context "fill size" do + before do + f.size = :fill + end + + it "does all the things" do + topleft = "foo \nbar \nbaz \n \n \n " + topcenter = " foo \n bar \n baz \n \n \n " + topright = " foo\n bar\n baz\n \n \n " + + centerleft = " \nfoo \nbar \nbaz \n \n " + centercenter = " \n foo \n bar \n baz \n \n " + centerright = " \n foo\n bar\n baz\n \n " + + bottomleft = " \n \n \nfoo \nbar \nbaz " + bottomcenter = " \n \n \n foo \n bar \n baz " + bottomright = " \n \n \n foo\n bar\n baz" - centerleft = " \nfoo \nbar \nbaz \n \n " - centercenter = " \n foo \n bar \n baz \n \n " - centerright = " \n foo\n bar\n baz\n \n " + actual = f.to_s + expect(actual).to eq topleft - bottomleft = " \n \n \nfoo \nbar \nbaz " - bottomcenter = " \n \n \n foo \n bar \n baz " - bottomright = " \n \n \n foo\n bar\n baz" + f.halign = :center + actual = f.to_s + expect(actual).to eq topcenter - actual = f.to_s - expect(actual).to eq topleft + f.halign = :right + actual = f.to_s + expect(actual).to eq topright - f.halign = :center - actual = f.to_s - expect(actual).to eq topcenter + f.valign = :center - f.halign = :right - actual = f.to_s - expect(actual).to eq topright + f.halign = :left + actual = f.to_s + expect(actual).to eq centerleft - f.valign = :center + f.halign = :center + actual = f.to_s + expect(actual).to eq centercenter - f.halign = :left - actual = f.to_s - expect(actual).to eq centerleft + f.halign = :right + actual = f.to_s + expect(actual).to eq centerright + + f.valign = :bottom + + f.halign = :left + actual = f.to_s + expect(actual).to eq bottomleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq bottomcenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq bottomright + end + end - f.halign = :center - actual = f.to_s - expect(actual).to eq centercenter + context "fixed size" do + before do + f.size = Tuple 5, 5 + end - f.halign = :right - actual = f.to_s - expect(actual).to eq centerright + it "does all the things" do + topleft = "foo \nbar \nbaz \n \n " + topcenter = " foo \n bar \n baz \n \n " + topright = " foo\n bar\n baz\n \n " - f.valign = :bottom + centerleft = " \nfoo \nbar \nbaz \n " + centercenter = " \n foo \n bar \n baz \n " + centerright = " \n foo\n bar\n baz\n " - f.halign = :left - actual = f.to_s - expect(actual).to eq bottomleft + bottomleft = " \n \nfoo \nbar \nbaz " + bottomcenter = " \n \n foo \n bar \n baz " + bottomright = " \n \n foo\n bar\n baz" - f.halign = :center - actual = f.to_s - expect(actual).to eq bottomcenter + actual = f.to_s + expect(actual).to eq topleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq topcenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq topright + + f.valign = :center + + f.halign = :left + actual = f.to_s + expect(actual).to eq centerleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq centercenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq centerright + + f.valign = :bottom + + f.halign = :left + actual = f.to_s + expect(actual).to eq bottomleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq bottomcenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq bottomright + end + end - f.halign = :right - actual = f.to_s - expect(actual).to eq bottomright end - context "columnar" do + context "columnar arrangement" do + let(:console_size) { sizeclass.new 4, 8 } before do f.arrangement = :columnar end - xit "does all the things" do - topleft = "foo \nbar \nbaz \n \n \n " - topcenter = " foo \n bar \n baz \n \n \n " - topright = " foo\n bar\n baz\n \n \n " + context "fill size" do + before do + f.size = :fill + end + + it "does all the things" do + topleft = "foobar \n baz \n \n " + topcenter = " foobar \n baz \n \n " + topright = " foobar\n baz\n \n " + + centerleft = " \nfoobar \n baz \n " + centercenter = " \n foobar \n baz \n " + centerright = " \n foobar\n baz\n " + + bottomleft = " \n \nfoobar \n baz " + bottomcenter = " \n \n foobar \n baz " + bottomright = " \n \n foobar\n baz" + + actual = f.to_s + expect(actual).to eq topleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq topcenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq topright + + f.valign = :center + + f.halign = :left + actual = f.to_s + expect(actual).to eq centerleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq centercenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq centerright + + f.valign = :bottom + + f.halign = :left + actual = f.to_s + expect(actual).to eq bottomleft + + f.halign = :center + actual = f.to_s + expect(actual).to eq bottomcenter + + f.halign = :right + actual = f.to_s + expect(actual).to eq bottomright + end + end + + context "fixed size" do + before do + f.size = Tuple 5, 7 + end + + it "does all the things" do + topleft = "foobar \n baz \n \n \n " + topcenter = "foobar \n baz \n \n \n " + topright = " foobar\n baz\n \n \n " - centerleft = " \nfoo \nbar \nbaz \n \n " - centercenter = " \n foo \n bar \n baz \n \n " - centerright = " \n foo\n bar\n baz\n \n " + centerleft = " \nfoobar \n baz \n \n " + centercenter = " \nfoobar \n baz \n \n " + centerright = " \n foobar\n baz\n \n " - bottomleft = " \n \n \nfoo \nbar \nbaz " - bottomcenter = " \n \n \n foo \n bar \n baz " - bottomright = " \n \n \n foo\n bar\n baz" + bottomleft = " \n \n \nfoobar \n baz " + bottomcenter = " \n \n \nfoobar \n baz " + bottomright = " \n \n \n foobar\n baz" - actual = f.to_s - expect(actual).to eq topleft + actual = f.to_s + expect(actual).to eq topleft - f.halign = :center - actual = f.to_s - expect(actual).to eq topcenter + f.halign = :center + actual = f.to_s + expect(actual).to eq topcenter - f.halign = :right - actual = f.to_s - expect(actual).to eq topright + f.halign = :right + actual = f.to_s + expect(actual).to eq topright - f.valign = :center + f.valign = :center - f.halign = :left - actual = f.to_s - expect(actual).to eq centerleft + f.halign = :left + actual = f.to_s + expect(actual).to eq centerleft - f.halign = :center - actual = f.to_s - expect(actual).to eq centercenter + f.halign = :center + actual = f.to_s + expect(actual).to eq centercenter - f.halign = :right - actual = f.to_s - expect(actual).to eq centerright + f.halign = :right + actual = f.to_s + expect(actual).to eq centerright - f.valign = :bottom + f.valign = :bottom - f.halign = :left - actual = f.to_s - expect(actual).to eq bottomleft + f.halign = :left + actual = f.to_s + expect(actual).to eq bottomleft - f.halign = :center - actual = f.to_s - expect(actual).to eq bottomcenter + f.halign = :center + actual = f.to_s + expect(actual).to eq bottomcenter - f.halign = :right - actual = f.to_s - expect(actual).to eq bottomright + f.halign = :right + actual = f.to_s + expect(actual).to eq bottomright + end end + end + end From 7a44b7e12fbfbec58cc5bed024a207bcda354a33 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 14:06:23 -0500 Subject: [PATCH 146/152] Constraint output of Screenbuffers to the specified size, regardless of allocated size --- lib/remedy/screenbuffer.rb | 18 ++++++++++-------- spec/screenbuffer_spec.rb | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/lib/remedy/screenbuffer.rb b/lib/remedy/screenbuffer.rb index d4e7c5d..d5f3911 100644 --- a/lib/remedy/screenbuffer.rb +++ b/lib/remedy/screenbuffer.rb @@ -97,19 +97,21 @@ def size # @return [Array] the contents of the buffer as an array of strings def to_a - buf + buf[0,size.height].map do |line| + line[0,size.width] + end end # Convert screenbuffer to single String. # Concatenates the contents of the buffer with the `nl` attribute. def to_s - buf.join nl + to_a.join nl end # Convert screenbuffer to single string for output to a display using ANSI line motions. # Standard newlines at screen edges cause many terminals to display extraneous empty lines. def to_ansi - buf.join ANSI.cursor.next_line + to_a.join ANSI.cursor.next_line end # Reset contents of buffer to the empty state using the @fill character. @@ -126,20 +128,20 @@ def reset! # @raise [ArgumentError] if passed anything other than a Remedy::Tuple def resize new_size raise ArgumentError unless new_size.is_a? Tuple - # FIXME: @size is getting reset to old versions somehow. - # But if we determine the actual size and use that instead, - # then we work around that behavior at some performance cost. + # @size is set to the externally visible value, + # this allows us to shrink the apparent size + # without destroying the contents of the buffer actual_size = compute_actual_size if new_size.height > actual_size.height then grow_by = new_size.height - actual_size.height grow_by.times do - @buf << new_buf_line + buf << new_buf_line end end if new_size.width > actual_size.width then grow_by = new_size.width - actual_size.width - @buf.each do |l| + buf.each do |l| # TODO: handle char width? l << fill * grow_by end diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index a4c9ad4..cde38ae 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -13,6 +13,31 @@ end end + describe "#to_a" do + it "dumps the screenbuffer" do + expected = ["..", ".."] + actual = sb.to_a + expect(actual).to eq expected + end + + context "allocated size is larger than set size" do + it "dumps the screenbuffer" do + sb.resize Tuple 5, 5 + expected = [".....", ".....", ".....", ".....", "....."] + actual = sb.to_a + expect(sb.buf.length).to eq 5 + expect(actual).to eq expected + + sb.resize Tuple 3, 3 + expected = ["...", "...", "..."] + actual = sb.to_a + expect(sb.buf.length).to eq 5 + expect(actual).to eq expected + end + end + + end + describe "#[]" do it "returns the character at a particular location" do expected = "x" From 81a942d7443e48079f3f3782c6b0ef871a93e673 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Wed, 11 Oct 2023 14:09:14 -0500 Subject: [PATCH 147/152] Specific tests for Frame sizes and resizing which will catch a lot more issues --- spec/frame_size_spec.rb | 77 +++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/spec/frame_size_spec.rb b/spec/frame_size_spec.rb index 5c22b02..52d8328 100644 --- a/spec/frame_size_spec.rb +++ b/spec/frame_size_spec.rb @@ -7,7 +7,6 @@ subject(:f) do f0 = described_class.new name: "subject" f0.available_size = console_size - f0.arrangement = :stacked f0 end @@ -16,27 +15,69 @@ f << "bar\nbaz" end - it "occupies the size specified" do - expected = "foo \nbar \nbaz \n \n " - f.size = Tuple 5, 5 - actual = f.to_s - expect(actual).to eq expected - end + context "stacked arrangement" do + before do + f.arrangement = :stacked + end - describe "#resize" do - let(:new_size) { sizeclass.new 5, 5 } + it "occupies the size specified" do + expected = "foo \nbar \nbaz \n \n " + f.size = Tuple 5, 5 + actual = f.to_s + expect(actual).to eq expected + end - before do - f.size = :fill + describe "#resize" do + let(:new_size) { sizeclass.new 5, 5 } + + before do + f.size = :fill + end + + it "resizes the buffer" do + f.compile_contents + expect(f.compute_actual_size).to eq console_size + f.available_size = new_size + expect(f.compute_actual_size).to eq new_size + f.compile_contents # buffer size is not updated until recompile + expect(f.buffer.size).to eq new_size + end end - it "it resizes the buffer" do - f.compile_contents - expect(f.compute_actual_size).to eq console_size - f.available_size = new_size - expect(f.compute_actual_size).to eq new_size - f.compile_contents # buffer size is not updated until recompile - expect(f.buffer.size).to eq new_size + it "does all the things" do + none = "foo\nbar\nbaz" + fill = "foo:::\nbar:::\nbaz:::\n::::::\n::::::\n::::::" + auto = "foo \nbar \nbaz \n \n \n " # ?? + t5x5 = "foo::\nbar::\nbaz::\n:::::\n:::::" + tzxs = "f…\nb…\nb…\n::\n::\n::" + tbxf = "foo\nbar\nbaz\n:::\n:::\n:::\n:::" + + f.fill = ":" + + f.size = :none + actual = f.to_s + expect(actual).to eq none + + f.size = :fill + actual = f.to_s + expect(actual).to eq fill + + f.size = :auto + actual = f.to_s + #expect(actual).to eq auto + + f.size = Tuple 5, 5 + actual = f.to_s + expect(actual).to eq t5x5 + + f.size = Tuple 0, 2 + actual = f.to_s + expect(actual).to eq tzxs + + f.size = Tuple 7, 0.5 + actual = f.to_s + expect(actual).to eq tbxf end end + end From 37e3652166937e92875d65e9ebe74e622fa5bab4 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Tue, 7 Nov 2023 02:47:22 -0600 Subject: [PATCH 148/152] Fix typo in comment --- lib/remedy/frame.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index fdc9fe6..76a705d 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -45,7 +45,7 @@ def initialize name: self.object_id, content: nil, parent: nil # the maximum size that this frame wants to be, the actual size may be smaller # :none - frame has no size, contents are not aligned - # :auto - frame conforms to the size of its largest content and alignts to it + # :auto - frame conforms to the size of its largest content and aligns to it # :fill - the frame tries to fill as much space as possible # Tuple - specify a Tuple to constrain it to a specific size # Tuple.zero is same as :none From ed6b55255df06908ed7954369ccd08be05e4dfaf Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Tue, 7 Nov 2023 02:47:38 -0600 Subject: [PATCH 149/152] Alias to shorter method name --- lib/remedy/frame.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/remedy/frame.rb b/lib/remedy/frame.rb index 76a705d..2a4fca0 100644 --- a/lib/remedy/frame.rb +++ b/lib/remedy/frame.rb @@ -220,6 +220,7 @@ def compute_actual_size arranged_size = content_size raise "Unknown max_size:#{size}" end end + alias_method :actual_size, :compute_actual_size def compute_horizontal_offset original_size, actual_size case halign From dd1f186d07d705754a0af3ce92b63fb1933041a8 Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Tue, 7 Nov 2023 02:48:00 -0600 Subject: [PATCH 150/152] Reformat comment --- lib/remedy/tuple.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/remedy/tuple.rb b/lib/remedy/tuple.rb index fbde3fc..a754737 100644 --- a/lib/remedy/tuple.rb +++ b/lib/remedy/tuple.rb @@ -1,8 +1,9 @@ module Remedy - # Formerly known as "Remedy::Size", with related concepts in my other projects - # called things like "Coordinate", "Pair", or similar # Used primarily to contain dimensional numeric values such as the sizes of screen areas, - # offsets in two or more dimensions, etc + # offsets in two or more dimensions, etc. + # + # Formerly known as `Remedy::Size`. Related to concepts in other projects + # called things like `Coordinate`, `Pair`, `Vector`, or similar. class Tuple def initialize *new_dimensions dims = new_dimensions.flatten From da96c5624227a654e098e658ea2b4c4e706301ab Mon Sep 17 00:00:00 2001 From: Anthony Cook Date: Tue, 7 Nov 2023 02:48:43 -0600 Subject: [PATCH 151/152] Tests use these classes so must be required if run individually --- spec/screenbuffer_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/screenbuffer_spec.rb b/spec/screenbuffer_spec.rb index cde38ae..00accf1 100644 --- a/spec/screenbuffer_spec.rb +++ b/spec/screenbuffer_spec.rb @@ -1,5 +1,7 @@ require_relative 'spec_helper' require 'remedy/screenbuffer' +require "remedy/partial" +require "remedy/view" describe Remedy::Screenbuffer do subject(:sb){ described_class.new size, fill: "." } From c80d1efc79f01e2a9468e8fafe49f4319e7cf89e Mon Sep 17 00:00:00 2001 From: "Anthony M. Cook" Date: Tue, 26 Dec 2023 11:18:57 -0600 Subject: [PATCH 152/152] Drop support for Ruby 2.3 Array#sum support was added in Ruby 2.4. https://github.com/ruby/ruby/blob/v2_4_0/NEWS#label-Compatibility+issues+-28excluding+feature+bug+fixes-29 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb0fb1e..cab44a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: ["2.3", "2.4", "2.5", "2.6", "2.7", "3.0", "3.1", "3.2", ruby-head, jruby-9.2, jruby-9.3, jruby-head] + ruby: ["2.4", "2.5", "2.6", "2.7", "3.0", "3.1", "3.2", ruby-head, jruby-9.2, jruby-9.3, jruby-head] steps: - uses: actions/checkout@v4