Skip to content

Commit

Permalink
TextEdit with custom IdMap (#10283)
Browse files Browse the repository at this point in the history
close #10182

Changelog:
- add: IdMap parameter to the `text/applyEdit` request
- add: IdMap to the runtime module
- update: set IdMap during the interactive compilation
- update: set the IR identifiers in the `TreeToIR` parsing step
  • Loading branch information
4e6 authored Jun 15, 2024
1 parent dee9e07 commit 04a92ef
Show file tree
Hide file tree
Showing 30 changed files with 582 additions and 118 deletions.
14 changes: 12 additions & 2 deletions docs/language-server/protocol-language-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -2834,10 +2834,20 @@ that some edits are applied and others are not.
interface TextApplyEditParameters {
/** The file edit. */
edit: FileEdit;
/** A flag indicating whether we should re-execute the program after applying

/**
* A flag indicating whether we should re-execute the program after applying
* the edit. Default value is `true`, indicating the program should be
* re-executed. */
* re-executed.
*/
execute?: boolean;

/**
* An identifiers map associated with this file as an array of
* index, length, uuid triples. The old id map format that was used in the
* source file is also supported.
*/
idMap?: [number, number, UUID][];
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class ApplyEditHandler(
bufferRegistry ! TextProtocol.ApplyEdit(
Some(rpcSession.clientId),
params.edit,
params.execute.getOrElse(true)
params.execute.getOrElse(true),
params.idMap
)
val cancellable =
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class BufferRegistry(
sender() ! CapabilityReleaseBadRequest
}

case msg @ ApplyEdit(_, FileEdit(path, _, _, _), _) =>
case msg @ ApplyEdit(_, FileEdit(path, _, _, _), _, _) =>
if (registry.contains(path)) {
registry(path).forward(msg)
} else {
Expand Down Expand Up @@ -245,7 +245,8 @@ class BufferRegistry(
msg.oldVersion,
msg.newVersion
),
execute = true
execute = true,
None
)
}
case None =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import org.enso.languageserver.util.UnhandledLogging
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.text.{ContentBasedVersioning, ContentVersion}
import org.enso.text.editing._
import org.enso.text.editing.model.TextEdit
import org.enso.text.editing.model.{IdMap, TextEdit}

import java.util.UUID

Expand Down Expand Up @@ -259,8 +259,17 @@ class CollaborativeBuffer(
sender() ! FileNotOpened
}

case ApplyEdit(clientId, change, execute) =>
edit(buffer, clients, lockHolder, clientId, change, execute, autoSave)
case ApplyEdit(clientId, change, execute, idMap) =>
edit(
buffer,
clients,
lockHolder,
clientId,
change,
execute,
idMap,
autoSave
)

case ApplyExpressionValue(
clientId,
Expand Down Expand Up @@ -412,7 +421,7 @@ class CollaborativeBuffer(
buffer.version.toHexString
)
runtimeConnector ! Api.Request(
Api.EditFileNotification(file.path, edits, execute = true)
Api.EditFileNotification(file.path, edits, execute = true, None)
)
clients.values.foreach { _.rpcController ! TextDidChange(List(change)) }
unstashAll()
Expand Down Expand Up @@ -727,6 +736,7 @@ class CollaborativeBuffer(
clientId: Option[ClientId],
change: FileEdit,
execute: Boolean,
idMap: Option[IdMap],
autoSave: Map[ClientId, (ContentVersion, Cancellable)]
): Unit = {
applyEdits(buffer, lockHolder, clientId, change) match {
Expand All @@ -743,7 +753,8 @@ class CollaborativeBuffer(
Api.EditFileNotification(
buffer.fileWithMetadata.file,
change.edits,
execute
execute,
idMap
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import org.enso.languageserver.data.CapabilityRegistration
import org.enso.languageserver.filemanager.Path
import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused}
import org.enso.polyglot.runtime.Runtime.Api.ExpressionId
import org.enso.text.editing.model.TextEdit
import org.enso.text.editing.model.{IdMap, TextEdit}

/** The text editing JSON RPC API provided by the language server.
* See [[https://github.com/enso-org/enso/blob/develop/docs/language-server/README.md]]
* for message specifications.
*/
object TextApi {
object TextApi extends TextProtocol.Codecs {

type Version = String

Expand Down Expand Up @@ -61,7 +61,11 @@ object TextApi {
}

case object ApplyEdit extends Method("text/applyEdit") {
case class Params(edit: FileEdit, execute: Option[Boolean])
case class Params(
edit: FileEdit,
execute: Option[Boolean],
idMap: Option[IdMap]
)
implicit val hasParams: HasParams.Aux[this.type, ApplyEdit.Params] =
new HasParams[this.type] {
type Params = ApplyEdit.Params
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.enso.languageserver.text

import io.circe.{Decoder, Encoder, Json}
import io.circe.syntax._
import org.enso.languageserver.data.{CapabilityRegistration, ClientId}
import org.enso.languageserver.filemanager.{
FileAttributes,
Expand All @@ -9,7 +11,9 @@ import org.enso.languageserver.filemanager.{
}
import org.enso.languageserver.session.JsonSession
import org.enso.polyglot.runtime.Runtime.Api.ExpressionId
import org.enso.text.editing.model.TextEdit
import org.enso.text.editing.model.{IdMap, Span, TextEdit}

import java.util.UUID

object TextProtocol {

Expand Down Expand Up @@ -69,11 +73,13 @@ object TextProtocol {
* @param clientId the client requesting edits.
* @param edit a diff describing changes made to a file
* @param execute whether to execute the program after applying the edits
* @param idMap external identifiers
*/
case class ApplyEdit(
clientId: Option[ClientId],
edit: FileEdit,
execute: Boolean
execute: Boolean,
idMap: Option[IdMap]
)

/** Signals the result of applying a series of edits.
Expand Down Expand Up @@ -214,4 +220,67 @@ object TextProtocol {
*/
case class ReadCollaborativeBufferResult(buffer: Option[Buffer])

trait Codecs {

private object IdMapOldCodecs {

private object CodecField {
val Index = "index"
val Size = "size"
val Value = "value"
}

implicit private val spanDecoder: Decoder[Span] =
Decoder.instance { cursor =>
for {
index <- cursor
.downField(CodecField.Index)
.downField(CodecField.Value)
.as[Int]
size <- cursor
.downField(CodecField.Size)
.downField(CodecField.Value)
.as[Int]
} yield Span(index, index + size)
}

implicit def idMapDecoder: Decoder[IdMap] =
Decoder.instance { cursor =>
for {
pairs <- Decoder[Vector[(Span, UUID)]].tryDecode(cursor)
} yield IdMap(pairs)
}
}

private object IdMapCodecs {

implicit def idMapEncoder: Encoder[IdMap] =
Encoder.instance { idMap =>
Json.fromValues(
idMap.values
.map({ case (span, uuid) =>
Json.arr((span.start, span.length, uuid).asJson)
})
)
}

implicit def idMapDecoder: Decoder[IdMap] =
Decoder.instance { cursor =>
for {
triples <- Decoder[Vector[(Int, Int, UUID)]].tryDecode(cursor)
} yield {
val pairs = triples.map({ case (start, length, uuid) =>
Span(start, start + length) -> uuid
})
IdMap(pairs)
}
}
}

implicit val encoder: Encoder[IdMap] =
IdMapCodecs.idMapEncoder

implicit val decoder: Decoder[IdMap] =
IdMapCodecs.idMapDecoder.or(IdMapOldCodecs.idMapDecoder)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ class FileNotificationsTest
Api.EditFileNotification(
file("foo.txt"),
Seq(TextEdit(Range(Position(0, 0), Position(0, 0)), "bar")),
execute = true
execute = true,
None
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2076,6 +2076,103 @@ class TextOperationsTest
}
""")
}

"apply edit with IdMap" in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "file/write",
"id": 0,
"params": {
"path": {
"rootId": $testContentRootId,
"segments": [ "fooIdMap.txt" ]
},
"contents": "123456789"
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 0,
"result": null
}
""")
client.send(json"""
{ "jsonrpc": "2.0",
"method": "text/openFile",
"id": 1,
"params": {
"path": {
"rootId": $testContentRootId,
"segments": [ "fooIdMap.txt" ]
}
}
}
""")
receiveAndReplyToOpenFile("fooIdMap.txt")
client.expectJson(json"""
{
"jsonrpc" : "2.0",
"id" : 1,
"result" : {
"writeCapability" : {
"method" : "text/canEdit",
"registerOptions" : {
"path" : {
"rootId" : $testContentRootId,
"segments" : [
"fooIdMap.txt"
]
}
}
},
"content" : "123456789",
"currentVersion" : "5795c3d628fd638c9835a4c79a55809f265068c88729a1a3fcdf8522"
}
}
""")

client.send(json"""
{ "jsonrpc": "2.0",
"method": "text/applyEdit",
"id": 2,
"params": {
"edit": {
"path": {
"rootId": $testContentRootId,
"segments": [ "fooIdMap.txt" ]
},
"oldVersion": "5795c3d628fd638c9835a4c79a55809f265068c88729a1a3fcdf8522",
"newVersion": "ebe55342f9c8b86857402797dd723fb4a2174e0b56d6ace0a6929ec3",
"edits": [
{
"range": {
"start": { "line": 0, "character": 0 },
"end": { "line": 0, "character": 0 }
},
"text": "bar"
},
{
"range": {
"start": { "line": 0, "character": 12 },
"end": { "line": 0, "character": 12 }
},
"text": "foo"
}
]
},
"idMap": [[0, 1, "00000000-0000-0000-0000-000000000001"]]
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 2,
"result": null
}
""")
}
}

"text/applyExpressionValue" must {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.enso.pkg.{ComponentGroups, QualifiedName}
import org.enso.polyglot.{ModuleExports, Suggestion}
import org.enso.polyglot.data.{Tree, TypeGraph}
import org.enso.text.editing.model
import org.enso.text.editing.model.{Range, TextEdit}
import org.enso.text.editing.model.{IdMap, Range, TextEdit}

import java.io.File
import java.util.UUID
Expand Down Expand Up @@ -1091,7 +1091,8 @@ object Runtime {
final case class EditFileNotification(
path: File,
edits: Seq[TextEdit],
execute: Boolean
execute: Boolean,
idMap: Option[IdMap]
) extends ApiRequest
with ToLogString {

Expand All @@ -1100,7 +1101,9 @@ object Runtime {
"EditFileNotification(" +
s"path=${MaskedPath(path.toPath).toLogString(shouldMask)},edits=" +
(if (shouldMask) edits.map(_ => STUB) else edits) +
",execute=" + execute + ")"
",execute=" + execute +
"idMap=" + idMap.map(_ => STUB) +
")"
}

/** A notification sent to the server about in-memory file contents being
Expand Down
Loading

0 comments on commit 04a92ef

Please sign in to comment.