canopus 0.1.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.
Files changed (146) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +148 -0
  5. data/docs/adr/001-runtime-gem-components.md +24 -0
  6. data/docs/adr/002-bounded-diff-engine.md +22 -0
  7. data/docs/adr/003-background-language-analysis.md +22 -0
  8. data/docs/adr/004-persistent-display-map.md +23 -0
  9. data/docs/adr/005-windows-runtime.md +27 -0
  10. data/docs/adr/README.md +36 -0
  11. data/docs/distribution.md +27 -0
  12. data/docs/lsp.md +38 -0
  13. data/docs/performance.md +40 -0
  14. data/docs/snippets.md +32 -0
  15. data/docs/vim.md +24 -0
  16. data/docs/workspace_edits.md +32 -0
  17. data/examples/native_smoke.rb +25 -0
  18. data/examples/plugins/word_count.rb +11 -0
  19. data/exe/canopus +19 -0
  20. data/lib/canopus/block_map.rb +19 -0
  21. data/lib/canopus/buffer.rb +293 -0
  22. data/lib/canopus/cli.rb +171 -0
  23. data/lib/canopus/controller.rb +507 -0
  24. data/lib/canopus/data_compat.rb +25 -0
  25. data/lib/canopus/display_map/line_builder.rb +54 -0
  26. data/lib/canopus/display_map/line_set.rb +8 -0
  27. data/lib/canopus/display_map/pending_line_set.rb +9 -0
  28. data/lib/canopus/display_map/summary.rb +23 -0
  29. data/lib/canopus/display_map/worker.rb +89 -0
  30. data/lib/canopus/display_map.rb +350 -0
  31. data/lib/canopus/display_point.rb +5 -0
  32. data/lib/canopus/editor/snippet_expandable.rb +281 -0
  33. data/lib/canopus/editor.rb +445 -0
  34. data/lib/canopus/error.rb +5 -0
  35. data/lib/canopus/fold_map.rb +87 -0
  36. data/lib/canopus/git/blame.rb +50 -0
  37. data/lib/canopus/git/commit.rb +7 -0
  38. data/lib/canopus/git/corrupt_object.rb +7 -0
  39. data/lib/canopus/git/diff.rb +131 -0
  40. data/lib/canopus/git/index.rb +94 -0
  41. data/lib/canopus/git/object_database.rb +45 -0
  42. data/lib/canopus/git/pack.rb +186 -0
  43. data/lib/canopus/git/repository.rb +313 -0
  44. data/lib/canopus/git/status.rb +74 -0
  45. data/lib/canopus/git/tree_entry.rb +7 -0
  46. data/lib/canopus/git.rb +15 -0
  47. data/lib/canopus/icon_theme.rb +43 -0
  48. data/lib/canopus/language/background_analysis/job.rb +12 -0
  49. data/lib/canopus/language/background_analysis/scheduler.rb +70 -0
  50. data/lib/canopus/language/background_analysis.rb +280 -0
  51. data/lib/canopus/language/definition.rb +7 -0
  52. data/lib/canopus/language/document.rb +143 -0
  53. data/lib/canopus/language/symbol.rb +7 -0
  54. data/lib/canopus/language/syntax_worker.rb +91 -0
  55. data/lib/canopus/language.rb +42 -0
  56. data/lib/canopus/lazy_rope.rb +218 -0
  57. data/lib/canopus/lsp/client.rb +299 -0
  58. data/lib/canopus/lsp/error.rb +7 -0
  59. data/lib/canopus/lsp/future/subscription.rb +9 -0
  60. data/lib/canopus/lsp/future.rb +87 -0
  61. data/lib/canopus/lsp/protocol.rb +83 -0
  62. data/lib/canopus/lsp/server_error.rb +13 -0
  63. data/lib/canopus/lsp/timeout.rb +7 -0
  64. data/lib/canopus/lsp/transport.rb +123 -0
  65. data/lib/canopus/lsp.rb +19 -0
  66. data/lib/canopus/markdown.rb +69 -0
  67. data/lib/canopus/match_data_compat.rb +17 -0
  68. data/lib/canopus/multi_buffer.rb +266 -0
  69. data/lib/canopus/pane.rb +61 -0
  70. data/lib/canopus/patch/composite.rb +12 -0
  71. data/lib/canopus/patch/reload.rb +18 -0
  72. data/lib/canopus/patch.rb +27 -0
  73. data/lib/canopus/performance_recorder.rb +207 -0
  74. data/lib/canopus/plugins/api.rb +50 -0
  75. data/lib/canopus/plugins/isolated_runtime.rb +167 -0
  76. data/lib/canopus/plugins/local_runtime.rb +25 -0
  77. data/lib/canopus/plugins/permission_denied.rb +7 -0
  78. data/lib/canopus/plugins/registry.rb +30 -0
  79. data/lib/canopus/plugins.rb +11 -0
  80. data/lib/canopus/project/ignore_matcher.rb +104 -0
  81. data/lib/canopus/project/search.rb +164 -0
  82. data/lib/canopus/project/search_worker/cancelled.rb +3 -0
  83. data/lib/canopus/project/search_worker/runner.rb +9 -0
  84. data/lib/canopus/project/search_worker.rb +108 -0
  85. data/lib/canopus/project/tree.rb +48 -0
  86. data/lib/canopus/project/watcher.rb +59 -0
  87. data/lib/canopus/project.rb +127 -0
  88. data/lib/canopus/regexp_compat.rb +30 -0
  89. data/lib/canopus/save_conflict.rb +5 -0
  90. data/lib/canopus/selection.rb +11 -0
  91. data/lib/canopus/settings.rb +118 -0
  92. data/lib/canopus/snippet/transform.rb +209 -0
  93. data/lib/canopus/snippet.rb +183 -0
  94. data/lib/canopus/tab_map.rb +30 -0
  95. data/lib/canopus/terminal/cell.rb +7 -0
  96. data/lib/canopus/terminal/grid.rb +321 -0
  97. data/lib/canopus/terminal/pty.rb +103 -0
  98. data/lib/canopus/terminal/scrollback.rb +38 -0
  99. data/lib/canopus/terminal/vt.rb +398 -0
  100. data/lib/canopus/terminal.rb +13 -0
  101. data/lib/canopus/theme.rb +43 -0
  102. data/lib/canopus/version.rb +5 -0
  103. data/lib/canopus/vim/commandable.rb +148 -0
  104. data/lib/canopus/vim/motionable.rb +321 -0
  105. data/lib/canopus/vim/operator_capable.rb +377 -0
  106. data/lib/canopus/vim/text_object_selectable.rb +141 -0
  107. data/lib/canopus/vim.rb +426 -0
  108. data/lib/canopus/workspace/edit/executable.rb +139 -0
  109. data/lib/canopus/workspace/edit/node.rb +8 -0
  110. data/lib/canopus/workspace/edit/plan.rb +170 -0
  111. data/lib/canopus/workspace/edit/resource_preparable.rb +98 -0
  112. data/lib/canopus/workspace/edit.rb +6 -0
  113. data/lib/canopus/workspace/file_change_aware.rb +40 -0
  114. data/lib/canopus/workspace/file_previewable.rb +31 -0
  115. data/lib/canopus/workspace/git_aware.rb +140 -0
  116. data/lib/canopus/workspace/language_aware.rb +413 -0
  117. data/lib/canopus/workspace/language_server_configurable.rb +181 -0
  118. data/lib/canopus/workspace/project_searchable.rb +208 -0
  119. data/lib/canopus/workspace/project_tree_editable.rb +82 -0
  120. data/lib/canopus/workspace/session_persistable.rb +153 -0
  121. data/lib/canopus/workspace/settings_aware.rb +91 -0
  122. data/lib/canopus/workspace/view/terminal_presentable.rb +185 -0
  123. data/lib/canopus/workspace/view.rb +643 -0
  124. data/lib/canopus/workspace.rb +525 -0
  125. data/lib/canopus/wrap_map.rb +108 -0
  126. data/lib/canopus.rb +24 -0
  127. data/sig/canopus.rbs +375 -0
  128. data/sig/controller.rbs +55 -0
  129. data/sig/display_stages.rbs +49 -0
  130. data/sig/git.rbs +140 -0
  131. data/sig/lsp.rbs +99 -0
  132. data/sig/markdown.rbs +11 -0
  133. data/sig/performance_recorder.rbs +23 -0
  134. data/sig/search_services.rbs +8 -0
  135. data/sig/services.rbs +81 -0
  136. data/sig/snippet.rbs +42 -0
  137. data/sig/terminal.rbs +125 -0
  138. data/sig/vim.rbs +27 -0
  139. data/sig/workspace_edits.rbs +11 -0
  140. data/sig/workspace_services.rbs +117 -0
  141. data/tools/certify_snippet_regex.rb +35 -0
  142. data/tools/check_dependencies.rb +80 -0
  143. data/tools/native_check.rb +70 -0
  144. data/tools/package.rb +113 -0
  145. data/tools/package_test.rb +32 -0
  146. metadata +314 -0
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Canopus::DisplayMap::Worker
4
+ BATCH_LINES = 32
5
+ QUEUE_LIMIT = 8
6
+ Job = Data.define(:generation, :tree, :builder, :prefix)
7
+ Result = Data.define(:generation, :first, :lines, :error)
8
+ Cancelled = Class.new(StandardError)
9
+ private_constant :Cancelled
10
+ attr_reader :thread, :results
11
+
12
+ def initialize
13
+ @requests, @results = Queue.new, SizedQueue.new(QUEUE_LIMIT)
14
+ @thread = Thread.new { run }
15
+ @thread.name = "canopus-wrap"
16
+ @thread.report_on_exception = false
17
+ end
18
+
19
+ def submit(generation, tree, builder, prefix)
20
+ @generation = generation
21
+ @requests.clear
22
+ @results.clear
23
+ @requests << Job.new(generation, tree, builder, prefix)
24
+ end
25
+
26
+ def close
27
+ return if @closed
28
+ @closed = true
29
+ @generation = nil
30
+ @requests.close
31
+ @results.close
32
+ # Cancellation checkpoints normally finish in milliseconds. No native
33
+ # resource or shared mutable document state belongs to this worker.
34
+ @thread.kill unless @thread.join(1)
35
+ @thread.join(0.1)
36
+ @results.clear
37
+ end
38
+
39
+ private
40
+
41
+ def run
42
+ while (job = @requests.pop)
43
+ yielded_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
44
+ checkpoint = lambda do
45
+ raise Cancelled if @closed || @generation != job.generation
46
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC) - yielded_at >= 0.002
47
+ Thread.pass
48
+ yielded_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
49
+ end
50
+ end
51
+ begin
52
+ first, lines, bytes = nil, [], 0
53
+ flush = lambda do
54
+ unless lines.empty?
55
+ checkpoint.call
56
+ @results << Result.new(job.generation, first, lines.freeze, nil)
57
+ first, lines, bytes = nil, [], 0
58
+ end
59
+ end
60
+ source_rows(job) do |row|
61
+ checkpoint.call
62
+ flush.call if first && row != first + lines.length
63
+ first ||= row
64
+ value = job.builder.line(row, checkpoint: checkpoint)
65
+ lines << value
66
+ bytes += value.rows.sum { |line| line.text.bytesize + line.offsets.length * 8 }
67
+ flush.call if lines.length >= BATCH_LINES || bytes >= 65_536
68
+ end
69
+ flush.call
70
+ rescue Cancelled
71
+ next
72
+ rescue ClosedQueueError
73
+ break if @closed
74
+ rescue StandardError => error
75
+ @results << Result.new(job.generation, 0, [].freeze, error) unless @closed
76
+ ensure
77
+ job = checkpoint = flush = lines = value = nil
78
+ end
79
+ end
80
+ rescue ClosedQueueError
81
+ nil
82
+ end
83
+
84
+ def source_rows(job)
85
+ count = job.tree.prefix_summary(job.prefix).pending_rows
86
+ count.times { |index| yield job.tree.locate(index, :pending_rows)[0] }
87
+ (job.prefix...job.tree.size).each { |row| yield row }
88
+ end
89
+ end
@@ -0,0 +1,350 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "fold_map"
4
+ require_relative "tab_map"
5
+ require_relative "wrap_map"
6
+ require_relative "block_map"
7
+ require_relative "display_point"
8
+
9
+ module Canopus
10
+ class DisplayMap
11
+ BACKGROUND_THRESHOLD = 1 << 20
12
+ Row = Data.define(:text, :offsets, :kind, :metadata)
13
+ attr_reader :fold_map, :tab_map, :wrap_map, :block_map, :tree, :recomputed_lines
14
+ attr_reader :layout_error
15
+
16
+ def initialize(buffer, tab_size: 4, wrap_width: nil, background_threshold: BACKGROUND_THRESHOLD)
17
+ raise ArgumentError, "background threshold must be nonnegative or nil" unless background_threshold.nil? || (background_threshold.is_a?(Integer) && background_threshold >= 0)
18
+ @background_threshold, @generation = background_threshold, 0
19
+ @buffer, @rope = buffer, buffer.rope
20
+ @fold_map, @tab_map, @wrap_map, @block_map = FoldMap.new, TabMap.new(tab_size: tab_size), WrapMap.new(width: wrap_width), BlockMap.new
21
+ rebuild
22
+ @subscription = buffer.on_edit { |patch| apply(patch) }
23
+ end
24
+ def dispose
25
+ return if @disposed
26
+ @disposed = true
27
+ @layout_error = nil
28
+ @subscription.detach
29
+ stop_wrap(@wrap_map, @worker)
30
+ @worker = @pending_result = nil
31
+ nil
32
+ end
33
+ def pending? = !!(!@disposed && !@failed && !@lazy && @background && (@computed_prefix < @tree.size || @tree.summary.pending_rows.positive?))
34
+ def progress
35
+ return [@rope.line_count, @rope.line_count] if @lazy
36
+ [@computed_prefix - @tree.prefix_summary(@computed_prefix).pending_rows, @tree.size]
37
+ end
38
+
39
+ # Call only from the foreground event loop, before painting. Read methods
40
+ # deliberately never install completed work halfway through a frame.
41
+ def poll(max_lines: 256)
42
+ raise ArgumentError, "max_lines must be positive" unless max_lines.is_a?(Integer) && max_lines.positive?
43
+ return false if @disposed || !@worker
44
+ changed, consumed = false, 0
45
+ while consumed < max_lines
46
+ result = @pending_result || @worker.results.pop(true)
47
+ @pending_result = nil
48
+ next unless result.generation == @generation
49
+ if result.error
50
+ @failed = true
51
+ @layout_error = result.error
52
+ @worker.close
53
+ @worker = nil
54
+ raise result.error
55
+ end
56
+ take = [max_lines - consumed, result.lines.length].min
57
+ values = result.lines.take(take)
58
+ replace_values(result.first, take, values)
59
+ @computed_prefix = [@computed_prefix, result.first + take].max if result.first <= @computed_prefix
60
+ take.times { |index| @row_cache.delete(result.first + index) }
61
+ @recomputed_lines += take
62
+ consumed += take
63
+ changed = true
64
+ if take < result.lines.length
65
+ @pending_result = result.with(first: result.first + take, lines: result.lines.drop(take).freeze)
66
+ end
67
+ end
68
+ changed
69
+ rescue ThreadError
70
+ changed
71
+ end
72
+ def row_count = @lazy ? @rope.line_count : @tree.summary.display_rows
73
+ def wrap_width=(width)
74
+ raise Error, "soft wrapping is disabled for large read-only files" if @lazy && width
75
+ return if width == @wrap_map.width && !@wrap_map.font
76
+ replace_wrap(WrapMap.new(width: width))
77
+ end
78
+ def wrap_pixels(width, font: nil, font_size:, font_paths: nil, typesetter: nil)
79
+ raise Error, "soft wrapping is disabled for large read-only files" if @lazy
80
+ current = @wrap_map
81
+ return if current.width == width && current.font.equal?(font || typesetter&.font) && current.font_size == font_size && current.font_paths == font_paths && current.typesetter.equal?(typesetter)
82
+ replace_wrap(WrapMap.new(width: width, font: font, font_size: font_size, font_paths: font_paths, typesetter: typesetter))
83
+ end
84
+ def tab_size=(size)
85
+ return if size == @tab_map.tab_size
86
+ @tab_map = TabMap.new(tab_size: size)
87
+ rebuild(reuse: true)
88
+ end
89
+ def fold(range)
90
+ raise Error, "folding is disabled for large read-only files" if @lazy
91
+ range = range.begin...(range.end + (range.exclude_end? ? 0 : 1))
92
+ @rope.point_at(range.begin)
93
+ @rope.point_at(range.end)
94
+ first, last = affected_lines(@rope.point_at(range.begin).row, @rope.point_at(range.end).row)
95
+ @fold_map.fold(range)
96
+ replace_lines(first, last, last)
97
+ end
98
+ def unfold(offset)
99
+ affected = @fold_map.ranges.select { |range| range.cover?(offset) }
100
+ return if affected.empty?
101
+ first = @rope.point_at(affected.map(&:begin).min).row
102
+ last = @rope.point_at(affected.map(&:end).max).row
103
+ first, last = affected_lines(first, last)
104
+ @fold_map.unfold(offset)
105
+ replace_lines(first, last, last)
106
+ end
107
+ def insert_block(id, row:, text:, kind: :diagnostic)
108
+ raise Error, "inline blocks are disabled for large read-only files" if @lazy
109
+ raise RangeError, "block row outside buffer" unless row.between?(0, @rope.line_count - 1)
110
+ previous = @block_map.blocks[id]
111
+ @block_map.insert(id, row: row, text: text, kind: kind)
112
+ replace_lines(previous.row, previous.row, previous.row) if previous && previous.row != row
113
+ replace_lines(row, row, row)
114
+ end
115
+ def remove_block(id)
116
+ block = @block_map.remove(id)
117
+ replace_lines(block.row, block.row, block.row) if block
118
+ end
119
+ def row(index)
120
+ if @lazy
121
+ raise RangeError, "display row outside buffer" unless index.between?(0, row_count - 1)
122
+ @row_cache.shift if @row_cache.length >= 256
123
+ return @row_cache[index] ||= build_lazy_row(index)
124
+ end
125
+ source, _lines, prefix = locate(index)
126
+ lines = display_lines(source)
127
+ lines.rows[index - prefix.display_rows]
128
+ end
129
+ def source_row(index) = @lazy ? index : locate(index)[0]
130
+ def each_row(range = 0...row_count)
131
+ return enum_for(__method__, range) unless block_given?
132
+ range.each { |i| yield row(i), i if i.between?(0, row_count - 1) }
133
+ end
134
+ def to_buffer(point, bias: :left)
135
+ if @lazy
136
+ source = point.row.clamp(0, row_count - 1)
137
+ line = row(source)
138
+ return @rope.line_start(source) + line.offsets[point.column.clamp(0, line.offsets.length - 1)]
139
+ end
140
+ row = point.row.clamp(0, row_count - 1)
141
+ source, _lines, prefix = locate(row)
142
+ lines = display_lines(source)
143
+ line = lines.rows[row - prefix.display_rows]
144
+ column = point.column.clamp(0, line.offsets.length - 1)
145
+ local = line.offsets[column]
146
+ @rope.line_start(source) + local
147
+ end
148
+ def to_display(offset, bias: :left)
149
+ if @lazy
150
+ point = @rope.point_at(offset)
151
+ local = offset - @rope.line_start(point.row)
152
+ line = row(point.row)
153
+ unless local.between?(line.offsets.first, line.offsets.last)
154
+ line = @row_cache[point.row] = build_lazy_row(point.row, from: [local - 8192, 0].max)
155
+ end
156
+ column = line.offsets.bsearch_index { |position| position >= local } || line.text.length
157
+ return DisplayPoint.new(point.row, column)
158
+ end
159
+ source = @rope.point_at(offset).row
160
+ loop do
161
+ start = @rope.line_start(source)
162
+ folded = @fold_map.ranges.find { |range| range.begin < start && range.end >= start }
163
+ break unless folded
164
+ source = @rope.point_at(folded.begin).row
165
+ end
166
+ local = offset - @rope.line_start(source)
167
+ lines = display_lines(source, local: local).rows
168
+ prefix = @tree.prefix_summary(source).display_rows
169
+ lines.each_with_index do |line, i|
170
+ next unless line.kind == :text
171
+ next if local > line.offsets.last
172
+ column = line.offsets.bsearch_index { |n| bias == :left ? n >= local : n > local }
173
+ column = column ? (bias == :right ? [column - 1, 0].max : column) : line.offsets.length - 1
174
+ return DisplayPoint.new(prefix + i, column)
175
+ end
176
+ DisplayPoint.new(prefix + lines.length - 1, lines.last&.text&.length || 0)
177
+ end
178
+
179
+ private
180
+ def replace_wrap(wrap)
181
+ # A copy may own native resources. Cancel/join before closing its providers,
182
+ # then transfer the new immutable typography snapshot to the next worker.
183
+ if @wrap_map.font
184
+ stop_wrap(@wrap_map, @worker)
185
+ @worker = @pending_result = nil
186
+ else
187
+ @wrap_map.close
188
+ end
189
+ @wrap_map = wrap
190
+ rebuild(reuse: true)
191
+ end
192
+
193
+ def stop_wrap(wrap, worker)
194
+ worker&.close
195
+ if worker&.thread&.alive?
196
+ # An uninterruptible custom native call can outlive Thread#kill. Never
197
+ # release its providers until it really stops. Default Ruby layout needs
198
+ # no reaper thread; native providers must keep their calls bounded.
199
+ Thread.new { worker.thread.join; wrap.close }.name = "canopus-layout-cleanup"
200
+ else
201
+ wrap.close
202
+ end
203
+ end
204
+
205
+ def affected_lines(first, last)
206
+ ranges = @fold_map.ranges.map { |range| [@rope.point_at(range.begin).row, @rope.point_at(range.end).row] }
207
+ loop do
208
+ previous = [first, last]
209
+ ranges.each do |from, to|
210
+ next if to < first || from > last
211
+ first, last = [first, from].min, [last, to].max
212
+ end
213
+ return [first, last] if previous == [first, last]
214
+ end
215
+ end
216
+
217
+ def display_lines(source, local: nil)
218
+ value = @tree[source]
219
+ return value unless value.is_a?(PendingLineSet)
220
+ cached = @row_cache[source]
221
+ if cached && local
222
+ positions = cached.rows.first.offsets
223
+ cached = nil unless local.between?(positions.first, positions.last)
224
+ end
225
+ unless cached
226
+ @row_cache.shift if @row_cache.length >= 256
227
+ cached = @row_cache[source] = @builder.line(source, provisional: true, from: local ? [local - 8192, 0].max : 0)
228
+ end
229
+ cached
230
+ end
231
+
232
+ def build_lazy_row(row, from: 0)
233
+ @recomputed_lines += 1
234
+ text, offset = @rope.line_window(row, from: from)
235
+ offsets = [offset]
236
+ text.each_char { |char| offset += char.bytesize; offsets << offset }
237
+ value, positions = @tab_map.transform(text, offsets)
238
+ Row.new(value.freeze, positions.freeze, :text, nil)
239
+ end
240
+ def locate(index)
241
+ raise RangeError, "display row outside buffer" unless index.between?(0, row_count - 1)
242
+ return @location_cache[index] if @location_cache.key?(index)
243
+ @location_cache.shift if @location_cache.length >= 256
244
+ @location_cache[index] = @tree.locate(index, :display_rows)
245
+ end
246
+ def build_line(row)
247
+ @recomputed_lines += 1
248
+ @builder.line(row)
249
+ end
250
+ def rebuild(reuse: false)
251
+ @recomputed_lines = 0
252
+ @row_cache = {}
253
+ @location_cache = {}
254
+ @pending_result = nil
255
+ @generation += 1
256
+ @failed = false
257
+ @layout_error = nil
258
+ @lazy = @rope.respond_to?(:lazy?) && @rope.lazy?
259
+ if @lazy
260
+ @worker&.close
261
+ @worker = nil
262
+ return
263
+ end
264
+ @builder = LineBuilder.new(@rope, @fold_map, @tab_map, @wrap_map, @block_map)
265
+ @background = background_layout?(@rope)
266
+ unless reuse && @background && @tree && @tree.size == @rope.line_count
267
+ values = @background ? @builder.pending_lines(0, @rope.line_count) : Array.new(@rope.line_count) { |row| build_line(row) }
268
+ @tree = Denebola::Tree.new(values, summary: Summary)
269
+ end
270
+ @computed_prefix = @background ? 0 : @tree.size
271
+ schedule
272
+ end
273
+ def replace_lines(first, old_last, new_last)
274
+ @generation += 1
275
+ @row_cache = {}
276
+ @pending_result = nil
277
+ @failed = false
278
+ @layout_error = nil
279
+ @builder = LineBuilder.new(@rope, @fold_map, @tab_map, @wrap_map, @block_map)
280
+ replacement = @background ? @builder.pending_lines(first, new_last - first + 1) : Array.new(new_last - first + 1) { |i| build_line(first + i) }
281
+ if first < @computed_prefix
282
+ @computed_prefix = old_last + 1 >= @computed_prefix ? first : @computed_prefix + new_last - old_last
283
+ end
284
+ replace_values(first, old_last - first + 1, replacement)
285
+ @computed_prefix = @tree.size unless @background
286
+ schedule
287
+ end
288
+ def replace_values(first, count, replacement)
289
+ tail = first + count
290
+ @tree = @tree.slice(0, first).append(replacement).append(@tree.slice(tail, @tree.size - tail))
291
+ @location_cache.clear
292
+ end
293
+ def schedule
294
+ if pending?
295
+ @worker ||= Worker.new
296
+ @worker.submit(@generation, @tree, @builder, @computed_prefix)
297
+ else
298
+ @worker&.close
299
+ @worker = nil
300
+ end
301
+ end
302
+ def background_layout?(rope)
303
+ @background_threshold && (@wrap_map.font || rope.bytesize >= @background_threshold)
304
+ end
305
+ def apply(patch)
306
+ if patch.is_a?(Patch::Reload)
307
+ @rope = patch.after
308
+ rebuild
309
+ return
310
+ end
311
+ if patch.is_a?(Patch::Composite)
312
+ patch.patches.each { |part| apply(part) }
313
+ return
314
+ end
315
+ if patch.edits.empty?
316
+ @rope = patch.after
317
+ return
318
+ end
319
+ if @lazy || !!background_layout?(@rope) != !!background_layout?(patch.after)
320
+ @fold_map.apply(patch)
321
+ @block_map.apply(patch)
322
+ @rope = patch.after
323
+ rebuild
324
+ return
325
+ end
326
+ @recomputed_lines = 0
327
+ first = patch.edits.map { |e| patch.before.point_at(e.old_range.begin).row }.min
328
+ old_last = patch.edits.map { |e| patch.before.point_at(e.old_range.end).row }.max
329
+ new_last = patch.edits.map { |e| patch.after.point_at(e.new_range.end).row }.max
330
+ first, old_last = affected_lines(first, old_last)
331
+ new_last = [new_last, patch.after.point_at(patch.map_offset(patch.before.line_start(old_last))).row].max
332
+ @fold_map.apply(patch)
333
+ @block_map.apply(patch)
334
+ @rope = patch.after
335
+ replace_lines(first, old_last, new_last)
336
+ end
337
+ end
338
+ end
339
+
340
+ require_relative "display_map/summary"
341
+ require_relative "display_map/line_set"
342
+ require_relative "display_map/pending_line_set"
343
+
344
+ Canopus::DisplayMap::EMPTY_LINES = Canopus::DisplayMap::LineSet.new([].freeze)
345
+ Canopus::DisplayMap::UNWRAPPED_LINE = Canopus::DisplayMap::PendingLineSet.new(1)
346
+ Canopus::DisplayMap.send(:private_constant, :LineSet, :PendingLineSet, :EMPTY_LINES, :UNWRAPPED_LINE)
347
+
348
+ require_relative "display_map/line_builder"
349
+ require_relative "display_map/worker"
350
+ Canopus::DisplayMap.send(:private_constant, :LineBuilder, :Worker)
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canopus
4
+ DisplayPoint = Data.define(:row, :column)
5
+ end