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
data/lib/canopus/workspace.rb
CHANGED
|
@@ -2,27 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "fileutils"
|
|
5
|
+
require "shellwords"
|
|
6
|
+
sadr_path = ENV["SADR_PATH"]
|
|
7
|
+
sadr_root = File.expand_path("../..", __dir__)
|
|
8
|
+
sadr_path ? require(File.expand_path("lib/sadr", File.expand_path(sadr_path, sadr_root))) : require("sadr")
|
|
5
9
|
require_relative "settings"
|
|
6
10
|
require_relative "theme"
|
|
7
11
|
require_relative "vim"
|
|
8
|
-
require_relative "lsp"
|
|
9
12
|
require_relative "pane"
|
|
10
13
|
require_relative "project/tree"
|
|
11
14
|
require_relative "command"
|
|
12
15
|
require_relative "panel"
|
|
13
16
|
require_relative "decoration"
|
|
17
|
+
require_relative "provider"
|
|
18
|
+
require_relative "minimap"
|
|
19
|
+
require_relative "diagnostics"
|
|
14
20
|
|
|
15
21
|
module Canopus
|
|
16
22
|
class Workspace
|
|
17
23
|
ClosedTab = Data.define(:path, :selections, :scroll_x, :scroll_y, :pane_id, :index)
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
LANGUAGE_SERVER_CAPABILITIES = {
|
|
25
|
+
"completion" => "completionProvider", "diagnostics" => nil, "codeAction" => "codeActionProvider",
|
|
26
|
+
"formatting" => "documentFormattingProvider", "definition" => "definitionProvider",
|
|
27
|
+
"typeDefinition" => "typeDefinitionProvider", "implementation" => "implementationProvider",
|
|
28
|
+
"hover" => "hoverProvider", "signatureHelp" => "signatureHelpProvider", "references" => "referencesProvider",
|
|
29
|
+
"rename" => "renameProvider", "documentSymbol" => "documentSymbolProvider", "codeLens" => "codeLensProvider",
|
|
30
|
+
"inlayHint" => "inlayHintProvider", "semanticTokens" => "semanticTokensProvider",
|
|
31
|
+
"documentHighlight" => "documentHighlightProvider", "foldingRange" => "foldingRangeProvider",
|
|
32
|
+
"selectionRange" => "selectionRangeProvider", "callHierarchy" => "callHierarchyProvider",
|
|
33
|
+
"typeHierarchy" => "typeHierarchyProvider", "documentLink" => "documentLinkProvider",
|
|
34
|
+
"linkedEditingRange" => "linkedEditingRangeProvider", "workspaceSymbol" => "workspaceSymbolProvider"
|
|
35
|
+
}.freeze
|
|
36
|
+
attr_reader :panes, :active_pane, :buffers, :actions, :commands, :settings, :theme, :project, :clients, :root, :docks, :panels, :decorations, :providers, :minimap, :diagnostics, :breakpoints, :trust
|
|
37
|
+
attr_reader :terminals, :active_terminal_index, :terminal_layout
|
|
20
38
|
attr_accessor :window, :terminal_composition, :selected_project_path, :performance
|
|
21
39
|
attr_reader :message, :palette
|
|
22
40
|
|
|
23
41
|
def initialize(root: Dir.pwd, settings: nil)
|
|
24
42
|
@root = File.realpath(root)
|
|
43
|
+
@trust = Trust.new(@root)
|
|
25
44
|
@settings = settings || Settings.new(Settings.user_path, File.join(@root, ".canopus", "settings.jsonc"))
|
|
45
|
+
@main_queue = Queue.new
|
|
26
46
|
@panes, @buffers = [Pane.new], {}
|
|
27
47
|
@active_pane = @panes.first
|
|
28
48
|
@layout = {pane: @active_pane}
|
|
@@ -41,6 +61,14 @@ module Canopus
|
|
|
41
61
|
@panels.register(Panel::Definition.new("terminal", "Terminal", nil, :bottom, -> { terminal }, nil))
|
|
42
62
|
@panels.register(Panel::Definition.new("search", "Search", nil, :left, -> { palette_open(:project_search) }, nil))
|
|
43
63
|
@panels.register(Panel::Definition.new("explorer", "Explorer", nil, :left, -> { project_tree }, nil))
|
|
64
|
+
@panels.register(Panel::Definition.new("scm", "Source Control", nil, :left, -> { scm_panel }, nil), visible: false)
|
|
65
|
+
@panels.register(Panel::Definition.new("hierarchy", "Hierarchy", nil, :right, -> { hierarchy_tree }, nil), visible: false)
|
|
66
|
+
@panels.register(Panel::Definition.new("problems", "Problems", nil, :right, -> { problems_tree }, nil), visible: false)
|
|
67
|
+
@panels.register(Panel::Definition.new("debug", "Debug", nil, :left, -> { debug_tree }, nil), visible: false)
|
|
68
|
+
@panels.register(Panel::Definition.new("debug_console", "Debug Console", nil, :bottom, -> { debug_console_tree }, nil), visible: false)
|
|
69
|
+
@panels.register(Panel::Definition.new("output", "Output", nil, :bottom, -> { task_output }, nil), visible: false)
|
|
70
|
+
@panels.register(Panel::Definition.new("test", "Tests", nil, :left, -> { test_tree }, nil), visible: false)
|
|
71
|
+
@panels.hide("hierarchy")
|
|
44
72
|
@decorations = Decoration::Registry.new
|
|
45
73
|
@decorations.register(:selection_match) do |buffer, rows, current|
|
|
46
74
|
selections = current.is_a?(Editor) && current.buffer.equal?(buffer) ? current.selections : buffer.selections
|
|
@@ -54,9 +82,30 @@ module Canopus
|
|
|
54
82
|
end
|
|
55
83
|
end
|
|
56
84
|
@decorations.register(:git) { |buffer, rows| git_decorations(buffer, rows) }
|
|
85
|
+
@decorations.register(:scm_diff) { |buffer, rows| scm_diff_decorations(buffer, rows) }
|
|
86
|
+
@decorations.register(:merge_conflict) { |buffer, rows| git_conflict_decorations(buffer, rows) }
|
|
87
|
+
@decorations.register(:blame) { |buffer, rows, current| git_blame_decorations(buffer, rows, current) }
|
|
88
|
+
@decorations.register(:diagnostics) { |buffer, rows| diagnostic_decorations(buffer, rows) }
|
|
89
|
+
@decorations.register(:document_highlight) { |buffer, rows, current| document_highlight_decorations(buffer, rows, current) }
|
|
90
|
+
@decorations.register(:document_link) { |buffer, rows, current| document_link_decorations(buffer, rows, current) }
|
|
91
|
+
@decorations.register(:inlay_hint) { |buffer, rows| inlay_hint_decorations(buffer, rows) }
|
|
92
|
+
@decorations.register(:code_lens) { |buffer, rows| code_lens_decorations(buffer, rows) }
|
|
93
|
+
@decorations.register(:bracket) { |buffer, rows, current| bracket_decorations(buffer, rows, current) }
|
|
94
|
+
initialize_breakpoints
|
|
95
|
+
@minimap = Minimap.new
|
|
96
|
+
@diagnostics = Diagnostics::Registry.new do |uri|
|
|
97
|
+
@window ? post { diagnostics_changed(uri) } : diagnostics_changed(uri)
|
|
98
|
+
end
|
|
99
|
+
publish_settings_diagnostics(@settings.paths)
|
|
100
|
+
@providers = Provider::Registry.new
|
|
101
|
+
@providers.register_completion(:lsp, priority: 100) { |buffer, offset, context| lsp_completions(buffer, offset, context) }
|
|
57
102
|
@languages, @terminals = {}, []
|
|
58
103
|
@active_terminal_index = 0
|
|
104
|
+
@terminal_layout = nil
|
|
59
105
|
@closed_tabs, @terminal_names = [], {}
|
|
106
|
+
@terminal_profiles = {}.compare_by_identity
|
|
107
|
+
initialize_tasks
|
|
108
|
+
initialize_tests
|
|
60
109
|
register_actions
|
|
61
110
|
end
|
|
62
111
|
def editor = @active_pane.active
|
|
@@ -66,7 +115,12 @@ module Canopus
|
|
|
66
115
|
end
|
|
67
116
|
def terminal_visible = @panels.visible?("terminal") && @docks[:bottom][:visible]
|
|
68
117
|
def terminal_visible=(visible)
|
|
69
|
-
visible
|
|
118
|
+
if visible
|
|
119
|
+
@panels.hide("output") if @panels.visible?("output")
|
|
120
|
+
@panels.show("terminal")
|
|
121
|
+
else
|
|
122
|
+
@panels.hide("terminal")
|
|
123
|
+
end
|
|
70
124
|
end
|
|
71
125
|
def terminal = @terminals[@active_terminal_index]
|
|
72
126
|
def terminal=(value)
|
|
@@ -76,10 +130,20 @@ module Canopus
|
|
|
76
130
|
@terminal_resize_times&.clear
|
|
77
131
|
@terminals = value ? [value] : []
|
|
78
132
|
@active_terminal_index = 0
|
|
133
|
+
@terminal_layout = value ? {terminal: value} : nil
|
|
134
|
+
@terminal_profiles.clear
|
|
79
135
|
value
|
|
80
136
|
end
|
|
81
137
|
def palette=(value)
|
|
138
|
+
@palette_generation = @palette_generation.to_i + 1
|
|
139
|
+
if @workspace_symbol_request && value&.dig(:workspace_symbol_generation) != @workspace_symbol_generation
|
|
140
|
+
cancel_workspace_symbol_search
|
|
141
|
+
end
|
|
82
142
|
previous, @palette = @palette, @closed ? nil : value
|
|
143
|
+
rename = previous&.dig(:kind) == :rename && previous[:rename]
|
|
144
|
+
if rename && !previous.equal?(@submitting_rename_palette) && !rename.equal?(@palette&.dig(:rename))
|
|
145
|
+
release_rename_snapshot(rename)
|
|
146
|
+
end
|
|
83
147
|
response = previous&.dig(:response)
|
|
84
148
|
unless response.nil? || response.equal?(@palette&.dig(:response))
|
|
85
149
|
response.fulfill({"applied" => false, "failureReason" => value ? "Replaced by another dialog" : "User cancelled"})
|
|
@@ -117,6 +181,7 @@ module Canopus
|
|
|
117
181
|
def refresh_files
|
|
118
182
|
@files = @project_tree = @project_entries = nil
|
|
119
183
|
@finder_index = nil
|
|
184
|
+
refresh_tests if @test_discovery_started && !@closed
|
|
120
185
|
if @palette && @palette[:kind] == :files
|
|
121
186
|
@palette.delete(:search)
|
|
122
187
|
update_palette
|
|
@@ -126,9 +191,16 @@ module Canopus
|
|
|
126
191
|
def vim = @vim_states[editor] ||= Vim.new(editor).tap { |state| state.on_command = ->(command) { ex_command(command) } }
|
|
127
192
|
def open(path)
|
|
128
193
|
absolute = canonical_path(path)
|
|
129
|
-
buffer = @buffers[absolute]
|
|
194
|
+
buffer = @buffers[absolute]
|
|
195
|
+
unless buffer
|
|
196
|
+
buffer = File.file?(absolute) ? Buffer.open(absolute) : Buffer.new("", path: absolute)
|
|
197
|
+
@buffers[absolute] = buffer
|
|
198
|
+
mark_persistent_undo_path(buffer, path)
|
|
199
|
+
load_persistent_undo(buffer)
|
|
200
|
+
end
|
|
130
201
|
@vim_states[editor]&.deactivate unless editor&.buffer.equal?(buffer)
|
|
131
202
|
opened = @active_pane.open(buffer)
|
|
203
|
+
attach_breakpoints(buffer)
|
|
132
204
|
@recent_files ||= []
|
|
133
205
|
@recent_files.delete(absolute)
|
|
134
206
|
@recent_files.unshift(absolute)
|
|
@@ -137,6 +209,7 @@ module Canopus
|
|
|
137
209
|
opened.language = definition_for(absolute)
|
|
138
210
|
apply_editor_settings(opened)
|
|
139
211
|
@project_tree&.reveal(absolute.delete_prefix(@root + File::SEPARATOR))
|
|
212
|
+
invalidate_hidden_selection_ranges
|
|
140
213
|
@window&.request_frame
|
|
141
214
|
opened
|
|
142
215
|
end
|
|
@@ -150,25 +223,46 @@ module Canopus
|
|
|
150
223
|
end
|
|
151
224
|
File.join(File.realpath(parent), *pieces)
|
|
152
225
|
end
|
|
153
|
-
def save_buffer(buffer = editor.buffer, path: buffer.path)
|
|
226
|
+
def save_buffer(buffer = editor.buffer, path: buffer.path, encoding: nil, confirmed: false)
|
|
154
227
|
if buffer.is_a?(MultiBuffer)
|
|
155
|
-
buffer.excerpts.map(&:buffer).uniq.each { |source| save_buffer(source) if source.dirty? }
|
|
228
|
+
buffer.excerpts.map(&:buffer).uniq.each { |source| save_buffer(source, encoding: encoding) if source.dirty? }
|
|
156
229
|
return buffer
|
|
157
230
|
end
|
|
158
231
|
target = path && canonical_path(path)
|
|
159
232
|
raise Error, "Choose a path before saving" unless target
|
|
160
233
|
existing = @buffers[target]
|
|
161
234
|
raise Error, "Destination is already open in another buffer" if existing && !existing.equal?(buffer)
|
|
235
|
+
unless confirmed || buffer.roundtrip?(encoding: encoding)
|
|
236
|
+
self.palette = {kind: :confirm_encoding_save, query: "Encoding cannot represent all characters. Save anyway?",
|
|
237
|
+
index: 1, matches: ["Save anyway", "Cancel"], buffer: buffer, target: target, encoding: encoding}
|
|
238
|
+
return buffer
|
|
239
|
+
end
|
|
162
240
|
previous = buffer.path
|
|
163
|
-
|
|
241
|
+
prepare_editorconfig_save(buffer, target)
|
|
242
|
+
@running_save_actions ||= {}.compare_by_identity
|
|
243
|
+
unless @running_save_actions.key?(buffer)
|
|
244
|
+
@running_save_actions[buffer] = true
|
|
245
|
+
begin
|
|
246
|
+
run_save_actions(buffer) if previous
|
|
247
|
+
rescue StandardError => error
|
|
248
|
+
notify("Save actions failed: #{error.message}")
|
|
249
|
+
ensure
|
|
250
|
+
@running_save_actions.delete(buffer)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
result = buffer.save(target, encoding: encoding)
|
|
164
254
|
if previous != buffer.path
|
|
255
|
+
relocate_breakpoints(buffer)
|
|
256
|
+
invalidate_hierarchy(buffer)
|
|
257
|
+
invalidate_prepare_rename(buffer)
|
|
258
|
+
invalidate_document_links(buffer)
|
|
259
|
+
invalidate_linked_editing_ranges(buffer)
|
|
165
260
|
invalidate_git
|
|
166
261
|
# Close the old URI before registering this buffer under its new path.
|
|
167
262
|
@opened_lsp_documents&.keys&.each do |client, document|
|
|
168
263
|
next unless document.equal?(buffer)
|
|
169
|
-
@opened_lsp_documents.delete([client, document])
|
|
170
264
|
begin
|
|
171
|
-
client
|
|
265
|
+
close_language_document(client, document, uri: Sadr::Protocol.uri(previous)) if previous
|
|
172
266
|
rescue StandardError => error
|
|
173
267
|
@message = "File saved; language server close failed: #{error.message}"
|
|
174
268
|
end
|
|
@@ -187,19 +281,48 @@ module Canopus
|
|
|
187
281
|
@opened_lsp_documents&.each_key do |client, document|
|
|
188
282
|
next unless document.equal?(buffer)
|
|
189
283
|
begin
|
|
190
|
-
client.
|
|
284
|
+
client.save(Sadr::Protocol.uri(buffer.path))
|
|
191
285
|
rescue StandardError => error
|
|
192
286
|
self.message = "File saved; language server notification failed: #{error.message}"
|
|
193
287
|
end
|
|
194
288
|
end
|
|
195
289
|
result
|
|
196
290
|
end
|
|
291
|
+
|
|
292
|
+
def show_encoding_actions(buffer = editor&.buffer)
|
|
293
|
+
return unless buffer&.path && !buffer.read_only
|
|
294
|
+
|
|
295
|
+
choices = [Encoding::UTF_8, Encoding::UTF_16LE, Encoding::UTF_16BE, Encoding::UTF_32LE, Encoding::UTF_32BE,
|
|
296
|
+
Encoding::Windows_31J,
|
|
297
|
+
Encoding::EUC_JP, Encoding::ISO_2022_JP].uniq
|
|
298
|
+
actions = choices.flat_map do |encoding|
|
|
299
|
+
[["Reload as #{encoding.name}", :reload, encoding], ["Save as #{encoding.name}", :save, encoding]]
|
|
300
|
+
end
|
|
301
|
+
self.palette = {kind: :encoding_actions, query: +"", index: 0, matches: actions.map(&:first),
|
|
302
|
+
encoding_actions: actions, buffer: buffer}
|
|
303
|
+
@palette
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def convert_line_endings(to, confirmed: false)
|
|
307
|
+
buffer = editor&.buffer
|
|
308
|
+
return unless buffer && !buffer.read_only
|
|
309
|
+
if buffer.mixed_line_endings? && !confirmed
|
|
310
|
+
self.palette = {kind: :confirm_line_ending_conversion,
|
|
311
|
+
query: "Mixed line endings will be normalized. Continue?", index: 1,
|
|
312
|
+
matches: ["Convert line endings", "Cancel"], buffer: buffer, to: to}
|
|
313
|
+
return buffer
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
buffer.convert_line_endings(to: to)
|
|
317
|
+
self.message = "Line endings converted to #{to.to_s.upcase}"
|
|
318
|
+
end
|
|
197
319
|
def new_buffer
|
|
198
320
|
@vim_states[editor]&.deactivate
|
|
199
321
|
buffer = Buffer.new
|
|
200
322
|
@buffers[buffer.object_id] = buffer
|
|
201
323
|
opened = @active_pane.open(buffer)
|
|
202
324
|
apply_editor_settings(opened)
|
|
325
|
+
invalidate_hidden_selection_ranges
|
|
203
326
|
opened
|
|
204
327
|
end
|
|
205
328
|
def focus(pane)
|
|
@@ -213,6 +336,7 @@ module Canopus
|
|
|
213
336
|
@vim_states[editor]&.deactivate unless tab.equal?(editor)
|
|
214
337
|
focus(pane)
|
|
215
338
|
pane.activate(pane.editors.index(tab))
|
|
339
|
+
invalidate_hidden_selection_ranges
|
|
216
340
|
end
|
|
217
341
|
def split(direction = :horizontal)
|
|
218
342
|
raise ArgumentError, "invalid split" unless [:horizontal, :vertical].include?(direction)
|
|
@@ -245,6 +369,16 @@ module Canopus
|
|
|
245
369
|
end
|
|
246
370
|
sources = current.buffer.is_a?(MultiBuffer) ? current.buffer.excerpts.map(&:buffer).uniq : []
|
|
247
371
|
@vim_states.delete(current)&.dispose
|
|
372
|
+
@sticky_fallback_cache&.delete(current)
|
|
373
|
+
@sticky_context_cache&.clear
|
|
374
|
+
invalidate_document_highlights(editor: current)
|
|
375
|
+
invalidate_folding_ranges(editor: current)
|
|
376
|
+
invalidate_selection_ranges(editor: current)
|
|
377
|
+
invalidate_hierarchy(editor: current)
|
|
378
|
+
invalidate_prepare_rename(editor: current)
|
|
379
|
+
invalidate_document_links(editor: current)
|
|
380
|
+
invalidate_linked_editing_ranges(editor: current)
|
|
381
|
+
invalidate_brackets(current.buffer)
|
|
248
382
|
pane.close(current, discard: discard, activate: @settings["tabs"]["activate_on_close"].to_sym)
|
|
249
383
|
release_buffer(current.buffer, discard: discard)
|
|
250
384
|
sources.each { |source| release_buffer(source, discard: discard) }
|
|
@@ -329,25 +463,63 @@ module Canopus
|
|
|
329
463
|
to.active_index = index
|
|
330
464
|
to.pin(editor) if pinned
|
|
331
465
|
focus(to)
|
|
466
|
+
invalidate_hidden_selection_ranges
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def new_terminal(cwd: nil, profile: nil)
|
|
470
|
+
previous = terminal
|
|
471
|
+
created = start_terminal(cwd: cwd, profile: profile)
|
|
472
|
+
@terminal_layout = replace_terminal_leaf(@terminal_layout, previous, created) || {terminal: created}
|
|
473
|
+
invalidate_terminal_layout_resize
|
|
474
|
+
created
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def split_terminal(direction, profile: nil)
|
|
478
|
+
raise ArgumentError, "invalid terminal split direction" unless %i[horizontal vertical].include?(direction)
|
|
479
|
+
previous = terminal
|
|
480
|
+
return new_terminal(profile: profile) unless previous
|
|
481
|
+
created = start_terminal(cwd: terminal_cwd(previous), profile: profile)
|
|
482
|
+
split = {direction: direction, ratio: 0.5, children: [{terminal: previous}, {terminal: created}]}
|
|
483
|
+
@terminal_layout = replace_terminal_node(@terminal_layout, previous, split) || split
|
|
484
|
+
invalidate_terminal_layout_resize
|
|
485
|
+
created
|
|
332
486
|
end
|
|
333
487
|
|
|
334
|
-
def
|
|
488
|
+
def terminal_profile(current = terminal) = @terminal_profiles[current]
|
|
489
|
+
|
|
490
|
+
def start_terminal(cwd: nil, profile: nil)
|
|
335
491
|
options = @settings["terminal"]
|
|
336
|
-
|
|
337
|
-
|
|
492
|
+
cwd ||= terminal&.respond_to?(:cwd) && terminal.cwd
|
|
493
|
+
cwd = terminal_working_directory unless cwd && File.directory?(cwd)
|
|
494
|
+
profile ||= options["default_profile"]
|
|
495
|
+
configured = profile && options["profiles"].fetch(profile)
|
|
496
|
+
fallback = Gem.win_platform? ? ENV.fetch("COMSPEC", "cmd.exe") : ENV.fetch("SHELL", "/bin/sh")
|
|
497
|
+
command = configured && terminal_profile_command(configured) || options["shell"] || fallback
|
|
498
|
+
env = options["env"].merge(configured&.fetch("env", {}) || {})
|
|
499
|
+
.merge("CANOPUS" => "1", "EDITOR" => "canopus --wait")
|
|
500
|
+
created = Tarazed::Session.new(command: command, cwd: cwd,
|
|
501
|
+
columns: 100, rows: 12, env: env, scrollback_limit: options["scrollback_lines"],
|
|
338
502
|
queue_limit_bytes: options["queue_limit_bytes"])
|
|
503
|
+
inject_shell_integration(created) if options["shell_integration"]
|
|
339
504
|
@terminals << created
|
|
505
|
+
@terminal_profiles[created] = profile if profile
|
|
340
506
|
@active_terminal_index = @terminals.length - 1
|
|
341
507
|
self.terminal_visible = true
|
|
342
|
-
resize_terminal(*@terminal_dimensions, final: true) if @terminal_dimensions
|
|
508
|
+
resize_terminal(*@terminal_dimensions, final: true, terminal: created) if @terminal_dimensions
|
|
343
509
|
@window&.request_frame
|
|
344
510
|
created
|
|
345
511
|
end
|
|
512
|
+
private :start_terminal
|
|
346
513
|
|
|
347
514
|
def activate_terminal(index)
|
|
348
515
|
raise IndexError, "terminal tab outside panel" unless index.is_a?(Integer) && index.between?(0, @terminals.length - 1)
|
|
516
|
+
previous = terminal
|
|
349
517
|
@active_terminal_index = index
|
|
350
|
-
|
|
518
|
+
unless terminal_layout_include?(terminal)
|
|
519
|
+
@terminal_layout = replace_terminal_leaf(@terminal_layout, previous, terminal)
|
|
520
|
+
invalidate_terminal_layout_resize
|
|
521
|
+
end
|
|
522
|
+
@terminal_layout ||= {terminal: terminal}
|
|
351
523
|
@window&.request_frame
|
|
352
524
|
terminal
|
|
353
525
|
end
|
|
@@ -376,14 +548,26 @@ module Canopus
|
|
|
376
548
|
current = @terminals.delete_at(index)
|
|
377
549
|
return unless current
|
|
378
550
|
@terminal_names.delete(current)
|
|
551
|
+
@terminal_profiles.delete(current)
|
|
379
552
|
@terminal_sizes&.delete(current)
|
|
553
|
+
@terminal_dimensions_by_terminal&.delete(current)
|
|
380
554
|
@terminal_resize_times&.delete(current)
|
|
381
555
|
current.close if current.respond_to?(:close)
|
|
556
|
+
visible = terminal_layout_include?(current)
|
|
557
|
+
@terminal_layout = remove_terminal_leaf(@terminal_layout, current) if visible
|
|
382
558
|
@active_terminal_index = if current.equal?(active)
|
|
383
559
|
[index, @terminals.length - 1].min.clamp(0, @terminals.length)
|
|
384
560
|
else
|
|
385
561
|
@terminals.index(active) || 0
|
|
386
562
|
end
|
|
563
|
+
if @terminals.empty?
|
|
564
|
+
@terminal_layout = nil
|
|
565
|
+
elsif visible
|
|
566
|
+
replacement = terminal_layout_terminals.first || terminal
|
|
567
|
+
@active_terminal_index = @terminals.index(replacement) || @active_terminal_index
|
|
568
|
+
@terminal_layout ||= {terminal: terminal}
|
|
569
|
+
invalidate_terminal_layout_resize
|
|
570
|
+
end
|
|
387
571
|
self.terminal_visible = false if @terminals.empty? && @settings["terminal"]["hide_when_empty"]
|
|
388
572
|
@window&.request_frame
|
|
389
573
|
current
|
|
@@ -392,19 +576,30 @@ module Canopus
|
|
|
392
576
|
def restart_terminal(index = @active_terminal_index)
|
|
393
577
|
current = @terminals[index]
|
|
394
578
|
return unless current
|
|
395
|
-
cwd =
|
|
579
|
+
cwd = terminal_cwd(current)
|
|
396
580
|
name = @terminal_names[current]
|
|
397
|
-
|
|
398
|
-
replacement =
|
|
399
|
-
|
|
581
|
+
profile = @terminal_profiles[current]
|
|
582
|
+
replacement = start_terminal(cwd: File.directory?(cwd) ? cwd : @root, profile: profile)
|
|
583
|
+
@terminals.delete(replacement)
|
|
584
|
+
@terminals[index] = replacement
|
|
585
|
+
@active_terminal_index = index
|
|
586
|
+
@terminal_layout = replace_terminal_leaf(@terminal_layout, current, replacement)
|
|
587
|
+
invalidate_terminal_layout_resize
|
|
588
|
+
@terminal_names.delete(current)
|
|
589
|
+
@terminal_profiles.delete(current)
|
|
590
|
+
@terminal_sizes&.delete(current)
|
|
591
|
+
@terminal_dimensions_by_terminal&.delete(current)
|
|
592
|
+
@terminal_resize_times&.delete(current)
|
|
593
|
+
current.close if current.respond_to?(:close)
|
|
400
594
|
rename_terminal(name, replacement) if name
|
|
401
595
|
replacement
|
|
402
596
|
end
|
|
403
597
|
|
|
404
598
|
def terminal_title(current = terminal)
|
|
405
599
|
return "Terminal" unless current
|
|
600
|
+
cwd = current.respond_to?(:cwd) ? current.cwd : current.vt.cwd
|
|
406
601
|
title = @terminal_names[current] || current.vt.title.to_s.then { |value| value.empty? ? nil : value } ||
|
|
407
|
-
|
|
602
|
+
cwd&.then { |path| File.basename(path).empty? ? path : File.basename(path) } ||
|
|
408
603
|
(current.respond_to?(:foreground_process_name) ? current.foreground_process_name : nil) ||
|
|
409
604
|
(current.respond_to?(:command_name) ? current.command_name : nil) || "shell"
|
|
410
605
|
title.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace).scrub.slice(0, 200)
|
|
@@ -416,6 +611,14 @@ module Canopus
|
|
|
416
611
|
value.empty? ? @terminal_names.delete(current) : @terminal_names[current] = value.slice(0, 200)
|
|
417
612
|
end
|
|
418
613
|
|
|
614
|
+
def show_terminal_commands(failed: false)
|
|
615
|
+
items = retained_terminal_commands
|
|
616
|
+
items = items.select { |command| command.exit_status && command.exit_status != 0 } if failed
|
|
617
|
+
self.palette = {kind: :terminal_commands, query: +"", index: 0, items: items,
|
|
618
|
+
matches: items.map { |command| terminal_command_label(command) }}
|
|
619
|
+
update_palette
|
|
620
|
+
end
|
|
621
|
+
|
|
419
622
|
def drain_terminals(now: Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
|
420
623
|
return false if @terminals.empty?
|
|
421
624
|
remaining = @settings["terminal"]["max_bytes_per_frame"]
|
|
@@ -436,15 +639,17 @@ module Canopus
|
|
|
436
639
|
changed
|
|
437
640
|
end
|
|
438
641
|
|
|
439
|
-
def resize_terminal(columns, rows, final: false, now: Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
|
642
|
+
def resize_terminal(columns, rows, final: false, now: Process.clock_gettime(Process::CLOCK_MONOTONIC), terminal: nil)
|
|
440
643
|
minimum = @settings["terminal"]
|
|
441
644
|
dimensions = [[columns.to_i, minimum["min_cols"]].max, [rows.to_i, minimum["min_rows"]].max]
|
|
442
|
-
@terminal_dimensions = dimensions
|
|
645
|
+
@terminal_dimensions = dimensions if !terminal || terminal.equal?(self.terminal)
|
|
443
646
|
return if @terminals.empty?
|
|
444
647
|
final ||= @terminal_resize_final
|
|
445
648
|
@terminal_resize_final = false
|
|
446
649
|
wait = minimum["resize_debounce_ms"] / 1000.0
|
|
447
|
-
@terminals
|
|
650
|
+
targets = terminal ? [terminal] : @terminals
|
|
651
|
+
targets.each do |current|
|
|
652
|
+
(@terminal_dimensions_by_terminal ||= {})[current] = dimensions
|
|
448
653
|
previous = (@terminal_resize_times ||= {})[current]
|
|
449
654
|
next if !final && previous && now - previous < wait
|
|
450
655
|
next if (@terminal_sizes ||= {})[current] == dimensions
|
|
@@ -455,7 +660,13 @@ module Canopus
|
|
|
455
660
|
end
|
|
456
661
|
|
|
457
662
|
def flush_terminal_resize
|
|
458
|
-
|
|
663
|
+
if @terminal_dimensions_by_terminal&.any?
|
|
664
|
+
@terminal_dimensions_by_terminal.dup.each do |current, dimensions|
|
|
665
|
+
resize_terminal(*dimensions, final: true, terminal: current) if @terminals.include?(current)
|
|
666
|
+
end
|
|
667
|
+
elsif @terminal_dimensions
|
|
668
|
+
resize_terminal(*@terminal_dimensions, final: true)
|
|
669
|
+
end
|
|
459
670
|
@terminal_resize_final = true
|
|
460
671
|
end
|
|
461
672
|
|
|
@@ -484,8 +695,8 @@ module Canopus
|
|
|
484
695
|
end
|
|
485
696
|
end
|
|
486
697
|
end
|
|
487
|
-
def command_context(terminal: false)
|
|
488
|
-
{"Terminal" => terminal, "Editor" => !!editor,
|
|
698
|
+
def command_context(terminal: false, task_output: false)
|
|
699
|
+
{"Terminal" => terminal, "TaskOutput" => task_output, "Editor" => !!editor,
|
|
489
700
|
"vim_mode" => @settings["vim_mode"] && editor ? vim.mode.to_s : false}
|
|
490
701
|
end
|
|
491
702
|
def call(name, *args, context: nil)
|
|
@@ -514,6 +725,8 @@ module Canopus
|
|
|
514
725
|
@palette[:search_options] = {regexp: false, case_sensitive: true, whole_word: false, selection_only: false}
|
|
515
726
|
@palette[:selection] = editor.primary.range unless editor.primary.empty?
|
|
516
727
|
@palette[:editor], @palette[:version] = editor, editor.buffer.version
|
|
728
|
+
elsif kind == :regex_matches
|
|
729
|
+
@palette[:editor], @palette[:version] = editor, editor.buffer.version
|
|
517
730
|
end
|
|
518
731
|
update_palette
|
|
519
732
|
end
|
|
@@ -538,9 +751,76 @@ module Canopus
|
|
|
538
751
|
@palette[:index] = @palette[:index].clamp(0, [@palette[:matches].length - 1, 0].max)
|
|
539
752
|
return
|
|
540
753
|
end
|
|
541
|
-
if
|
|
754
|
+
if @palette[:kind] == :completion
|
|
755
|
+
labels = @palette[:all_matches] ||= @palette[:matches].dup
|
|
756
|
+
candidates = @palette[:items].map do |item|
|
|
757
|
+
item.is_a?(Provider::Completion) ? item.filter_text || item.label : item["filterText"] || item.fetch("label")
|
|
758
|
+
end
|
|
759
|
+
groups = @palette[:completion_indices] ||= candidates.each_with_index.each_with_object({}) do |(candidate, index), grouped|
|
|
760
|
+
(grouped[candidate] ||= []) << index
|
|
761
|
+
end
|
|
762
|
+
session = @palette[:search] ||= Spica::Index.new(groups.keys, tie_break: :index).session
|
|
763
|
+
session.query = @palette[:query]
|
|
764
|
+
matches = session.matches(12)
|
|
765
|
+
@palette[:indices] = matches.flat_map { |match| groups.fetch(match.candidate) }.first(12)
|
|
766
|
+
@palette[:matches] = @palette[:indices].map { |index| labels.fetch(index) }
|
|
767
|
+
@palette[:index] = 0
|
|
768
|
+
return
|
|
769
|
+
end
|
|
770
|
+
if @palette[:kind] == :workspace_symbol_results
|
|
771
|
+
labels = @palette.fetch(:all_matches)
|
|
772
|
+
if @palette[:query].empty?
|
|
773
|
+
@palette[:indices] = (0...[labels.length, 12].min).to_a
|
|
774
|
+
@palette[:matches] = @palette[:indices].map { |index| labels.fetch(index) }
|
|
775
|
+
else
|
|
776
|
+
session = @palette[:search] ||= @palette.fetch(:workspace_symbol_index).session
|
|
777
|
+
session.query = @palette[:query]
|
|
778
|
+
groups = @palette.fetch(:workspace_symbol_groups)
|
|
779
|
+
@palette[:indices] = session.matches(12).flat_map { |match| groups.fetch(match.candidate) }.first(12)
|
|
780
|
+
@palette[:matches] = @palette[:indices].map { |index| labels.fetch(index) }
|
|
781
|
+
end
|
|
782
|
+
@palette[:index] = 0
|
|
783
|
+
return
|
|
784
|
+
end
|
|
785
|
+
if @palette[:kind] == :git_commit_history
|
|
786
|
+
labels = @palette[:all_matches] ||= @palette[:matches].dup
|
|
787
|
+
groups = @palette[:history_search_groups] ||= @palette.fetch(:search_values).each_with_index.each_with_object({}) do |(value, index), result|
|
|
788
|
+
(result[value] ||= []) << index
|
|
789
|
+
end
|
|
790
|
+
session = @palette[:search] ||= Spica::Index.new(groups.keys, tie_break: :index).session
|
|
791
|
+
session.query = @palette[:query]
|
|
792
|
+
@palette[:indices] = session.matches(12).flat_map { |match| groups.fetch(match.candidate) }.first(12)
|
|
793
|
+
@palette[:matches] = @palette[:indices].map { |index| labels.fetch(index) }
|
|
794
|
+
@palette[:index] = 0
|
|
795
|
+
return
|
|
796
|
+
end
|
|
797
|
+
if @palette[:kind] == :terminal_commands
|
|
542
798
|
labels = @palette[:all_matches] ||= @palette[:matches].dup
|
|
543
|
-
|
|
799
|
+
groups = @palette[:terminal_command_groups] ||= labels.each_with_index.each_with_object({}) do |(label, index), result|
|
|
800
|
+
(result[label] ||= []) << index
|
|
801
|
+
end
|
|
802
|
+
session = @palette[:search] ||= Spica::Index.new(groups.keys).session
|
|
803
|
+
session.query = @palette[:query]
|
|
804
|
+
@palette[:indices] = session.matches(12).flat_map { |match| groups.fetch(match.candidate) }.first(12)
|
|
805
|
+
@palette[:matches] = @palette[:indices].map { |index| labels.fetch(index) }
|
|
806
|
+
@palette[:index] = 0
|
|
807
|
+
return
|
|
808
|
+
end
|
|
809
|
+
if @palette[:kind] == :settings_edit
|
|
810
|
+
@palette[:index] = @palette[:index].clamp(0, @palette[:matches].length - 1)
|
|
811
|
+
return
|
|
812
|
+
end
|
|
813
|
+
if @palette[:kind] == :keymap_edit
|
|
814
|
+
@palette[:index] = @palette[:index].clamp(0, @palette[:matches].length - 1)
|
|
815
|
+
return
|
|
816
|
+
end
|
|
817
|
+
if [:locations, :symbols, :code_actions, :outline, :branches, :settings_keys, :settings_gui, :keymap_gui, :keymap_presets, :snippet_choices,
|
|
818
|
+
:breadcrumbs, :hierarchy_roots, :language_servers, :debug_configurations, :debug_watch_remove, :tasks,
|
|
819
|
+
:git_file_history, :git_commit_paths, :git_remotes].include?(@palette[:kind])
|
|
820
|
+
labels = @palette[:all_matches] ||= @palette[:matches].dup
|
|
821
|
+
index = [:language_servers, :git_file_history].include?(@palette[:kind]) ?
|
|
822
|
+
Spica::Index.new(labels, tie_break: :index) : Spica::Index.new(labels)
|
|
823
|
+
session = @palette[:search] ||= index.session
|
|
544
824
|
session.query = @palette[:query]
|
|
545
825
|
matches = session.matches(12)
|
|
546
826
|
@palette[:indices] = matches.map(&:index)
|
|
@@ -561,13 +841,51 @@ module Canopus
|
|
|
561
841
|
@palette[:index] = @palette[:index].clamp(0, [@palette[:matches].length - 1, 0].max)
|
|
562
842
|
end
|
|
563
843
|
def palette_accept
|
|
844
|
+
return accept_task_palette if @palette[:kind] == :tasks
|
|
845
|
+
if @palette[:kind] == :encoding_actions
|
|
846
|
+
current = @palette
|
|
847
|
+
action = current[:encoding_actions][current[:index]]
|
|
848
|
+
self.palette = nil
|
|
849
|
+
return unless action
|
|
850
|
+
|
|
851
|
+
buffer = current[:buffer]
|
|
852
|
+
return unless @buffers.value?(buffer) || editor&.buffer.equal?(buffer)
|
|
853
|
+
if action[1] == :reload
|
|
854
|
+
buffer.reload(force: false, encoding: action[2])
|
|
855
|
+
self.message = "Reloaded as #{action[2].name}"
|
|
856
|
+
else
|
|
857
|
+
save_buffer(buffer, encoding: action[2])
|
|
858
|
+
self.message = "Saved as #{action[2].name}" unless @palette&.fetch(:kind, nil) == :confirm_encoding_save
|
|
859
|
+
end
|
|
860
|
+
return buffer
|
|
861
|
+
end
|
|
862
|
+
if @palette[:kind] == :confirm_line_ending_conversion
|
|
863
|
+
current = @palette
|
|
864
|
+
self.palette = nil
|
|
865
|
+
return if current[:index] != 0
|
|
866
|
+
|
|
867
|
+
buffer = current[:buffer]
|
|
868
|
+
return unless @buffers.value?(buffer) || editor&.buffer.equal?(buffer)
|
|
869
|
+
return convert_line_endings(current[:to], confirmed: true)
|
|
870
|
+
end
|
|
871
|
+
if @palette[:kind] == :confirm_encoding_save
|
|
872
|
+
current = @palette
|
|
873
|
+
self.palette = nil
|
|
874
|
+
return if current[:index] != 0
|
|
875
|
+
|
|
876
|
+
return save_buffer(current[:buffer], path: current[:target], encoding: current[:encoding], confirmed: true)
|
|
877
|
+
end
|
|
878
|
+
return accept_breakpoint_palette if [:breakpoint_actions, :breakpoint_edit].include?(@palette[:kind])
|
|
879
|
+
return accept_debug_watch_palette if [:debug_watch_add, :debug_watch_remove].include?(@palette[:kind])
|
|
880
|
+
return accept_debug_console_palette if @palette[:kind] == :debug_console
|
|
881
|
+
return accept_git_credentials if @palette[:kind] == :git_credentials
|
|
564
882
|
search_state = @palette.slice(:search_options, :selection, :editor, :version)
|
|
565
883
|
after_save = @palette[:after_save]
|
|
566
884
|
if @palette[:kind] == :snippet_choices
|
|
567
885
|
current = @palette
|
|
568
886
|
self.palette = nil
|
|
569
887
|
return @panes.any? { |pane| pane.editors.include?(current[:editor]) } && current[:editor].choose_snippet(current[:matches][current[:index]])
|
|
570
|
-
elsif [:completion, :locations, :symbols, :code_actions].include?(@palette[:kind])
|
|
888
|
+
elsif [:completion, :locations, :symbols, :code_actions, :workspace_symbol_results].include?(@palette[:kind])
|
|
571
889
|
current = @palette
|
|
572
890
|
self.palette = nil
|
|
573
891
|
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
@@ -582,6 +900,76 @@ module Canopus
|
|
|
582
900
|
editor.reveal_cursor
|
|
583
901
|
end
|
|
584
902
|
return
|
|
903
|
+
elsif @palette[:kind] == :breadcrumbs
|
|
904
|
+
current = @palette
|
|
905
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
906
|
+
self.palette = nil
|
|
907
|
+
return accept_breadcrumb_palette(current, index)
|
|
908
|
+
elsif @palette[:kind] == :hierarchy_roots
|
|
909
|
+
current = @palette
|
|
910
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
911
|
+
self.palette = nil
|
|
912
|
+
return accept_hierarchy_root(current, index)
|
|
913
|
+
elsif @palette[:kind] == :language_servers
|
|
914
|
+
current = @palette
|
|
915
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
916
|
+
server = index && current[:items][index]
|
|
917
|
+
self.palette = nil
|
|
918
|
+
return restart_language_server(server[:language], server[:index]) if server
|
|
919
|
+
elsif @palette[:kind] == :debug_configurations
|
|
920
|
+
current = @palette
|
|
921
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
922
|
+
configuration = index && current[:items][index]
|
|
923
|
+
self.palette = nil
|
|
924
|
+
return start_debugging(configuration.fetch("name")) if configuration
|
|
925
|
+
elsif @palette[:kind] == :terminal_commands
|
|
926
|
+
current = @palette
|
|
927
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
928
|
+
self.palette = nil
|
|
929
|
+
return index && current[:items][index]
|
|
930
|
+
elsif @palette[:kind] == :problem_filter
|
|
931
|
+
query = @palette[:query]
|
|
932
|
+
self.palette = nil
|
|
933
|
+
return apply_problem_filter(query)
|
|
934
|
+
elsif @palette[:kind] == :git_file_history
|
|
935
|
+
current = @palette
|
|
936
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
937
|
+
entry = index && current[:items][index]
|
|
938
|
+
self.palette = nil
|
|
939
|
+
return compare_git_revisions(entry.parent, entry.oid, path: entry.path) if entry
|
|
940
|
+
elsif @palette[:kind] == :git_commit_history
|
|
941
|
+
current = @palette
|
|
942
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
943
|
+
entry = index && current[:items][index]
|
|
944
|
+
self.palette = nil
|
|
945
|
+
return open_git_commit(entry) if entry
|
|
946
|
+
elsif @palette[:kind] == :git_commit_paths
|
|
947
|
+
current = @palette
|
|
948
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
949
|
+
path = index && current[:items][index]
|
|
950
|
+
self.palette = nil
|
|
951
|
+
commit = current.fetch(:commit)
|
|
952
|
+
return compare_git_revisions(commit.parents.first, commit.oid, path: path) if path
|
|
953
|
+
elsif @palette[:kind] == :git_remotes
|
|
954
|
+
current = @palette
|
|
955
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
956
|
+
remote = index && current[:items][index]
|
|
957
|
+
self.palette = nil
|
|
958
|
+
return public_send(:"#{current.fetch(:operation)}_git", remote) if remote
|
|
959
|
+
elsif @palette[:kind] == :git_revision_compare
|
|
960
|
+
current = @palette
|
|
961
|
+
revisions = current[:query].split("..", -1)
|
|
962
|
+
self.palette = nil
|
|
963
|
+
raise Error, "Enter revisions as FROM..TO" unless revisions.length == 2 && revisions.all? { |revision| !revision.empty? }
|
|
964
|
+
return compare_git_revisions(revisions.first, revisions.last, path: current[:path])
|
|
965
|
+
elsif @palette[:kind] == :rename && @palette[:rename]
|
|
966
|
+
current = @submitting_rename_palette = @palette
|
|
967
|
+
begin
|
|
968
|
+
self.palette = nil
|
|
969
|
+
ensure
|
|
970
|
+
@submitting_rename_palette = nil
|
|
971
|
+
end
|
|
972
|
+
return rename_prepared(current[:rename], current[:query])
|
|
585
973
|
end
|
|
586
974
|
selected = @palette[:matches][@palette[:index]]
|
|
587
975
|
selected_command = if @palette[:kind] == :commands && selected
|
|
@@ -590,6 +978,7 @@ module Canopus
|
|
|
590
978
|
end
|
|
591
979
|
command_context = @palette[:command_context]
|
|
592
980
|
kind, query, pattern = @palette.values_at(:kind, :query, :pattern)
|
|
981
|
+
current = @palette
|
|
593
982
|
self.palette = nil
|
|
594
983
|
if kind == :files && selected
|
|
595
984
|
open(selected)
|
|
@@ -598,8 +987,12 @@ module Canopus
|
|
|
598
987
|
elsif kind == :search
|
|
599
988
|
pattern, options = search_query(query, search_state)
|
|
600
989
|
matches = editor.search(pattern, **options)
|
|
990
|
+
@minimap.record_search(editor.buffer, editor.buffer.version, matches)
|
|
601
991
|
editor.select(matches.first.begin, matches.first.end) unless matches.empty?
|
|
602
992
|
@message = "#{matches.length} matches"
|
|
993
|
+
elsif kind == :regex_matches
|
|
994
|
+
pattern, options = search_query(query, search_state.merge(search_options: {regexp: true}))
|
|
995
|
+
@message = "#{editor.select_matches(pattern, **options).length} matches"
|
|
603
996
|
elsif kind == :project_search
|
|
604
997
|
search_project(query, **search_state.fetch(:search_options, {}).reject { |key, _| key == :selection_only })
|
|
605
998
|
elsif kind == :replace_query
|
|
@@ -628,7 +1021,46 @@ module Canopus
|
|
|
628
1021
|
elsif kind == :branches && selected
|
|
629
1022
|
checkout_branch(selected)
|
|
630
1023
|
elsif kind == :settings_keys && selected
|
|
631
|
-
editor.insert_text("#{JSON.generate(selected)}: #{JSON.generate(Settings::DEFAULTS.fetch(selected))}", auto_indent: false)
|
|
1024
|
+
editor.insert_text("#{JSON.generate(selected)}: #{JSON.generate(Settings::DEFAULTS.fetch(selected))}", auto_indent: false, pair: false)
|
|
1025
|
+
elsif kind == :settings_gui && selected
|
|
1026
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
1027
|
+
field = current.fetch(:fields).fetch(index)
|
|
1028
|
+
self.palette = {kind: :settings_edit, query: JSON.generate(field[:value]), index: 0,
|
|
1029
|
+
matches: ["Save", "Reset to default"], field: field, settings_file: current.fetch(:settings_file)}
|
|
1030
|
+
elsif kind == :settings_edit
|
|
1031
|
+
field = current.fetch(:field)
|
|
1032
|
+
value = current[:index] == 1 ? field[:default] : JSON.parse(query)
|
|
1033
|
+
begin
|
|
1034
|
+
@settings.set_file(current.fetch(:settings_file), field.fetch(:path), value)
|
|
1035
|
+
self.palette = nil
|
|
1036
|
+
poll_settings(force: true)
|
|
1037
|
+
@message = "Updated #{field[:path].join(".")}"
|
|
1038
|
+
rescue StandardError => error
|
|
1039
|
+
@message = error.message
|
|
1040
|
+
self.palette = current
|
|
1041
|
+
end
|
|
1042
|
+
elsif kind == :keymap_gui && selected
|
|
1043
|
+
index = current[:indices] ? current[:indices][current[:index]] : current[:index]
|
|
1044
|
+
entry = current.fetch(:entries).fetch(index)
|
|
1045
|
+
self.palette = {kind: :keymap_edit, query: entry[:keys].dup, index: 0, matches: ["Save"],
|
|
1046
|
+
entry: entry, settings_file: current.fetch(:settings_file)}
|
|
1047
|
+
elsif kind == :keymap_edit
|
|
1048
|
+
begin
|
|
1049
|
+
apply_keymap_binding(current.fetch(:entry), query)
|
|
1050
|
+
self.palette = nil
|
|
1051
|
+
@message = "Updated #{current[:entry][:id]} key binding"
|
|
1052
|
+
rescue StandardError => error
|
|
1053
|
+
@message = error.message
|
|
1054
|
+
self.palette = current
|
|
1055
|
+
end
|
|
1056
|
+
elsif kind == :keymap_presets && selected
|
|
1057
|
+
begin
|
|
1058
|
+
load_keymap_preset(selected)
|
|
1059
|
+
self.palette = nil
|
|
1060
|
+
@message = "Loaded #{selected} keymap preset"
|
|
1061
|
+
rescue StandardError => error
|
|
1062
|
+
@message = error.message
|
|
1063
|
+
end
|
|
632
1064
|
end
|
|
633
1065
|
end
|
|
634
1066
|
def search_query(query, state)
|
|
@@ -645,7 +1077,7 @@ module Canopus
|
|
|
645
1077
|
[pattern, search_options]
|
|
646
1078
|
end
|
|
647
1079
|
def connect_server(language, command)
|
|
648
|
-
options =
|
|
1080
|
+
options = normalize_server_configuration(command)
|
|
649
1081
|
(@client_lock ||= Mutex.new).synchronize { ensure_language_server(language, options) }
|
|
650
1082
|
end
|
|
651
1083
|
def drain
|
|
@@ -663,27 +1095,61 @@ module Canopus
|
|
|
663
1095
|
nil
|
|
664
1096
|
end
|
|
665
1097
|
def close
|
|
1098
|
+
reset_auto_save
|
|
666
1099
|
@closed = true
|
|
1100
|
+
cancel_workspace_symbol_search
|
|
1101
|
+
cancel_completion_requests
|
|
667
1102
|
cancel_project_search
|
|
1103
|
+
invalidate_hierarchy
|
|
1104
|
+
invalidate_document_highlights
|
|
1105
|
+
invalidate_folding_ranges
|
|
1106
|
+
invalidate_selection_ranges
|
|
1107
|
+
invalidate_prepare_rename
|
|
1108
|
+
invalidate_document_links
|
|
1109
|
+
invalidate_linked_editing_ranges
|
|
1110
|
+
invalidate_inlay_hints
|
|
1111
|
+
@inlay_hint_requests&.clear
|
|
1112
|
+
invalidate_code_lenses
|
|
1113
|
+
invalidate_sticky_symbols
|
|
1114
|
+
@sticky_symbol_requests&.clear
|
|
668
1115
|
@palette&.dig(:response)&.fulfill({"applied" => false, "failureReason" => "Workspace closed"})
|
|
669
1116
|
self.palette = nil
|
|
670
1117
|
@plugins&.close
|
|
671
|
-
|
|
672
|
-
|
|
1118
|
+
failure = nil
|
|
1119
|
+
cleanup = lambda do |&operation|
|
|
1120
|
+
operation.call
|
|
1121
|
+
rescue StandardError => error
|
|
1122
|
+
failure ||= error
|
|
1123
|
+
end
|
|
1124
|
+
cleanup.call { stop_debugging }
|
|
1125
|
+
cleanup.call { @breakpoints.close }
|
|
1126
|
+
cleanup.call { @minimap.close }
|
|
1127
|
+
cleanup.call { @watcher&.close }
|
|
1128
|
+
cleanup.call { close_git }
|
|
1129
|
+
cleanup.call { stop_language_servers }
|
|
1130
|
+
cleanup.call { close_tests }
|
|
1131
|
+
cleanup.call { close_tasks }
|
|
673
1132
|
terminal_closers = @terminals.filter_map do |current|
|
|
674
1133
|
Thread.new { current.close } if current.respond_to?(:close)
|
|
675
1134
|
end
|
|
676
|
-
terminal_closers.each
|
|
1135
|
+
terminal_closers.each { |thread| cleanup.call { thread.join } }
|
|
677
1136
|
@terminals.clear
|
|
678
|
-
clear_vim_states
|
|
679
|
-
@
|
|
680
|
-
@
|
|
1137
|
+
cleanup.call { clear_vim_states }
|
|
1138
|
+
@buffers.each_value { |buffer| cleanup.call { save_persistent_undo(buffer) } }
|
|
1139
|
+
@panes.each { |pane| pane.editors.each { |current| cleanup.call { current.dispose } } }
|
|
1140
|
+
@buffers.each_value { |buffer| cleanup.call { buffer.close } }
|
|
1141
|
+
cleanup.call { close_recovery(clean: !@preserve_recovery && !failure) }
|
|
1142
|
+
raise failure if failure
|
|
681
1143
|
end
|
|
682
1144
|
|
|
683
1145
|
private
|
|
684
1146
|
def register_actions
|
|
685
1147
|
register_action("file.new") { new_buffer }
|
|
686
1148
|
register_action("file.save") { editor.buffer.path || editor.buffer.is_a?(MultiBuffer) ? save_buffer : palette_open(:save_as) }
|
|
1149
|
+
register_action("file.encoding", description: "Encoding and Line Endings") { show_encoding_actions }
|
|
1150
|
+
register_action("file.newline.lf", description: "Convert Line Endings to LF") { convert_line_endings(:lf) }
|
|
1151
|
+
register_action("file.newline.crlf", description: "Convert Line Endings to CRLF") { convert_line_endings(:crlf) }
|
|
1152
|
+
register_action("file.newline.cr", description: "Convert Line Endings to CR") { convert_line_endings(:cr) }
|
|
687
1153
|
register_action("file.close") { request_close }
|
|
688
1154
|
register_action("tab.close") { request_close }
|
|
689
1155
|
register_action("tab.close_others") { request_close(@active_pane.editors.reject { |current| current.equal?(editor) }) }
|
|
@@ -693,27 +1159,68 @@ module Canopus
|
|
|
693
1159
|
register_action("tab.reopen_closed") { reopen_closed }
|
|
694
1160
|
register_action("pane.close") { request_close(@active_pane.editors.dup) }
|
|
695
1161
|
register_action("debug.buffer_refs") { self.message = buffer_refs(editor.buffer).map { |kind, id| "#{kind}:#{id}" }.join(", ") }
|
|
1162
|
+
register_action("debug.start", description: "Start Debugging") { start_debugging }
|
|
1163
|
+
register_action("debug.stop", description: "Stop Debugging") { stop_debugging }
|
|
1164
|
+
register_action("debug.watch.add", description: "Add Debug Watch") { show_debug_watch_add }
|
|
1165
|
+
register_action("debug.watch.remove", description: "Remove Debug Watch") { show_debug_watch_remove }
|
|
1166
|
+
register_action("debug.console.evaluate", description: "Evaluate in Debug Console") { show_debug_console }
|
|
1167
|
+
register_action("task.run", description: "Run Task") { show_tasks }
|
|
1168
|
+
register_action("task.stop", description: "Stop Task", condition: "TaskOutput") { stop_task }
|
|
1169
|
+
register_action("test.refresh", description: "Refresh Tests") { refresh_tests }
|
|
696
1170
|
register_action("file.find") { palette_open(:files) }
|
|
697
1171
|
register_action("project.new_file") { project_prompt(:create_file) }
|
|
698
1172
|
register_action("project.new_folder") { project_prompt(:create_folder) }
|
|
699
1173
|
register_action("project.rename") { project_prompt(:rename_file) }
|
|
700
1174
|
register_action("project.trash") { project_prompt(:trash_file) }
|
|
701
1175
|
register_action("git.diff") { show_git_diff }
|
|
1176
|
+
register_action("git.stage", description: "Stage File") { stage_git_file }
|
|
1177
|
+
register_action("git.unstage", description: "Unstage File") { unstage_git_file }
|
|
1178
|
+
register_action("git.stage_hunk", description: "Stage Hunk") { stage_git_hunk }
|
|
1179
|
+
register_action("git.unstage_hunk", description: "Unstage Hunk") { unstage_git_hunk }
|
|
1180
|
+
register_action("git.stage_line", description: "Stage Line") { stage_git_line }
|
|
1181
|
+
register_action("git.unstage_line", description: "Unstage Line") { unstage_git_line }
|
|
1182
|
+
register_action("git.commit", description: "Commit Staged Changes") { commit_git }
|
|
1183
|
+
register_action("git.diff.toggle_mode", description: "Toggle Inline / Side-by-Side Diff") { toggle_git_diff_mode }
|
|
1184
|
+
register_action("git.compare_revisions", description: "Compare Git Revisions") { show_git_revision_compare }
|
|
1185
|
+
register_action("git.file_history", description: "Show File History") { show_git_file_history }
|
|
1186
|
+
register_action("git.history", description: "Show Commit History") { show_git_commit_history }
|
|
1187
|
+
register_action("git.fetch", description: "Fetch from Remote") { fetch_git }
|
|
1188
|
+
register_action("git.pull", description: "Pull from Remote") { pull_git }
|
|
1189
|
+
register_action("git.push", description: "Push to Remote") { push_git }
|
|
1190
|
+
register_action("git.cancel_transfer", description: "Cancel Git Transfer") { cancel_git_transfer }
|
|
1191
|
+
register_action("git.conflicts", description: "Resolve Merge Conflicts") { show_git_conflicts }
|
|
1192
|
+
register_action("git.conflict.ours", description: "Resolve Conflict with Ours") { resolve_git_conflict(:ours) }
|
|
1193
|
+
register_action("git.conflict.theirs", description: "Resolve Conflict with Theirs") { resolve_git_conflict(:theirs) }
|
|
1194
|
+
register_action("git.conflict.both", description: "Resolve Conflict with Both") { resolve_git_conflict(:both) }
|
|
1195
|
+
register_action("git.conflict.manual", description: "Resolve Conflict with Manual Edit") { resolve_git_conflict(:manual) }
|
|
702
1196
|
register_action("git.toggle_hunk") { toggle_git_hunk }
|
|
703
1197
|
register_action("git.blame") { show_git_blame }
|
|
704
1198
|
register_action("git.revert_hunk") { revert_current_hunk }
|
|
705
|
-
register_action("git.branches") { self.palette = {kind: :branches, query: +"", index: 0, matches:
|
|
1199
|
+
register_action("git.branches") { self.palette = {kind: :branches, query: +"", index: 0, matches: git_branches} }
|
|
706
1200
|
register_action("command.palette") { palette_open(:commands) }
|
|
707
1201
|
register_action("pane.split_right") { split(:horizontal) }
|
|
708
1202
|
register_action("pane.split_down") { split(:vertical) }
|
|
709
1203
|
register_action("pane.next") { focus(@panes[(@panes.index(@active_pane) + 1) % @panes.length]) }
|
|
710
1204
|
register_action("tab.pin") { @active_pane.pin }
|
|
711
|
-
register_action("tab.back") { @vim_states[editor]&.deactivate; @active_pane.back }
|
|
712
|
-
register_action("tab.forward") { @vim_states[editor]&.deactivate; @active_pane.forward }
|
|
1205
|
+
register_action("tab.back") { @vim_states[editor]&.deactivate; @active_pane.back; invalidate_hidden_selection_ranges }
|
|
1206
|
+
register_action("tab.forward") { @vim_states[editor]&.deactivate; @active_pane.forward; invalidate_hidden_selection_ranges }
|
|
713
1207
|
register_action("edit.undo") { editor.undo }
|
|
714
1208
|
register_action("edit.redo") { editor.redo }
|
|
715
1209
|
register_action("edit.select_all") { editor.select_all }
|
|
716
1210
|
register_action("edit.select_next") { editor.select_next_occurrence }
|
|
1211
|
+
%i[up down].each do |direction|
|
|
1212
|
+
register_action("edit.add_cursor_#{direction}", description: "Add Cursor #{direction.capitalize}", condition: "Editor && !vim_mode") do
|
|
1213
|
+
editor.add_cursor(direction)
|
|
1214
|
+
end
|
|
1215
|
+
end
|
|
1216
|
+
%i[start end].each do |edge|
|
|
1217
|
+
register_action("edit.add_cursors_to_line_#{edge}s", description: "Add Cursors to Line #{edge.capitalize}s", condition: "Editor && !vim_mode") do
|
|
1218
|
+
editor.add_cursors_to_selected_lines(edge)
|
|
1219
|
+
end
|
|
1220
|
+
end
|
|
1221
|
+
register_action("edit.select_all_regex_matches", description: "Select All Regular Expression Matches", condition: "Editor && !vim_mode") do
|
|
1222
|
+
palette_open(:regex_matches)
|
|
1223
|
+
end
|
|
717
1224
|
register_action("edit.select_all_occurrences") { editor.select_next_occurrence(all: true) }
|
|
718
1225
|
register_action("edit.duplicate_line") { editor.duplicate_lines }
|
|
719
1226
|
%i[up down].each do |direction|
|
|
@@ -722,11 +1229,16 @@ module Canopus
|
|
|
722
1229
|
end
|
|
723
1230
|
register_action("edit.toggle_comment") { editor.toggle_comment(prefix: editor.language_document.definition.comment) }
|
|
724
1231
|
register_action("language.outline") { show_outline }
|
|
725
|
-
register_action("language.workspace_symbols") { palette_open(:workspace_symbols) }
|
|
1232
|
+
register_action("language.workspace_symbols", description: "Workspace Symbols") { palette_open(:workspace_symbols) }
|
|
726
1233
|
%i[completion hover definition typeDefinition implementation references formatting codeAction signatureHelp documentSymbol inlayHint codeLens diagnostic semantic_tokens].each do |kind|
|
|
727
1234
|
register_action("language.#{kind}") { language_request(kind) }
|
|
728
1235
|
end
|
|
729
|
-
register_action("language.rename") {
|
|
1236
|
+
register_action("language.rename") { prepare_rename }
|
|
1237
|
+
register_action("language.linked_editing") { linked_editing_range }
|
|
1238
|
+
register_action("language.call_hierarchy") { show_call_hierarchy }
|
|
1239
|
+
register_action("language.type_hierarchy") { show_type_hierarchy }
|
|
1240
|
+
register_action("language.expand_selection") { expand_selection }
|
|
1241
|
+
register_action("language.shrink_selection") { shrink_selection }
|
|
730
1242
|
register_action("editor.fold") { fold_current }
|
|
731
1243
|
register_action("editor.unfold") { editor.display_map.unfold(editor.primary.head) }
|
|
732
1244
|
register_action("edit.indent") { editor.indent }
|
|
@@ -736,11 +1248,26 @@ module Canopus
|
|
|
736
1248
|
register_action("search.replace") { palette_open(:replace_query) }
|
|
737
1249
|
register_action("settings.open") { open_settings }
|
|
738
1250
|
register_action("settings.complete") { settings_completions }
|
|
1251
|
+
register_action("settings.gui", description: "Settings GUI") { settings_gui }
|
|
1252
|
+
register_action("settings.gui_changed", description: "Changed Settings GUI") { settings_gui(changed_only: true) }
|
|
1253
|
+
register_action("keymap.gui", description: "Keymap GUI") { keymap_gui }
|
|
1254
|
+
register_action("keymap.preset", description: "Keymap Preset") { keymap_presets }
|
|
1255
|
+
register_action("workspace.trust.toggle", description: "Toggle Workspace Trust") { toggle_workspace_trust }
|
|
1256
|
+
register_action("workspace.trust.show", description: "Show Workspace Trust") { show_workspace_trust }
|
|
739
1257
|
register_action("language.diagnostics") { show_diagnostics }
|
|
1258
|
+
register_action("language.restart_server") { show_language_server_restart }
|
|
740
1259
|
register_action("view.project") { @panels.toggle("explorer") }
|
|
741
1260
|
register_action("panel.explorer") { @panels.toggle("explorer") }
|
|
1261
|
+
register_action("panel.scm") { @panels.toggle("scm"); refresh_scm if @panels.visible?("scm") }
|
|
742
1262
|
register_action("panel.search") { @panels.fetch("search").build.call }
|
|
743
1263
|
register_action("panel.terminal") { @terminals.empty? ? new_terminal : @panels.toggle("terminal") }
|
|
1264
|
+
register_action("panel.hierarchy") { @panels.toggle("hierarchy") if @hierarchy_state }
|
|
1265
|
+
register_action("panel.problems") { @panels.toggle("problems") }
|
|
1266
|
+
register_action("panel.debug") { @panels.toggle("debug") }
|
|
1267
|
+
register_action("panel.debug_console") { @panels.toggle("debug_console") }
|
|
1268
|
+
register_action("panel.output") { toggle_task_output }
|
|
1269
|
+
register_action("panel.test") { toggle_tests }
|
|
1270
|
+
register_action("problems.filter") { show_problem_filter }
|
|
744
1271
|
[:left, :right, :bottom].each { |side| register_action("view.dock_#{side}") { toggle_dock(side) } }
|
|
745
1272
|
register_action("view.wrap") { editor.display_map.wrap_width = editor.display_map.wrap_map.width ? nil : 100 }
|
|
746
1273
|
register_action("view.theme") { self.theme = @theme.name.include?("Dark") ? "Canopus Light" : "Canopus Dark" }
|
|
@@ -753,13 +1280,46 @@ module Canopus
|
|
|
753
1280
|
end
|
|
754
1281
|
register_action("terminal.toggle") { call("view.terminal") }
|
|
755
1282
|
register_action("terminal.new") { new_terminal }
|
|
1283
|
+
register_action("terminal.split_right", description: "Split Terminal Right", condition: "Terminal") { split_terminal(:horizontal) }
|
|
1284
|
+
register_action("terminal.split_down", description: "Split Terminal Down", condition: "Terminal") { split_terminal(:vertical) }
|
|
756
1285
|
register_action("terminal.close") { request_terminal_close }
|
|
757
1286
|
register_action("terminal.restart") { restart_terminal }
|
|
758
1287
|
register_action("terminal.next") { activate_terminal((@active_terminal_index + 1) % @terminals.length) unless @terminals.empty? }
|
|
759
1288
|
register_action("terminal.prev") { activate_terminal((@active_terminal_index - 1) % @terminals.length) unless @terminals.empty? }
|
|
760
1289
|
9.times { |index| register_action("terminal.select_#{index + 1}") { activate_terminal(index) if index < @terminals.length } }
|
|
761
1290
|
register_action("terminal.rename") { self.palette = {kind: :terminal_rename, query: +"", index: 0, matches: []} if terminal }
|
|
762
|
-
register_action("terminal.
|
|
1291
|
+
register_action("terminal.commands", description: "Show Terminal Commands", condition: "Terminal") { show_terminal_commands }
|
|
1292
|
+
register_action("terminal.commands.failed", description: "Show Failed Terminal Commands", condition: "Terminal") do
|
|
1293
|
+
show_terminal_commands(failed: true)
|
|
1294
|
+
end
|
|
1295
|
+
register_action("terminal.clear") do
|
|
1296
|
+
terminal&.grid&.reset
|
|
1297
|
+
terminal&.clear_commands
|
|
1298
|
+
end
|
|
1299
|
+
end
|
|
1300
|
+
|
|
1301
|
+
def inject_shell_integration(current)
|
|
1302
|
+
shell = current.command_name
|
|
1303
|
+
path = Tarazed::ShellIntegration.path(shell)
|
|
1304
|
+
source = File.basename(shell).downcase.delete_suffix(".exe") == "fish" ? "source" : "."
|
|
1305
|
+
current.input("#{source} #{Shellwords.escape(path)}\n")
|
|
1306
|
+
rescue ArgumentError, IOError, SystemCallError
|
|
1307
|
+
nil
|
|
1308
|
+
end
|
|
1309
|
+
|
|
1310
|
+
def retained_terminal_commands
|
|
1311
|
+
return [] unless terminal&.respond_to?(:commands) && !terminal.grid.alternate?
|
|
1312
|
+
grid = terminal.grid
|
|
1313
|
+
first = grid.scrollback.total - grid.scrollback.length
|
|
1314
|
+
last = grid.scrollback.total + grid.rows - 1
|
|
1315
|
+
terminal.commands.select { |command| command.prompt_row.between?(first, last) }
|
|
1316
|
+
end
|
|
1317
|
+
|
|
1318
|
+
def terminal_command_label(command)
|
|
1319
|
+
input = command.input.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace).scrub
|
|
1320
|
+
.gsub(/[\x00-\x1f\x7f]+/, " ").strip
|
|
1321
|
+
input = "(empty command)" if input.empty?
|
|
1322
|
+
"exit #{command.exit_status} #{input}".slice(0, 240)
|
|
763
1323
|
end
|
|
764
1324
|
def encode_layout(node)
|
|
765
1325
|
node[:pane] ? {pane: @panes.index(node[:pane])} : {direction: node[:direction], ratio: node.fetch(:ratio, 0.5), children: node[:children].map { |child| encode_layout(child) }}
|
|
@@ -812,7 +1372,11 @@ module Canopus
|
|
|
812
1372
|
def release_buffer(buffer, discard: false)
|
|
813
1373
|
return unless buffer_refs(buffer).empty?
|
|
814
1374
|
return if buffer.dirty? && !discard
|
|
1375
|
+
@minimap.release(buffer)
|
|
1376
|
+
invalidate_sticky_symbols(buffer)
|
|
815
1377
|
close_language_documents(buffer)
|
|
1378
|
+
detach_breakpoints(buffer)
|
|
1379
|
+
save_persistent_undo(buffer)
|
|
816
1380
|
@buffers.delete_if { |_, current| current.equal?(buffer) }
|
|
817
1381
|
buffer.close
|
|
818
1382
|
end
|
|
@@ -847,6 +1411,52 @@ module Canopus
|
|
|
847
1411
|
path
|
|
848
1412
|
end
|
|
849
1413
|
|
|
1414
|
+
def terminal_profile_command(profile)
|
|
1415
|
+
return profile["command"] if profile["command"]
|
|
1416
|
+
[profile["path"], *profile.fetch("args", [])] if profile["path"]
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1419
|
+
def invalidate_terminal_layout_resize
|
|
1420
|
+
@terminal_resize_times&.clear
|
|
1421
|
+
end
|
|
1422
|
+
|
|
1423
|
+
def terminal_cwd(current)
|
|
1424
|
+
cwd = current.respond_to?(:cwd) ? current.cwd : current.vt.cwd
|
|
1425
|
+
cwd || (current.respond_to?(:initial_cwd) ? current.initial_cwd : @root)
|
|
1426
|
+
end
|
|
1427
|
+
|
|
1428
|
+
def terminal_layout_include?(current, node = @terminal_layout)
|
|
1429
|
+
return false unless node
|
|
1430
|
+
return node[:terminal].equal?(current) if node[:terminal]
|
|
1431
|
+
node[:children].any? { |child| terminal_layout_include?(current, child) }
|
|
1432
|
+
end
|
|
1433
|
+
|
|
1434
|
+
def terminal_layout_terminals(node = @terminal_layout)
|
|
1435
|
+
return [] unless node
|
|
1436
|
+
return [node[:terminal]] if node[:terminal]
|
|
1437
|
+
node[:children].flat_map { |child| terminal_layout_terminals(child) }
|
|
1438
|
+
end
|
|
1439
|
+
|
|
1440
|
+
def replace_terminal_leaf(node, current, replacement)
|
|
1441
|
+
replace_terminal_node(node, current, {terminal: replacement})
|
|
1442
|
+
end
|
|
1443
|
+
|
|
1444
|
+
def replace_terminal_node(node, current, replacement)
|
|
1445
|
+
return unless node
|
|
1446
|
+
return replacement if node[:terminal]&.equal?(current)
|
|
1447
|
+
return if node[:terminal]
|
|
1448
|
+
children = node[:children].map { |child| replace_terminal_node(child, current, replacement) || child }
|
|
1449
|
+
node.merge(children: children)
|
|
1450
|
+
end
|
|
1451
|
+
|
|
1452
|
+
def remove_terminal_leaf(node, current)
|
|
1453
|
+
return unless node
|
|
1454
|
+
return if node[:terminal]&.equal?(current)
|
|
1455
|
+
return node if node[:terminal]
|
|
1456
|
+
children = node[:children].filter_map { |child| remove_terminal_leaf(child, current) }
|
|
1457
|
+
children.length == 1 ? children.first : node.merge(children: children)
|
|
1458
|
+
end
|
|
1459
|
+
|
|
850
1460
|
def reap_exited_terminals
|
|
851
1461
|
policy = @settings["terminal"]["close_on_exit"]
|
|
852
1462
|
@terminals.dup.each do |current|
|
|
@@ -859,20 +1469,54 @@ module Canopus
|
|
|
859
1469
|
end
|
|
860
1470
|
|
|
861
1471
|
require_relative "workspace/session_persistable"
|
|
1472
|
+
require_relative "workspace/recoverable"
|
|
862
1473
|
require_relative "workspace/project_tree_editable"
|
|
863
1474
|
require_relative "workspace/file_change_aware"
|
|
864
1475
|
require_relative "workspace/language_server_configurable"
|
|
865
1476
|
require_relative "workspace/language_aware"
|
|
1477
|
+
require_relative "workspace/hierarchy_aware"
|
|
1478
|
+
require_relative "workspace/problems_aware"
|
|
866
1479
|
require_relative "workspace/git_aware"
|
|
1480
|
+
require_relative "workspace/git_staging"
|
|
1481
|
+
require_relative "workspace/git_diff_view"
|
|
1482
|
+
require_relative "workspace/git_conflict_resolution"
|
|
1483
|
+
require_relative "workspace/git_history"
|
|
1484
|
+
require_relative "workspace/git_remote"
|
|
1485
|
+
require_relative "workspace/git_blame"
|
|
867
1486
|
require_relative "workspace/project_searchable"
|
|
1487
|
+
require_relative "workspace/editor_configurable"
|
|
868
1488
|
require_relative "workspace/settings_aware"
|
|
1489
|
+
require_relative "workspace/keymap_aware"
|
|
1490
|
+
require_relative "workspace/trust"
|
|
1491
|
+
require_relative "workspace/auto_savable"
|
|
1492
|
+
require_relative "workspace/persistent_undo"
|
|
869
1493
|
require_relative "workspace/file_previewable"
|
|
1494
|
+
require_relative "workspace/debug_aware"
|
|
1495
|
+
require_relative "workspace/task_aware"
|
|
1496
|
+
require_relative "workspace/test_aware"
|
|
870
1497
|
Canopus::Workspace.include Canopus::Workspace::SessionPersistable
|
|
1498
|
+
Canopus::Workspace.include Canopus::Workspace::Recoverable
|
|
871
1499
|
Canopus::Workspace.include Canopus::Workspace::ProjectTreeEditable
|
|
872
1500
|
Canopus::Workspace.include Canopus::Workspace::FileChangeAware
|
|
873
1501
|
Canopus::Workspace.include Canopus::Workspace::LanguageServerConfigurable
|
|
874
1502
|
Canopus::Workspace.include Canopus::Workspace::LanguageAware
|
|
1503
|
+
Canopus::Workspace.include Canopus::Workspace::HierarchyAware
|
|
1504
|
+
Canopus::Workspace.include Canopus::Workspace::ProblemsAware
|
|
875
1505
|
Canopus::Workspace.include Canopus::Workspace::GitAware
|
|
1506
|
+
Canopus::Workspace.include Canopus::Workspace::GitStaging
|
|
1507
|
+
Canopus::Workspace.include Canopus::Workspace::GitDiffView
|
|
1508
|
+
Canopus::Workspace.include Canopus::Workspace::GitConflictResolution
|
|
1509
|
+
Canopus::Workspace.include Canopus::Workspace::GitHistory
|
|
1510
|
+
Canopus::Workspace.include Canopus::Workspace::GitRemote
|
|
1511
|
+
Canopus::Workspace.include Canopus::Workspace::GitBlame
|
|
876
1512
|
Canopus::Workspace.include Canopus::Workspace::ProjectSearchable
|
|
1513
|
+
Canopus::Workspace.include Canopus::Workspace::EditorConfigurable
|
|
877
1514
|
Canopus::Workspace.include Canopus::Workspace::SettingsAware
|
|
1515
|
+
Canopus::Workspace.include Canopus::Workspace::KeymapAware
|
|
1516
|
+
Canopus::Workspace.include Canopus::Workspace::TrustAware
|
|
1517
|
+
Canopus::Workspace.include Canopus::Workspace::AutoSavable
|
|
1518
|
+
Canopus::Workspace.include Canopus::Workspace::PersistentUndo
|
|
878
1519
|
Canopus::Workspace.include Canopus::Workspace::FilePreviewable
|
|
1520
|
+
Canopus::Workspace.include Canopus::Workspace::DebugAware
|
|
1521
|
+
Canopus::Workspace.include Canopus::Workspace::TaskAware
|
|
1522
|
+
Canopus::Workspace.include Canopus::Workspace::TestAware
|