canopus 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +118 -86
- data/assets/canopus-editor.png +0 -0
- data/assets/logo.png +0 -0
- data/docs/adr/000-template.md +24 -0
- data/docs/adr/002-bounded-diff-engine.md +4 -4
- data/docs/adr/006-tab-close-lifetime.md +25 -0
- data/docs/adr/007-terminal-key-routing.md +24 -0
- data/lib/canopus/controller.rb +101 -23
- data/lib/canopus/language/syntax_worker.rb +1 -0
- data/lib/canopus/pane.rb +18 -2
- data/lib/canopus/settings.rb +53 -1
- data/lib/canopus/terminal/pty.rb +92 -17
- data/lib/canopus/version.rb +1 -1
- data/lib/canopus/workspace/git_aware.rb +25 -20
- data/lib/canopus/workspace/session_persistable.rb +32 -1
- data/lib/canopus/workspace/view/terminal_presentable.rb +57 -17
- data/lib/canopus/workspace/view.rb +22 -10
- data/lib/canopus/workspace.rb +303 -17
- data/lib/canopus.rb +2 -1
- data/sig/canopus.rbs +28 -1
- data/sig/controller.rbs +1 -0
- data/sig/terminal.rbs +7 -2
- data/sig/workspace_services.rbs +4 -2
- data/tools/check_dependencies.rb +1 -1
- metadata +40 -15
- data/lib/canopus/git/blame.rb +0 -50
- data/lib/canopus/git/commit.rb +0 -7
- data/lib/canopus/git/corrupt_object.rb +0 -7
- data/lib/canopus/git/diff.rb +0 -131
- data/lib/canopus/git/index.rb +0 -94
- data/lib/canopus/git/object_database.rb +0 -45
- data/lib/canopus/git/pack.rb +0 -186
- data/lib/canopus/git/repository.rb +0 -313
- data/lib/canopus/git/status.rb +0 -74
- data/lib/canopus/git/tree_entry.rb +0 -7
- data/lib/canopus/git.rb +0 -15
- data/sig/git.rbs +0 -140
|
@@ -4,7 +4,7 @@ module Canopus
|
|
|
4
4
|
module Workspace::GitAware
|
|
5
5
|
def git
|
|
6
6
|
return @git if defined?(@git)
|
|
7
|
-
@git =
|
|
7
|
+
@git = Thuban::Repository.new(@root)
|
|
8
8
|
@git = nil unless @git.root
|
|
9
9
|
rescue ArgumentError
|
|
10
10
|
@git = nil
|
|
@@ -29,7 +29,7 @@ module Canopus
|
|
|
29
29
|
end
|
|
30
30
|
def invalidate_git
|
|
31
31
|
@git_generation = (@git_generation || 0) + 1
|
|
32
|
-
@git_status = @
|
|
32
|
+
@git_status = @git_diff_cache = nil
|
|
33
33
|
@panes.each do |pane|
|
|
34
34
|
pane.editors.each do |current|
|
|
35
35
|
current.display_map.block_map.blocks.values.each { |block| current.display_map.remove_block(block.id) if block.kind == :git_diff }
|
|
@@ -57,43 +57,48 @@ module Canopus
|
|
|
57
57
|
raise Error, "Current document is not in a Git repository" unless git && buffer.path && buffer.path.start_with?(git.root + File::SEPARATOR)
|
|
58
58
|
buffer.path.delete_prefix(git.root + File::SEPARATOR)
|
|
59
59
|
end
|
|
60
|
-
def
|
|
61
|
-
return
|
|
60
|
+
def git_diff(buffer = editor.buffer, async: true)
|
|
61
|
+
return unless git && buffer.path && buffer.path.start_with?(git.root + File::SEPARATOR) && buffer.rope.bytesize < 10 << 20
|
|
62
62
|
path = git_relative_path(buffer)
|
|
63
|
-
@
|
|
63
|
+
@git_diff_cache ||= {}
|
|
64
64
|
key = [buffer.object_id, path, buffer.version, git.head]
|
|
65
|
-
@
|
|
66
|
-
return @
|
|
65
|
+
@git_diff_cache.clear if @git_diff_cache.length > 20
|
|
66
|
+
return @git_diff_cache[key] if @git_diff_cache.key?(key)
|
|
67
67
|
if @window && async
|
|
68
|
-
@
|
|
69
|
-
unless @
|
|
68
|
+
@git_diff_jobs ||= {}
|
|
69
|
+
unless @git_diff_jobs[buffer]&.alive?
|
|
70
70
|
snapshot = buffer.rope
|
|
71
|
-
@
|
|
71
|
+
@git_diff_jobs[buffer] = Thread.new do
|
|
72
72
|
before = Buffer.decode_bytes(git.blob(path, reference: key.last || "HEAD").to_s).first
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
diff = Porrima.diff(before, snapshot.to_s, context: 0)
|
|
74
|
+
diff.hunks
|
|
75
|
+
diff.marks
|
|
76
|
+
post { (@git_diff_cache ||= {})[key] = diff } unless @closed
|
|
75
77
|
rescue StandardError => error
|
|
76
78
|
post { @message = "Git diff: #{error.message}" } unless @closed
|
|
77
79
|
end
|
|
78
80
|
end
|
|
79
|
-
|
|
81
|
+
nil
|
|
80
82
|
else
|
|
81
83
|
before = Buffer.decode_bytes(git.blob(path).to_s).first
|
|
82
|
-
|
|
84
|
+
diff = Porrima.diff(before, buffer.text, context: 0)
|
|
85
|
+
diff.hunks
|
|
86
|
+
diff.marks
|
|
87
|
+
@git_diff_cache[key] = diff
|
|
83
88
|
end
|
|
84
89
|
end
|
|
90
|
+
def git_hunks(buffer = editor.buffer, async: true) = git_diff(buffer, async: async)&.hunks || []
|
|
91
|
+
def git_gutter_marks(buffer = editor.buffer) = git_diff(buffer)&.marks || []
|
|
85
92
|
def show_git_diff
|
|
86
93
|
path = git_relative_path
|
|
87
94
|
before = Buffer.decode_bytes(git.blob(path).to_s).first
|
|
88
|
-
buffer = Buffer.new(
|
|
95
|
+
buffer = Buffer.new(Porrima.unified(before, editor.buffer.text, old_name: "a/#{path}", new_name: "b/#{path}"), read_only: true)
|
|
89
96
|
@buffers[buffer.object_id] = buffer
|
|
90
97
|
@active_pane.open(buffer).language = Language::Definition.new("diff", "diff", [], "", /\A\z/, /\A\z/, [])
|
|
91
98
|
end
|
|
92
99
|
def toggle_git_hunk(current = editor, row: nil)
|
|
93
100
|
row ||= current.buffer.rope.point_at(current.primary.head).row
|
|
94
|
-
hunk =
|
|
95
|
-
(row + 1).between?([item.new_start, 1].max, [item.new_start + item.new_count - 1, item.new_start].max)
|
|
96
|
-
end
|
|
101
|
+
hunk = git_diff(current.buffer, async: false)&.hunk_at(new_line: row + 1)
|
|
97
102
|
raise Error, "No change at the cursor" unless hunk
|
|
98
103
|
id = [:git_hunk, current.buffer.path, current.buffer.version, git.head, hunk.old_start, hunk.new_start]
|
|
99
104
|
map = current.display_map
|
|
@@ -122,9 +127,9 @@ module Canopus
|
|
|
122
127
|
end
|
|
123
128
|
def revert_current_hunk
|
|
124
129
|
row = editor.buffer.rope.point_at(editor.primary.head).row + 1
|
|
125
|
-
hunk =
|
|
130
|
+
hunk = git_diff(async: false)&.hunk_at(new_line: row)
|
|
126
131
|
raise Error, "No change at the cursor" unless hunk
|
|
127
|
-
replacement =
|
|
132
|
+
replacement = Porrima.revert(editor.buffer.text, hunk)
|
|
128
133
|
editor.buffer.edit([[0...editor.buffer.rope.bytesize, replacement]], kind: :revert_hunk)
|
|
129
134
|
end
|
|
130
135
|
def checkout_branch(name)
|
|
@@ -20,6 +20,8 @@ module Canopus
|
|
|
20
20
|
end
|
|
21
21
|
state = {version: 2, root: @root, layout: encode_layout(@layout), recent_files: @recent_files || [],
|
|
22
22
|
active_pane: @panes.index(@active_pane), docks: @docks,
|
|
23
|
+
terminals: @terminals.map { |current| {cwd: current.vt.cwd || (current.respond_to?(:initial_cwd) ? current.initial_cwd : @root), title: @terminal_names[current]} },
|
|
24
|
+
active_terminal: @active_terminal_index, terminal_visible: !!@terminal_visible,
|
|
23
25
|
panes: @panes.map do |pane|
|
|
24
26
|
{active: pane.active_index, tabs: pane.editors.map do |current|
|
|
25
27
|
{buffer_id: record.call(current.buffer), cursor: current.primary.head, pinned: pane.pinned.include?(current),
|
|
@@ -133,6 +135,7 @@ module Canopus
|
|
|
133
135
|
end
|
|
134
136
|
active = data.fetch("active_pane", 0)
|
|
135
137
|
raise Error, "invalid active pane" unless active.is_a?(Integer) && active.between?(0, restored_panes.length - 1)
|
|
138
|
+
terminal_records = validate_session_terminals(data) if @settings["terminal"]["restore_on_startup"]
|
|
136
139
|
clear_vim_states
|
|
137
140
|
close_language_documents
|
|
138
141
|
@panes.each { |pane| pane.editors.each(&:dispose) }
|
|
@@ -140,7 +143,7 @@ module Canopus
|
|
|
140
143
|
@panes, @buffers, @layout, @docks = restored_panes, restored_buffers, restored_layout, restored_docks
|
|
141
144
|
@active_pane = @panes[active]
|
|
142
145
|
@recent_files = Array(data["recent_files"]).select { |item| item.is_a?(String) && File.file?(item) }.first(100)
|
|
143
|
-
|
|
146
|
+
restore_terminals(data, terminal_records) if terminal_records
|
|
144
147
|
self
|
|
145
148
|
rescue StandardError
|
|
146
149
|
unless @panes.equal?(restored_panes)
|
|
@@ -149,5 +152,33 @@ module Canopus
|
|
|
149
152
|
end
|
|
150
153
|
raise
|
|
151
154
|
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def validate_session_terminals(data)
|
|
159
|
+
records = data.fetch("terminals", [])
|
|
160
|
+
raise Error, "invalid session terminals" unless records.is_a?(Array) && records.length <= 100 && records.all? { |item| item.is_a?(Hash) && item["cwd"].is_a?(String) }
|
|
161
|
+
active = data.fetch("active_terminal", 0)
|
|
162
|
+
raise Error, "invalid active terminal" unless records.empty? || active.is_a?(Integer) && active.between?(0, records.length - 1)
|
|
163
|
+
records
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def restore_terminals(data, records)
|
|
167
|
+
old, old_index, old_visible = @terminals, @active_terminal_index, @terminal_visible
|
|
168
|
+
@terminals = []
|
|
169
|
+
records.each do |record|
|
|
170
|
+
cwd = File.directory?(record["cwd"]) ? record["cwd"] : @root
|
|
171
|
+
created = new_terminal(cwd: cwd)
|
|
172
|
+
rename_terminal(record["title"], created) if record["title"].is_a?(String)
|
|
173
|
+
end
|
|
174
|
+
@active_terminal_index = @terminals.empty? ? 0 : data.fetch("active_terminal", 0)
|
|
175
|
+
@terminal_visible = !!data["terminal_visible"] && !@terminals.empty?
|
|
176
|
+
old.each { |current| current.close if current.respond_to?(:close) }
|
|
177
|
+
rescue StandardError
|
|
178
|
+
@terminals.each { |current| current.close if current.respond_to?(:close) }
|
|
179
|
+
@terminals = old
|
|
180
|
+
@active_terminal_index, @terminal_visible = old_index, old_visible
|
|
181
|
+
@message = "Session restored; terminals could not start"
|
|
182
|
+
end
|
|
152
183
|
end
|
|
153
184
|
end
|
|
@@ -65,7 +65,7 @@ module Canopus
|
|
|
65
65
|
def terminal_point(point)
|
|
66
66
|
grid = @workspace.terminal.grid
|
|
67
67
|
column = ((point.x - @terminal_bounds.x) / @terminal_cell_width).floor.clamp(0, grid.columns - 1)
|
|
68
|
-
row = ((point.y - @terminal_bounds.y) / @line_height).floor.clamp(0, grid.rows - 1)
|
|
68
|
+
row = ((point.y - @terminal_bounds.y) / (@terminal_line_height || @line_height)).floor.clamp(0, grid.rows - 1)
|
|
69
69
|
[column, row]
|
|
70
70
|
end
|
|
71
71
|
def terminal_scroll(delta)
|
|
@@ -136,15 +136,36 @@ module Canopus
|
|
|
136
136
|
end
|
|
137
137
|
def render_terminal(bounds)
|
|
138
138
|
fill(bounds, :panel)
|
|
139
|
+
@terminal_bounds = nil
|
|
139
140
|
terminal = @workspace.terminal
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
unless terminal
|
|
142
|
+
text("No terminals — Ctrl+Shift+` to create one", bounds.x + 12, bounds.y + 8, color: :muted, size: 12)
|
|
143
|
+
return
|
|
144
|
+
end
|
|
145
|
+
if @terminal_owner != terminal
|
|
146
|
+
@terminal_owner, @terminal_scroll, @terminal_selection = terminal, 0, nil
|
|
147
|
+
end
|
|
148
|
+
x = bounds.x
|
|
149
|
+
@workspace.terminals.each_with_index do |current, index|
|
|
150
|
+
label = @workspace.terminal_title(current)
|
|
151
|
+
width = [[label.length * 7.5 + 42, 100].max, 220].min
|
|
152
|
+
tab = Zaniah::Bounds.new(x, bounds.y, width, 28)
|
|
153
|
+
fill(tab, :active_tab) if current.equal?(terminal)
|
|
154
|
+
text(label, tab.x + 10, tab.y + 6, color: current.equal?(terminal) ? :foreground : :muted, size: 12)
|
|
155
|
+
region(tab, role: :tab, label: label, action: [:terminal_tab, index])
|
|
156
|
+
close = Zaniah::Bounds.new(tab.right - 25, tab.y, 25, tab.height)
|
|
157
|
+
text("×", close.x + 6, close.y + 5, color: :muted, size: 12)
|
|
158
|
+
region(close, role: :button, label: "Close #{label}", action: [:terminal_close, index])
|
|
159
|
+
x += width
|
|
160
|
+
end
|
|
161
|
+
@terminal_font_size = @workspace.settings["terminal"]["font_size"] || @font_size
|
|
162
|
+
@terminal_line_height = (@terminal_font_size * @workspace.settings["terminal"]["line_height"]).ceil
|
|
163
|
+
@terminal_cell_width = @cx.text_system&.layout_line("M", size: @terminal_font_size)&.width || @terminal_font_size * 0.6
|
|
164
|
+
columns = [(bounds.width - 24) / @terminal_cell_width, @workspace.settings["terminal"]["min_cols"]].max.floor
|
|
165
|
+
rows = [(bounds.height - 40) / @terminal_line_height, @workspace.settings["terminal"]["min_rows"]].max.floor
|
|
166
|
+
@workspace.resize_terminal(columns, rows)
|
|
146
167
|
grid = terminal.grid
|
|
147
|
-
@terminal_bounds = Zaniah::Bounds.new(bounds.x + 12, bounds.y +
|
|
168
|
+
@terminal_bounds = Zaniah::Bounds.new(bounds.x + 12, bounds.y + 32, columns * @terminal_cell_width, rows * @terminal_line_height).intersect(bounds)
|
|
148
169
|
@terminal_scroll = (@terminal_scroll || 0).clamp(0, grid.scrollback.length)
|
|
149
170
|
@terminal_first = grid.scrollback.length - @terminal_scroll.floor
|
|
150
171
|
selection = @terminal_selection&.sort
|
|
@@ -153,11 +174,11 @@ module Canopus
|
|
|
153
174
|
cells = terminal_row(@terminal_first + row)
|
|
154
175
|
cells.each_with_index do |cell, column|
|
|
155
176
|
next if cell.width.zero?
|
|
156
|
-
x, y = @terminal_bounds.x + column * @terminal_cell_width, @terminal_bounds.y + row * @
|
|
177
|
+
x, y = @terminal_bounds.x + column * @terminal_cell_width, @terminal_bounds.y + row * @terminal_line_height
|
|
157
178
|
foreground = terminal_color(cell.foreground, :foreground)
|
|
158
179
|
background = terminal_color(cell.background, :panel)
|
|
159
180
|
foreground, background = background, foreground if cell.attributes[:inverse]
|
|
160
|
-
rect = Zaniah::Bounds.new(x, y, cell.width * @terminal_cell_width, @
|
|
181
|
+
rect = Zaniah::Bounds.new(x, y, cell.width * @terminal_cell_width, @terminal_line_height)
|
|
161
182
|
fill(rect, background) if background != @theme[:panel]
|
|
162
183
|
position = [@terminal_first + row, column]
|
|
163
184
|
fill(rect, :selection) if selection && (position <=> selection.first) >= 0 && (position <=> selection.last) <= 0
|
|
@@ -165,20 +186,39 @@ module Canopus
|
|
|
165
186
|
foreground = Zaniah::Color.parse(foreground).opacity(0.6) if cell.attributes[:dim]
|
|
166
187
|
unless cell.text.strip.empty?
|
|
167
188
|
font = terminal_font(cell)
|
|
168
|
-
text(cell.text, x, y, color: foreground, font: font)
|
|
169
|
-
text(cell.text, x + 0.5, y, color: foreground, font: font) if cell.attributes[:bold]
|
|
189
|
+
text(cell.text, x, y, color: foreground, size: @terminal_font_size, font: font)
|
|
190
|
+
text(cell.text, x + 0.5, y, color: foreground, size: @terminal_font_size, font: font) if cell.attributes[:bold]
|
|
170
191
|
end
|
|
171
192
|
if cell.attributes[:underline] || cell.hyperlink
|
|
172
|
-
fill(Zaniah::Bounds.new(x, y + @
|
|
193
|
+
fill(Zaniah::Bounds.new(x, y + @terminal_line_height - 2, rect.width, 1), terminal_color(cell.attributes[:underline_color], :foreground))
|
|
173
194
|
end
|
|
174
|
-
fill(Zaniah::Bounds.new(x, y + @
|
|
195
|
+
fill(Zaniah::Bounds.new(x, y + @terminal_line_height / 2, rect.width, 1), foreground) if cell.attributes[:strikethrough]
|
|
175
196
|
end
|
|
176
197
|
end
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
blinking = @workspace.settings["terminal"]["blinking"]
|
|
199
|
+
if grid.cursor_visible && @terminal_scroll.zero? &&
|
|
200
|
+
(blinking == "off" || @cursor_visible || !@workspace.terminal_composition&.text.to_s.empty?)
|
|
201
|
+
x = @terminal_bounds.x + grid.cursor_x * @terminal_cell_width
|
|
202
|
+
y = @terminal_bounds.y + grid.cursor_y * @terminal_line_height
|
|
203
|
+
cursor = case @workspace.settings["terminal"]["cursor_shape"]
|
|
204
|
+
when "bar" then Zaniah::Bounds.new(x, y, 2, @terminal_line_height)
|
|
205
|
+
when "underline" then Zaniah::Bounds.new(x, y + @terminal_line_height - 2, @terminal_cell_width, 2)
|
|
206
|
+
else Zaniah::Bounds.new(x, y, @terminal_cell_width, @terminal_line_height)
|
|
207
|
+
end
|
|
208
|
+
fill(cursor, "#e9a66166")
|
|
209
|
+
composition = @workspace.terminal_composition
|
|
210
|
+
if composition && !composition.text.empty?
|
|
211
|
+
layout = text(composition.text, x, y, color: :accent, size: @terminal_font_size)
|
|
212
|
+
fill(Zaniah::Bounds.new(x, y + @terminal_line_height - 2, layout&.width || 20, 1), :accent)
|
|
213
|
+
@cx.window.ime_state = Zaniah::Bounds.new(x, y, 2, @terminal_line_height)
|
|
214
|
+
end
|
|
180
215
|
end
|
|
181
216
|
end
|
|
217
|
+
if terminal.respond_to?(:status) && terminal.status
|
|
218
|
+
code = terminal.status.exited? ? terminal.status.exitstatus : "signal #{terminal.status.termsig}"
|
|
219
|
+
text("Process exited with #{code} — press Enter to restart", @terminal_bounds.x + 4,
|
|
220
|
+
[@terminal_bounds.bottom - @terminal_line_height, @terminal_bounds.y].max, color: :muted, size: 11)
|
|
221
|
+
end
|
|
182
222
|
region(@terminal_bounds, role: :terminal, label: "Terminal", action: [:terminal])
|
|
183
223
|
end
|
|
184
224
|
end
|
|
@@ -170,7 +170,7 @@ module Canopus
|
|
|
170
170
|
height = bounds.height / panels.length
|
|
171
171
|
area = Zaniah::Bounds.new(bounds.x + 8, bounds.y + height * index, [bounds.width - 16, 0].max, height)
|
|
172
172
|
text(name, area.x + 4, area.y + 10, color: :muted, size: 12)
|
|
173
|
-
key = [name, @workspace.editor
|
|
173
|
+
key = [name, @workspace.editor&.buffer&.object_id, @workspace.editor&.buffer&.version, @theme.object_id]
|
|
174
174
|
@panel_cache ||= {}
|
|
175
175
|
@panel_cache.clear if @panel_cache.length > 50
|
|
176
176
|
element = @panel_cache[key] ||= render.call
|
|
@@ -219,11 +219,19 @@ module Canopus
|
|
|
219
219
|
end
|
|
220
220
|
text(name, x + 14, bounds.y + 10, color: pane.active.equal?(editor) ? :foreground : :muted, size: 12)
|
|
221
221
|
region(tab, role: :tab, label: name, action: [:tab, pane, editor])
|
|
222
|
+
close = Zaniah::Bounds.new(tab.right - 26, tab.y, 26, tab.height)
|
|
223
|
+
text(editor.buffer.dirty? ? "●" : "×", close.x + 6, close.y + 9, color: :muted, size: 12)
|
|
224
|
+
region(close, role: :button, label: "Close #{name}", action: [:tab_close, pane, editor])
|
|
222
225
|
x += width
|
|
223
226
|
end
|
|
224
227
|
end
|
|
225
228
|
editor = pane.active
|
|
226
|
-
|
|
229
|
+
unless editor
|
|
230
|
+
text("Open a file or create a new one", bounds.x + 24, bounds.y + 58, color: :muted, size: 14)
|
|
231
|
+
text("Cmd/Ctrl+P Open Cmd/Ctrl+N New", bounds.x + 24, bounds.y + 84, color: :muted, size: 11)
|
|
232
|
+
fill(Zaniah::Bounds.new(bounds.right - 1, bounds.y, 1, bounds.height), :border)
|
|
233
|
+
return
|
|
234
|
+
end
|
|
227
235
|
area = Zaniah::Bounds.new(bounds.x, bounds.y + 34, bounds.width, [bounds.height - 34, 0].max)
|
|
228
236
|
@editor_bounds[editor] = area
|
|
229
237
|
region(area, role: :textbox, label: editor.buffer.path || "Untitled document", action: [:editor, pane, editor])
|
|
@@ -236,7 +244,7 @@ module Canopus
|
|
|
236
244
|
editor.viewport_rows = [(bounds.height / @line_height).floor, 1].max
|
|
237
245
|
map, first = editor.display_map, editor.scroll_y.floor
|
|
238
246
|
last = [first + editor.viewport_rows + 1, map.row_count].min
|
|
239
|
-
|
|
247
|
+
marks = @workspace.git_gutter_marks(editor.buffer)
|
|
240
248
|
cursor_offset = @workspace.settings["vim_mode"] && active ? @workspace.vim.cursor_position : editor.primary.head
|
|
241
249
|
cursor = map.to_display(cursor_offset)
|
|
242
250
|
if map.wrap_map.width && !editor.buffer.read_only
|
|
@@ -280,9 +288,12 @@ module Canopus
|
|
|
280
288
|
y = bounds.y + (index - editor.scroll_y) * @line_height + 4
|
|
281
289
|
fill(Zaniah::Bounds.new(bounds.x, y - 2, bounds.width, @line_height), :current_line) if cursor.row == index
|
|
282
290
|
source_row = map.source_row(index)
|
|
283
|
-
change =
|
|
291
|
+
change = marks.find do |mark|
|
|
292
|
+
first = [mark.new_line, 1].max
|
|
293
|
+
mark.kind == :removed ? source_row + 1 == first : (source_row + 1).between?(first, first + mark.count - 1)
|
|
294
|
+
end
|
|
284
295
|
if change
|
|
285
|
-
color = change.
|
|
296
|
+
color = change.kind == :removed ? @theme[:error] : change.kind == :added ? "#80b987" : @theme[:accent]
|
|
286
297
|
fill(Zaniah::Bounds.new(bounds.x + 2, y - 1, 3, @line_height), color)
|
|
287
298
|
region(Zaniah::Bounds.new(bounds.x, y - 1, 10, @line_height), role: :button, label: "Toggle Git hunk", action: [:git_hunk, editor, source_row]) if row.kind == :text
|
|
288
299
|
end
|
|
@@ -327,9 +338,9 @@ module Canopus
|
|
|
327
338
|
previous_width = @line_widths[editor]
|
|
328
339
|
measured_width = [measured_width, previous_width.last].max if previous_width && previous_width.first == editor.buffer.version
|
|
329
340
|
@line_widths[editor] = [editor.buffer.version, measured_width]
|
|
330
|
-
paint_scrollbars(editor, bounds, measured_width,
|
|
341
|
+
paint_scrollbars(editor, bounds, measured_width, marks)
|
|
331
342
|
end
|
|
332
|
-
def paint_scrollbars(editor, bounds, content_width,
|
|
343
|
+
def paint_scrollbars(editor, bounds, content_width, marks)
|
|
333
344
|
track = Zaniah::Bounds.new(bounds.right - 9, bounds.y, 9, [bounds.height - 9, 0].max)
|
|
334
345
|
total = editor.display_map.row_count
|
|
335
346
|
maximum = [total - editor.viewport_rows, 0].max
|
|
@@ -337,8 +348,8 @@ module Canopus
|
|
|
337
348
|
height = [24, track.height * editor.viewport_rows / total].max.clamp(0, track.height)
|
|
338
349
|
thumb = Zaniah::Bounds.new(track.x + 2, track.y + (track.height - height) * editor.scroll_y / maximum, 5, height)
|
|
339
350
|
fill(thumb, :muted)
|
|
340
|
-
|
|
341
|
-
y = track.y + track.height * [
|
|
351
|
+
marks.first(500).each do |mark|
|
|
352
|
+
y = track.y + track.height * [mark.new_line - 1, 0].max / [editor.buffer.line_count, 1].max
|
|
342
353
|
fill(Zaniah::Bounds.new(track.x, y, 3, 2), :accent)
|
|
343
354
|
end
|
|
344
355
|
frame_diagnostics(editor.buffer).first(500).each do |diagnostic|
|
|
@@ -576,7 +587,8 @@ module Canopus
|
|
|
576
587
|
end
|
|
577
588
|
def shortcut_labels
|
|
578
589
|
return {} unless @keymap
|
|
579
|
-
context = {"Editor" =>
|
|
590
|
+
context = {"Editor" => !!@workspace.editor,
|
|
591
|
+
"vim_mode" => @workspace.settings["vim_mode"] && @workspace.editor ? @workspace.vim.mode.to_s : false}
|
|
580
592
|
seen, labels = {}, {}
|
|
581
593
|
@keymap.bindings.reverse_each do |binding|
|
|
582
594
|
next unless binding.predicate.call(context)
|