Module: Canopus::Workspace::GitDiffView
- Defined in:
- lib/canopus/workspace/git_diff_view.rb
Defined Under Namespace
Classes: GitDiffSource, GitDiffView, GitHistoryEntry
Constant Summary collapse
- MAX_DIFF_BYTES =
10 << 20
- MAX_HISTORY =
200- MAX_HISTORY_SCAN =
20_000- MAX_PATH_BYTES =
4_096- MAX_REVISION_BYTES =
1_024- SIDE_COLUMN_WIDTH =
160- DIFF_BUDGET =
Porrima::Budget.new(max_bytes: 1 << 20, max_lines: 2_000)
- DELETED_STYLE =
{color: "#f9758344"}.freeze
- INSERTED_STYLE =
{color: "#80b98744"}.freeze
Instance Method Summary collapse
- #close_git ⇒ Object
- #compare_git_revisions(before, after, path: nil, mode: :inline, async: !!@window)) ⇒ Object
- #git_file_history(path = nil, revision: "HEAD", limit: 100) ⇒ Object
- #scm_diff_decorations(buffer, rows) ⇒ Object
- #show_git_diff(mode: :inline, async: !!@window)) ⇒ Object
- #show_git_file_history(path = nil, revision: "HEAD", limit: 100, async: !!@window)) ⇒ Object
- #show_git_revision_compare(path = nil) ⇒ Object
- #show_git_revision_diff(before, after, **options) ⇒ Object
- #show_revision_diff(before, after, **options) ⇒ Object
- #show_scm_diff(change = @scm_selection, mode: :inline, async: !!@window)) ⇒ Object
- #toggle_git_diff_mode(current = editor, async: !!@window)) ⇒ Object (also: #toggle_scm_diff_mode)
Instance Method Details
#close_git ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 163 def close_git @git_view_generation = (@git_view_generation || 0) + 1 @git_history_generation = (@git_history_generation || 0) + 1 jobs = [*@git_view_jobs, *@git_history_jobs].compact.uniq.reject { |thread| thread.equal?(Thread.current) } deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 0.1 jobs.each do |thread| remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC) break unless remaining.positive? thread.join(remaining) end jobs.each { |thread| thread.kill if thread.alive? } jobs.each(&:join) @git_view_jobs&.clear @git_history_jobs&.clear super end |
#compare_git_revisions(before, after, path: nil, mode: :inline, async: !!@window)) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 58 def compare_git_revisions(before, after, path: nil, mode: :inline, async: !!@window) state = git_state raise Error, "Not a Git repository" unless state path = safe_git_diff_path(git_diff_target_path(path)) before = validate_git_revision(before, allow_nil: true) after = validate_git_revision(after) mode = normalize_git_diff_mode(mode) schedule_git_diff(mode: mode, async: async) do captured = state.synchronize do |repository| left_commit = resolve_git_revision(repository, before) right_commit = resolve_git_revision(repository, after) left, left_text = revision_file_content(repository, left_commit, path) right, right_text = revision_file_content(repository, right_commit, path) = (left, right) if left_text == right_text && (left.exists != right.exists || left.mode != right.mode) unavailable = git_diff_unavailable(path, left_text, right_text, byte_values: [left.raw, right.raw]) [left_text, right_text, , unavailable].freeze end build_git_diff_source(path, captured[0], captured[1], snapshot: nil, metadata: captured[2], unavailable: captured[3], before_label: revision_label(path, before), after_label: revision_label(path, after)) end end |
#git_file_history(path = nil, revision: "HEAD", limit: 100) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 95 def git_file_history(path = nil, revision: "HEAD", limit: 100) state = git_state raise Error, "Not a Git repository" unless state path = safe_git_diff_path(git_diff_target_path(path)) revision = validate_git_revision(revision) unless limit.is_a?(Integer) && limit.between?(1, MAX_HISTORY) raise ArgumentError, "history limit must be between 1 and #{MAX_HISTORY}" end state.synchronize { |repository| collect_git_file_history(repository, path, revision, limit) } end |
#scm_diff_decorations(buffer, rows) ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 133 def scm_diff_decorations(buffer, rows) view = buffer.instance_variable_get(:@git_diff_view) return super unless view items = view.highlights.select do |item| row = buffer.rope.point_at(item.range.begin).row rows.cover?(row) end snapshot = view.source.snapshot return items unless snapshot&.diff && !snapshot.conflicted action = snapshot.kind == :staged ? :unstage : :stage verb = action == :stage ? "Stage" : "Unstage" view.line_rows.each do |display_row, index| next unless rows.cover?(display_row) click = ->(current, row) { change_scm_line(action, index, current.buffer, row) } items << Decoration::Item.new(:gutter, nil, display_row, "#{verb} line", {color: :accent, gutter_offset: 2, gutter_width: 3, hit_width: 10}.freeze, 1, :scm_diff, click) end view.hunk_rows.each do |display_row, index| next unless rows.cover?(display_row) && !view.line_rows.key?(display_row) click = ->(current, row) { change_scm_hunk(action, index, current.buffer, row) } items << Decoration::Item.new(:gutter, nil, display_row, "#{verb} hunk", {color: :muted, gutter_offset: 2, gutter_width: 3, hit_width: 10}.freeze, 2, :scm_diff, click) end items end |
#show_git_diff(mode: :inline, async: !!@window)) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 42 def show_git_diff(mode: :inline, async: !!@window) current = editor path = safe_git_diff_path(git_relative_path(current.buffer)) after_size = current.buffer.rope.bytesize after = after_size <= MAX_DIFF_BYTES ? current.buffer.text.dup.freeze : nil state = git_state raise Error, "Not a Git repository" unless state mode = normalize_git_diff_mode(mode) schedule_git_diff(mode: mode, async: async) do raw = state.synchronize { |repository| repository.blob(path) } before = decode_git_diff_content(raw) unavailable = git_diff_unavailable(path, before, after, byte_values: [raw, after_size]) build_git_diff_source(path, before, after, unavailable: unavailable) end end |
#show_git_file_history(path = nil, revision: "HEAD", limit: 100, async: !!@window)) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 107 def show_git_file_history(path = nil, revision: "HEAD", limit: 100, async: !!@window) path = safe_git_diff_path(git_diff_target_path(path)) revision = validate_git_revision(revision) if @window && async generation = @git_history_generation = (@git_history_generation || 0) + 1 palette_generation = @palette_generation.to_i @message = "Loading Git history…" worker = Thread.new do current_worker = Thread.current entries = git_file_history(path, revision: revision, limit: limit) post { finish_git_history(current_worker, generation, palette_generation, path, entries, nil) } unless @closed rescue StandardError => error post { finish_git_history(current_worker, generation, palette_generation, path, nil, error) } unless @closed end (@git_history_jobs ||= []) << worker return worker end install_git_history(path, git_file_history(path, revision: revision, limit: limit)) end |
#show_git_revision_compare(path = nil) ⇒ Object
128 129 130 131 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 128 def show_git_revision_compare(path = nil) path = safe_git_diff_path(git_diff_target_path(path)) self.palette = {kind: :git_revision_compare, query: +"", index: 0, matches: [], path: path} end |
#show_git_revision_diff(before, after, **options) ⇒ Object
81 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 81 def show_git_revision_diff(before, after, **) = compare_git_revisions(before, after, **) |
#show_revision_diff(before, after, **options) ⇒ Object
82 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 82 def show_revision_diff(before, after, **) = compare_git_revisions(before, after, **) |
#show_scm_diff(change = @scm_selection, mode: :inline, async: !!@window)) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 21 def show_scm_diff(change = @scm_selection, mode: :inline, async: !!@window) sections = Workspace::GitStaging::SCM_SECTIONS raise Error, "Select a changed file" unless change.is_a?(Hash) && sections.key?(change[:kind]) state = git_state raise Error, "Not a Git repository" unless state path = safe_git_diff_path(change.fetch(:path)) mode = normalize_git_diff_mode(mode) schedule_git_diff(mode: mode, async: async) do capture = state.synchronize { |repository| capture_scm_diff(repository, path, change.fetch(:kind)) } snapshot, fallback, pair = materialize_scm_diff(capture) = (snapshot.before, snapshot.after) if snapshot.diff&.empty? && !snapshot.hunks.empty? unavailable = if pair.compact.empty? git_diff_unavailable(path, snapshot.before.text, snapshot.after.text, byte_values: [snapshot.before.raw, snapshot.after.raw]) || fallback end build_git_diff_source(path, pair.first, pair.last, snapshot: snapshot, metadata: , diff: snapshot.diff, unavailable: unavailable) end end |
#toggle_git_diff_mode(current = editor, async: !!@window)) ⇒ Object Also known as: toggle_scm_diff_mode
84 85 86 87 88 89 90 91 |
# File 'lib/canopus/workspace/git_diff_view.rb', line 84 def toggle_git_diff_mode(current = editor, async: !!@window) view = current&.buffer&.instance_variable_get(:@git_diff_view) raise Error, "Open a Git diff first" unless view mode = view.mode == :inline ? :side_by_side : :inline pane = @panes.find { |candidate| candidate.editors.include?(current) } schedule_git_diff(mode: mode, async: async, pane: pane, replacing: current) { view.source } end |