Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7e5c757

Browse files
author
Antonio Scandurra
committed
Add initial implementation of ReviewCommentTracker
1 parent 4b7c700 commit 7e5c757

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"etch-stateless": "^1.0.0",
2424
"keytar": "3.0.2",
2525
"marked": "^0.3.4",
26+
"marker-index": "4.0.0",
2627
"moment": "2.12.0",
2728
"nan": "2.x",
2829
"object-hash": "^1.1.2",
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
'use babel'
2+
3+
import {Point, CompositeDisposable} from 'atom'
4+
import {diffLines} from 'diff'
5+
import MarkerIndex from 'marker-index'
6+
import temp from 'temp'
7+
8+
class ReviewCommentTracker {
9+
constructor (editor) {
10+
this.editor = editor
11+
this.invalidComments = new Map
12+
this.subscriptions = new CompositeDisposable()
13+
14+
// TODO: change to onDidStopChanging
15+
this.editor.onDidSave(() => {
16+
let invalidComments = this.invalidComments
17+
this.invalidComments = new Map
18+
invalidComments.forEach(({fileContents, rowInFileContents}, id) => {
19+
this.track(id, fileContents, rowInFileContents)
20+
})
21+
})
22+
}
23+
24+
destroy () {
25+
this.subscriptions.dispose()
26+
}
27+
28+
track (id, fileContents, rowInFileContents) {
29+
let index = new MarkerIndex()
30+
index.insert(id, {row: rowInFileContents, column: 0}, {row: rowInFileContents + 1, column: 0})
31+
index.setExclusive(id, true)
32+
let currentRow = 0
33+
for (let change of diffLines(fileContents, this.editor.getText())) {
34+
if (change.added) {
35+
let {inside} = index.splice({row: currentRow, column: 0}, {row: 0, column: 0}, {row: change.count, column: 0})
36+
if (inside.has(id)) {
37+
this.invalidComments.set(id, {fileContents, rowInFileContents})
38+
return
39+
}
40+
// TODO: add specs
41+
// currentRow += change.count
42+
} else if (change.removed) {
43+
let {inside} = index.splice({row: currentRow, column: 0}, {row: change.count, column: 0}, {row: 0, column: 0})
44+
if (inside.has(id)) {
45+
this.invalidComments.set(id, {fileContents, rowInFileContents})
46+
return
47+
}
48+
// TODO: add specs
49+
// currentRow = Math.max(0, currentRow - change.count)
50+
} else {
51+
currentRow += change.count
52+
}
53+
}
54+
55+
let marker = this.editor.markBufferRange(index.getRange(id), {reversed: true, invalidate: 'inside'})
56+
let subscription = marker.onDidChange(({isValid}) => {
57+
if (!isValid) {
58+
this.invalidComments.set(id, {fileContents, rowInFileContents})
59+
marker.destroy()
60+
subscription.dispose()
61+
}
62+
})
63+
this.editor.decorateMarker(marker, {type: 'block', position: 'after'})
64+
}
65+
}
66+
67+
describe('ReviewCommentTracker', () => {
68+
let editor, foo
69+
70+
beforeEach(() => {
71+
editor = atom.workspace.buildTextEditor()
72+
foo = new ReviewCommentTracker(editor)
73+
})
74+
75+
it("adds a decoration on the same row when the buffers' contents match", () => {
76+
editor.setText('abc\ndef\nghi')
77+
78+
foo.track(1, 'abc\ndef\nghi', 1)
79+
80+
let decorations = editor.getDecorations({type: 'block'})
81+
expect(decorations.length).toBe(1)
82+
expect(decorations[0].getMarker().getHeadBufferPosition()).toEqual([1, 0])
83+
})
84+
85+
it("adds a decoration on a translated row in the current buffer corresponding to row in the original buffer", () => {
86+
editor.setText('def\nghi\nABC\nDEF\nlmn\nopq\nrst')
87+
88+
foo.track(1, 'abc\ndef\nghi\nlmn\nopq\nrst', 3)
89+
90+
let decorations = editor.getDecorations({type: 'block'})
91+
expect(decorations.length).toBe(1)
92+
expect(decorations[0].getMarker().getHeadBufferPosition()).toEqual([4, 0])
93+
94+
editor.setCursorBufferPosition([4, 0])
95+
editor.insertNewline()
96+
97+
decorations = editor.getDecorations({type: 'block'})
98+
expect(decorations.length).toBe(1)
99+
expect(decorations[0].getMarker().getHeadBufferPosition()).toEqual([5, 0])
100+
})
101+
102+
it("doesn't add a decoration when the comment position doesn't exist anymore, and adds it back on save if it becomes valid again", () => {
103+
editor.setText('def\nghi\nABC\nDEF\nGHI\nopq\nrst')
104+
105+
foo.track(1, 'abc\ndef\nghi\nlmn\nopq\nrst', 3)
106+
107+
let decorations = editor.getDecorations({type: 'block'})
108+
expect(decorations.length).toBe(0)
109+
110+
editor.setSelectedBufferRange([[4, 0], [4, 3]])
111+
editor.insertText('lmn')
112+
editor.saveAs(temp.path())
113+
114+
decorations = editor.getDecorations({type: 'block'})
115+
expect(decorations.length).toBe(1)
116+
expect(decorations[0].getMarker().getHeadBufferPosition()).toEqual([4, 0])
117+
})
118+
119+
it("removes decorations as soon as they become invalid and adds them back on save if they become valid again", () => {
120+
editor.setText('def\nghi\nABC\nDEF\nlmn\nopq\nrst')
121+
122+
foo.track(1, 'abc\ndef\nghi\nlmn\nopq\nrst', 3)
123+
editor.setCursorBufferPosition([4, 0])
124+
editor.deleteLine()
125+
126+
let decorations = editor.getDecorations({type: 'block'})
127+
expect(decorations.length).toBe(0)
128+
129+
editor.undo()
130+
editor.saveAs(temp.path())
131+
132+
decorations = editor.getDecorations({type: 'block'})
133+
expect(decorations.length).toBe(1)
134+
expect(decorations[0].getMarker().getHeadBufferPosition()).toEqual([4, 0])
135+
136+
editor.redo()
137+
138+
decorations = editor.getDecorations({type: 'block'})
139+
expect(decorations.length).toBe(0)
140+
141+
editor.save()
142+
143+
decorations = editor.getDecorations({type: 'block'})
144+
expect(decorations.length).toBe(0)
145+
146+
editor.undo()
147+
editor.save()
148+
149+
decorations = editor.getDecorations({type: 'block'})
150+
expect(decorations.length).toBe(1)
151+
expect(decorations[0].getMarker().getHeadBufferPosition()).toEqual([4, 0])
152+
})
153+
})

0 commit comments

Comments
 (0)