Class: Docsmith::Diff::Renderers::Base
- Inherits:
-
Object
- Object
- Docsmith::Diff::Renderers::Base
- Defined in:
- lib/docsmith/diff/renderers/base.rb
Overview
Line-level diff renderer, and the base for the format-aware parsers.
Subclasses override #tokenize only. All grouping, offset arithmetic, and HTML rendering lives here, so every content type produces the same edit structure and only the token boundaries differ.
An edit is a contiguous run of changes collapsed into one entry:
{ type: :replace,
old: { start: 0, end: 11, line: 1, column: 1, text: "my document" },
new: { start: 0, end: 30, line: 1, column: 1, text: "<h1>..." } }
Both sides are always present. A pure insertion carries a zero-width old span at the insertion point; a pure deletion a zero-width new span. Every edit therefore has identical keys, and offsets on the two sides never share a coordinate space.
text is sliced straight out of the source between the run's offsets, so
whitespace the tokenizer discarded is preserved verbatim and both
documents round-trip exactly.
Direct Known Subclasses
Constant Summary collapse
- OLD_SIDE =
Actions that consume a token from the old side / the new side.
%w[- !].freeze
- NEW_SIDE =
%w[+ !].freeze
Instance Method Summary collapse
-
#compute(old_content, new_content) ⇒ Array<Hash>
Computes grouped edits between two content strings.
-
#render_html(edits) ⇒ String
Renders grouped edits as an HTML diff representation.
-
#tokenize(content) ⇒ Array<Array(String, Integer)>
Splits content into [text, start_offset] pairs.
Instance Method Details
#compute(old_content, new_content) ⇒ Array<Hash>
Computes grouped edits between two content strings.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/docsmith/diff/renderers/base.rb', line 58 def compute(old_content, new_content) old_content = old_content.to_s new_content = new_content.to_s old_tokens = tokenize(old_content) new_tokens = tokenize(new_content) edits = [] run = [] ::Diff::LCS.sdiff(old_tokens.map(&:first), new_tokens.map(&:first)).each do |hunk| if hunk.action == "=" edits << build_edit(run, old_tokens, new_tokens, old_content, new_content) unless run.empty? run = [] else run << hunk end end edits << build_edit(run, old_tokens, new_tokens, old_content, new_content) unless run.empty? edits end |
#render_html(edits) ⇒ String
Renders grouped edits as an HTML diff representation.
84 85 86 87 88 89 90 91 92 93 |
# File 'lib/docsmith/diff/renderers/base.rb', line 84 def render_html(edits) lines = edits.map do |edit| case edit[:type] when :insert then insertion(edit[:new][:text]) when :delete then deletion(edit[:old][:text]) when :replace then deletion(edit[:old][:text]) + insertion(edit[:new][:text]) end end %(<div class="docsmith-diff">#{lines.join("\n")}</div>) end |
#tokenize(content) ⇒ Array<Array(String, Integer)>
Splits content into [text, start_offset] pairs. Lines here, excluding the newline itself. Override in subclasses for other content types.
The newline is deliberately not part of the token: including it made appending a line report the previous line as changed, because that line gained a trailing newline. Empty lines are preserved as empty tokens so that blank-line changes are still detected.
44 45 46 47 48 49 50 51 |
# File 'lib/docsmith/diff/renderers/base.rb', line 44 def tokenize(content) offset = 0 content.to_s.split("\n", -1).map do |line| token = [line, offset] offset += line.length + 1 token end end |