canopus 0.4.0 → 0.5.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 +55 -0
- data/README.md +191 -19
- data/assets/keymaps/emacs.jsonc +5 -0
- data/assets/keymaps/jetbrains.jsonc +5 -0
- data/assets/keymaps/sublime.jsonc +5 -0
- data/assets/keymaps/vscode.jsonc +5 -0
- data/docs/debugging.md +143 -0
- data/docs/distribution.md +40 -9
- data/docs/lsp.md +131 -0
- data/docs/performance.md +18 -0
- data/docs/providers.md +36 -0
- data/docs/release-log.md +12 -0
- data/docs/tasks.md +64 -0
- data/docs/testing.md +49 -0
- data/docs/vim.md +3 -0
- data/exe/canopus +4 -1
- data/lib/canopus/buffer.rb +291 -50
- data/lib/canopus/cli.rb +5 -2
- data/lib/canopus/command.rb +13 -4
- data/lib/canopus/controller.rb +183 -40
- data/lib/canopus/debug/breakpoints.rb +550 -0
- data/lib/canopus/debug/configuration.rb +291 -0
- data/lib/canopus/debug/console.rb +243 -0
- data/lib/canopus/debug/panel.rb +487 -0
- data/lib/canopus/debug/session.rb +354 -0
- data/lib/canopus/decoration.rb +4 -2
- data/lib/canopus/denebola_compat.rb +27 -0
- data/lib/canopus/diagnostics.rb +173 -0
- data/lib/canopus/display_map/overlay_map.rb +6 -2
- data/lib/canopus/editor/snippet_expandable.rb +3 -2
- data/lib/canopus/editor.rb +122 -31
- data/lib/canopus/git/state.rb +100 -0
- data/lib/canopus/language/background_analysis.rb +93 -14
- data/lib/canopus/language/document.rb +70 -34
- data/lib/canopus/language/symbol.rb +1 -0
- data/lib/canopus/language/syntax_worker.rb +61 -9
- data/lib/canopus/language.rb +3 -0
- data/lib/canopus/minimap.rb +201 -0
- data/lib/canopus/patch.rb +3 -1
- data/lib/canopus/plugins/api.rb +4 -2
- data/lib/canopus/plugins/isolated_runtime.rb +64 -7
- data/lib/canopus/plugins/registry.rb +114 -3
- data/lib/canopus/plugins.rb +8 -7
- data/lib/canopus/project.rb +15 -6
- data/lib/canopus/provider.rb +192 -0
- data/lib/canopus/regexp_compat.rb +36 -4
- data/lib/canopus/selection.rb +12 -0
- data/lib/canopus/settings/schema.rb +201 -0
- data/lib/canopus/settings.rb +328 -49
- data/lib/canopus/task/configuration.rb +356 -0
- data/lib/canopus/task/problem_matcher.rb +264 -0
- data/lib/canopus/task/runner.rb +314 -0
- data/lib/canopus/test_runner/bounded_ignore.rb +144 -0
- data/lib/canopus/test_runner/discovery.rb +97 -0
- data/lib/canopus/test_runner/execution.rb +194 -0
- data/lib/canopus/test_runner/minitest.rb +116 -0
- data/lib/canopus/test_runner/rspec.rb +103 -0
- data/lib/canopus/test_runner/safe_walker.rb +37 -0
- data/lib/canopus/test_runner.rb +54 -0
- data/lib/canopus/theme/import.rb +131 -0
- data/lib/canopus/theme.rb +28 -4
- data/lib/canopus/version.rb +1 -1
- data/lib/canopus/vim/motionable.rb +1 -1
- data/lib/canopus/vim.rb +10 -16
- data/lib/canopus/workspace/auto_savable.rb +83 -0
- data/lib/canopus/workspace/debug_aware.rb +600 -0
- data/lib/canopus/workspace/edit/plan.rb +2 -2
- data/lib/canopus/workspace/editor_configurable.rb +113 -0
- data/lib/canopus/workspace/file_change_aware.rb +11 -3
- data/lib/canopus/workspace/git_aware.rb +80 -34
- data/lib/canopus/workspace/git_blame.rb +175 -0
- data/lib/canopus/workspace/git_conflict_resolution.rb +410 -0
- data/lib/canopus/workspace/git_diff_view.rb +641 -0
- data/lib/canopus/workspace/git_history.rb +171 -0
- data/lib/canopus/workspace/git_remote.rb +267 -0
- data/lib/canopus/workspace/git_staging.rb +583 -0
- data/lib/canopus/workspace/hierarchy_aware.rb +477 -0
- data/lib/canopus/workspace/keymap_aware.rb +66 -0
- data/lib/canopus/workspace/language_aware.rb +3721 -135
- data/lib/canopus/workspace/language_server_configurable.rb +287 -71
- data/lib/canopus/workspace/persistent_undo.rb +169 -0
- data/lib/canopus/workspace/problems_aware.rb +131 -0
- data/lib/canopus/workspace/project_searchable.rb +88 -11
- data/lib/canopus/workspace/project_tree_editable.rb +3 -1
- data/lib/canopus/workspace/recoverable.rb +204 -0
- data/lib/canopus/workspace/session_persistable.rb +85 -13
- data/lib/canopus/workspace/settings_aware.rb +76 -2
- data/lib/canopus/workspace/task_aware.rb +194 -0
- data/lib/canopus/workspace/test_aware.rb +394 -0
- data/lib/canopus/workspace/trust.rb +73 -0
- data/lib/canopus/workspace/view/terminal_presentable.rb +298 -47
- data/lib/canopus/workspace/view.rb +399 -74
- data/lib/canopus/workspace.rb +688 -44
- data/lib/canopus.rb +20 -2
- data/sig/canopus.rbs +138 -31
- data/sig/controller.rbs +10 -3
- data/sig/debug.rbs +121 -0
- data/sig/decoration.rbs +12 -2
- data/sig/diagnostics.rbs +26 -0
- data/sig/minimap.rbs +20 -0
- data/sig/provider.rbs +29 -0
- data/sig/services.rbs +1 -16
- data/sig/task.rbs +92 -0
- data/sig/test_runner.rbs +109 -0
- data/sig/trust.rbs +17 -0
- data/sig/workspace_edits.rbs +2 -2
- data/sig/workspace_services.rbs +234 -2
- data/tools/check_dependencies.rb +21 -2
- data/tools/package.rb +172 -14
- data/tools/package_signature_test.rb +33 -0
- data/tools/package_test.rb +29 -0
- data/tools/sign_package.rb +123 -0
- data/tools/update.rb +189 -0
- data/tools/update_test.rb +71 -0
- metadata +206 -31
- data/lib/canopus/lsp/client.rb +0 -307
- data/lib/canopus/lsp/error.rb +0 -7
- data/lib/canopus/lsp/future/subscription.rb +0 -9
- data/lib/canopus/lsp/future.rb +0 -87
- data/lib/canopus/lsp/protocol.rb +0 -83
- data/lib/canopus/lsp/server_error.rb +0 -13
- data/lib/canopus/lsp/timeout.rb +0 -7
- data/lib/canopus/lsp/transport.rb +0 -123
- data/lib/canopus/lsp.rb +0 -19
- data/lib/canopus/project/search.rb +0 -164
- data/lib/canopus/project/search_worker/cancelled.rb +0 -3
- data/lib/canopus/project/search_worker/runner.rb +0 -9
- data/lib/canopus/project/search_worker.rb +0 -108
- data/lib/canopus/terminal/cell.rb +0 -7
- data/lib/canopus/terminal/grid.rb +0 -321
- data/lib/canopus/terminal/pty.rb +0 -178
- data/lib/canopus/terminal/scrollback.rb +0 -38
- data/lib/canopus/terminal/vt.rb +0 -398
- data/lib/canopus/terminal.rb +0 -13
- data/sig/lsp.rbs +0 -99
- data/sig/terminal.rbs +0 -130
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
module Canopus
|
|
4
4
|
# A viewport is one element; only visible document rows become draw commands.
|
|
5
5
|
class Workspace::View < Zaniah::Element
|
|
6
|
-
attr_reader :editor_bounds, :row_layouts, :regions, :accessibility
|
|
6
|
+
attr_reader :editor_bounds, :minimap_bounds, :row_layouts, :regions, :accessibility
|
|
7
7
|
attr_accessor :keymap
|
|
8
8
|
def initialize(workspace)
|
|
9
9
|
super()
|
|
10
10
|
@workspace = workspace
|
|
11
|
-
@editor_bounds, @row_layouts, @regions, @accessibility = {}, {}, [], []
|
|
11
|
+
@editor_bounds, @minimap_bounds, @row_layouts, @regions, @accessibility = {}, {}, {}, [], []
|
|
12
12
|
@project_scroll = 0
|
|
13
13
|
reset_blink
|
|
14
14
|
@revealed_cursors, @line_widths, @code_caches = {}, {}, {}
|
|
15
|
-
@frame_diagnostics = {}
|
|
16
15
|
end
|
|
17
16
|
def reset_blink(now = Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
|
18
17
|
@blink_started, @cursor_visible = now, true
|
|
@@ -33,12 +32,15 @@ module Canopus
|
|
|
33
32
|
@font_size = @workspace.settings["font_size"]
|
|
34
33
|
@line_height = (@font_size * 1.55).ceil
|
|
35
34
|
@line_height = 20 if @cx.window.is_a?(Zaniah::Platform::TUI::Window)
|
|
35
|
+
@frame_id = @frame_id.to_i + 1
|
|
36
|
+
@workspace.minimap.begin_frame(@frame_id, text_system: @cx.text_system, font_size: @font_size,
|
|
37
|
+
font_family: @workspace.settings["font_family"], scale_factor: @cx.window.scale_factor)
|
|
36
38
|
@regions.clear
|
|
37
39
|
@row_layouts.clear
|
|
38
40
|
@overlay_rows = []
|
|
39
41
|
@editor_bounds.clear
|
|
42
|
+
@minimap_bounds.clear
|
|
40
43
|
@accessibility.clear
|
|
41
|
-
@frame_diagnostics.clear
|
|
42
44
|
visible_editors = @workspace.panes.map(&:active)
|
|
43
45
|
@revealed_cursors.delete_if { |editor, _| !visible_editors.include?(editor) }
|
|
44
46
|
@line_widths.delete_if { |editor, _| !visible_editors.include?(editor) }
|
|
@@ -46,7 +48,7 @@ module Canopus
|
|
|
46
48
|
fill(bounds, :background)
|
|
47
49
|
left_panels = drawable_panels(:left)
|
|
48
50
|
bottom_panels = drawable_panels(:bottom)
|
|
49
|
-
bottom_visible = @workspace.docks[:bottom][:visible] && (@workspace.terminal_visible || !bottom_panels.empty?)
|
|
51
|
+
bottom_visible = @workspace.docks[:bottom][:visible] && (@workspace.task_output_visible || @workspace.terminal_visible || !bottom_panels.empty?)
|
|
50
52
|
bottom = bottom_visible ? [bounds.height * 0.7, @workspace.docks[:bottom][:size]].min : 0
|
|
51
53
|
left_visible = @workspace.docks[:left][:visible] && (@workspace.show_project || !left_panels.empty?)
|
|
52
54
|
sidebar = left_visible && bounds.width >= 620 ? [@workspace.docks[:left][:size], bounds.width * 0.4].min : 0
|
|
@@ -63,7 +65,13 @@ module Canopus
|
|
|
63
65
|
paint_layout(@workspace.layout, body)
|
|
64
66
|
if bottom.positive?
|
|
65
67
|
area = Zaniah::Bounds.new(sidebar, body.height, body.width, bottom)
|
|
66
|
-
@workspace.
|
|
68
|
+
if @workspace.task_output_visible
|
|
69
|
+
paint_task_output(area)
|
|
70
|
+
elsif @workspace.terminal_visible
|
|
71
|
+
paint_terminal(area)
|
|
72
|
+
else
|
|
73
|
+
paint_panels(:bottom, area)
|
|
74
|
+
end
|
|
67
75
|
end
|
|
68
76
|
paint_panels(:right, Zaniah::Bounds.new(bounds.width - right, 0, right, bounds.height - 26)) if right.positive?
|
|
69
77
|
region(Zaniah::Bounds.new(sidebar - 3, 0, 6, body.height), role: :separator, label: "Resize left dock", action: [:dock_resize, :left, bounds]) if sidebar.positive?
|
|
@@ -101,6 +109,18 @@ module Canopus
|
|
|
101
109
|
def project_scroll(delta)
|
|
102
110
|
@project_scroll = [@project_scroll + delta, 0].max
|
|
103
111
|
end
|
|
112
|
+
def minimap_scroll(editor, point)
|
|
113
|
+
entry = @minimap_bounds[editor]
|
|
114
|
+
return false unless entry && @workspace.panes.include?(entry[:pane]) && entry[:pane].active.equal?(editor)
|
|
115
|
+
|
|
116
|
+
bounds = entry[:bounds]
|
|
117
|
+
fraction = ((point.y - bounds.y).to_f / [bounds.height, 1].max).clamp(0, 1)
|
|
118
|
+
source_row = (fraction * editor.buffer.line_count).floor.clamp(0, editor.buffer.line_count - 1)
|
|
119
|
+
display_row = editor.display_map.to_display(editor.buffer.rope.line_start(source_row)).row
|
|
120
|
+
target = (display_row - editor.viewport_rows / 2.0).clamp(0, [editor.display_map.row_count - editor.viewport_rows, 0].max)
|
|
121
|
+
editor.scroll(dy: target - editor.scroll_y)
|
|
122
|
+
true
|
|
123
|
+
end
|
|
104
124
|
|
|
105
125
|
private
|
|
106
126
|
def paint_notifications(bounds)
|
|
@@ -197,7 +217,7 @@ module Canopus
|
|
|
197
217
|
end
|
|
198
218
|
end
|
|
199
219
|
def drawable_panels(side)
|
|
200
|
-
@workspace.panels.active(side).reject { |definition| %w[terminal explorer search].include?(definition.id) }
|
|
220
|
+
@workspace.panels.active(side).reject { |definition| %w[terminal output explorer search].include?(definition.id) }
|
|
201
221
|
end
|
|
202
222
|
def paint_layout(node, bounds)
|
|
203
223
|
return paint_pane(node[:pane], bounds) if node[:pane]
|
|
@@ -222,7 +242,8 @@ module Canopus
|
|
|
222
242
|
@scene.clip(bounds) do
|
|
223
243
|
x = bounds.x
|
|
224
244
|
pane.editors.each do |editor|
|
|
225
|
-
name = editor.buffer.
|
|
245
|
+
name = editor.buffer.instance_variable_get(:@display_name) ||
|
|
246
|
+
(editor.buffer.path ? File.basename(editor.buffer.path) : "Untitled")
|
|
226
247
|
name += " *" if editor.buffer.dirty?
|
|
227
248
|
name = "[#{name}]" if pane.pinned.include?(editor)
|
|
228
249
|
width = [[name.length * 7.5 + 30, 100].max, 240].min
|
|
@@ -247,19 +268,36 @@ module Canopus
|
|
|
247
268
|
return
|
|
248
269
|
end
|
|
249
270
|
area = Zaniah::Bounds.new(bounds.x, bounds.y + 34, bounds.width, [bounds.height - 34, 0].max)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
271
|
+
minimap = @workspace.minimap_settings(editor)
|
|
272
|
+
minimap_width = if minimap["enabled"] && !@cx.window.is_a?(Zaniah::Platform::TUI::Window) &&
|
|
273
|
+
area.height.positive? && area.width >= minimap["width"] + gutter(editor) + 80
|
|
274
|
+
minimap["width"]
|
|
275
|
+
else 0
|
|
276
|
+
end
|
|
277
|
+
editor_area = Zaniah::Bounds.new(area.x, area.y, area.width - minimap_width, area.height)
|
|
278
|
+
minimap_area = Zaniah::Bounds.new(editor_area.right, area.y, minimap_width, area.height)
|
|
279
|
+
breadcrumbs = @workspace.breadcrumb_context(editor)
|
|
280
|
+
breadcrumb_height = breadcrumbs && editor_area.height >= @line_height * 2 ? @line_height : 0
|
|
281
|
+
content = Zaniah::Bounds.new(editor_area.x, editor_area.y + breadcrumb_height, editor_area.width,
|
|
282
|
+
[editor_area.height - breadcrumb_height, 0].max)
|
|
283
|
+
decorations = prepare_editor_map(editor, content)
|
|
284
|
+
sticky = prepare_sticky_scroll(editor, content)
|
|
285
|
+
sticky_height = sticky.length * @line_height
|
|
286
|
+
body = Zaniah::Bounds.new(content.x, content.y + sticky_height, content.width,
|
|
287
|
+
[content.height - sticky_height, 0].max)
|
|
288
|
+
@editor_bounds[editor] = body
|
|
289
|
+
region(body, role: :textbox, label: editor.buffer.path || "Untitled document", action: [:editor, pane, editor])
|
|
290
|
+
overview = paint_editor(editor, body, pane == @workspace.active_pane, decorations)
|
|
291
|
+
paint_sticky_scroll(editor, pane,
|
|
292
|
+
Zaniah::Bounds.new(content.x, content.y, content.width, sticky_height), sticky) unless sticky.empty?
|
|
293
|
+
paint_breadcrumbs(editor, pane,
|
|
294
|
+
Zaniah::Bounds.new(editor_area.x, editor_area.y, editor_area.width, breadcrumb_height), breadcrumbs) if breadcrumb_height.positive?
|
|
295
|
+
paint_minimap(editor, pane, minimap_area, overview, minimap) if minimap_width.positive?
|
|
253
296
|
fill(Zaniah::Bounds.new(bounds.right - 1, bounds.y, 1, bounds.height), :border)
|
|
254
297
|
end
|
|
255
298
|
def gutter(editor) = [editor.buffer.line_count.to_s.length * @font_size * 0.6 + 24, 52].max
|
|
256
|
-
def
|
|
257
|
-
|
|
258
|
-
editor.viewport_rows = [(bounds.height / @line_height).floor, 1].max
|
|
259
|
-
map, first = editor.display_map, editor.scroll_y.floor
|
|
260
|
-
last = [first + editor.viewport_rows + 1, map.row_count].min
|
|
261
|
-
cursor_offset = @workspace.settings["vim_mode"] && active ? @workspace.vim.cursor_position : editor.primary.head
|
|
262
|
-
cursor = map.to_display(cursor_offset)
|
|
299
|
+
def prepare_editor_map(editor, bounds)
|
|
300
|
+
map = editor.display_map
|
|
263
301
|
if map.wrap_map.width && !editor.buffer.read_only
|
|
264
302
|
width = [bounds.width - gutter(editor) - 16, 1].max
|
|
265
303
|
if @cx.window.is_a?(Zaniah::Platform::TUI::Window)
|
|
@@ -272,19 +310,76 @@ module Canopus
|
|
|
272
310
|
@workspace.message = "Soft wrap disabled: #{error.message}"
|
|
273
311
|
end
|
|
274
312
|
end
|
|
275
|
-
last = [first + editor.viewport_rows + 1, map.row_count].min
|
|
276
|
-
cursor = map.to_display(cursor_offset)
|
|
277
313
|
end
|
|
314
|
+
prepare_editor_decorations(editor, bounds)
|
|
315
|
+
end
|
|
316
|
+
def prepare_editor_decorations(editor, bounds)
|
|
317
|
+
map = editor.display_map
|
|
318
|
+
rows = [(bounds.height / @line_height).floor, 1].max
|
|
319
|
+
first = editor.scroll_y.clamp(0, [map.row_count - rows, 0].max).floor
|
|
320
|
+
last = [first + rows + 1, map.row_count].min
|
|
278
321
|
decorations = decorations_for_display_rows(editor, map, first, last)
|
|
322
|
+
overlays = decorations.select { |item| %i[inline block].include?(item.kind) }
|
|
323
|
+
if map.set_overlays(overlays, font: @cx.text_system.respond_to?(:font) ? @cx.text_system.font : nil,
|
|
324
|
+
font_size: @font_size, line_height: @line_height)
|
|
325
|
+
first = editor.scroll_y.clamp(0, [map.row_count - rows, 0].max).floor
|
|
326
|
+
last = [first + rows + 1, map.row_count].min
|
|
327
|
+
decorations = (decorations + decorations_for_display_rows(editor, map, first, last)).uniq.sort_by(&:priority).freeze
|
|
328
|
+
overlays = decorations.select { |item| %i[inline block].include?(item.kind) }
|
|
329
|
+
map.set_overlays(overlays, font: @cx.text_system.respond_to?(:font) ? @cx.text_system.font : nil,
|
|
330
|
+
font_size: @font_size, line_height: @line_height)
|
|
331
|
+
end
|
|
332
|
+
decorations
|
|
333
|
+
end
|
|
334
|
+
def prepare_sticky_scroll(editor, bounds)
|
|
335
|
+
rows = [(bounds.height / @line_height).floor, 1].max
|
|
336
|
+
sticky, scroll = [], editor.scroll_y
|
|
337
|
+
loop do
|
|
338
|
+
candidate = @workspace.sticky_context(editor, scroll.floor)
|
|
339
|
+
candidate = candidate.last([[rows - 1, 0].max, candidate.length].min)
|
|
340
|
+
viewport = [rows - candidate.length, 1].max
|
|
341
|
+
clamped = scroll.clamp(0, [editor.display_map.row_count - viewport, 0].max)
|
|
342
|
+
break sticky = candidate if candidate == sticky && clamped == scroll
|
|
343
|
+
sticky, scroll = candidate, clamped
|
|
344
|
+
end
|
|
345
|
+
editor.viewport_rows = [rows - sticky.length, 1].max
|
|
346
|
+
editor.scroll(dy: scroll - editor.scroll_y)
|
|
347
|
+
sticky
|
|
348
|
+
end
|
|
349
|
+
def paint_editor(editor, bounds, active, decorations = nil)
|
|
350
|
+
@selection_spans = {}
|
|
351
|
+
editor.viewport_rows = [(bounds.height / @line_height).floor, 1].max
|
|
352
|
+
editor.scroll
|
|
353
|
+
map, first = editor.display_map, editor.scroll_y.floor
|
|
354
|
+
last = [first + editor.viewport_rows + 1, map.row_count].min
|
|
355
|
+
vim_cursor = @workspace.settings["vim_mode"] && active
|
|
356
|
+
cursor_offsets = vim_cursor ? [@workspace.vim.cursor_position] : editor.selections.map(&:head)
|
|
357
|
+
cursor_offset = cursor_offsets.last
|
|
358
|
+
cursor = map.to_display(cursor_offset)
|
|
359
|
+
cursors = cursor_offsets.map { |offset| map.to_display(offset) }
|
|
360
|
+
decorations ||= decorations_for_display_rows(editor, map, first, last)
|
|
279
361
|
if map.set_overlays(decorations.select { |item| %i[inline block].include?(item.kind) },
|
|
280
|
-
font: @cx.text_system
|
|
362
|
+
font: @cx.text_system.respond_to?(:font) ? @cx.text_system.font : nil,
|
|
363
|
+
font_size: @font_size, line_height: @line_height)
|
|
364
|
+
editor.scroll
|
|
365
|
+
first = editor.scroll_y.floor
|
|
281
366
|
last = [first + editor.viewport_rows + 1, map.row_count].min
|
|
282
367
|
cursor = map.to_display(cursor_offset)
|
|
283
|
-
|
|
368
|
+
cursors = cursor_offsets.map { |offset| map.to_display(offset) }
|
|
369
|
+
decorations = (decorations + decorations_for_display_rows(editor, map, first, last)).uniq.sort_by(&:priority).freeze
|
|
284
370
|
end
|
|
285
371
|
gutter_items = decorations.select { |item| item.kind == :gutter }
|
|
286
372
|
highlight_items = decorations.select { |item| item.kind == :highlight }
|
|
287
373
|
line_items = decorations.select { |item| item.kind == :line }
|
|
374
|
+
foreground_items, guide_items = highlight_items.partition do |item|
|
|
375
|
+
item.style.is_a?(Hash) && item.style[:foreground]
|
|
376
|
+
end
|
|
377
|
+
guide_items, highlight_items = guide_items.partition do |item|
|
|
378
|
+
item.style.is_a?(Hash) && item.style[:guide]
|
|
379
|
+
end
|
|
380
|
+
foreground_by_row = foreground_items.group_by { |item| map.to_display(item.range.begin).row }
|
|
381
|
+
guides_by_row = guide_items.map { |item| [item, map.to_display(item.range.begin)] }
|
|
382
|
+
.group_by { |_item, point| point.row }
|
|
288
383
|
cursor_row = map.row(cursor.row)
|
|
289
384
|
cursor_line = @cx.text_system&.layout_line(cursor_row.text, size: @font_size)
|
|
290
385
|
brackets = []
|
|
@@ -316,12 +411,15 @@ module Canopus
|
|
|
316
411
|
style = item.style.is_a?(Hash) ? item.style : {color: item.style, rows: 1}
|
|
317
412
|
next unless source_row.between?(item.row, item.row + style.fetch(:rows, 1) - 1)
|
|
318
413
|
|
|
319
|
-
marker = Zaniah::Bounds.new(bounds.x + 2, y - 1,
|
|
414
|
+
marker = Zaniah::Bounds.new(bounds.x + style.fetch(:gutter_offset, 2), y - 1,
|
|
415
|
+
style.fetch(:gutter_width, 3), @line_height)
|
|
320
416
|
paint_instrumented_fill(marker, style.fetch(:color, :accent),
|
|
321
417
|
"syrma:decoration:gutter:#{source_row}:#{instrumentation_component(item.source)}")
|
|
322
418
|
if row.kind == :text && item.on_click
|
|
323
|
-
|
|
324
|
-
|
|
419
|
+
action = item.on_click.respond_to?(:right_click) ? :context_decoration : :decoration
|
|
420
|
+
region(Zaniah::Bounds.new(bounds.x + style.fetch(:hit_offset, 0), y - 1,
|
|
421
|
+
style.fetch(:hit_width, 10), @line_height), role: :button,
|
|
422
|
+
label: item.content.to_s, action: [action, item.on_click, editor, source_row])
|
|
325
423
|
end
|
|
326
424
|
end
|
|
327
425
|
number = editor.relative_line_numbers ? (source_row - editor.buffer.rope.point_at(editor.primary.head).row).abs : source_row + 1
|
|
@@ -340,7 +438,7 @@ module Canopus
|
|
|
340
438
|
fill(Zaniah::Bounds.new(left + x, y + @line_height - 2, width, 2), :accent)
|
|
341
439
|
end
|
|
342
440
|
if row.kind == :text && line && @cx.text_system
|
|
343
|
-
spans = code_spans(editor, source_row, row)
|
|
441
|
+
spans = code_spans(editor, source_row, row, foreground_by_row[index] || [])
|
|
344
442
|
@cx.text_system.paint_line(@scene, line, x: left, y: y + line.ascent, color: @theme[:foreground], spans: spans)
|
|
345
443
|
@cx.window.text_runs << [left, y, row.text, @theme[:foreground]]
|
|
346
444
|
elsif row.kind == :overlay_block
|
|
@@ -353,16 +451,21 @@ module Canopus
|
|
|
353
451
|
end
|
|
354
452
|
text(row.text, left, y, color: color)
|
|
355
453
|
end
|
|
356
|
-
|
|
454
|
+
paint_whitespace(editor, map, index, source_row, row, line, left, y, bounds) if row.kind == :text
|
|
357
455
|
paint_overlays&.call
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
456
|
+
paint_indent_guides(row, line, left, y, guides_by_row[index] || [])
|
|
457
|
+
if active
|
|
458
|
+
cursors.each_with_index do |caret, caret_index|
|
|
459
|
+
next unless caret.row == index
|
|
460
|
+
x = left + column_x(row.text, line, caret.column)
|
|
461
|
+
width = vim_cursor && @workspace.vim.mode != :insert ? @font_size * 0.6 : 1.5
|
|
462
|
+
fill(Zaniah::Bounds.new(x, y, width, @line_height - 2), width > 2 ? "#e9a66177" : :cursor) if @cursor_visible || editor.composition
|
|
463
|
+
next unless caret_index == cursors.length - 1
|
|
464
|
+
@cx.window.ime_state = Zaniah::Bounds.new(x, y, 2, @line_height)
|
|
465
|
+
if editor.composition && !editor.composition.text.empty?
|
|
466
|
+
composition = text(editor.composition.text, x, y, color: :accent)
|
|
467
|
+
fill(Zaniah::Bounds.new(x, y + @line_height - 2, composition&.width || 20, 1), :accent)
|
|
468
|
+
end
|
|
366
469
|
end
|
|
367
470
|
end
|
|
368
471
|
end
|
|
@@ -370,10 +473,130 @@ module Canopus
|
|
|
370
473
|
previous_width = @line_widths[editor]
|
|
371
474
|
measured_width = [measured_width, previous_width.last].max if previous_width && previous_width.first == editor.buffer.version
|
|
372
475
|
@line_widths[editor] = [editor.buffer.version, measured_width]
|
|
373
|
-
overview = @workspace.decorations.items_for(editor.buffer, 0...editor.buffer.line_count
|
|
374
|
-
paint_scrollbars(editor, bounds, measured_width, overview
|
|
476
|
+
overview = @workspace.decorations.items_for(editor.buffer, 0...editor.buffer.line_count)
|
|
477
|
+
paint_scrollbars(editor, bounds, measured_width, overview)
|
|
478
|
+
overview
|
|
479
|
+
end
|
|
480
|
+
def paint_sticky_scroll(editor, pane, bounds, symbols)
|
|
481
|
+
fill(bounds, :panel)
|
|
482
|
+
left = bounds.x + gutter(editor) - editor.scroll_x
|
|
483
|
+
generation = @workspace.document_symbol_generation(editor)
|
|
484
|
+
@scene.clip(bounds) do
|
|
485
|
+
symbols.each_with_index do |symbol, index|
|
|
486
|
+
y = bounds.y + index * @line_height
|
|
487
|
+
row = editor.buffer.rope.point_at(symbol.selection.begin).row
|
|
488
|
+
text(row + 1, bounds.x + 12, y + 4, color: :muted, size: @font_size - 1)
|
|
489
|
+
text(symbol.name, left, y + 4, color: :foreground)
|
|
490
|
+
area = Zaniah::Bounds.new(bounds.x, y, bounds.width, @line_height)
|
|
491
|
+
region(area, role: :button, label: "Go to #{symbol.name}",
|
|
492
|
+
action: [:sticky, pane, editor, symbol.selection.begin, editor.buffer.version, generation])
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
fill(Zaniah::Bounds.new(bounds.x, bounds.bottom - 1, bounds.width, 1), :border)
|
|
496
|
+
end
|
|
497
|
+
def paint_breadcrumbs(editor, pane, bounds, context)
|
|
498
|
+
fill(bounds, :panel)
|
|
499
|
+
items = context[:items].first(3)
|
|
500
|
+
slot = bounds.width / items.length
|
|
501
|
+
@scene.clip(bounds) do
|
|
502
|
+
items.each_with_index do |item, index|
|
|
503
|
+
area = Zaniah::Bounds.new(bounds.x + slot * index, bounds.y, slot, bounds.height)
|
|
504
|
+
inset = index.zero? ? 8 : 20
|
|
505
|
+
text("›", area.x + 5, area.y + 3, color: :muted, size: 12) unless index.zero?
|
|
506
|
+
label = clip_breadcrumb(item[:label], [area.width - inset - 6, 0].max, 12)
|
|
507
|
+
next if label.empty?
|
|
508
|
+
|
|
509
|
+
text(label, area.x + inset, area.y + 3, color: :foreground, size: 12)
|
|
510
|
+
region(area, role: :button, label: "Choose siblings for #{item[:label]}",
|
|
511
|
+
action: [:breadcrumb, pane, editor, item, context])
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
fill(Zaniah::Bounds.new(bounds.x, bounds.bottom - 1, bounds.width, 1), :border)
|
|
375
515
|
end
|
|
376
|
-
def
|
|
516
|
+
def paint_minimap(editor, pane, bounds, overview, settings)
|
|
517
|
+
fill(bounds, :panel)
|
|
518
|
+
@minimap_bounds[editor] = {pane: pane, bounds: bounds}.freeze
|
|
519
|
+
rows = minimap_rows(editor.buffer.line_count, bounds.height)
|
|
520
|
+
@scene.clip(bounds) do
|
|
521
|
+
rows.each do |row|
|
|
522
|
+
texture = @workspace.minimap.texture(editor.buffer, row, width: bounds.width.to_i)
|
|
523
|
+
next unless texture
|
|
524
|
+
|
|
525
|
+
y = bounds.y + bounds.height * row / [editor.buffer.line_count, 1].max
|
|
526
|
+
@scene.sprite(bounds.x, y, bounds.width, 2, texture: texture, color: @theme[:muted])
|
|
527
|
+
end
|
|
528
|
+
paint_minimap_markers(editor, bounds, overview, settings)
|
|
529
|
+
paint_minimap_viewport(editor, bounds)
|
|
530
|
+
end
|
|
531
|
+
region(bounds, role: :scrollbar, label: "Document minimap", action: [:minimap, pane, editor])
|
|
532
|
+
fill(Zaniah::Bounds.new(bounds.x, bounds.y, 1, bounds.height), :border)
|
|
533
|
+
@cx.window.request_frame if @workspace.minimap.pending?
|
|
534
|
+
end
|
|
535
|
+
def minimap_rows(line_count, height)
|
|
536
|
+
count = [[(height / 2).floor, line_count, Minimap::ENTRY_LIMIT].min, 1].max
|
|
537
|
+
return [0] if count == 1
|
|
538
|
+
|
|
539
|
+
count.times.map { |index| index * (line_count - 1) / (count - 1) }.uniq
|
|
540
|
+
end
|
|
541
|
+
def paint_minimap_markers(editor, bounds, overview, settings)
|
|
542
|
+
pixels = {}
|
|
543
|
+
overview.each_with_index do |item, index|
|
|
544
|
+
break if index >= 10_000
|
|
545
|
+
if item.source == :git && item.row
|
|
546
|
+
style = item.style.is_a?(Hash) ? item.style : {color: item.style, rows: 1}
|
|
547
|
+
minimap_marker(pixels, bounds, editor.buffer.line_count, item.row, style.fetch(:rows, 1), style.fetch(:color, :accent))
|
|
548
|
+
elsif settings["show_diagnostics"] && item.source == :diagnostics && item.range
|
|
549
|
+
row = editor.buffer.rope.point_at(item.range.begin).row
|
|
550
|
+
style = item.style.is_a?(Hash) ? item.style : {color: item.style}
|
|
551
|
+
minimap_marker(pixels, bounds, editor.buffer.line_count, row, 1, style.fetch(:color, :error))
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
@workspace.minimap.search_rows(editor.buffer, editor.buffer.version).each do |row|
|
|
555
|
+
minimap_marker(pixels, bounds, editor.buffer.line_count, row, 1, :accent)
|
|
556
|
+
end
|
|
557
|
+
pixels.each { |y, color| fill(Zaniah::Bounds.new(bounds.right - 5, y, 5, 1), color) }
|
|
558
|
+
end
|
|
559
|
+
def minimap_marker(pixels, bounds, line_count, row, count, color)
|
|
560
|
+
first = (bounds.y + bounds.height * row / [line_count, 1].max).floor
|
|
561
|
+
last = (bounds.y + bounds.height * (row + count) / [line_count, 1].max).ceil - 1
|
|
562
|
+
first.clamp(bounds.y.ceil, bounds.bottom.ceil - 1).upto(last.clamp(bounds.y.ceil, bounds.bottom.ceil - 1)) do |y|
|
|
563
|
+
pixels[y] = color
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
def paint_minimap_viewport(editor, bounds)
|
|
567
|
+
map = editor.display_map
|
|
568
|
+
first = editor.scroll_y.floor.clamp(0, map.row_count - 1)
|
|
569
|
+
last = [first + editor.viewport_rows - 1, map.row_count - 1].min
|
|
570
|
+
top = bounds.y + bounds.height * map.source_row(first) / [editor.buffer.line_count, 1].max
|
|
571
|
+
bottom = bounds.y + bounds.height * (map.source_row(last) + 1) / [editor.buffer.line_count, 1].max
|
|
572
|
+
height = [bottom - top, 8].max.clamp(0, bounds.bottom - top)
|
|
573
|
+
@scene.quad(bounds.x, top, bounds.width, height, color: "#ffffff12", border_width: 1, border_color: @theme[:accent])
|
|
574
|
+
end
|
|
575
|
+
def clip_breadcrumb(value, maximum, size)
|
|
576
|
+
source = value.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace).scrub
|
|
577
|
+
return "" unless maximum.positive?
|
|
578
|
+
|
|
579
|
+
layout = @cx.text_system&.layout_line(source, size: size)
|
|
580
|
+
width = layout&.width || Zaniah::Unicode.width(source) * size * 0.6
|
|
581
|
+
return source if width <= maximum
|
|
582
|
+
|
|
583
|
+
ellipsis = "…"
|
|
584
|
+
ellipsis_width = @cx.text_system&.layout_line(ellipsis, size: size)&.width || size * 0.6
|
|
585
|
+
return "" if ellipsis_width > maximum
|
|
586
|
+
|
|
587
|
+
shown = +""
|
|
588
|
+
bytes = 0
|
|
589
|
+
shown_width = 0
|
|
590
|
+
source.each_grapheme_cluster do |grapheme|
|
|
591
|
+
bytes += grapheme.bytesize
|
|
592
|
+
grapheme_width = layout ? layout.x_for_index(bytes) : shown_width + Zaniah::Unicode.width(grapheme) * size * 0.6
|
|
593
|
+
break if grapheme_width + ellipsis_width > maximum
|
|
594
|
+
shown << grapheme
|
|
595
|
+
shown_width = grapheme_width
|
|
596
|
+
end
|
|
597
|
+
shown << ellipsis
|
|
598
|
+
end
|
|
599
|
+
def paint_scrollbars(editor, bounds, content_width, decorations)
|
|
377
600
|
track = Zaniah::Bounds.new(bounds.right - 9, bounds.y, 9, [bounds.height - 9, 0].max)
|
|
378
601
|
total = editor.display_map.row_count
|
|
379
602
|
maximum = [total - editor.viewport_rows, 0].max
|
|
@@ -381,15 +604,15 @@ module Canopus
|
|
|
381
604
|
height = [24, track.height * editor.viewport_rows / total].max.clamp(0, track.height)
|
|
382
605
|
thumb = Zaniah::Bounds.new(track.x + 2, track.y + (track.height - height) * editor.scroll_y / maximum, 5, height)
|
|
383
606
|
fill(thumb, :muted)
|
|
384
|
-
|
|
607
|
+
decorations.select { |item| item.kind == :gutter }.first(500).each do |item|
|
|
385
608
|
y = track.y + track.height * item.row / [editor.buffer.line_count, 1].max
|
|
386
609
|
fill(Zaniah::Bounds.new(track.x, y, 3, 2), :accent)
|
|
387
610
|
end
|
|
388
|
-
|
|
389
|
-
row =
|
|
390
|
-
|
|
611
|
+
decorations.select { |item| item.source == :diagnostics && item.kind == :highlight }.first(500).each do |item|
|
|
612
|
+
row = editor.buffer.rope.point_at(item.range.begin).row
|
|
613
|
+
style = item.style.is_a?(Hash) ? item.style : {}
|
|
391
614
|
y = track.y + track.height * row / [editor.buffer.line_count, 1].max
|
|
392
|
-
fill(Zaniah::Bounds.new(track.x + 5, y, 4, 2), :error)
|
|
615
|
+
fill(Zaniah::Bounds.new(track.x + 5, y, 4, 2), style.fetch(:color, :error))
|
|
393
616
|
end
|
|
394
617
|
region(track, role: :scrollbar, label: "Vertical scroll", action: [:scrollbar, editor, :vertical, track, thumb, maximum])
|
|
395
618
|
end
|
|
@@ -405,6 +628,61 @@ module Canopus
|
|
|
405
628
|
def column_x(value, line, column)
|
|
406
629
|
line ? line.x_for_index(value[0, column].to_s.bytesize) : column * @font_size * 0.6
|
|
407
630
|
end
|
|
631
|
+
def paint_whitespace(editor, map, index, source_row, row, line, left, y, bounds)
|
|
632
|
+
settings = @workspace.settings
|
|
633
|
+
language = settings["languages"].fetch(editor.language_document.definition.name, {})
|
|
634
|
+
mode = language.fetch("render_whitespace", settings["render_whitespace"])
|
|
635
|
+
ideographic = language.fetch("render_ideographic_space", settings["render_ideographic_space"])
|
|
636
|
+
return if mode == "none" && !ideographic
|
|
637
|
+
|
|
638
|
+
rope = editor.buffer.rope
|
|
639
|
+
source_lines = {source_row => [rope.line_start(source_row), rope.line(source_row)]}
|
|
640
|
+
byte = column = cells = 0
|
|
641
|
+
row.text.each_grapheme_cluster do |grapheme|
|
|
642
|
+
if grapheme == " " || grapheme == " "
|
|
643
|
+
offset = map.to_buffer(DisplayPoint.new(index, column))
|
|
644
|
+
point = rope.point_at(offset)
|
|
645
|
+
start, source = source_lines[point.row] ||= [rope.line_start(point.row), rope.line(point.row)]
|
|
646
|
+
local = offset - start
|
|
647
|
+
character = source.byteslice(local..)&.each_char&.first
|
|
648
|
+
marker = whitespace_marker(character, mode, ideographic, source, local, offset, editor.selections)
|
|
649
|
+
if marker && (character != "\t" || whitespace_origin?(map, index, column, offset))
|
|
650
|
+
x = line ? line.x_for_index(byte) : cells * @font_size * 0.6
|
|
651
|
+
right = line ? line.x_for_index(byte + grapheme.bytesize) : x + Zaniah::Unicode.width(grapheme) * @font_size * 0.6
|
|
652
|
+
text(marker, left + x, y, color: :muted) if left + right > bounds.x + gutter(editor) && left + x < bounds.right
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
byte += grapheme.bytesize
|
|
656
|
+
column += grapheme.length
|
|
657
|
+
cells += grapheme.ascii_only? ? grapheme.length : Zaniah::Unicode.width(grapheme)
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
def whitespace_origin?(map, row, column, offset)
|
|
661
|
+
point = map.to_display(offset)
|
|
662
|
+
point == DisplayPoint.new(row, column) || column.zero? && point.row + 1 == row &&
|
|
663
|
+
point.column == map.row(point.row).text.length
|
|
664
|
+
end
|
|
665
|
+
def whitespace_marker(character, mode, ideographic, source, local, offset, selections)
|
|
666
|
+
return "□" if character == " " && ideographic
|
|
667
|
+
return unless character == " " || character == "\t"
|
|
668
|
+
return unless mode == "all" ||
|
|
669
|
+
mode == "boundary" && (character == "\t" || whitespace_boundary?(source, local)) ||
|
|
670
|
+
mode == "selection" && selections.any? { |selection| !selection.empty? && selection.start < offset + character.bytesize && offset < selection.end }
|
|
671
|
+
|
|
672
|
+
character == "\t" ? "→" : "·"
|
|
673
|
+
end
|
|
674
|
+
def whitespace_boundary?(source, offset)
|
|
675
|
+
following = offset + 1
|
|
676
|
+
offset.zero? || following == source.bytesize || whitespace_before?(source, offset) || whitespace_at?(source, following)
|
|
677
|
+
end
|
|
678
|
+
def whitespace_before?(source, offset)
|
|
679
|
+
byte = source.getbyte(offset - 1)
|
|
680
|
+
byte == 32 || byte == 9 || source.byteslice(offset - 3, 3) == " "
|
|
681
|
+
end
|
|
682
|
+
def whitespace_at?(source, offset)
|
|
683
|
+
byte = source.getbyte(offset)
|
|
684
|
+
byte == 32 || byte == 9 || source.byteslice(offset, 3) == " "
|
|
685
|
+
end
|
|
408
686
|
def decorations_for_display_rows(editor, map, first, last)
|
|
409
687
|
rows = (first...last).flat_map do |index|
|
|
410
688
|
row = map.row(index)
|
|
@@ -487,23 +765,33 @@ module Canopus
|
|
|
487
765
|
Zaniah::Text.new(ui_text(item.content), size: [@font_size - 2, 1].max, color: color)
|
|
488
766
|
end
|
|
489
767
|
element = Zaniah::Div.new.w(width).h(height).items_center.child(content)
|
|
768
|
+
element.style(padding: [0, style.fetch(:padding_right, 0), 0, style.fetch(:padding_left, 0)])
|
|
490
769
|
element.bg(style[:background]) if style[:background]
|
|
491
770
|
element
|
|
492
771
|
end
|
|
493
772
|
def register_overlay_region(editor, item, bounds)
|
|
494
773
|
return unless item.on_click
|
|
495
774
|
offset = item.range&.begin || editor.buffer.rope.line_start(item.row)
|
|
775
|
+
action = item.on_click.respond_to?(:right_click) ? :context_decoration : :decoration
|
|
496
776
|
region(bounds, role: :button, label: item.content.to_s,
|
|
497
|
-
action: [
|
|
777
|
+
action: [action, item.on_click, editor, offset])
|
|
498
778
|
end
|
|
499
|
-
def code_spans(editor, source_row, row)
|
|
779
|
+
def code_spans(editor, source_row, row, highlight_items = [])
|
|
500
780
|
return [] if editor.buffer.rope.respond_to?(:lazy?) && editor.buffer.rope.lazy?
|
|
501
781
|
raw = editor.language_document.tokens_for(source_row)
|
|
502
782
|
semantic_state = @workspace.semantic_styles&.[](editor.buffer)
|
|
503
783
|
semantic_state = nil unless semantic_state && semantic_state.first == editor.buffer.version
|
|
784
|
+
base = editor.buffer.rope.line_start(source_row)
|
|
785
|
+
foreground = highlight_items.each_with_object({}) do |item, colors|
|
|
786
|
+
style = item.style.is_a?(Hash) ? item.style : nil
|
|
787
|
+
next unless item.range && style&.[](:foreground)
|
|
788
|
+
next unless item.range.begin.between?(base + row.offsets.first, base + row.offsets.last)
|
|
789
|
+
colors[item.range.begin - base] = @theme[style.fetch(:color)]
|
|
790
|
+
end
|
|
504
791
|
cache = @code_caches[editor] ||= {}
|
|
505
792
|
cached = cache[row.object_id]
|
|
506
|
-
return cached[
|
|
793
|
+
return cached[4] if cached && cached[5].equal?(row) && cached[0] == raw && cached[1].equal?(@theme) &&
|
|
794
|
+
cached[2].equal?(semantic_state) && cached[3] == foreground
|
|
507
795
|
offset = 0
|
|
508
796
|
tokens = raw.map do |token, value|
|
|
509
797
|
start = offset
|
|
@@ -519,6 +807,7 @@ module Canopus
|
|
|
519
807
|
color = tokens[index] && original >= tokens[index][0] ? tokens[index][2] : @theme[:foreground]
|
|
520
808
|
overlay = semantic.find { |first, last, _| original >= first && original < last }
|
|
521
809
|
color = overlay[2] if overlay
|
|
810
|
+
color = foreground[original] if foreground.key?(original)
|
|
522
811
|
if spans.last && spans.last[2] == color
|
|
523
812
|
spans.last[1] += length
|
|
524
813
|
else
|
|
@@ -528,27 +817,9 @@ module Canopus
|
|
|
528
817
|
end
|
|
529
818
|
cache.shift if cache.length >= 512
|
|
530
819
|
spans.each(&:freeze).freeze
|
|
531
|
-
cache[row.object_id] = [raw, @theme, semantic_state, spans, row]
|
|
820
|
+
cache[row.object_id] = [raw, @theme, semantic_state, foreground, spans, row]
|
|
532
821
|
spans
|
|
533
822
|
end
|
|
534
|
-
def paint_diagnostics(editor, index, row, line, left, y)
|
|
535
|
-
frame_diagnostics(editor.buffer).each do |diagnostic|
|
|
536
|
-
range = diagnostic["range"]
|
|
537
|
-
first = editor.display_map.to_display(LSP::Protocol.offset(editor.buffer.rope, range.fetch("start")))
|
|
538
|
-
last = editor.display_map.to_display(LSP::Protocol.offset(editor.buffer.rope, range.fetch("end")))
|
|
539
|
-
next unless index.between?(first.row, last.row)
|
|
540
|
-
from = index == first.row ? first.column : 0
|
|
541
|
-
to = index == last.row ? last.column : row.text.length
|
|
542
|
-
x = column_x(row.text, line, from)
|
|
543
|
-
width = [column_x(row.text, line, to) - x, 6].max
|
|
544
|
-
@scene.underline(left + x, y + @line_height - 2, width, color: @theme[:error], wave: true)
|
|
545
|
-
rescue RangeError, KeyError
|
|
546
|
-
next # Stale server positions must not break a frame after a local edit.
|
|
547
|
-
end
|
|
548
|
-
end
|
|
549
|
-
def frame_diagnostics(buffer)
|
|
550
|
-
@frame_diagnostics[buffer] ||= @workspace.diagnostics_for(buffer)
|
|
551
|
-
end
|
|
552
823
|
def paint_highlights(editor, index, row, line, left, y, items)
|
|
553
824
|
items.each do |item|
|
|
554
825
|
range = item.range
|
|
@@ -561,7 +832,9 @@ module Canopus
|
|
|
561
832
|
from = row.offsets.bsearch_index { |offset| offset >= first } || row.text.length
|
|
562
833
|
to = row.offsets.bsearch_index { |offset| offset >= last } || row.text.length
|
|
563
834
|
x = column_x(row.text, line, from)
|
|
564
|
-
|
|
835
|
+
width = column_x(row.text, line, to) - x
|
|
836
|
+
paint_highlight(item, left + x, y, width)
|
|
837
|
+
register_highlight_region(editor, item, left + x, y, width)
|
|
565
838
|
next
|
|
566
839
|
end
|
|
567
840
|
span = highlight_span(editor, item, index)
|
|
@@ -575,8 +848,39 @@ module Canopus
|
|
|
575
848
|
before && right > before[0] && right <= before[1] ? 0 : 3,
|
|
576
849
|
after && right > after[0] && right <= after[1] ? 0 : 3,
|
|
577
850
|
after && x >= after[0] && x < after[1] ? 0 : 3]
|
|
578
|
-
|
|
579
|
-
|
|
851
|
+
paint_highlight(item, left + x, y, right - x, radii: radii)
|
|
852
|
+
register_highlight_region(editor, item, left + x, y, right - x)
|
|
853
|
+
end
|
|
854
|
+
end
|
|
855
|
+
def register_highlight_region(editor, item, x, y, width)
|
|
856
|
+
return unless item.on_click && width.positive?
|
|
857
|
+
|
|
858
|
+
region(Zaniah::Bounds.new(x, y - 1, width, @line_height), role: :link, label: item.content.to_s,
|
|
859
|
+
action: [:decoration, item.on_click, editor, item.range.begin])
|
|
860
|
+
end
|
|
861
|
+
def paint_indent_guides(row, line, left, y, items)
|
|
862
|
+
items.each do |item, point|
|
|
863
|
+
style = item.style
|
|
864
|
+
x = column_x(row.text, line, point.column)
|
|
865
|
+
color = style.fetch(:color, :muted)
|
|
866
|
+
color = @theme[color] if color.is_a?(Symbol)
|
|
867
|
+
if @cx.window.is_a?(Zaniah::Platform::TUI::Window)
|
|
868
|
+
column = [point.column - 1, 0].max
|
|
869
|
+
text("│", left + column_x(row.text, line, column), y, color: color)
|
|
870
|
+
else
|
|
871
|
+
@scene.quad(left + x, y - 1, style[:active] ? 2 : 1, @line_height, color: color)
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
end
|
|
875
|
+
def paint_highlight(item, x, y, width, radii: 0)
|
|
876
|
+
style = item.style.is_a?(Hash) ? item.style : {}
|
|
877
|
+
color = style.fetch(:color, item.style.is_a?(Symbol) ? item.style : :selection)
|
|
878
|
+
color = @theme[color] if color.is_a?(Symbol)
|
|
879
|
+
if style[:underline]
|
|
880
|
+
@scene.underline(x, y + @line_height - 2, [width, 6].max, color: color,
|
|
881
|
+
thickness: style.fetch(:thickness, 1), wave: style[:underline] == :wave)
|
|
882
|
+
else
|
|
883
|
+
@scene.quad(x, y - 1, width, @line_height, color: color, radius: radii)
|
|
580
884
|
end
|
|
581
885
|
end
|
|
582
886
|
def highlight_span(editor, item, index)
|
|
@@ -592,11 +896,16 @@ module Canopus
|
|
|
592
896
|
x = column_x(row.text, line, from)
|
|
593
897
|
right = column_x(row.text, line, [to, row.text.length].min)
|
|
594
898
|
right += @font_size * 0.6 if to > row.text.length
|
|
899
|
+
minimum = item.style.is_a?(Hash) && item.style[:underline] && (!item.on_click || to > from) ? 6 : 0
|
|
900
|
+
right = [right, x + minimum].max
|
|
595
901
|
cache[index] = right > x ? [x, right] : nil
|
|
596
902
|
end
|
|
597
903
|
def paint_terminal(bounds)
|
|
598
904
|
render_terminal(bounds)
|
|
599
905
|
end
|
|
906
|
+
def paint_task_output(bounds)
|
|
907
|
+
render_task_output(bounds)
|
|
908
|
+
end
|
|
600
909
|
def paint_status(bounds)
|
|
601
910
|
fill(bounds, :status)
|
|
602
911
|
editor = @workspace.editor
|
|
@@ -607,13 +916,20 @@ module Canopus
|
|
|
607
916
|
branch = @workspace.git&.branch
|
|
608
917
|
left = "#{branch} #{left}" if branch
|
|
609
918
|
left = ":#{@workspace.vim.command_line}" if @workspace.settings["vim_mode"] && @workspace.vim.command_line
|
|
610
|
-
|
|
919
|
+
diagnostic_counts = @workspace.diagnostics.counts
|
|
920
|
+
diagnostics = "#{diagnostic_counts[:error].zero? ? '' : "E#{diagnostic_counts[:error]} "}" \
|
|
921
|
+
"#{diagnostic_counts[:warning].zero? ? '' : "W#{diagnostic_counts[:warning]} "}"
|
|
611
922
|
language = editor.language_document.definition.name
|
|
612
|
-
|
|
613
|
-
|
|
923
|
+
servers = @workspace.language_server_states(language)
|
|
924
|
+
lsp = servers.empty? ? "" : " LSP:#{servers.map { |server| "#{server[:name]}=#{server[:state]}" }.join(',')}"
|
|
925
|
+
newline = editor.buffer.mixed_line_endings? ? "mixed" : editor.buffer.newline.to_s.upcase
|
|
926
|
+
right = "#{diagnostics}#{language}#{lsp} #{point.row + 1}:#{point.column + 1} #{editor.use_tabs ? 'Tab' : 'Spaces'}:#{editor.tab_size} #{editor.buffer.encoding.name} #{newline}"
|
|
614
927
|
@scene.clip(bounds) do
|
|
615
928
|
text(left, 12, bounds.y + 6, color: :foreground, size: 12)
|
|
616
|
-
|
|
929
|
+
x = [bounds.width - right.length * 7.2 - 16, bounds.width * 0.5].max
|
|
930
|
+
text(right, x, bounds.y + 6, color: :muted, size: 12)
|
|
931
|
+
region(Zaniah::Bounds.new(x, bounds.y, bounds.width - x, bounds.height), role: :button,
|
|
932
|
+
label: "Encoding #{editor.buffer.encoding.name}, line endings #{newline}", action: [:buffer_format, editor.buffer])
|
|
617
933
|
end
|
|
618
934
|
end
|
|
619
935
|
def paint_hover(bounds)
|
|
@@ -737,7 +1053,11 @@ module Canopus
|
|
|
737
1053
|
fill(bounds, "#0007")
|
|
738
1054
|
@scene.shadow(box.x, box.y, box.width, box.height, color: "#0008", blur: 14)
|
|
739
1055
|
@scene.quad(box.x, box.y, box.width, box.height, color: @theme[:panel], radius: 8, border_width: 1, border_color: @theme[:border])
|
|
740
|
-
labels = {commands: "Command palette", files: "Open file", search: "Find in buffer", save_as: "Save as",
|
|
1056
|
+
labels = {commands: "Command palette", files: "Open file", search: "Find in buffer", save_as: "Save as",
|
|
1057
|
+
confirm_close: "Unsaved changes", breakpoint_actions: "Breakpoint", breakpoint_edit: "Breakpoint value",
|
|
1058
|
+
git_file_history: "File history", git_commit_history: "Commit history", git_commit_paths: "Changed files",
|
|
1059
|
+
git_revision_compare: "Compare revisions (FROM..TO)", git_remotes: "Git remote",
|
|
1060
|
+
git_credentials: palette[:stage] == :username ? "Git username (blank for token)" : "Git credential"}
|
|
741
1061
|
title = labels.fetch(palette[:kind], palette[:kind].to_s)
|
|
742
1062
|
if palette[:search_options]
|
|
743
1063
|
title += " Ctrl+Alt " + {regexp: "R:regex", case_sensitive: "C:case", whole_word: "W:word", selection_only: "S:selection"}.filter_map do |key, label|
|
|
@@ -745,7 +1065,8 @@ module Canopus
|
|
|
745
1065
|
end.join(" ")
|
|
746
1066
|
end
|
|
747
1067
|
@scene.clip(box) { text(title, box.x + 16, box.y + 12, color: :muted, size: palette[:search_options] ? 10 : 12) }
|
|
748
|
-
|
|
1068
|
+
query = palette_query_text(palette)
|
|
1069
|
+
text(query.empty? ? "Type here…" : query, box.x + 16, box.y + 36, color: query.empty? ? :muted : :foreground)
|
|
749
1070
|
@scene.clip(box) do
|
|
750
1071
|
matches.each_with_index do |match, index|
|
|
751
1072
|
row = Zaniah::Bounds.new(box.x + 6, box.y + 74 + index * 28, list_width - 12, 28)
|
|
@@ -774,6 +1095,10 @@ module Canopus
|
|
|
774
1095
|
end
|
|
775
1096
|
end
|
|
776
1097
|
end
|
|
1098
|
+
|
|
1099
|
+
def palette_query_text(palette)
|
|
1100
|
+
palette[:secret] && !palette[:query].empty? ? "•" * [palette[:query].length, 64].min : palette[:query]
|
|
1101
|
+
end
|
|
777
1102
|
end
|
|
778
1103
|
end
|
|
779
1104
|
|