Skip to content

Commit e1e0b5b

Browse files
committed
Create visitors, add folding range reporting
1 parent a61a756 commit e1e0b5b

File tree

6 files changed

+173
-32
lines changed

6 files changed

+173
-32
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.gradle.declarative.lsp.modelutils
2+
3+
import org.gradle.internal.declarativedsl.dom.DeclarativeDocument
4+
import org.gradle.internal.declarativedsl.dom.DocumentNodeContainer
5+
6+
7+
open class DocumentNodeVisitor {
8+
9+
open fun visitNode(node: DeclarativeDocument.Node) {}
10+
11+
open fun visitDocumentNode(node: DeclarativeDocument.DocumentNode) {}
12+
13+
open fun visitDocumentElementNode(node: DeclarativeDocument.DocumentNode.ElementNode) {}
14+
15+
open fun visitDocumentPropertyNode(node: DeclarativeDocument.DocumentNode.PropertyNode) {}
16+
17+
open fun visitDocumentErrorNode(node: DeclarativeDocument.DocumentNode.ErrorNode) {}
18+
19+
open fun visitValueNode(node: DeclarativeDocument.ValueNode) {}
20+
21+
open fun visitValueLiteralNode(node: DeclarativeDocument.ValueNode.LiteralValueNode) {}
22+
23+
open fun visitValueFactoryNode(node: DeclarativeDocument.ValueNode.ValueFactoryNode) {}
24+
}
25+
26+
// Extend the DocumentNode with a visitor pattern
27+
fun DeclarativeDocument.visit(visitor: DocumentNodeVisitor) {
28+
// Initialize the list of nodes to visit with the root nodes of the forest
29+
val nodesToVisit = this.content.toMutableList()
30+
31+
while (nodesToVisit.isNotEmpty()) {
32+
val node = nodesToVisit.removeFirst()
33+
34+
visitor.visitNode(node)
35+
if (node is DocumentNodeContainer) {
36+
nodesToVisit.addAll(node.content)
37+
}
38+
when (node) {
39+
is DeclarativeDocument.DocumentNode -> {
40+
visitor.visitDocumentNode(node)
41+
when (node) {
42+
is DeclarativeDocument.DocumentNode.ElementNode -> visitor.visitDocumentElementNode(node)
43+
is DeclarativeDocument.DocumentNode.ErrorNode -> visitor.visitDocumentErrorNode(node)
44+
is DeclarativeDocument.DocumentNode.PropertyNode -> visitor.visitDocumentPropertyNode(node)
45+
}
46+
}
47+
is DeclarativeDocument.ValueNode -> {
48+
visitor.visitValueNode(node)
49+
when (node) {
50+
is DeclarativeDocument.ValueNode.LiteralValueNode -> visitor.visitValueLiteralNode(node)
51+
is DeclarativeDocument.ValueNode.ValueFactoryNode -> visitor.visitValueFactoryNode(node)
52+
else -> {}
53+
}
54+
}
55+
}
56+
}
57+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.gradle.declarative.lsp.modelutils
2+
3+
import org.eclipse.lsp4j.FoldingRange
4+
import org.eclipse.lsp4j.FoldingRangeKind
5+
import org.gradle.internal.declarativedsl.dom.DeclarativeDocument
6+
7+
class FoldingRangeVisitor: DocumentNodeVisitor() {
8+
9+
val foldingRanges: MutableList<FoldingRange> = mutableListOf()
10+
11+
override fun visitDocumentElementNode(node: DeclarativeDocument.DocumentNode.ElementNode) {
12+
val range = FoldingRange(
13+
// 1-based to 0-based
14+
node.sourceData.lineRange.first - 1,
15+
// 1-based to 0-based, but DCL is inclusive and LSP is exclusive (hence nothing)
16+
node.sourceData.lineRange.last,
17+
).apply {
18+
kind = FoldingRangeKind.Region
19+
startCharacter = node.sourceData.startColumn - 1
20+
endCharacter = node.sourceData.endColumn - 1
21+
collapsedText = node.name
22+
}
23+
foldingRanges.add(range)
24+
}
25+
26+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.gradle.declarative.lsp.modelutils
2+
3+
import org.gradle.internal.declarativedsl.dom.DeclarativeDocument
4+
5+
/**
6+
* Visitor capable of finding the node in a document that matches a given cursor position.
7+
*/
8+
class LocationMatchingVisitor(val line: Int, val column: Int) : DocumentNodeVisitor() {
9+
10+
var matchingNode: DeclarativeDocument.Node? = null
11+
12+
override fun visitNode(node: DeclarativeDocument.Node) {
13+
if (isPositionInNode(node, line, column)) {
14+
matchingNode = node
15+
}
16+
}
17+
18+
companion object {
19+
private fun isPositionInNode(node: DeclarativeDocument.Node, line: Int, column: Int): Boolean {
20+
// If we are not in the line range of the node, we can be sure that we are not in the node
21+
if (!node.sourceData.lineRange.contains(line)) {
22+
return false
23+
}
24+
25+
// In the first and last line, we need to have extra checks for the column
26+
// Note that in a single line node, the start and end column are the same, so both will be checked
27+
if (node.sourceData.lineRange.first == line && node.sourceData.startColumn > column) {
28+
return false
29+
}
30+
if (node.sourceData.lineRange.last == line && node.sourceData.endColumn < column) {
31+
return false
32+
}
33+
34+
return true
35+
}
36+
}
37+
38+
}

lsp/src/main/kotlin/org/gradle/declarative/lsp/server/DeclarativeLanguageServer.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class DeclarativeLanguageServer : LanguageServer, LanguageClientAware {
3333
val serverCapabilities = ServerCapabilities()
3434
serverCapabilities.setTextDocumentSync(TextDocumentSyncKind.Full)
3535
serverCapabilities.setHoverProvider(true)
36+
serverCapabilities.setFoldingRangeProvider(true)
3637

3738
val workspaceFolder = params!!.workspaceFolders[0]
3839
val workspaceFolderFile = File(URI.create(workspaceFolder.uri))

lsp/src/main/kotlin/org/gradle/declarative/lsp/server/DeclarativeTextDocumentService.kt

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import org.eclipse.lsp4j.services.LanguageClient
55
import org.eclipse.lsp4j.services.LanguageClientAware
66
import org.eclipse.lsp4j.services.TextDocumentService
77
import org.gradle.declarative.lsp.build.model.ResolvedDeclarativeResourcesModel
8+
import org.gradle.declarative.lsp.modelutils.FoldingRangeVisitor
9+
import org.gradle.declarative.lsp.modelutils.LocationMatchingVisitor
10+
import org.gradle.declarative.lsp.modelutils.visit
811
import org.gradle.internal.declarativedsl.dom.DeclarativeDocument
9-
import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.ElementNode
10-
import org.gradle.internal.declarativedsl.dom.DeclarativeDocument.DocumentNode.PropertyNode
1112
import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils
1213
import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator
13-
import org.gradle.internal.declarativedsl.language.SourceData
1414
import java.io.File
1515
import java.net.URI
1616
import java.util.concurrent.CompletableFuture
@@ -49,6 +49,7 @@ class DeclarativeTextDocumentService : TextDocumentService, LanguageClientAware
4949
AnalysisDocumentUtils.documentWithConventions(settingsSchema, fileSchema)?.let {
5050
domStore[uri] = it.document
5151
}
52+
5253
System.err.println("Stored declarative model for document: $uri")
5354
}
5455
}
@@ -66,34 +67,59 @@ class DeclarativeTextDocumentService : TextDocumentService, LanguageClientAware
6667
}
6768

