Module: Canopus::Workspace::View::TerminalPresentable

Included in:
Canopus::Workspace::View
Defined in:
lib/canopus/workspace/view/terminal_presentable.rb,
sig/controller.rbs

Defined Under Namespace

Classes: Link

Constant Summary collapse

ANSI_COLORS =

Returns:

  • (Array[String])
%w[#1b1d23 #e06c75 #98c379 #e5c07b #61afef #c678dd #56b6c2 #abb2bf #5c6370 #ff7a85 #b5e890 #ffdb8a #82c7ff #e79aff #79dce7 #ffffff].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#terminal_bounds ⇒ Zaniah::Bounds? (readonly)

Returns the value of attribute terminal_bounds.

Returns:

  • (Zaniah::Bounds, nil)


8
9
10
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 8

def terminal_bounds
  @terminal_bounds
end

#terminal_selection ⇒ [[Integer, Integer], [Integer, Integer]]? (readonly)

Returns the value of attribute terminal_selection.

Returns:

  • ([[Integer, Integer], [Integer, Integer]], nil)


8
9
10
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 8

def terminal_selection
  @terminal_selection
end

Instance Method Details

#focus_terminal(terminal) ⇒ Tarazed::PTY?

Parameters:

  • terminal (Tarazed::PTY, nil)

Returns:

  • (Tarazed::PTY, nil)


152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 152

def focus_terminal(terminal)
  return terminal if @terminal_owner.equal?(terminal)
  store_terminal_state if @terminal_owner
  @terminal_owner = terminal
  state = terminal&.instance_variable_get(:@canopus_terminal_view_state)
  @terminal_scroll = state&.fetch(:scroll, 0) || 0
  @terminal_selection = state&.[](:selection)
  @terminal_bounds = state&.[](:bounds)
  @terminal_visible_rows = state&.[](:visible_rows)
  @terminal_first = state&.fetch(:first, 0) || 0
  @terminal_first_display = state&.fetch(:first_display, 0) || 0
  @terminal_cell_width = state&.[](:cell_width)
  @terminal_line_height = state&.[](:line_height)
  @terminal_font_size = state&.[](:font_size)
  terminal
end

#terminal_color(value, fallback) ⇒ String?

Parameters:

  • value (Tarazed::color)
  • fallback (Symbol)

Returns:

  • (String, nil)


58
59
60
61
62
63
64
65
66
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 58

def terminal_color(value, fallback)
  return @theme[fallback] if value.nil?
  return format("#%02x%02x%02x", *value) if value.is_a?(Array)
  return ANSI_COLORS[value] if value < 16
  return format("#%02x%02x%02x", *Array.new(3, 8 + (value - 232) * 10)) if value >= 232
  index = value - 16
  ramp = [0, 95, 135, 175, 215, 255]
  format("#%02x%02x%02x", ramp[index / 36], ramp[index / 6 % 6], ramp[index % 6])
end

#terminal_command(direction) ⇒ Boolean

Parameters:

  • direction (:previous, :next)

Returns:

  • (Boolean)


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 103

def terminal_command(direction)
  terminal = displayed_terminal
  return false unless terminal&.respond_to?(:commands) && !terminal.grid.alternate?
  map = terminal_row_map(terminal)
  maximum = [map[:visible] - terminal.grid.rows, 0].max
  first_display = maximum - (@terminal_scroll || 0).floor
  current = map[:base] + terminal_source_for_display(map, first_display)
  commands = terminal.commands.select { |command| command.prompt_row >= map[:base] }
  command = if direction == :previous
    commands.reverse.find { |item| @terminal_scroll.to_i.zero? ? item.prompt_row <= current : item.prompt_row < current }
  elsif direction == :next
    commands.find { |item| item.prompt_row > current }
  else
    raise ArgumentError, "invalid terminal command direction"
  end
  terminal_command_jump(command)
end

#terminal_command_jump(command) ⇒ Boolean

Parameters:

  • command (Tarazed::Command, nil)

Returns:

  • (Boolean)


121
122
123
124
125
126
127
128
129
130
131
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 121

def terminal_command_jump(command)
  return false unless command && displayed_terminal && !displayed_terminal.grid.alternate?
  map = terminal_row_map
  source = command.prompt_row - map[:base]
  display = terminal_display_for_source(map, source)
  return false unless display
  maximum = [map[:visible] - displayed_terminal.grid.rows, 0].max
  @terminal_scroll = maximum - [display, maximum].min
  @terminal_selection = nil
  true
end

Parameters:

  • point (Zaniah::Point)

Returns:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 11

def terminal_link_at(point)
  return unless @terminal_bounds&.contains?(point) && displayed_terminal
  position = terminal_source_point(point)
  return unless position
  column, row = position
  cells = terminal_row(row)
  column -= 1 while column.positive? && cells[column].width.zero?
  cell = cells[column]
  return terminal_link(cell.hyperlink, explicit: true) if cell.hyperlink
  # ponytail: bare-link detection stays within one screen row; OSC 8 links
  # retain their metadata across wrapping and do not have this limit.
  text = cells.map(&:text).join
  offset = cells.first(column).sum { |value| value.text.bytesize }
  text.to_enum(:scan, /[^\s<>"'`]+/).each do
    match = Regexp.last_match
    first = text[0...match.begin(0)].bytesize
    next unless offset >= first && offset < first + match[0].bytesize
    value = match[0].sub(/[.,;]+\z/, "")
    value = value.delete_prefix("(").delete_suffix(")") if value.start_with?("(")
    value = value.delete_suffix(")") if value.count(")") > value.count("(")
    return terminal_link(value)
  end
  nil
end

This is intentionally an explicit user gesture, never an output handler.

Parameters:

  • point (Zaniah::Point)
  • modifiers: (Array[String])

Returns:

  • (Boolean)


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 37

def terminal_open_link(point, modifiers:)
  return false if (modifiers & %w[cmd ctrl]).empty?
  link = terminal_link_at(point)
  return false unless link
  if link.kind == :url
    return false unless @workspace.window&.respond_to?(:open_url)
    @workspace.window.open_url(link.target)
  else
    editor = @workspace.open(link.target)
    if link.line
      row = (link.line - 1).clamp(0, editor.buffer.line_count - 1)
      value = editor.buffer.line(row)
      prefix = value.each_char.take((link.column.to_i - 1).clamp(0, value.length)).join
      editor.select(editor.buffer.rope.line_start(row) + prefix.bytesize)
      editor.reveal_cursor
    end
    @workspace.window&.request_frame
  end
  true
end

#terminal_point(point) ⇒ [Integer, Integer]?

Parameters:

  • point (Zaniah::Point)

Returns:

  • ([Integer, Integer], nil)


67
68
69
70
71
72
73
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 67

def terminal_point(point)
  grid = displayed_terminal.grid
  position = terminal_source_point(point)
  return unless position
  column, row = position
  [column, (row - grid.scrollback.length).clamp(0, grid.rows - 1)]
end

#terminal_scroll(delta) ⇒ nil

Parameters:

  • delta (Numeric)

Returns:

  • (nil)


74
75
76
77
78
79
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 74

def terminal_scroll(delta)
  map = terminal_row_map
  maximum = [map[:visible] - displayed_terminal.grid.rows, 0].max
  @terminal_scroll = ((@terminal_scroll || 0) - delta).clamp(0, maximum)
  @terminal_selection = nil
end

#terminal_select(point, extend: false) ⇒ [[Integer, Integer], [Integer, Integer]]?

Parameters:

  • point (Zaniah::Point)
  • extend: (Boolean) (defaults to: false)

Returns:

  • ([[Integer, Integer], [Integer, Integer]], nil)


80
81
82
83
84
85
86
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 80

def terminal_select(point, extend: false)
  position = terminal_source_point(point)
  return unless position
  column, row = position
  index = [row, column]
  @terminal_selection = extend && @terminal_selection ? [@terminal_selection.first, index] : [index, index]
end

#terminal_selected_text ⇒ String

Returns:

  • (String)


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 87

def terminal_selected_text
  return "" unless @terminal_selection
  first, last = @terminal_selection.sort
  map = terminal_row_map
  from_row = terminal_display_for_source(map, first.first)
  to_row = terminal_display_for_source(map, last.first)
  return "" unless from_row && to_row
  (from_row..to_row).map do |display_row|
    row = terminal_source_for_display(map, display_row)
    cells = terminal_row(row)
    from = row == first.first ? first.last : 0
    to = row == last.first ? last.last : cells.length - 1
    cells[from..to].to_a.map(&:text).join.rstrip
  end.join("\n")
end

#terminal_toggle_command(command = nil) ⇒ Boolean

Parameters:

  • command (Tarazed::Command, nil) (defaults to: nil)

Returns:

  • (Boolean)


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/canopus/workspace/view/terminal_presentable.rb', line 133

def terminal_toggle_command(command = nil)
  terminal = displayed_terminal
  return false unless terminal&.respond_to?(:commands) && !terminal.grid.alternate?
  map = terminal_row_map(terminal)
  unless command
    maximum = [map[:visible] - terminal.grid.rows, 0].max
    first = maximum - (@terminal_scroll || 0).floor
    last = [first + terminal.grid.rows - 1, map[:visible] - 1].min
    current = map[:base] + terminal_source_for_display(map, last)
    command = terminal.commands.reverse.find { |item| item.prompt_row <= current && terminal_fold_interval(item, map) }
  end
  return false unless command && terminal_fold_interval(command, map)
  folds = terminal_folds(terminal)
  folds[command.id] ? folds.delete(command.id) : folds[command.id] = true
  @terminal_selection = nil
  terminal_command_jump(command)
  true
end