Class: Terminal::Screen

Inherits:
Object
  • Object
show all
Defined in:
lib/terminal/screen.rb

Defined Under Namespace

Classes: Node

Constant Summary collapse

END_OF_LINE =
:end_of_line
START_OF_LINE =
:start_of_line
EMPTY =
Node.new(" ", [])

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeScreen

Returns a new instance of Screen.



46
47
48
49
50
51
52
53
54
# File 'lib/terminal/screen.rb', line 46

def initialize
  @x = 0
  @y = 0
  @screen = []

  @fg_color = nil
  @bg_color = nil
  @other_colors = []
end

Instance Attribute Details

#xObject

Returns the value of attribute x.



44
45
46
# File 'lib/terminal/screen.rb', line 44

def x
  @x
end

#yObject

Returns the value of attribute y.



44
45
46
# File 'lib/terminal/screen.rb', line 44

def y
  @y
end

Instance Method Details

#<<(character) ⇒ Object



74
75
76
77
# File 'lib/terminal/screen.rb', line 74

def <<(character)
  write(character)
  @x += 1
end

#backward(value = nil) ⇒ Object



204
205
206
# File 'lib/terminal/screen.rb', line 204

def backward(value = nil)
  self.x -= parse_integer(value)
end

#clear(y, x_start, x_end) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/terminal/screen.rb', line 95

def clear(y, x_start, x_end)
  line = @screen[y]

  # If the line isn't there, we can't clean it.
  return if line.nil?

  x_start = 0 if x_start == START_OF_LINE
  x_end = line.length - 1 if x_end == END_OF_LINE

  if x_start == START_OF_LINE && x_end == END_OF_LINE
    @screen[y] = []
  else
    line.fill(EMPTY, x_start..x_end)
  end
end

#color(color_code) ⇒ Object

Changes the current foreground color that all new characters will be written with.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/terminal/screen.rb', line 113

def color(color_code)
  colors = color_code.scan(/\d+/)

  # Extended set foreground x-term color
  if colors[0] == "38" && colors[1] == "5"
    return @fg_color = "term-fgx#{colors[2]}"
  end

  # Extended set background x-term color
  if colors[0] == "48" && colors[1] == "5"
    return @bg_color = "term-bgx#{colors[2]}"
  end

  # If multiple colors are defined, i.e. \e[30;42m\e then loop through each
  # one, and assign it to @fg_color or @bg_color
  colors.each do |cc|
    c_integer = cc.to_i

    # Reset all styles
    if c_integer == 0
      @fg_color = nil
      @bg_color = nil
      @other_colors = []

    # Primary (default) font
    elsif c_integer == 10
      # no-op

    # Turn off bold / Normal color or intensity (21 & 22 essentially do the same thing)
    elsif c_integer == 21 || c_integer == 22
      @other_colors.delete("term-fg1")
      @other_colors.delete("term-fg2")

    # Turn off italic
    elsif c_integer == 23
      @other_colors.delete("term-fg3")

    # Turn off underline
    elsif c_integer == 24
      @other_colors.delete("term-fg4")

    # Turn off crossed-out
    elsif c_integer == 29
      @other_colors.delete("term-fg9")

    # Reset foreground color only
    elsif c_integer ==  39
      @fg_color = nil

    # Reset background color only
    elsif c_integer ==  49
      @bg_color = nil

    # 30–37, then it's a foreground color
    elsif c_integer >= 30 && c_integer <= 37
      @fg_color = "term-fg#{cc}"

    # 40–47, then it's a background color.
    elsif c_integer >= 40 && c_integer <= 47
      @bg_color = "term-bg#{cc}"

    # 90-97 is like the regular fg color, but high intensity
    elsif c_integer >= 90 && c_integer <= 97
      @fg_color = "term-fgi#{cc}"

    # 100-107 is like the regular bg color, but high intensity
    elsif c_integer >= 100 && c_integer <= 107
      @fg_color = "term-bgi#{cc}"

    # 1-9 random other styles
    elsif c_integer >= 1 && c_integer <= 9
      @other_colors << "term-fg#{cc}"
    end
  end
end

#down(value = nil) ⇒ Object



193
194
195
196
197
198
199
200
201
202
# File 'lib/terminal/screen.rb', line 193

def down(value = nil)
  new_y = @y + parse_integer(value)

  # Only jump down if the line exists
  if @screen[new_y]
    self.y = new_y
  else
    false
  end
end

#foward(value = nil) ⇒ Object



208
209
210
# File 'lib/terminal/screen.rb', line 208

def foward(value = nil)
  self.x += parse_integer(value)
end

#to_aObject



212
213
214
# File 'lib/terminal/screen.rb', line 212

def to_a
  @screen.to_a.map { |chars| chars.map(&:to_s) }
end

#to_sObject

Renders each node to a string. This looks at each node, and then inserts escape characters that will be gsubed into <span> elements.

ANSI codes generally span across lines. So if you e[12mnnhello, the hello will inhert the styles of e[12m. This doesn’t work so great in HTML, especially if you wrap divs around each line, so this method also copies any styles that are left open at the end of a line, to the begining of new lines, so you end up with something like this:

e[12mne[12mne[12mhello

It also attempts to only insert escapes that are required. Given the following:

e[12mhe[12mee[12mle[12mle[12moe[0m

A general purpose ANSI renderer will convert it to:

<span class=“c12”>h<span class=“c12”>e<span class=“c12”>l<span class=“c12”>l<span class=“c12”>o</span></span></span></span>

But ours is smart, and tries to do stuff like this:

<span class=“c12”>hello</span>



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/terminal/screen.rb', line 237

def to_s
  last_line_index = @screen.length - 1
  buffer = []

  @screen.each_with_index do |line, line_index|
    previous = nil

    # Keep track of every open style we have, so we know
    # that we need to close any open ones at the end.
    open_styles = 0

    line.each do |node|
      # If there is no previous node, and the current node has a color
      # (first node in a line) then add the escape character.
      if !previous && node.style.length > 0
        buffer << "\terminal[#{node.style.join(" ")}]"

        # Increment the open style counter
        open_styles += 1

      # If we have a previous node, and the last node's style doesn't
      # match this nodes, then we start a new escape character.
      elsif previous && previous.style != node.style
        # If the new node has no style, that means that all the styles
        # have been closed.
        if node.style.length == 0
          # Add our reset escape character
          buffer << "\terminal[0]"

          # Decrement the open style counter
          open_styles -= 1
        else
          buffer << "\terminal[#{node.style.join(" ")}]"

          # Increment the open style counter
          open_styles += 1
        end
      end

      # Add the nodes blob to te buffer
      buffer << node.blob

      # Set this node as being the previous node
      previous = node
    end

    # Be sure to close off any open styles for this line
    open_styles.times { buffer << "\terminal[0]" }

    # Add a new line as long as this line isn't the last
    buffer << "\n" if line_index != last_line_index
  end

  buffer.join("")
end

#up(value = nil) ⇒ Object



189
190
191
# File 'lib/terminal/screen.rb', line 189

def up(value = nil)
  self.y -= parse_integer(value)
end

#write(character) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/terminal/screen.rb', line 56

def write(character)
  # Expand the screen if we need to
  ((@y + 1) - @screen.length).times do
    @screen << []
  end

  line = @screen[@y]
  line_length = line.length

  # Write empty slots until we reach the line
  (@x - line_length).times do |i|
    line[line_length + i] = EMPTY
  end

  # Write the character to the slot
  line[@x] = Node.new(character, [ @fg_color, @bg_color, *@other_colors ].compact)
end