@@ -7,15 +7,15 @@ import type {
7
7
RangeCodeFormatProvider ,
8
8
} from "./types"
9
9
10
- import { registerOnWillSave } from "@atom-ide-community/nuclide-commons-atom/FileEventHandlers"
11
10
import { getFormatOnSave , getFormatOnType } from "./config"
12
11
import { getLogger } from "log4js"
13
12
import { ProviderRegistry } from "atom-ide-base/commons-atom/ProviderRegistry"
14
13
import { applyTextEditsToBuffer } from "@atom-ide-community/nuclide-commons-atom/text-edit"
15
14
import nuclideUri from "@atom-ide-community/nuclide-commons/nuclideUri"
15
+ import { debounce } from "./utils"
16
16
17
17
// Save events are critical, so don't allow providers to block them.
18
- export const SAVE_TIMEOUT = 2500
18
+ export const SAVE_TIMEOUT = 500
19
19
20
20
export default class CodeFormatManager {
21
21
_subscriptions : CompositeDisposable
@@ -49,28 +49,33 @@ export default class CodeFormatManager {
49
49
}
50
50
} ) ,
51
51
52
- // Events from typing in the editor
53
52
atom . workspace . observeTextEditors ( ( editor ) => {
54
- const onChangeSubs = editor . getBuffer ( ) . onDidStopChanging ( async ( event ) => {
55
- try {
56
- await this . _formatCodeOnTypeInTextEditor ( editor , event )
57
- } catch ( err ) {
58
- getLogger ( "code-format" ) . warn ( "Failed to format code on type:" , err )
59
- }
60
- } )
53
+ const editorSubs = new CompositeDisposable (
54
+ // Format on typing in the editor
55
+ editor . getBuffer ( ) . onDidStopChanging ( async ( event ) => {
56
+ try {
57
+ await this . _formatCodeOnTypeInTextEditor ( editor , event )
58
+ } catch ( err ) {
59
+ getLogger ( "code-format" ) . warn ( "Failed to format code on type:" , err )
60
+ }
61
+ } ) ,
62
+ // Format on save
63
+ editor . onDidSave (
64
+ debounce ( async ( ) => {
65
+ const edits = await this . _formatCodeOnSaveInTextEditor ( editor )
66
+ const success = applyTextEditsToBuffer ( editor . getBuffer ( ) , edits ) as boolean
67
+ if ( ! success ) {
68
+ throw new Error ( "No code formatting providers found!" )
69
+ }
70
+ } , SAVE_TIMEOUT )
71
+ )
72
+ )
61
73
// Make sure we halt everything when the editor gets destroyed.
62
74
// We need to capture when editors are about to be destroyed in order to
63
75
// interrupt any pending formatting operations. (Otherwise, we may end up
64
76
// attempting to save a destroyed editor!)
65
- editor . onDidDestroy ( ( ) => onChangeSubs . dispose ( ) )
66
- } ) ,
67
-
68
- // Format on save
69
- registerOnWillSave ( ( ) => ( {
70
- priority : 0 ,
71
- timeout : SAVE_TIMEOUT ,
72
- callback : this . _formatCodeOnSaveInTextEditor . bind ( this ) ,
73
- } ) )
77
+ editor . onDidDestroy ( ( ) => editorSubs . dispose ( ) )
78
+ } )
74
79
)
75
80
76
81
this . _rangeProviders = new ProviderRegistry ( )
@@ -80,15 +85,19 @@ export default class CodeFormatManager {
80
85
}
81
86
82
87
// Return the text edits used to format code in the editor specified.
83
- async _formatCodeInTextEditor ( editor : TextEditor , range ?: Range ) : Promise < Array < TextEdit > > {
88
+ async _formatCodeInTextEditor (
89
+ editor : TextEditor ,
90
+ selectionRange : Range = editor . getSelectedBufferRange ( )
91
+ ) : Promise < Array < TextEdit > > {
84
92
const buffer = editor . getBuffer ( )
85
- const selectionRange = range ?? editor . getSelectedBufferRange ( )
86
- const { start : selectionStart , end : selectionEnd } = selectionRange
93
+ const bufferRange = buffer . getRange ( )
94
+
87
95
let formatRange : Range
88
96
if ( selectionRange . isEmpty ( ) ) {
89
97
// If no selection is done, then, the whole file is wanted to be formatted.
90
- formatRange = buffer . getRange ( )
98
+ formatRange = bufferRange
91
99
} else {
100
+ const { start : selectionStart , end : selectionEnd } = selectionRange
92
101
// Format selections should start at the beginning of the line,
93
102
// and include the last selected line end.
94
103
// (If the user has already selected complete rows, then depending on how they
@@ -101,34 +110,38 @@ export default class CodeFormatManager {
101
110
selectionEnd . column === 0 ? selectionEnd : [ selectionEnd . row + 1 , 0 ]
102
111
)
103
112
}
104
- const rangeProviders = [ ...this . _rangeProviders . getAllProvidersForEditor ( editor ) ]
105
- const fileProviders = [ ...this . _fileProviders . getAllProvidersForEditor ( editor ) ]
106
- const contents = editor . getText ( )
107
113
108
- const allEdits = await this . _reportBusy (
114
+ // range providers
115
+ const rangeProviders = [ ...this . _rangeProviders . getAllProvidersForEditor ( editor ) ]
116
+ const allRangeEdits = await this . _reportBusy (
109
117
editor ,
110
118
Promise . all ( rangeProviders . map ( ( p ) => p . formatCode ( editor , formatRange ) ) )
111
119
)
112
- const rangeEdits = allEdits . filter ( ( edits ) => edits . length > 0 )
120
+ const rangeEdits = allRangeEdits . filter ( ( edits ) => edits . length > 0 )
113
121
114
- const allResults = await this . _reportBusy (
122
+ // file providers
123
+ const fileProviders = [ ...this . _fileProviders . getAllProvidersForEditor ( editor ) ]
124
+ const allFileEdits = await this . _reportBusy (
115
125
editor ,
116
126
Promise . all ( fileProviders . map ( ( p ) => p . formatEntireFile ( editor , formatRange ) ) )
117
127
)
118
- const nonNullResults = allResults . filter ( ( result ) => result !== null && result !== undefined ) as {
128
+ const nonNullFileEdits = allFileEdits . filter ( ( result ) => result !== null && result !== undefined ) as {
119
129
newCursor ?: number
120
130
formatted : string
121
131
} [ ]
122
- const fileEdits = nonNullResults . map ( ( { formatted } ) => [
132
+ const contents = editor . getText ( )
133
+ const editorRange = editor . getBuffer ( ) . getRange ( )
134
+ const fileEdits = nonNullFileEdits . map ( ( { formatted } ) => [
123
135
{
124
- oldRange : editor . getBuffer ( ) . getRange ( ) ,
136
+ oldRange : editorRange ,
125
137
newText : formatted ,
126
138
oldText : contents ,
127
139
} as TextEdit ,
128
140
] )
129
141
142
+ // merge edits
130
143
// When formatting the entire file, prefer file-based providers.
131
- const preferFileEdits = formatRange . isEqual ( buffer . getRange ( ) )
144
+ const preferFileEdits = formatRange . isEqual ( bufferRange )
132
145
const edits = preferFileEdits ? fileEdits . concat ( rangeEdits ) : rangeEdits . concat ( fileEdits )
133
146
return edits . flat ( ) // TODO or [0]?
134
147
}
0 commit comments