Skip to content
This repository was archived by the owner on Apr 1, 2020. It is now read-only.

[WIP] #2302 - Integrate with the buffer updates API #2330

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
126 changes: 32 additions & 94 deletions browser/src/neovim/NeovimBufferUpdateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,126 +19,64 @@ export interface INeovimBufferUpdate {
contentChanges: types.TextDocumentContentChangeEvent[]
}

export type NeovimBufferLineEvent = [number, number, number, number, string[], boolean]

export class NeovimBufferUpdateManager {
private _onBufferUpdateEvent = new Event<INeovimBufferUpdate>()
private _lastEventContext: EventContext
private _lastMode: string
// private _lastEventContext: EventContext
// private _lastMode: string

// private _isRequestInProgress: boolean = false
// private _queuedRequest: EventContext

private _isRequestInProgress: boolean = false
private _queuedRequest: EventContext
private _trackedBuffers: Set<number> = new Set<number>()

public get onBufferUpdate(): IEvent<INeovimBufferUpdate> {
return this._onBufferUpdateEvent
}

constructor(private _configuration: Configuration, private _neovimInstance: NeovimInstance) {}

public async notifyFullBufferUpdate(eventContext: EventContext): Promise<void> {
if (!this._shouldSubscribeToUpdates(eventContext)) {
return
}

this._doFullUpdate(eventContext)
constructor(private _configuration: Configuration, private _neovimInstance: NeovimInstance) {
this._neovimInstance.autoCommands.onBufEnter.subscribe(async buf => {
if (!this._trackedBuffers.has(buf.current.bufferNumber)) {
if (this._shouldSubscribeToUpdates(buf.current)) {
this._trackedBuffers.add(buf.current.bufferNumber)
await this._neovimInstance.request("nvim_buf_attach", [
buf.current.bufferNumber,
true,
{},
])
}
}
})
}

public async notifyModeChanged(newMode: string): Promise<void> {
const shouldUpdate = newMode === "insert" && this._lastMode !== "insert"
this._lastMode = newMode

if (!shouldUpdate) {
return
}
public async handleUpdateEvent(lineEvent: NeovimBufferLineEvent): Promise<void> {
const [bufferId, , firstLine, lastLine, linedata] = lineEvent

const eventContext = await this._neovimInstance.getContext()

if (!this._shouldSubscribeToUpdates(eventContext)) {
return
}

this._doFullUpdate(eventContext)
}

public notifyIncrementalBufferUpdate(
eventContext: EventContext,
lineNumber: number,
lineContents: string,
): void {
if (!this._shouldSubscribeToUpdates(eventContext)) {
return
}

const shouldDoFullUpdate = this._shouldDoFullUpdate(eventContext, this._lastEventContext)

if (shouldDoFullUpdate) {
this._doFullUpdate(eventContext)
return
if (eventContext.bufferNumber !== bufferId) {
console.warn("Buffer ids don't match for update event")
}

const changedLine = lineContents

const update: INeovimBufferUpdate = {
eventContext,
contentChanges: [
{
range: types.Range.create(lineNumber - 1, 0, lineNumber, 0),
text: changedLine + os.EOL,
range: types.Range.create(
firstLine,
0,
lastLine === -1 ? linedata.length : lastLine,
0,
),
text: linedata.join(os.EOL) + os.EOL,
},
],
}

this._onBufferUpdateEvent.dispatch(update)
}

private async _doFullUpdate(eventContext: EventContext): Promise<void> {
if (this._isRequestInProgress) {
this._queuedRequest = eventContext
return
}

this._isRequestInProgress = true
const bufferLines = await this._neovimInstance.request<string[]>("nvim_buf_get_lines", [
eventContext.bufferNumber,
0,
eventContext.bufferTotalLines,
false,
])
this._isRequestInProgress = false

const update: INeovimBufferUpdate = {
eventContext,
contentChanges: [{ text: bufferLines.join(os.EOL) }],
}

this._lastEventContext = eventContext
this._onBufferUpdateEvent.dispatch(update)

// Is there a queued request?
if (this._queuedRequest) {
const req = this._queuedRequest
this._queuedRequest = null
this._doFullUpdate(req)
}
}

private _shouldDoFullUpdate(
currentContext: EventContext,
previousContext: EventContext,
): boolean {
if (!previousContext) {
return true
}

if (currentContext.bufferFullPath !== previousContext.bufferFullPath) {
return true
}

if (currentContext.bufferTotalLines !== previousContext.bufferTotalLines) {
return true
}

return false
}

private _shouldSubscribeToUpdates(context: EventContext): boolean {
if (
context.bufferTotalLines >
Expand Down
26 changes: 4 additions & 22 deletions browser/src/neovim/NeovimInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,16 +382,12 @@ export class NeovimInstance extends EventEmitter implements INeovimInstance {

this._bufferUpdateManager = new NeovimBufferUpdateManager(this._configuration, this)

const s1 = this._onModeChanged.subscribe(newMode => {
this._bufferUpdateManager.notifyModeChanged(newMode)
})

const dispatchScroll = () => this._dispatchScrollEvent()

const s2 = this._autoCommands.onCursorMoved.subscribe(dispatchScroll)
const s3 = this._autoCommands.onCursorMovedI.subscribe(dispatchScroll)

this._disposables = [s1, s2, s3]
this._disposables = [s2, s3]
}

public dispose(): void {
Expand Down Expand Up @@ -928,13 +924,7 @@ export class NeovimInstance extends EventEmitter implements INeovimInstance {
const pluginArgs = args[0]
const pluginMethod = pluginArgs.shift()

// TODO: Update pluginManager to subscribe from event here, instead of dupliating this

if (pluginMethod === "buffer_update") {
const eventContext: EventContext = args[0][0]

this._bufferUpdateManager.notifyFullBufferUpdate(eventContext)
} else if (pluginMethod === "oni_yank") {
if (pluginMethod === "oni_yank") {
this._onYank.dispatch(args[0][0])
} else if (pluginMethod === "oni_command") {
this._onOniCommand.dispatch(args[0][0])
Expand All @@ -954,19 +944,11 @@ export class NeovimInstance extends EventEmitter implements INeovimInstance {
this._autoCommands.notifyAutocommand(eventName, eventContext)

this._dispatchEvent(eventName, eventContext)
} else if (pluginMethod === "incremental_buffer_update") {
const eventContext = args[0][0]
const lineContents = args[0][1]
const lineNumber = args[0][2]

this._bufferUpdateManager.notifyIncrementalBufferUpdate(
eventContext,
lineNumber,
lineContents,
)
} else {
Log.warn("Unknown event from oni_plugin_notify: " + pluginMethod)
}
} else if (method === "nvim_buf_lines_event") {
this._bufferUpdateManager.handleUpdateEvent(args)
} else {
Log.warn("Unknown notification: " + method)
}
Expand Down
156 changes: 78 additions & 78 deletions browser/test/neovim/NeovimBufferUpdateManagerTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,81 +21,81 @@ const createTestEventContext = (bufferNumber: number, bufferTotalLines: number):
} as any
}

describe("NeovimBufferUpdateManagerTests", () => {
let configuration: MockConfiguration = null
let neovimInstance: MockNeovimInstance = null
let bufferUpdateManager: NeovimBufferUpdateManager = null

beforeEach(() => {
configuration = new MockConfiguration({ "editor.maxLinesForLanguageServices": 1000 })
neovimInstance = new MockNeovimInstance()

bufferUpdateManager = new NeovimBufferUpdateManager(
configuration as any,
neovimInstance as any,
)
})

it("dispatches when there is a full update", async () => {
const evt = createTestEventContext(1, 100)
bufferUpdateManager.notifyFullBufferUpdate(evt)

let hitCount = 0
bufferUpdateManager.onBufferUpdate.subscribe(() => hitCount++)

assert.strictEqual(neovimInstance.getPendingRequests().length, 1)

neovimInstance.flushFirstRequest(["a", "b", "c"])
await waitForAllAsyncOperations()

assert.strictEqual(hitCount, 1, "Validate 'onBufferUpdate' was dispatched")
})

it("debounces multiple full updates, while a request is pending", async () => {
let hitCount = 0
let lastResult: INeovimBufferUpdate = null
bufferUpdateManager.onBufferUpdate.subscribe(result => {
lastResult = result
hitCount++
})

// Send five requests
const evt1 = createTestEventContext(1, 100)
const evt2 = createTestEventContext(1, 101)
const evt3 = createTestEventContext(1, 102)
const evt4 = createTestEventContext(1, 104)
const evt5 = createTestEventContext(1, 105)

bufferUpdateManager.notifyFullBufferUpdate(evt1)
bufferUpdateManager.notifyFullBufferUpdate(evt2)
bufferUpdateManager.notifyFullBufferUpdate(evt3)
bufferUpdateManager.notifyFullBufferUpdate(evt4)
bufferUpdateManager.notifyFullBufferUpdate(evt5)

// Validate there is only a _single_ request queued up
assert.strictEqual(neovimInstance.getPendingRequests().length, 1)

neovimInstance.flushFirstRequest(["a", "b", "c"])
await waitForAllAsyncOperations()

// Validate there is now _another_ request queued up
assert.strictEqual(neovimInstance.getPendingRequests().length, 1)
const firstRequest = neovimInstance.getPendingRequests()[0]

// Validate the request corresponds to the last item
assert.strictEqual(firstRequest.args[2] /* 3rd parameter - totalBufferLines */, 105)

neovimInstance.flushFirstRequest(["d", "e", "f"])
await waitForAllAsyncOperations()

assert.strictEqual(
hitCount,
2,
"Validate the onBufferUpdate event was dispatched twice - first on the leading and second on the trailing call",
)
assert.strictEqual(lastResult.contentChanges[0].text, ["d", "e", "f"].join(os.EOL))

// Validate there are no additional pending requests
assert.strictEqual(neovimInstance.getPendingRequests().length, 0)
})
})
// describe("NeovimBufferUpdateManagerTests", () => {
// let configuration: MockConfiguration = null
// let neovimInstance: MockNeovimInstance = null
// let bufferUpdateManager: NeovimBufferUpdateManager = null

// beforeEach(() => {
// configuration = new MockConfiguration({ "editor.maxLinesForLanguageServices": 1000 })
// neovimInstance = new MockNeovimInstance()

// bufferUpdateManager = new NeovimBufferUpdateManager(
// configuration as any,
// neovimInstance as any,
// )
// })

// it("dispatches when there is a full update", async () => {
// const evt = createTestEventContext(1, 100)
// bufferUpdateManager.notifyFullBufferUpdate(evt)

// let hitCount = 0
// bufferUpdateManager.onBufferUpdate.subscribe(() => hitCount++)

// assert.strictEqual(neovimInstance.getPendingRequests().length, 1)

// neovimInstance.flushFirstRequest(["a", "b", "c"])
// await waitForAllAsyncOperations()

// assert.strictEqual(hitCount, 1, "Validate 'onBufferUpdate' was dispatched")
// })

// it("debounces multiple full updates, while a request is pending", async () => {
// let hitCount = 0
// let lastResult: INeovimBufferUpdate = null
// bufferUpdateManager.onBufferUpdate.subscribe(result => {
// lastResult = result
// hitCount++
// })

// // Send five requests
// const evt1 = createTestEventContext(1, 100)
// const evt2 = createTestEventContext(1, 101)
// const evt3 = createTestEventContext(1, 102)
// const evt4 = createTestEventContext(1, 104)
// const evt5 = createTestEventContext(1, 105)

// bufferUpdateManager.notifyFullBufferUpdate(evt1)
// bufferUpdateManager.notifyFullBufferUpdate(evt2)
// bufferUpdateManager.notifyFullBufferUpdate(evt3)
// bufferUpdateManager.notifyFullBufferUpdate(evt4)
// bufferUpdateManager.notifyFullBufferUpdate(evt5)

// // Validate there is only a _single_ request queued up
// assert.strictEqual(neovimInstance.getPendingRequests().length, 1)

// neovimInstance.flushFirstRequest(["a", "b", "c"])
// await waitForAllAsyncOperations()

// // Validate there is now _another_ request queued up
// assert.strictEqual(neovimInstance.getPendingRequests().length, 1)
// const firstRequest = neovimInstance.getPendingRequests()[0]

// // Validate the request corresponds to the last item
// assert.strictEqual(firstRequest.args[2] /* 3rd parameter - totalBufferLines */, 105)

// neovimInstance.flushFirstRequest(["d", "e", "f"])
// await waitForAllAsyncOperations()

// assert.strictEqual(
// hitCount,
// 2,
// "Validate the onBufferUpdate event was dispatched twice - first on the leading and second on the trailing call",
// )
// assert.strictEqual(lastResult.contentChanges[0].text, ["d", "e", "f"].join(os.EOL))

// // Validate there are no additional pending requests
// assert.strictEqual(neovimInstance.getPendingRequests().length, 0)
// })
// })
Loading