6869
override fun hover(params: HoverParams?): CompletableFuture<Hover> {
69-
params?.textDocument?.uri?.let { uri ->
70-
val position = params.position
71-
System.err.println("Hovering over document: $uri at line ${position.line} column ${position.character}")
72-
73-
domStore[uri]?.let {
74-
val closest = findBestFitting(it.content, position)
75-
System.err.println("Closest node: $closest")
70+
val hover = params?.let { nonNullParams ->
71+
withDom(nonNullParams.textDocument) { dom ->
72+
val position = nonNullParams.position
73+
74+
// LSPs are supplying 0-based line and column numbers, while the DSL model is 1-based
75+
val visitor = LocationMatchingVisitor(position.line + 1, position.character + 1)
76+
dom.visit(visitor)
77+
78+
visitor.matchingNode?.let { node ->
79+
when (node) {
80+
is DeclarativeDocument.DocumentNode.PropertyNode -> node.name
81+
is DeclarativeDocument.DocumentNode.ElementNode -> node.name
82+
else -> null
83+
}?.let { name ->
84+
// We need to convert back the 1-based locations to 0-based ones
85+
val startPosition = Position(
86+
node.sourceData.lineRange.first - 1,
87+
node.sourceData.startColumn - 1
88+
)
89+
// End position is tricky, as...
90+
val endPosition = Position(
91+
node.sourceData.lineRange.last - 1,
92+
// ... the end column is exclusive, so we DON'T need to subtract 1
93+
node.sourceData.endColumn
94+
)
95+
96+
Hover(
97+
MarkupContent("plaintext", name),
98+
Range(startPosition, endPosition)
99+
)
100+
}
101+
}
76102
}
77103
}
104+
return CompletableFuture.completedFuture(hover)
105+
}
78106

79-
return CompletableFuture()
107+
override fun foldingRange(params: FoldingRangeRequestParams?): CompletableFuture<MutableList<FoldingRange>> {
108+
System.err.println("Asking for folding ranges")
109+
val foldingRanges = withDom(params?.textDocument) { dom ->
110+
val visitor = FoldingRangeVisitor()
111+
dom.visit(visitor)
112+
visitor.foldingRanges
113+
}
114+
return CompletableFuture.completedFuture(foldingRanges)
80115
}
81116

82-
companion object {
83-
fun findBestFitting(
84-
nodes: List<DeclarativeDocument.DocumentNode>, cursorPosition: Position
85-
): DeclarativeDocument.DocumentNode? {
86-
return nodes.firstNotNullOf { node ->
87-
when (node) {
88-
is ElementNode -> findBestFitting(node.content, cursorPosition)
89-
is PropertyNode -> if (cursorIsInside(node.sourceData, cursorPosition)) node else null
90-
else -> null
91-
}
117+
private fun <T> withDom(textDocument: TextDocumentIdentifier?, work: (DeclarativeDocument) -> T): T? =
118+
textDocument?.uri?.let {
119+
val dom = domStore[it]
120+
dom?.let {
121+
work.invoke(dom)
92122
}
93123
}
94124

95-
private fun cursorIsInside(candidatePosition: SourceData, cursorPosition: Position): Boolean {
96-
return candidatePosition.lineRange.contains(cursorPosition.line) && candidatePosition.startColumn <= cursorPosition.character && candidatePosition.endColumn >= cursorPosition.character
97-
}
98-
}
99125
}

lsp/src/main/kotlin/org/gradle/declarative/lsp/utils/ParamsUtils.kt

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)