From 2f540c4db1519f6de06286d3e63ec1c9751c778d Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Fri, 16 Dec 2016 15:20:55 +0100 Subject: [PATCH] Manual edit: Simplify add/remove of full lines Always choose the simplest diff. --- nbdime-web/src/common/mergeview.ts | 7 +-- nbdime-web/src/merge/manualedit.ts | 82 +++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/nbdime-web/src/common/mergeview.ts b/nbdime-web/src/common/mergeview.ts index c3f4a9b3..e44e65bc 100644 --- a/nbdime-web/src/common/mergeview.ts +++ b/nbdime-web/src/common/mergeview.ts @@ -50,7 +50,7 @@ import { } from './exceptions'; import { - updateModel + updateModel, shiftAddRemoveLines } from '../merge/manualedit'; @@ -375,12 +375,13 @@ class DiffView { debounceChange = window.setTimeout( update.bind(self, mode), fast === true ? 20 : 250); } - function change(_cm: CodeMirror.Editor, change: CodeMirror.EditorChangeLinkedList) { + function change(_cm: CodeMirror.Editor, change: CodeMirror.EditorChange) { let userEdit = !valueIn(change.origin, ['setValue', 'syncModel']); if (userEdit) { // Edited by hand! - let baseLine = getMatchingBaseLine(change.from.line, self.lineChunks); let fullLines = splitLines(self.model.remote!); + shiftAddRemoveLines(fullLines, change); + let baseLine = getMatchingBaseLine(change.from.line, self.lineChunks); // Update lines with changes replaceCodeMirrorRange(fullLines, change.from, change.to, change.text); updateModel({ diff --git a/nbdime-web/src/merge/manualedit.ts b/nbdime-web/src/merge/manualedit.ts index 8f610634..f7f50dcd 100644 --- a/nbdime-web/src/merge/manualedit.ts +++ b/nbdime-web/src/merge/manualedit.ts @@ -214,7 +214,7 @@ function getPartials(diff: AddRem[], options: IUpdateModelOptions, startOffset: let line = options.baseLine; let ch = options.editCh; let {oldval, newval} = options; - let lines: string[] | null = null; + let lines: string[] = options.fullLines; let key = line; let linediff = 0; @@ -245,14 +245,12 @@ function getPartials(diff: AddRem[], options: IUpdateModelOptions, startOffset: } else if (partialStart) { // Addrange before current line (abutting) // and partial edit of current (unedited) line - lines = options.fullLines; before.push(lines[options.editLine].slice(0, ch)); // Indicate that one unedited line should be removed linediff = 1; } } else if (partialStart) { // Replace previously unedited line: - lines = options.fullLines; before = [lines[options.editLine].slice(0, ch)]; // Indicate that one unedited line should be removed linediff = 1; @@ -279,9 +277,6 @@ function getPartials(diff: AddRem[], options: IUpdateModelOptions, startOffset: } if (!after && !fullDeleteEnd && !fullLineInsertions) { // Add remains of previously unedited line - if (lines === null) { - lines = options.fullLines; - } let fullLine = lines[options.editLine + newval.length - 1]; let cut = lastAddedLine.length; if (newval.length === 1) { @@ -773,6 +768,81 @@ function patchPatchedModel(options: IUpdateModelOptions, diffs: 'all' | 'custom' // Add new decisions for changes that do no overlap } + +/** + * Modify certain edits to use trailing newlines instead of leading + * + * Certain edits, e.g. adding a newline link this (bracketed part added): + * line1[\n + * line2]\n + * can better be treated as: + * line1\n + * [line2\n] + * + * Similarly (bracketed part deleted): + * line1[\n + * line2]\n + * can better be treated as: + * line1\n + * [line2\n] + * + * The end results are the same, but the diffs become simpler. + * + * Note that this needs to be called before base line is computed! + */ +export +function shiftAddRemoveLines(base: string[], change: CodeMirror.EditorChange) { + if (change.from.ch === 0) { + // Nothing to do here + return; + } + if (change.removed.length === 1 && change.removed[0] === '') { + // Nothing removed + // Check whether first inserted character is newline + if (change.text.length > 1 && change.text[0] === '') { + // and first subsequent character is newline + let trailingLine = base[change.to.line]; + if (trailingLine.length > change.to.ch && trailingLine[change.to.ch] === '\n') { + // Match, shift newline from head to tail + change.from.line += 1; + change.from.ch = 0; + change.text.push(change.text.shift()!); + } + } + } else if (change.text.length === 1 && change.text[0] === '') { + // Nothing added + // Check whether first removed character is newline + if (change.removed.length > 1 && change.removed[0] === '') { + // and first subsequent character is newline + let trailingLine = base[change.to.line]; + if (trailingLine.length > change.to.ch && trailingLine[change.to.ch] === '\n') { + // Match, shift newline from head to tail + change.from.line += 1; + change.from.ch = 0; + change.to.line += 1; + change.to.ch = 0; + change.removed.push(change.removed.shift()!); + } + } + } else { + // Both added and removed + // Check whether first removed character is newline + if (change.removed.length > 1 && change.removed[0] === '') { + // and first subsequent character is newline + let trailingLine = base[change.to.line]; + if (trailingLine.length > change.to.ch && trailingLine[change.to.ch] === '\n') { + // Match, shift newline from head to tail + change.from.line += 1; + change.from.ch = 0; + change.to.line += 1; + change.to.ch = 0; + change.text.push(change.text.shift()!); + change.removed.push(change.removed.shift()!); + } + } + } +} + export function updateModel(options: IUpdateModelOptions) { let model = options.model;