Skip to content

Commit 51c2f7f

Browse files
committed
fix: fix formatting on typing and saving
1 parent 6722c9d commit 51c2f7f

File tree

1 file changed

+30
-86
lines changed

1 file changed

+30
-86
lines changed

src/CodeFormatManager.ts

Lines changed: 30 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import type { TextEditor, TextChange, Disposable } from "atom"
2-
// TODO add to @types/atom
3-
type AggregatedTextChange = {
4-
changes: Array<TextChange>
5-
}
1+
import { Range, CompositeDisposable, TextEditor, TextChange, Disposable, BufferStoppedChangingEvent } from "atom"
62
import type { TextEdit, BusySignalService } from "atom-ide-base"
73
import type {
84
FileCodeFormatProvider,
@@ -14,57 +10,32 @@ import type {
1410
import nullthrows from "nullthrows"
1511
import { registerOnWillSave } from "@atom-ide-community/nuclide-commons-atom/FileEventHandlers"
1612
import { getFormatOnSave, getFormatOnType } from "./config"
17-
import { Range } from "atom"
1813
import { getLogger } from "log4js"
1914
import { ProviderRegistry } from "atom-ide-base/commons-atom/ProviderRegistry"
2015
import { applyTextEditsToBuffer } from "@atom-ide-community/nuclide-commons-atom/text-edit"
21-
import { observeEditorDestroy } from "@atom-ide-community/nuclide-commons-atom/text-editor"
22-
import { observableFromSubscribeFunction } from "@atom-ide-community/nuclide-commons/event"
2316
import nuclideUri from "@atom-ide-community/nuclide-commons/nuclideUri"
24-
import { completingSwitchMap, microtask } from "@atom-ide-community/nuclide-commons/observable"
17+
import { microtask } from "@atom-ide-community/nuclide-commons/observable"
2518
import UniversalDisposable from "@atom-ide-community/nuclide-commons/UniversalDisposable"
2619
import { Observable } from "rxjs-compat/bundles/rxjs-compat.umd.min.js"
27-
import type { Subscription } from "rxjs"
2820

2921
// Save events are critical, so don't allow providers to block them.
3022
export const SAVE_TIMEOUT = 2500
3123

32-
type FormatEvent =
33-
| {
34-
type: "command" | "save" | "new-save"
35-
editor: TextEditor
36-
}
37-
| {
38-
type: "type"
39-
editor: TextEditor
40-
edit: AggregatedTextChange
41-
}
42-
4324
export default class CodeFormatManager {
44-
_subscriptions = new UniversalDisposable()
25+
_subscriptions: CompositeDisposable
4526
_rangeProviders: ProviderRegistry<RangeCodeFormatProvider>
4627
_fileProviders: ProviderRegistry<FileCodeFormatProvider>
4728
_onTypeProviders: ProviderRegistry<OnTypeCodeFormatProvider>
4829
_onSaveProviders: ProviderRegistry<OnSaveCodeFormatProvider>
4930
_busySignalService: BusySignalService | undefined | null
5031

5132
constructor() {
52-
this._subscriptions = new UniversalDisposable(
53-
registerOnWillSave(this._onWillSaveProvider())
54-
)
55-
this._rangeProviders = new ProviderRegistry()
56-
this._fileProviders = new ProviderRegistry()
57-
this._onTypeProviders = new ProviderRegistry()
58-
this._onSaveProviders = new ProviderRegistry()
59-
}
60-
61-
/**
62-
* Subscribe to all formatting events (commands, saves, edits) and dispatch formatters as necessary. By handling all
63-
* events in a central location, we ensure that no buffer runs into race conditions with simultaneous formatters.
64-
*/
65-
_subscribeToEvents(): Subscription {
66-
// Events from the explicit Atom command.
67-
this._subscriptions.add(
33+
/**
34+
* Subscribe to all formatting events (commands, saves, edits) and dispatch formatters as necessary. By handling all
35+
* events in a central location, we ensure that no buffer runs into race conditions with simultaneous formatters.
36+
*/
37+
this._subscriptions = new CompositeDisposable(
38+
// Events from the explicit Atom command.
6839
atom.commands.add("atom-text-editor", "code-format:format-code", async (event) => {
6940
const editorElement = event.currentTarget
7041
if (!editorElement) {
@@ -84,41 +55,30 @@ export default class CodeFormatManager {
8455
detail: err.detail,
8556
})
8657
}
87-
})
88-
)
58+
}),
8959

90-
// Events from editor actions (saving, typing).
91-
const editorEvents = observableFromSubscribeFunction((cb) => atom.workspace.observeTextEditors(cb)).mergeMap(
92-
(editor) => _getEditorEventStream(editor)
93-
)
60+
// Events from typing in the editor
61+
atom.workspace.observeTextEditors((editor) => {
62+
const onChangeSubs = editor.getBuffer().onDidStopChanging((event) => {
63+
this._formatCodeOnTypeInTextEditor(editor, event).catch((err) => {
64+
getLogger("code-format").warn("Failed to format code on type:", err)
65+
})
66+
})
67+
// Make sure we halt everything when the editor gets destroyed.
68+
// We need to capture when editors are about to be destroyed in order to
69+
// interrupt any pending formatting operations. (Otherwise, we may end up
70+
// attempting to save a destroyed editor!)
71+
editor.onDidDestroy(() => onChangeSubs.dispose())
72+
}),
9473

95-
return (
96-
editorEvents
97-
// Group events by buffer to prevent simultaneous formatting operations.
98-
.groupBy(
99-
(event) => event.editor.getBuffer(),
100-
(event) => event,
101-
(grouped) => observableFromSubscribeFunction((callback) => grouped.key.onDidDestroy(callback))
102-
)
103-
.mergeMap((events) =>
104-
// Make sure we halt everything when the editor gets destroyed.
105-
events.let(completingSwitchMap((event) => this._handleEvent(event)))
106-
)
107-
.subscribe()
74+
// Format on save
75+
registerOnWillSave(this._onWillSaveProvider())
10876
)
109-
}
11077

111-
async _handleEvent(event: FormatEvent) {
112-
const { editor } = event
113-
switch (event.type) {
114-
case "type":
115-
return this._formatCodeOnTypeInTextEditor(editor, event.edit).catch((err) => {
116-
getLogger("code-format").warn("Failed to format code on type:", err)
117-
return Observable.empty()
118-
})
119-
default:
120-
return Observable.throw(`unknown event type ${event.type}`)
121-
}
78+
this._rangeProviders = new ProviderRegistry()
79+
this._fileProviders = new ProviderRegistry()
80+
this._onTypeProviders = new ProviderRegistry()
81+
this._onSaveProviders = new ProviderRegistry()
12282
}
12383

12484
// Return the text edits used to format code in the editor specified.
@@ -177,7 +137,7 @@ export default class CodeFormatManager {
177137

178138
_formatCodeOnTypeInTextEditor(
179139
editor: TextEditor,
180-
aggregatedEvent: AggregatedTextChange
140+
aggregatedEvent: BufferStoppedChangingEvent
181141
): Observable<Array<TextEdit>> {
182142
return Observable.defer(() => {
183143
// Don't try to format changes with multiple cursors.
@@ -359,19 +319,3 @@ function _checkContentsAreSame(before: string, after: string): void {
359319
throw new Error("The file contents were changed before formatting was complete.")
360320
}
361321
}
362-
363-
/** Returns a stream of all typing and saving operations from the editor. */
364-
function _getEditorEventStream(editor: TextEditor): Observable<FormatEvent> {
365-
const changeEvents = observableFromSubscribeFunction((callback) => editor.getBuffer().onDidChangeText(callback))
366-
367-
// We need to capture when editors are about to be destroyed in order to
368-
// interrupt any pending formatting operations. (Otherwise, we may end up
369-
// attempting to save a destroyed editor!)
370-
const willDestroyEvents = observableFromSubscribeFunction((cb) => atom.workspace.onWillDestroyPaneItem(cb)).filter(
371-
(event) => event.item === editor
372-
)
373-
374-
return Observable.merge(changeEvents.map((edit) => ({ type: "type", editor, edit }))).takeUntil(
375-
Observable.merge(observeEditorDestroy(editor), willDestroyEvents)
376-
)
377-
}

0 commit comments

Comments
 (0)