Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Take a look at [demos on Ace-diff page](https://ace-diff.github.io/ace-diff/). T
- Readonly option for left/right editors
- Control how aggressively diffs are combined
- Allow users to copy diffs from one side to the other
- Character-level diff highlighting (shows exact changes within lines)
- Gutter decorations marking changed lines

## How to install

Expand Down Expand Up @@ -113,6 +115,7 @@ Here are all the defaults. I'll explain each one in details below. Note: you onl
diffGranularity: 'broad',
showDiffs: true,
showConnectors: true,
charDiffs: true,
maxDiffs: 5000,
left: {
id: null,
Expand All @@ -133,6 +136,8 @@ Here are all the defaults. I'll explain each one in details below. Note: you onl
classes: {
gutterID: 'acediff__gutter',
diff: 'acediff__diffLine',
diffChar: 'acediff__diffChar',
diffGutter: 'acediff__diffGutter',
connector: 'acediff__connector',
newCodeConnectorLink: 'acediff__newCodeConnector',
newCodeConnectorLinkContent: '→',
Expand All @@ -154,6 +159,7 @@ Here are all the defaults. I'll explain each one in details below. Note: you onl
- `diffGranularity` (string, optional, default: `broad`). this has two options (`specific`, and `broad`). Basically this determines how aggressively AceDiff combines diffs to simplify the interface. I found that often it's a judgement call as to whether multiple diffs on one side should be grouped. This setting provides a little control over it.
- `showDiffs` (boolean, optional, default: `true`). Whether or not the diffs are enabled. This basically turns everything off.
- `showConnectors` (boolean, optional, default: `true`). Whether or not the gutter in the middle show show connectors visualizing where the left and right changes map to one another.
- `charDiffs` (boolean, optional, default: `true`). When enabled, highlights the specific characters that changed within a line, not just the whole line. Provides more granular diff visualization.
- `maxDiffs` (integer, optional, default: `5000`). This was added a safety precaution. For really massive files with vast numbers of diffs, it's possible the Ace instances or AceDiff will become too laggy. This simply disables the diffing altogether once you hit a certain number of diffs.
- `left/right`. this object contains settings specific to the leftmost editor.
- `left.content / right.content` (string, optional, default: `null`). If you like, when you instantiate AceDiff you can include the content that should appear in the leftmost editor via this property.
Expand All @@ -167,6 +173,8 @@ Here are all the defaults. I'll explain each one in details below. Note: you onl

- `gutterID`: the ID for the gutter element between editors
- `diff`: the class for a diff line on either editor
- `diffChar`: the class for character-level diff highlighting (used when `charDiffs` is enabled)
- `diffGutter`: the class for gutter decorations on diff lines
- `connector`: the SVG connector class
- `newCodeConnectorLink`: the class for the copy-to-right link element
- `newCodeConnectorLinkContent`: the content of the copy to right link. Defaults to a unicode right arrow ('→')
Expand All @@ -183,6 +191,7 @@ There are a few API methods available on your AceDiff instance.
- `aceInstance.setOptions()`: this lets you set many of the above options on the fly. Note: certain things used during the construction of the editor, like the classes can't be overridden.
- `aceInstance.getNumDiffs()`: returns the number of diffs currently being displayed.
- `aceInstance.diff()`: updates the diff. This shouldn't ever be required because AceDiff automatically recognizes the key events like changes to the editor and window resizing. But I've included it because there may always be that fringe case...
- `aceInstance.clear()`: clears all diff markers, gutter decorations, and connectors without destroying the editors. Useful when you want to temporarily hide diffs.
- `aceInstance.destroy()`: destroys the AceDiff instance. Basically this just destroys both editors and cleans out the gutter.

## Browser Support
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"main": "dist/ace-diff.min.js",
"module": "dist/module.js",
"scripts": {
"build": "parcel build",
"build": "rm -rf .parcel-cache && parcel build",
"watch": "parcel watch",
"dev": "parcel serve test/fixtures/index.html --open",
"serve": "parcel test/fixtures/*.html -p 8081",
Expand Down
132 changes: 120 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import query from './dom/query.js'
import C from './constants.js'

import './styles/ace-diff.scss'
import './styles/ace-diff-dark.scss'

// Range module placeholder
let Range
Expand Down Expand Up @@ -56,6 +55,7 @@ export default function AceDiff(options = {}) {
lockScrolling: false, // not implemented yet
showDiffs: true,
showConnectors: true,
charDiffs: true,
maxDiffs: 5000,
left: {
id: null,
Expand All @@ -76,6 +76,8 @@ export default function AceDiff(options = {}) {
classes: {
gutterID: 'acediff__gutter',
diff: 'acediff__diffLine',
diffChar: 'acediff__diffChar',
diffGutter: 'acediff__diffGutter',
connector: 'acediff__connector',
newCodeConnectorLink: 'acediff__newCodeConnector',
newCodeConnectorLinkContent: '→',
Expand Down Expand Up @@ -132,7 +134,7 @@ export default function AceDiff(options = {}) {
)
acediff.options.right.id = ensureElement(acediff.el, 'acediff__right')

acediff.el.innerHTML = `<div class="acediff__wrap">${acediff.el.innerHTML}</div>`
acediff.el.innerHTML = `<div class="acediff acediff__wrap">${acediff.el.innerHTML}</div>`

// instantiate the editors in an internal data structure
// that will store a little info about the diffs and
Expand All @@ -142,11 +144,13 @@ export default function AceDiff(options = {}) {
ace: ace.edit(acediff.options.left.id),
markers: [],
lineLengths: [],
diffGutters: [],
},
right: {
ace: ace.edit(acediff.options.right.id),
markers: [],
lineLengths: [],
diffGutters: [],
},
editorHeight: null,
}
Expand Down Expand Up @@ -260,6 +264,12 @@ AceDiff.prototype = {
decorate(this)
},

clear() {
clearDiffs(this)
clearGutter(this)
clearArrows(this)
},

destroy() {
// destroy the two editors
const leftValue = this.editors.left.ace.getValue()
Expand Down Expand Up @@ -398,7 +408,7 @@ function getLineLengths(editor) {
}

// shows a diff in one of the two editors.
function showDiff(acediff, editor, startLine, endLine, className) {
function showDiff(acediff, editor, startLine, endLine, chars, className) {
const editorInstance = acediff.editors[editor]

if (endLine < startLine) {
Expand All @@ -408,20 +418,42 @@ function showDiff(acediff, editor, startLine, endLine, className) {

const classNames = `${className} ${
endLine > startLine ? 'lines' : 'targetOnly'
}`
} ${editor}`

if (endLine > startLine) {
endLine -= 1 /* because endLine is usually + 1 */
let markerEndLine = endLine
if (markerEndLine > startLine) {
markerEndLine -= 1 /* because endLine is usually + 1 */
}

// to get Ace to highlight the full row we just set the start and end chars to 0 and 1
editorInstance.markers.push(
editorInstance.ace.session.addMarker(
new Range(startLine, 0, endLine, 1),
new Range(startLine, 0, markerEndLine, 1),
classNames,
'fullLine',
),
)

// Add character-level highlighting if charDiffs is enabled
if (acediff.options.charDiffs && chars && chars.length > 0) {
const charClassName = `${acediff.options.classes.diffChar} ${editor}`
chars.forEach((char) => {
editorInstance.markers.push(
editorInstance.ace.session.addMarker(
new Range(char.lineStart, char.start, char.lineEnd - 1, char.end),
charClassName,
'text',
),
)
})
}

// Add gutter decorations for diff lines
const gutterClassName = `${acediff.options.classes.diffGutter} ${editor}`
for (let line = startLine; line < endLine; line += 1) {
editorInstance.ace.session.addGutterDecoration(line, gutterClassName)
editorInstance.diffGutters.push({ line, className: gutterClassName })
}
}

// called onscroll. Updates the gap to ensure the connectors are all lining up
Expand All @@ -434,12 +466,31 @@ function updateGap(acediff) {
}

function clearDiffs(acediff) {
// Clear markers
acediff.editors.left.markers.forEach((marker) => {
acediff.editors.left.ace.getSession().removeMarker(marker)
}, acediff)
acediff.editors.right.markers.forEach((marker) => {
acediff.editors.right.ace.getSession().removeMarker(marker)
}, acediff)
acediff.editors.left.markers = []
acediff.editors.right.markers = []

// Clear gutter decorations
acediff.editors.left.diffGutters.forEach((gutter) => {
acediff.editors.left.ace.session.removeGutterDecoration(
gutter.line,
gutter.className,
)
}, acediff)
acediff.editors.right.diffGutters.forEach((gutter) => {
acediff.editors.right.ace.session.removeGutterDecoration(
gutter.line,
gutter.className,
)
}, acediff)
acediff.editors.left.diffGutters = []
acediff.editors.right.diffGutters = []
}

function addConnector(
Expand Down Expand Up @@ -599,6 +650,9 @@ function computeDiff(acediff, diffType, offsetLeft, offsetRight, diffText) {
leftEndOffset: offsetLeft + diffText.length,
rightStartOffset: offsetRight,
rightEndOffset: offsetRight,
// Store character positions for charDiffs highlighting
leftStartChar: info.startChar,
leftEndChar: info.endChar,
}
} else {
let info = getSingleDiffInfo(acediff.editors.right, offsetRight, diffText)
Expand Down Expand Up @@ -644,6 +698,9 @@ function computeDiff(acediff, diffType, offsetLeft, offsetRight, diffText) {
leftEndOffset: offsetLeft,
rightStartOffset: offsetRight,
rightEndOffset: offsetRight + diffText.length,
// Store character positions for charDiffs highlighting
rightStartChar: info.startChar,
rightEndChar: info.endChar,
}
}

Expand Down Expand Up @@ -783,14 +840,20 @@ function clearGutter(acediff) {
// gutter.innerHTML = '';

const gutterEl = document.getElementById(acediff.options.classes.gutterID)
gutterEl.removeChild(acediff.gutterSVG)
if (acediff.gutterSVG) {
gutterEl.removeChild(acediff.gutterSVG)
}

createGutter(acediff)
}

function clearArrows(acediff) {
acediff.copyLeftContainer.innerHTML = ''
acediff.copyRightContainer.innerHTML = ''
if (acediff.copyLeftContainer) {
acediff.copyLeftContainer.innerHTML = ''
}
if (acediff.copyRightContainer) {
acediff.copyRightContainer.innerHTML = ''
}
}

/*
Expand All @@ -806,9 +869,35 @@ function simplifyDiffs(acediff, diffs) {
: val <= 1
}

// Helper to create a diff object with character arrays for charDiffs
function createDiffWithChars(diff) {
const newDiff = Object.assign({}, diff, {
leftChars: [],
rightChars: [],
})
// Add character range info if present
if (diff.leftEndChar !== undefined) {
newDiff.leftChars.push({
start: diff.leftStartChar,
end: diff.leftEndChar,
lineStart: diff.leftStartLine,
lineEnd: diff.leftEndLine,
})
}
if (diff.rightEndChar !== undefined) {
newDiff.rightChars.push({
start: diff.rightStartChar,
end: diff.rightEndChar,
lineStart: diff.rightStartLine,
lineEnd: diff.rightEndLine,
})
}
return newDiff
}

diffs.forEach((diff, index) => {
if (index === 0) {
groupedDiffs.push(diff)
groupedDiffs.push(createDiffWithChars(diff))
return
}

Expand Down Expand Up @@ -854,13 +943,30 @@ function simplifyDiffs(acediff, diffs) {
diff.rightEndOffset,
groupedDiffs[i].rightEndOffset,
)
// Add character ranges to the grouped diff
if (diff.leftEndChar !== undefined) {
groupedDiffs[i].leftChars.push({
start: diff.leftStartChar,
end: diff.leftEndChar,
lineStart: diff.leftStartLine,
lineEnd: diff.leftEndLine,
})
}
if (diff.rightEndChar !== undefined) {
groupedDiffs[i].rightChars.push({
start: diff.rightStartChar,
end: diff.rightEndChar,
lineStart: diff.rightStartLine,
lineEnd: diff.rightEndLine,
})
}
isGrouped = true
break
}
}

if (!isGrouped) {
groupedDiffs.push(diff)
groupedDiffs.push(createDiffWithChars(diff))
}
})

Expand Down Expand Up @@ -890,13 +996,15 @@ function decorate(acediff) {
C.EDITOR_LEFT,
info.leftStartLine,
info.leftEndLine,
info.leftChars,
acediff.options.classes.diff,
)
showDiff(
acediff,
C.EDITOR_RIGHT,
info.rightStartLine,
info.rightEndLine,
info.rightChars,
acediff.options.classes.diff,
)

Expand Down
13 changes: 13 additions & 0 deletions src/styles/_ace-diff-base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ $gutterBackground: #efefef !default;
$copyArrowsColor: #000 !default;
$mergeRightColor: #c98100 !default;
$mergeLeftColor: #004ea0 !default;
$charDiffBackground: color.adjust($connectorBackground, $lightness: -15%) !default;

.acediff {
// .acediff class itself got no styles
Expand Down Expand Up @@ -58,6 +59,18 @@ $mergeLeftColor: #004ea0 !default;
}
}

// Character-level diff highlighting (darker shade within diff lines)
&__diffChar {
background-color: $charDiffBackground;
position: absolute;
z-index: 5;
}

// Gutter decoration for diff lines
&__diffGutter {
background-color: $connectorBackground !important;
}

// SVG connector
&__connector {
fill: $connectorBackground;
Expand Down