Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit 77600bd

Browse files
authored
Merge pull request #353 from odoyle71/mod
[Code Submission] RFC-002: Sync Buffer Path Changes from Host To Guest
2 parents 5369397 + 8d5ea4e commit 77600bd

10 files changed

+209
-131
lines changed

doc/rfcs/002-sync-buffer-path-changes-from-host-to-guest.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
### Status
44

5-
Accepted
5+
Implemented. See [#352](https://github.com/atom/teletype/issues/352).
66

77
### Summary
88

lib/buffer-binding.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
const {Range, TextBuffer} = require('atom')
1+
const {Emitter, Range, CompositeDisposable, TextBuffer} = require('atom')
2+
const getPathWithNativeSeparators = require('./get-path-with-native-separators')
3+
const path = require('path')
24

35
function doNothing () {}
46

@@ -12,15 +14,21 @@ class BufferBinding {
1214
this.pendingChanges = []
1315
this.disposed = false
1416
this.disableHistory = false
17+
this.subscriptions = new CompositeDisposable()
18+
if (isHost) {
19+
this.subscriptions.add(buffer.onDidChangePath(this.relayURIChange.bind(this)))
20+
}
1521
}
1622

1723
dispose () {
1824
if (this.disposed) return
1925

2026
this.disposed = true
27+
this.subscriptions.dispose()
2128
this.buffer.restoreDefaultHistoryProvider(this.bufferProxy.getHistory(this.buffer.maxUndoEntries))
2229
this.buffer = null
2330
if (this.bufferDestroySubscription) this.bufferDestroySubscription.dispose()
31+
if (this.remoteFile) this.remoteFile.dispose()
2432
this.emitDidDispose()
2533
}
2634

@@ -31,6 +39,10 @@ class BufferBinding {
3139
this.pushChange(this.pendingChanges.shift())
3240
}
3341
this.pendingChanges = null
42+
if (!this.isHost) {
43+
this.remoteFile = new RemoteFile({uri: bufferProxy.uri})
44+
this.buffer.setFile(this.remoteFile)
45+
}
3446
this.bufferDestroySubscription = this.buffer.onDidDestroy(() => {
3547
if (this.isHost) {
3648
bufferProxy.dispose()
@@ -152,6 +164,25 @@ class BufferBinding {
152164
if (this.buffer.getPath()) return this.saveBuffer()
153165
}
154166

167+
relayURIChange () {
168+
this.bufferProxy.setURI(this.getBufferProxyURI())
169+
}
170+
171+
didChangeURI (uri) {
172+
if (this.remoteFile) this.remoteFile.setURI(uri)
173+
}
174+
175+
getBufferProxyURI () {
176+
if (!this.buffer.getPath()) return 'untitled'
177+
const [projectPath, relativePath] = atom.workspace.project.relativizePath(this.buffer.getPath())
178+
if (projectPath) {
179+
const projectName = path.basename(projectPath)
180+
return path.join(projectName, relativePath)
181+
} else {
182+
return relativePath
183+
}
184+
}
185+
155186
serialize (options) {
156187
return this.serializeUsingDefaultHistoryProviderFormat(options)
157188
}
@@ -166,3 +197,31 @@ class BufferBinding {
166197
return serializedDefaultHistoryProvider
167198
}
168199
}
200+
201+
class RemoteFile {
202+
constructor ({uri}) {
203+
this.uri = uri
204+
this.emitter = new Emitter()
205+
}
206+
207+
dispose () {
208+
this.emitter.dispose()
209+
}
210+
211+
getPath () {
212+
return getPathWithNativeSeparators(this.uri)
213+
}
214+
215+
setURI (uri) {
216+
this.uri = uri
217+
this.emitter.emit('did-rename')
218+
}
219+
220+
onDidRename (callback) {
221+
return this.emitter.on('did-rename', callback)
222+
}
223+
224+
existsSync () {
225+
return false
226+
}
227+
}

lib/editor-binding.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
/* global ResizeObserver */
22

33
const path = require('path')
4-
const {Range, Emitter, Disposable, CompositeDisposable} = require('atom')
5-
const getPathWithNativeSeparators = require('./get-path-with-native-separators')
4+
const {Range, Emitter, Disposable, CompositeDisposable, TextBuffer} = require('atom')
65
const {getEditorURI} = require('./uri-helpers')
76
const {FollowState} = require('@atom/teletype-client')
87

@@ -58,23 +57,23 @@ class EditorBinding {
5857
}
5958

6059
monkeyPatchEditorMethods (editor, editorProxy) {
61-
const buffer = editor.getBuffer()
60+
const remoteBuffer = editor.getBuffer()
61+
const originalRemoteBufferGetPath = TextBuffer.prototype.getPath.bind(remoteBuffer)
6262
const {bufferProxy} = editorProxy
6363
const hostIdentity = this.portal.getSiteIdentity(1)
6464
const prefix = hostIdentity ? `@${hostIdentity.login}` : 'remote'
65-
const bufferURI = getPathWithNativeSeparators(bufferProxy.uri)
6665

67-
editor.getTitle = () => `${prefix}: ${path.basename(bufferURI)}`
66+
editor.getTitle = () => `${prefix}: ${path.basename(originalRemoteBufferGetPath())}`
6867
editor.getURI = () => getEditorURI(this.portal.id, editorProxy.id)
6968
editor.copy = () => null
7069
editor.serialize = () => null
7170
editor.isRemote = true
7271

73-
let remoteEditorCountForBuffer = buffer.remoteEditorCount || 0
74-
buffer.remoteEditorCount = ++remoteEditorCountForBuffer
75-
buffer.getPath = () => `${prefix}:${bufferURI}`
76-
buffer.save = () => { bufferProxy.requestSave() }
77-
buffer.isModified = () => false
72+
let remoteEditorCountForBuffer = remoteBuffer.remoteEditorCount || 0
73+
remoteBuffer.remoteEditorCount = ++remoteEditorCountForBuffer
74+
remoteBuffer.getPath = () => `${prefix}:${originalRemoteBufferGetPath()}`
75+
remoteBuffer.save = () => { bufferProxy.requestSave() }
76+
remoteBuffer.isModified = () => false
7877

7978
editor.element.classList.add('teletype-RemotePaneItem')
8079
}

lib/host-portal-binding.js

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const path = require('path')
21
const {CompositeDisposable, Emitter} = require('atom')
32
const {FollowState} = require('@atom/teletype-client')
43
const BufferBinding = require('./buffer-binding')
@@ -145,7 +144,7 @@ class HostPortalBinding {
145144
} else {
146145
bufferBinding = new BufferBinding({buffer, isHost: true})
147146
const bufferProxy = this.portal.createBufferProxy({
148-
uri: this.getBufferProxyURI(buffer),
147+
uri: bufferBinding.getBufferProxyURI(),
149148
history: buffer.getHistory()
150149
})
151150
bufferBinding.setBufferProxy(bufferProxy)
@@ -156,16 +155,4 @@ class HostPortalBinding {
156155
return bufferProxy
157156
}
158157
}
159-
160-
getBufferProxyURI (buffer) {
161-
if (!buffer.getPath()) return 'untitled'
162-
163-
const [projectPath, relativePath] = this.workspace.project.relativizePath(buffer.getPath())
164-
if (projectPath) {
165-
const projectName = path.basename(projectPath)
166-
return path.join(projectName, relativePath)
167-
} else {
168-
return relativePath
169-
}
170-
}
171158
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"temp": "^0.8.3"
2626
},
2727
"dependencies": {
28-
"@atom/teletype-client": "^0.36.1",
28+
"@atom/teletype-client": "^0.37.0",
2929
"etch": "^0.12.6"
3030
},
3131
"providedServices": {

test/buffer-binding.test.js

Lines changed: 32 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
const assert = require('assert')
22
const fs = require('fs')
3+
const path = require('path')
34
const temp = require('temp')
4-
const {TextBuffer, Point} = require('atom')
5+
const {TextBuffer} = require('atom')
56
const BufferBinding = require('../lib/buffer-binding')
7+
const FakeBufferProxy = require('./helpers/fake-buffer-proxy')
68

79
suite('BufferBinding', function () {
810
if (process.env.CI) this.timeout(process.env.TEST_TIMEOUT_IN_MS)
911

1012
test('relays changes to and from the shared buffer', () => {
1113
const buffer = new TextBuffer('hello\nworld')
1214
const binding = new BufferBinding({buffer})
13-
const bufferProxy = new FakeBufferProxy(binding, buffer.getText())
15+
const bufferProxy = new FakeBufferProxy({delegate: binding, text: buffer.getText()})
1416
binding.setBufferProxy(bufferProxy)
1517

1618
bufferProxy.simulateRemoteTextUpdate([
@@ -36,7 +38,7 @@ suite('BufferBinding', function () {
3638
test('does not relay empty changes to the shared buffer', () => {
3739
const buffer = new TextBuffer('hello\nworld')
3840
const binding = new BufferBinding({buffer})
39-
const bufferProxy = new FakeBufferProxy(binding, buffer.getText())
41+
const bufferProxy = new FakeBufferProxy({delegate: binding, text: buffer.getText()})
4042
binding.setBufferProxy(bufferProxy)
4143

4244
buffer.setTextInRange([[0, 0], [0, 0]], '')
@@ -49,8 +51,8 @@ suite('BufferBinding', function () {
4951
// This line ensures saving works correctly even if the save function has been monkey-patched.
5052
buffer.save = () => {}
5153

52-
const binding = new BufferBinding({buffer})
53-
const bufferProxy = new FakeBufferProxy(binding, buffer.getText())
54+
const binding = new BufferBinding({buffer, isHost: true})
55+
const bufferProxy = new FakeBufferProxy({delegate: binding, text: buffer.getText()})
5456
binding.setBufferProxy(bufferProxy)
5557

5658
// Calling binding.save with an in-memory buffer is ignored.
@@ -69,11 +71,34 @@ suite('BufferBinding', function () {
6971
assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed')
7072
})
7173

74+
test('relays path changes from host to guest', async () => {
75+
{
76+
const hostBuffer = new TextBuffer('')
77+
const hostBinding = new BufferBinding({buffer: hostBuffer, isHost: true})
78+
const hostBufferProxy = new FakeBufferProxy({delegate: hostBinding, text: hostBuffer.getText()})
79+
hostBinding.setBufferProxy(hostBufferProxy)
80+
81+
await hostBuffer.saveAs(path.join(temp.path(), 'new-filename'))
82+
assert(hostBufferProxy.uri.includes('new-filename'))
83+
}
84+
85+
{
86+
const guestBuffer = new TextBuffer('')
87+
const guestBinding = new BufferBinding({buffer: guestBuffer, isHost: false})
88+
const guestBufferProxy = new FakeBufferProxy({delegate: guestBinding, text: guestBuffer.getText()})
89+
guestBinding.setBufferProxy(guestBufferProxy)
90+
91+
guestBufferProxy.simulateRemoteURIChange('some/uri/new-filename')
92+
assert(guestBuffer.getPath().includes('new-filename'))
93+
assert.equal(guestBufferProxy.uri, 'some/uri/new-filename')
94+
}
95+
})
96+
7297
suite('destroying the buffer', () => {
7398
test('on the host, disposes the underlying buffer proxy', () => {
7499
const buffer = new TextBuffer('')
75100
const binding = new BufferBinding({buffer, isHost: true})
76-
const bufferProxy = new FakeBufferProxy(binding, buffer.getText())
101+
const bufferProxy = new FakeBufferProxy({delegate: binding, text: buffer.getText()})
77102
binding.setBufferProxy(bufferProxy)
78103

79104
buffer.destroy()
@@ -83,79 +108,12 @@ suite('BufferBinding', function () {
83108
test('on guests, disposes the buffer binding', () => {
84109
const buffer = new TextBuffer('')
85110
const binding = new BufferBinding({buffer, isHost: false})
86-
const bufferProxy = new FakeBufferProxy(binding, buffer.getText())
111+
const bufferProxy = new FakeBufferProxy({delegate: binding, text: buffer.getText()})
87112
binding.setBufferProxy(bufferProxy)
88113

89114
buffer.destroy()
90115
assert(binding.disposed)
91116
assert(!bufferProxy.disposed)
92117
})
93118
})
94-
95-
class FakeBufferProxy {
96-
constructor (delegate, text) {
97-
this.delegate = delegate
98-
this.text = text
99-
this.disposed = false
100-
}
101-
102-
dispose () {
103-
this.disposed = true
104-
}
105-
106-
getHistory () {
107-
return {undoStack: [], redoStack: [], nextCheckpointId: 1}
108-
}
109-
110-
setTextInRange (oldStart, oldEnd, newText) {
111-
const oldStartIndex = characterIndexForPosition(this.text, oldStart)
112-
const oldEndIndex = characterIndexForPosition(this.text, oldEnd)
113-
this.text = this.text.slice(0, oldStartIndex) + newText + this.text.slice(oldEndIndex)
114-
}
115-
116-
simulateRemoteTextUpdate (changes) {
117-
assert(changes.length > 0, 'Must update text with at least one change')
118-
119-
for (let i = changes.length - 1; i >= 0; i--) {
120-
const {oldStart, oldEnd, newText} = changes[i]
121-
this.setTextInRange(oldStart, oldEnd, newText)
122-
}
123-
124-
this.delegate.updateText(changes)
125-
}
126-
127-
createCheckpoint () {
128-
return 1
129-
}
130-
131-
groupChangesSinceCheckpoint () {
132-
return []
133-
}
134-
135-
groupLastChanges () {
136-
return true
137-
}
138-
139-
applyGroupingInterval () {
140-
141-
}
142-
}
143-
144-
function characterIndexForPosition (text, target) {
145-
target = Point.fromObject(target)
146-
const position = Point(0, 0)
147-
let index = 0
148-
while (position.compare(target) < 0 && index < text.length) {
149-
if (text[index] === '\n') {
150-
position.row++
151-
position.column = 0
152-
} else {
153-
position.column++
154-
}
155-
156-
index++
157-
}
158-
159-
return index
160-
}
161119
})

test/editor-binding.test.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const {
1313
setEditorScrollLeftInChars
1414
} = require('./helpers/editor-helpers')
1515
const {FollowState} = require('@atom/teletype-client')
16+
const FakeBufferProxy = require('./helpers/fake-buffer-proxy')
17+
const BufferBinding = require('../lib/buffer-binding')
1618

1719
suite('EditorBinding', function () {
1820
if (process.env.CI) this.timeout(process.env.TEST_TIMEOUT_IN_MS)
@@ -323,9 +325,11 @@ suite('EditorBinding', function () {
323325
const buffer = new TextBuffer({text: SAMPLE_TEXT})
324326
const editor = new TextEditor({buffer})
325327

326-
const binding = new EditorBinding({editor, portal: new FakePortal(), isHost: false})
327-
const editorProxy = new FakeEditorProxy(binding)
328-
binding.setEditorProxy(editorProxy)
328+
const editorBinding = new EditorBinding({editor, portal: new FakePortal(), isHost: false})
329+
const editorProxy = new FakeEditorProxy(editorBinding)
330+
const bufferBinding = new BufferBinding({buffer})
331+
bufferBinding.setBufferProxy(editorProxy.bufferProxy)
332+
editorBinding.setEditorProxy(editorProxy)
329333
assert.equal(editor.getTitle(), '@site-1: fake-buffer-proxy-uri')
330334
assert.equal(editor.copy(), null)
331335
assert.equal(editor.serialize(), null)
@@ -458,13 +462,7 @@ suite('EditorBinding', function () {
458462
class FakeEditorProxy {
459463
constructor (delegate, {siteId} = {}) {
460464
this.delegate = delegate
461-
this.bufferProxy = {
462-
uri: 'fake-buffer-proxy-uri',
463-
saveRequestCount: 0,
464-
requestSave () {
465-
this.saveRequestCount++
466-
}
467-
}
465+
this.bufferProxy = new FakeBufferProxy({uri: 'fake-buffer-proxy-uri'})
468466
this.selections = {}
469467
this.siteId = (siteId == null) ? 1 : siteId
470468
this.disposed = false

0 commit comments

Comments
 (0)