@herb-tools/language-server 0.8.10 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action_view_helpers.js +19 -0
- package/dist/action_view_helpers.js.map +1 -0
- package/dist/autofix_service.js +1 -1
- package/dist/autofix_service.js.map +1 -1
- package/dist/code_action_service.js +3 -6
- package/dist/code_action_service.js.map +1 -1
- package/dist/comment_ast_utils.js +206 -0
- package/dist/comment_ast_utils.js.map +1 -0
- package/dist/comment_service.js +175 -0
- package/dist/comment_service.js.map +1 -0
- package/dist/diagnostics.js +0 -233
- package/dist/diagnostics.js.map +1 -1
- package/dist/document_highlight_service.js +196 -0
- package/dist/document_highlight_service.js.map +1 -0
- package/dist/document_save_service.js +16 -6
- package/dist/document_save_service.js.map +1 -1
- package/dist/folding_range_service.js +209 -0
- package/dist/folding_range_service.js.map +1 -0
- package/dist/formatting_service.js +4 -4
- package/dist/formatting_service.js.map +1 -1
- package/dist/herb-language-server.js +152936 -41156
- package/dist/herb-language-server.js.map +1 -1
- package/dist/hover_service.js +70 -0
- package/dist/hover_service.js.map +1 -0
- package/dist/index.cjs +1299 -333
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/line_context_collector.js +73 -0
- package/dist/line_context_collector.js.map +1 -0
- package/dist/linter_service.js +27 -6
- package/dist/linter_service.js.map +1 -1
- package/dist/parser_service.js +6 -5
- package/dist/parser_service.js.map +1 -1
- package/dist/range_utils.js +65 -0
- package/dist/range_utils.js.map +1 -0
- package/dist/rewrite_code_action_service.js +135 -0
- package/dist/rewrite_code_action_service.js.map +1 -0
- package/dist/server.js +39 -2
- package/dist/server.js.map +1 -1
- package/dist/service.js +10 -0
- package/dist/service.js.map +1 -1
- package/dist/types/action_view_helpers.d.ts +5 -0
- package/dist/types/comment_ast_utils.d.ts +20 -0
- package/dist/types/comment_service.d.ts +14 -0
- package/dist/types/diagnostics.d.ts +1 -35
- package/dist/types/document_highlight_service.d.ts +28 -0
- package/dist/types/document_save_service.d.ts +8 -0
- package/dist/types/folding_range_service.d.ts +35 -0
- package/dist/types/formatting_service.d.ts +1 -1
- package/dist/types/hover_service.d.ts +8 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/line_context_collector.d.ts +19 -0
- package/dist/types/linter_service.d.ts +1 -0
- package/dist/types/parser_service.d.ts +2 -1
- package/dist/types/range_utils.d.ts +16 -0
- package/dist/types/rewrite_code_action_service.d.ts +11 -0
- package/dist/types/service.d.ts +10 -0
- package/dist/types/utils.d.ts +4 -8
- package/dist/utils.js +10 -15
- package/dist/utils.js.map +1 -1
- package/package.json +10 -5
- package/src/action_view_helpers.ts +23 -0
- package/src/autofix_service.ts +1 -1
- package/src/code_action_service.ts +3 -6
- package/src/comment_ast_utils.ts +282 -0
- package/src/comment_service.ts +228 -0
- package/src/diagnostics.ts +1 -305
- package/src/document_highlight_service.ts +267 -0
- package/src/document_save_service.ts +19 -7
- package/src/folding_range_service.ts +287 -0
- package/src/formatting_service.ts +4 -4
- package/src/hover_service.ts +90 -0
- package/src/index.ts +4 -0
- package/src/line_context_collector.ts +97 -0
- package/src/linter_service.ts +35 -9
- package/src/parser_service.ts +9 -10
- package/src/range_utils.ts +90 -0
- package/src/rewrite_code_action_service.ts +165 -0
- package/src/server.ts +54 -2
- package/src/service.ts +15 -0
- package/src/utils.ts +12 -21
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { DocumentHighlight, DocumentHighlightKind, Range, Position } from "vscode-languageserver/node"
|
|
2
|
+
import { TextDocument } from "vscode-languageserver-textdocument"
|
|
3
|
+
|
|
4
|
+
import { Visitor } from "@herb-tools/core"
|
|
5
|
+
import { ParserService } from "./parser_service"
|
|
6
|
+
|
|
7
|
+
import { isERBIfNode, isERBElseNode, isHTMLOpenTagNode } from "@herb-tools/core"
|
|
8
|
+
import { erbTagToRange, tokenToRange, nodeToRange, openTagRanges, isPositionInRange, rangeSize } from "./range_utils"
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
Node,
|
|
12
|
+
ERBNode,
|
|
13
|
+
ERBContentNode,
|
|
14
|
+
HTMLElementNode,
|
|
15
|
+
HTMLConditionalElementNode,
|
|
16
|
+
HTMLAttributeNode,
|
|
17
|
+
HTMLCommentNode,
|
|
18
|
+
ERBIfNode,
|
|
19
|
+
ERBUnlessNode,
|
|
20
|
+
ERBCaseNode,
|
|
21
|
+
ERBCaseMatchNode,
|
|
22
|
+
ERBBeginNode,
|
|
23
|
+
ERBRescueNode,
|
|
24
|
+
} from "@herb-tools/core"
|
|
25
|
+
|
|
26
|
+
export class DocumentHighlightCollector extends Visitor {
|
|
27
|
+
public groups: Range[][] = []
|
|
28
|
+
private processedIfNodes: Set<ERBIfNode> = new Set()
|
|
29
|
+
|
|
30
|
+
visitERBNode(node: ERBNode): void {
|
|
31
|
+
if ('end_node' in node && node.end_node) {
|
|
32
|
+
this.addGroup([erbTagToRange(node), erbTagToRange(node.end_node)])
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
visitERBContentNode(node: ERBContentNode): void {
|
|
37
|
+
this.addGroup([tokenToRange(node.tag_opening), tokenToRange(node.tag_closing)])
|
|
38
|
+
this.visitChildNodes(node)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
visitHTMLCommentNode(node: HTMLCommentNode): void {
|
|
42
|
+
this.addGroup([tokenToRange(node.comment_start), tokenToRange(node.comment_end)])
|
|
43
|
+
this.visitChildNodes(node)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
47
|
+
const ranges: (Range | null)[] = []
|
|
48
|
+
|
|
49
|
+
if (node.open_tag && isHTMLOpenTagNode(node.open_tag)) {
|
|
50
|
+
ranges.push(...openTagRanges(node.open_tag))
|
|
51
|
+
} else if (node.open_tag) {
|
|
52
|
+
ranges.push(nodeToRange(node.open_tag))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (node.close_tag) {
|
|
56
|
+
ranges.push(nodeToRange(node.close_tag))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.addGroup(ranges)
|
|
60
|
+
this.visitChildNodes(node)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
visitHTMLAttributeNode(node: HTMLAttributeNode): void {
|
|
64
|
+
const ranges: (Range | null)[] = []
|
|
65
|
+
|
|
66
|
+
if (node.name) {
|
|
67
|
+
ranges.push(nodeToRange(node.name))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (node.equals) {
|
|
71
|
+
ranges.push(tokenToRange(node.equals))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (node.value) {
|
|
75
|
+
ranges.push(nodeToRange(node.value))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.addGroup(ranges)
|
|
79
|
+
this.visitChildNodes(node)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
visitHTMLConditionalElementNode(node: HTMLConditionalElementNode): void {
|
|
83
|
+
const ranges: (Range | null)[] = []
|
|
84
|
+
|
|
85
|
+
if (node.open_conditional) {
|
|
86
|
+
ranges.push(erbTagToRange(node.open_conditional))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (node.open_tag) {
|
|
90
|
+
ranges.push(...openTagRanges(node.open_tag))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (node.close_tag) {
|
|
94
|
+
ranges.push(nodeToRange(node.close_tag))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (node.close_conditional) {
|
|
98
|
+
ranges.push(erbTagToRange(node.close_conditional))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.addGroup(ranges)
|
|
102
|
+
this.visitChildNodes(node)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
visitERBIfNode(node: ERBIfNode): void {
|
|
106
|
+
if (this.processedIfNodes.has(node)) {
|
|
107
|
+
this.visitChildNodes(node)
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.markIfChainAsProcessed(node)
|
|
112
|
+
|
|
113
|
+
const ranges: (Range | null)[] = []
|
|
114
|
+
ranges.push(erbTagToRange(node))
|
|
115
|
+
|
|
116
|
+
let current: Node | null = node.subsequent
|
|
117
|
+
|
|
118
|
+
while (current) {
|
|
119
|
+
if (isERBIfNode(current)) {
|
|
120
|
+
ranges.push(erbTagToRange(current))
|
|
121
|
+
current = current.subsequent
|
|
122
|
+
} else if (isERBElseNode(current)) {
|
|
123
|
+
ranges.push(erbTagToRange(current))
|
|
124
|
+
break
|
|
125
|
+
} else {
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (node.end_node) {
|
|
131
|
+
ranges.push(erbTagToRange(node.end_node))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.addGroup(ranges)
|
|
135
|
+
this.visitChildNodes(node)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
visitERBUnlessNode(node: ERBUnlessNode): void {
|
|
139
|
+
const ranges: (Range | null)[] = []
|
|
140
|
+
ranges.push(erbTagToRange(node))
|
|
141
|
+
|
|
142
|
+
if (node.else_clause) {
|
|
143
|
+
ranges.push(erbTagToRange(node.else_clause))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (node.end_node) {
|
|
147
|
+
ranges.push(erbTagToRange(node.end_node))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.addGroup(ranges)
|
|
151
|
+
this.visitChildNodes(node)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
visitERBCaseNode(node: ERBCaseNode): void {
|
|
155
|
+
this.visitERBAnyCaseNode(node)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
visitERBCaseMatchNode(node: ERBCaseMatchNode): void {
|
|
159
|
+
this.visitERBAnyCaseNode(node)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
visitERBAnyCaseNode(node: ERBCaseNode | ERBCaseMatchNode): void {
|
|
163
|
+
const ranges: (Range | null)[] = []
|
|
164
|
+
ranges.push(erbTagToRange(node))
|
|
165
|
+
|
|
166
|
+
for (const condition of node.conditions) {
|
|
167
|
+
ranges.push(erbTagToRange(condition as ERBNode))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (node.else_clause) {
|
|
171
|
+
ranges.push(erbTagToRange(node.else_clause))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (node.end_node) {
|
|
175
|
+
ranges.push(erbTagToRange(node.end_node))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.addGroup(ranges)
|
|
179
|
+
this.visitChildNodes(node)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
visitERBBeginNode(node: ERBBeginNode): void {
|
|
183
|
+
const ranges: (Range | null)[] = []
|
|
184
|
+
ranges.push(erbTagToRange(node))
|
|
185
|
+
|
|
186
|
+
let rescue: ERBRescueNode | null = node.rescue_clause
|
|
187
|
+
|
|
188
|
+
while (rescue) {
|
|
189
|
+
ranges.push(erbTagToRange(rescue))
|
|
190
|
+
rescue = rescue.subsequent
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (node.else_clause) {
|
|
194
|
+
ranges.push(erbTagToRange(node.else_clause))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (node.ensure_clause) {
|
|
198
|
+
ranges.push(erbTagToRange(node.ensure_clause))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (node.end_node) {
|
|
202
|
+
ranges.push(erbTagToRange(node.end_node))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.addGroup(ranges)
|
|
206
|
+
this.visitChildNodes(node)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private markIfChainAsProcessed(node: ERBIfNode): void {
|
|
210
|
+
this.processedIfNodes.add(node)
|
|
211
|
+
|
|
212
|
+
let current: Node | null = node.subsequent
|
|
213
|
+
|
|
214
|
+
while (current) {
|
|
215
|
+
if (isERBIfNode(current)) {
|
|
216
|
+
this.processedIfNodes.add(current)
|
|
217
|
+
current = current.subsequent
|
|
218
|
+
} else {
|
|
219
|
+
break
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private addGroup(ranges: (Range | null)[]): void {
|
|
225
|
+
const filtered = ranges.filter((r): r is Range => r !== null)
|
|
226
|
+
if (filtered.length >= 2) {
|
|
227
|
+
this.groups.push(filtered)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export class DocumentHighlightService {
|
|
233
|
+
private parserService: ParserService
|
|
234
|
+
|
|
235
|
+
constructor(parserService: ParserService) {
|
|
236
|
+
this.parserService = parserService
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
getDocumentHighlights(textDocument: TextDocument, position: Position): DocumentHighlight[] {
|
|
240
|
+
const parseResult = this.parserService.parseDocument(textDocument)
|
|
241
|
+
const collector = new DocumentHighlightCollector()
|
|
242
|
+
collector.visit(parseResult.document)
|
|
243
|
+
|
|
244
|
+
let bestGroup: Range[] | null = null
|
|
245
|
+
let bestSize = Infinity
|
|
246
|
+
|
|
247
|
+
for (const group of collector.groups) {
|
|
248
|
+
const matchingRange = group.find(range => isPositionInRange(position, range))
|
|
249
|
+
|
|
250
|
+
if (matchingRange) {
|
|
251
|
+
const size = rangeSize(matchingRange)
|
|
252
|
+
|
|
253
|
+
if (size < bestSize) {
|
|
254
|
+
bestSize = size
|
|
255
|
+
bestGroup = group
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (bestGroup) {
|
|
261
|
+
return bestGroup.map(range => DocumentHighlight.create(range, DocumentHighlightKind.Text))
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return []
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
}
|
|
@@ -11,6 +11,15 @@ export class DocumentSaveService {
|
|
|
11
11
|
private autofixService: AutofixService
|
|
12
12
|
private formattingService: FormattingService
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Tracks documents that were recently autofixed via applyFixesAndFormatting
|
|
16
|
+
* (triggered by onDocumentFormatting). When editor.formatOnSave is enabled,
|
|
17
|
+
* onDocumentFormatting fires BEFORE willSaveWaitUntil. If applyFixesAndFormatting
|
|
18
|
+
* already applied autofix, applyFixes must skip to avoid conflicting edits
|
|
19
|
+
* (since this.documents hasn't been updated between the two events).
|
|
20
|
+
*/
|
|
21
|
+
private recentlyAutofixedViaFormatting = new Set<string>()
|
|
22
|
+
|
|
14
23
|
constructor(connection: Connection, settings: Settings, autofixService: AutofixService, formattingService: FormattingService) {
|
|
15
24
|
this.connection = connection
|
|
16
25
|
this.settings = settings
|
|
@@ -30,6 +39,11 @@ export class DocumentSaveService {
|
|
|
30
39
|
|
|
31
40
|
if (!fixOnSave) return []
|
|
32
41
|
|
|
42
|
+
if (this.recentlyAutofixedViaFormatting.delete(document.uri)) {
|
|
43
|
+
this.connection.console.log(`[DocumentSave] applyFixes skipping: already autofixed via formatting`)
|
|
44
|
+
return []
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
return this.autofixService.autofix(document)
|
|
34
48
|
}
|
|
35
49
|
|
|
@@ -48,6 +62,10 @@ export class DocumentSaveService {
|
|
|
48
62
|
|
|
49
63
|
if (fixOnSave) {
|
|
50
64
|
autofixEdits = await this.autofixService.autofix(document)
|
|
65
|
+
|
|
66
|
+
if (autofixEdits.length > 0) {
|
|
67
|
+
this.recentlyAutofixedViaFormatting.add(document.uri)
|
|
68
|
+
}
|
|
51
69
|
}
|
|
52
70
|
|
|
53
71
|
if (!formatterEnabled) return autofixEdits
|
|
@@ -56,12 +74,6 @@ export class DocumentSaveService {
|
|
|
56
74
|
return this.formattingService.formatOnSave(document, reason)
|
|
57
75
|
}
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
...document,
|
|
61
|
-
uri: document.uri,
|
|
62
|
-
getText: () => autofixEdits[0].newText,
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return this.formattingService.formatOnSave(autofixedDocument, reason)
|
|
77
|
+
return this.formattingService.formatOnSave(document, reason, autofixEdits[0].newText)
|
|
66
78
|
}
|
|
67
79
|
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { FoldingRange, FoldingRangeKind } from "vscode-languageserver/node"
|
|
2
|
+
import { TextDocument } from "vscode-languageserver-textdocument"
|
|
3
|
+
|
|
4
|
+
import { Visitor } from "@herb-tools/core"
|
|
5
|
+
import { ParserService } from "./parser_service"
|
|
6
|
+
|
|
7
|
+
import { isERBIfNode } from "@herb-tools/core"
|
|
8
|
+
import { lspLine } from "./range_utils"
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
Node,
|
|
12
|
+
ERBNode,
|
|
13
|
+
ERBContentNode,
|
|
14
|
+
HTMLElementNode,
|
|
15
|
+
HTMLOpenTagNode,
|
|
16
|
+
HTMLAttributeValueNode,
|
|
17
|
+
HTMLCommentNode,
|
|
18
|
+
HTMLConditionalElementNode,
|
|
19
|
+
CDATANode,
|
|
20
|
+
ERBIfNode,
|
|
21
|
+
ERBUnlessNode,
|
|
22
|
+
ERBCaseNode,
|
|
23
|
+
ERBCaseMatchNode,
|
|
24
|
+
ERBBeginNode,
|
|
25
|
+
ERBRescueNode,
|
|
26
|
+
ERBEnsureNode,
|
|
27
|
+
ERBElseNode,
|
|
28
|
+
ERBWhenNode,
|
|
29
|
+
ERBInNode,
|
|
30
|
+
SerializedPosition,
|
|
31
|
+
} from "@herb-tools/core"
|
|
32
|
+
|
|
33
|
+
export class FoldingRangeService {
|
|
34
|
+
private parserService: ParserService
|
|
35
|
+
|
|
36
|
+
constructor(parserService: ParserService) {
|
|
37
|
+
this.parserService = parserService
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getFoldingRanges(textDocument: TextDocument): FoldingRange[] {
|
|
41
|
+
const parseResult = this.parserService.parseDocument(textDocument)
|
|
42
|
+
const collector = new FoldingRangeCollector()
|
|
43
|
+
|
|
44
|
+
collector.visit(parseResult.document)
|
|
45
|
+
|
|
46
|
+
return collector.ranges
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class FoldingRangeCollector extends Visitor {
|
|
51
|
+
public ranges: FoldingRange[] = []
|
|
52
|
+
private processedIfNodes: Set<ERBIfNode> = new Set()
|
|
53
|
+
|
|
54
|
+
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
55
|
+
if (node.body.length > 0 && node.open_tag && node.close_tag) {
|
|
56
|
+
this.addRange(node.open_tag.location.end, node.close_tag.location.start)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.visitChildNodes(node)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
63
|
+
if (node.children.length > 0 && node.tag_opening && node.tag_closing) {
|
|
64
|
+
this.addRange(node.tag_opening.location.end, node.tag_closing.location.start)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.visitChildNodes(node)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
visitHTMLCommentNode(node: HTMLCommentNode): void {
|
|
71
|
+
if (node.comment_start && node.comment_end) {
|
|
72
|
+
this.addRange(node.comment_start.location.end, node.comment_end.location.start, FoldingRangeKind.Comment)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.visitChildNodes(node)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
visitHTMLAttributeValueNode(node: HTMLAttributeValueNode): void {
|
|
79
|
+
if (node.children.length > 0) {
|
|
80
|
+
const first = node.children[0]
|
|
81
|
+
const last = node.children[node.children.length - 1]
|
|
82
|
+
|
|
83
|
+
this.addRange(first.location.start, last.location.end)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.visitChildNodes(node)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
visitCDATANode(node: CDATANode): void {
|
|
90
|
+
this.addRange(node.location.start, node.location.end)
|
|
91
|
+
this.visitChildNodes(node)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
visitHTMLConditionalElementNode(node: HTMLConditionalElementNode): void {
|
|
95
|
+
this.addRange(node.location.start, node.location.end)
|
|
96
|
+
this.visitChildNodes(node)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
visitERBNode(node: ERBNode): void {
|
|
100
|
+
if (node.tag_closing && 'end_node' in node && node.end_node?.tag_opening) {
|
|
101
|
+
this.addRange(node.tag_closing.location.end, node.end_node.tag_opening.location.start)
|
|
102
|
+
} else {
|
|
103
|
+
this.addRange(node.location.start, node.location.end)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
visitERBContentNode(node: ERBContentNode): void {
|
|
108
|
+
if (node.tag_opening && node.tag_closing) {
|
|
109
|
+
this.addRange(node.tag_opening.location.end, node.tag_closing.location.start)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.visitChildNodes(node)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
visitERBIfNode(node: ERBIfNode): void {
|
|
116
|
+
if (this.processedIfNodes.has(node)) {
|
|
117
|
+
this.visitChildNodes(node)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.markIfChainAsProcessed(node)
|
|
122
|
+
|
|
123
|
+
const nextAfterIf = node.subsequent ?? node.end_node
|
|
124
|
+
|
|
125
|
+
if (node.tag_closing && nextAfterIf?.tag_opening) {
|
|
126
|
+
this.addRange(node.tag_closing.location.end, nextAfterIf.tag_opening.location.start)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let current: ERBIfNode | ERBElseNode | null = node.subsequent
|
|
130
|
+
|
|
131
|
+
while (current) {
|
|
132
|
+
if (isERBIfNode(current)) {
|
|
133
|
+
const nextAfterElsif = current.subsequent ?? node.end_node
|
|
134
|
+
|
|
135
|
+
if (current.tag_closing && nextAfterElsif?.tag_opening) {
|
|
136
|
+
this.addRange(current.tag_closing.location.end, nextAfterElsif.tag_opening.location.start)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
current = current.subsequent
|
|
140
|
+
} else {
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.visitChildNodes(node)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
visitERBUnlessNode(node: ERBUnlessNode): void {
|
|
149
|
+
const nextAfterUnless = node.else_clause ?? node.end_node
|
|
150
|
+
|
|
151
|
+
if (node.tag_closing && nextAfterUnless?.tag_opening) {
|
|
152
|
+
this.addRange(node.tag_closing.location.end, nextAfterUnless.tag_opening.location.start)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (node.else_clause) {
|
|
156
|
+
if (node.else_clause.tag_closing && node.end_node?.tag_opening) {
|
|
157
|
+
this.addRange(node.else_clause.tag_closing.location.end, node.end_node.tag_opening.location.start)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.visitChildNodes(node)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
visitERBCaseNode(node: ERBCaseNode): void {
|
|
165
|
+
this.addCaseFoldingRanges(node)
|
|
166
|
+
this.visitChildNodes(node)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
visitERBCaseMatchNode(node: ERBCaseMatchNode): void {
|
|
170
|
+
this.addCaseFoldingRanges(node)
|
|
171
|
+
this.visitChildNodes(node)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
visitERBWhenNode(node: ERBWhenNode): void {
|
|
175
|
+
this.visitChildNodes(node)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
visitERBInNode(node: ERBInNode): void {
|
|
179
|
+
this.visitChildNodes(node)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
visitERBBeginNode(node: ERBBeginNode): void {
|
|
183
|
+
const nextAfterBegin = node.rescue_clause ?? node.else_clause ?? node.ensure_clause ?? node.end_node
|
|
184
|
+
|
|
185
|
+
if (node.tag_closing && nextAfterBegin?.tag_opening) {
|
|
186
|
+
this.addRange(node.tag_closing.location.end, nextAfterBegin.tag_opening.location.start)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let rescue: ERBRescueNode | null = node.rescue_clause
|
|
190
|
+
|
|
191
|
+
while (rescue) {
|
|
192
|
+
const nextAfterRescue = rescue.subsequent ?? node.else_clause ?? node.ensure_clause ?? node.end_node
|
|
193
|
+
|
|
194
|
+
if (rescue.tag_closing && nextAfterRescue?.tag_opening) {
|
|
195
|
+
this.addRange(rescue.tag_closing.location.end, nextAfterRescue.tag_opening.location.start)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
rescue = rescue.subsequent
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (node.else_clause) {
|
|
202
|
+
const nextAfterElse = node.ensure_clause ?? node.end_node
|
|
203
|
+
|
|
204
|
+
if (node.else_clause.tag_closing && nextAfterElse?.tag_opening) {
|
|
205
|
+
this.addRange(node.else_clause.tag_closing.location.end, nextAfterElse.tag_opening.location.start)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (node.ensure_clause) {
|
|
210
|
+
if (node.ensure_clause.tag_closing && node.end_node?.tag_opening) {
|
|
211
|
+
this.addRange(node.ensure_clause.tag_closing.location.end, node.end_node.tag_opening.location.start)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.visitChildNodes(node)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
visitERBRescueNode(node: ERBRescueNode): void {
|
|
219
|
+
this.visitChildNodes(node)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
visitERBElseNode(node: ERBElseNode): void {
|
|
223
|
+
this.addRange(node.location.start, node.location.end)
|
|
224
|
+
this.visitChildNodes(node)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
visitERBEnsureNode(node: ERBEnsureNode): void {
|
|
228
|
+
this.visitChildNodes(node)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private markIfChainAsProcessed(node: ERBIfNode): void {
|
|
232
|
+
this.processedIfNodes.add(node)
|
|
233
|
+
|
|
234
|
+
let current: Node | null = node.subsequent
|
|
235
|
+
|
|
236
|
+
while (current) {
|
|
237
|
+
if (isERBIfNode(current)) {
|
|
238
|
+
this.processedIfNodes.add(current)
|
|
239
|
+
current = current.subsequent
|
|
240
|
+
} else {
|
|
241
|
+
break
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private addCaseFoldingRanges(node: ERBCaseNode | ERBCaseMatchNode): void {
|
|
247
|
+
type ConditionNode = ERBWhenNode | ERBInNode
|
|
248
|
+
const conditions = node.conditions as ConditionNode[]
|
|
249
|
+
|
|
250
|
+
const firstCondition = conditions[0]
|
|
251
|
+
const nextAfterCase = firstCondition ?? node.else_clause ?? node.end_node
|
|
252
|
+
|
|
253
|
+
if (node.tag_closing && nextAfterCase?.tag_opening) {
|
|
254
|
+
this.addRange(node.tag_closing.location.end, nextAfterCase.tag_opening.location.start)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < conditions.length; i++) {
|
|
258
|
+
const condition = conditions[i]
|
|
259
|
+
const nextCondition = conditions[i + 1] ?? node.else_clause ?? node.end_node
|
|
260
|
+
|
|
261
|
+
if (condition.tag_closing && nextCondition?.tag_opening) {
|
|
262
|
+
this.addRange(condition.tag_closing.location.end, nextCondition.tag_opening.location.start)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (node.else_clause) {
|
|
267
|
+
if (node.else_clause.tag_closing && node.end_node?.tag_opening) {
|
|
268
|
+
this.addRange(node.else_clause.tag_closing.location.end, node.end_node.tag_opening.location.start)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private addRange(start: SerializedPosition, end: SerializedPosition, kind?: FoldingRangeKind): void {
|
|
274
|
+
const startLine = lspLine(start)
|
|
275
|
+
const endLine = lspLine(end) - 1
|
|
276
|
+
|
|
277
|
+
if (endLine > startLine) {
|
|
278
|
+
this.ranges.push({
|
|
279
|
+
startLine,
|
|
280
|
+
startCharacter: start.column,
|
|
281
|
+
endLine,
|
|
282
|
+
endCharacter: end.column,
|
|
283
|
+
kind,
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -186,7 +186,7 @@ export class FormattingService {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
async formatOnSave(document: TextDocument, reason: TextDocumentSaveReason): Promise<TextEdit[]> {
|
|
189
|
+
async formatOnSave(document: TextDocument, reason: TextDocumentSaveReason, textOverride?: string): Promise<TextEdit[]> {
|
|
190
190
|
this.connection.console.log(`[Formatting] formatOnSave called for ${document.uri}`)
|
|
191
191
|
|
|
192
192
|
if (reason !== TextDocumentSaveReason.Manual) {
|
|
@@ -203,7 +203,7 @@ export class FormattingService {
|
|
|
203
203
|
return []
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
return this.performFormatting({ textDocument: { uri: document.uri }, options: { tabSize: 2, insertSpaces: true } })
|
|
206
|
+
return this.performFormatting({ textDocument: { uri: document.uri }, options: { tabSize: 2, insertSpaces: true } }, textOverride)
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
private shouldFormatFile(filePath: string): boolean {
|
|
@@ -235,7 +235,7 @@ export class FormattingService {
|
|
|
235
235
|
} as Config
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
private async performFormatting(params: DocumentFormattingParams): Promise<TextEdit[]> {
|
|
238
|
+
private async performFormatting(params: DocumentFormattingParams, textOverride?: string): Promise<TextEdit[]> {
|
|
239
239
|
const document = this.documents.get(params.textDocument.uri)
|
|
240
240
|
|
|
241
241
|
if (!document) {
|
|
@@ -243,7 +243,7 @@ export class FormattingService {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
try {
|
|
246
|
-
const text = document.getText()
|
|
246
|
+
const text = textOverride ?? document.getText()
|
|
247
247
|
const config = await this.getConfigWithSettings(params.textDocument.uri)
|
|
248
248
|
|
|
249
249
|
this.connection.console.log(`[Formatting] Creating formatter with ${this.preRewriters.length} pre-rewriters, ${this.postRewriters.length} post-rewriters`)
|