Allow file/read to return the contents of a collaborative buffer (#9994)

part of #9960

Changelog:
- feat: file/read return contents of a collaborative buffer if they are available and fallback to reading the file from disk
This commit is contained in:
Dmitry Bushev 2024-05-20 15:01:59 +01:00 committed by GitHub
parent 1991aab19d
commit 643b66d0b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 200 additions and 4 deletions

View File

@ -538,7 +538,7 @@ class JsonConnectionController(
WriteFile -> file.WriteTextualFileHandler
.props(requestTimeout, fileManager),
ReadFile -> file.ReadTextualFileHandler
.props(requestTimeout, fileManager),
.props(requestTimeout, bufferRegistry, fileManager),
CreateFile -> file.CreateFileHandler.props(requestTimeout, fileManager),
DeleteFile -> file.DeleteFileHandler.props(requestTimeout, fileManager),
CopyFile -> file.CopyFileHandler.props(requestTimeout, fileManager),

View File

@ -9,6 +9,10 @@ import org.enso.languageserver.filemanager.{
}
import org.enso.languageserver.filemanager.FileManagerApi.ReadFile
import org.enso.languageserver.requesthandler.RequestTimeout
import org.enso.languageserver.text.TextProtocol.{
ReadCollaborativeBuffer,
ReadCollaborativeBufferResult
}
import org.enso.languageserver.util.UnhandledLogging
import org.enso.logger.masking.MaskedString
@ -16,6 +20,7 @@ import scala.concurrent.duration.FiniteDuration
class ReadTextualFileHandler(
requestTimeout: FiniteDuration,
bufferRegistry: ActorRef,
fileManager: ActorRef
) extends Actor
with LazyLogging
@ -27,10 +32,53 @@ class ReadTextualFileHandler(
private def requestStage: Receive = {
case Request(ReadFile, id, params: ReadFile.Params) =>
bufferRegistry ! ReadCollaborativeBuffer(params.path)
val cancellable = context.system.scheduler
.scheduleOnce(requestTimeout, self, RequestTimeout)
context.become(requestBufferStage(id, sender(), cancellable, params))
}
private def requestBufferStage(
id: Id,
replyTo: ActorRef,
cancellable: Cancellable,
params: ReadFile.Params
): Receive = {
case Status.Failure(ex) =>
logger.error(
"Failure during [{}] operation: {}",
ReadFile,
MaskedString(ex.getMessage)
)
replyTo ! ResponseError(Some(id), Errors.ServiceError)
cancellable.cancel()
context.stop(self)
case RequestTimeout =>
logger.warn(
"Read collaborative buffer timed out. " +
"Falling back to reading file contents."
)
fileManager ! FileManagerProtocol.ReadFile(params.path)
val cancellable = context.system.scheduler
.scheduleOnce(requestTimeout, self, RequestTimeout)
context.become(responseStage(id, sender(), cancellable))
context.become(responseStage(id, replyTo, cancellable))
case ReadCollaborativeBufferResult(None) =>
cancellable.cancel()
fileManager ! FileManagerProtocol.ReadFile(params.path)
val newCancellable = context.system.scheduler
.scheduleOnce(requestTimeout, self, RequestTimeout)
context.become(responseStage(id, replyTo, newCancellable))
case ReadCollaborativeBufferResult(Some(buffer)) =>
replyTo ! ResponseResult(
ReadFile,
id,
ReadFile.Result(buffer.contents.toString)
)
cancellable.cancel()
context.stop(self)
}
private def responseStage(
@ -70,7 +118,11 @@ class ReadTextualFileHandler(
object ReadTextualFileHandler {
def props(timeout: FiniteDuration, fileManager: ActorRef): Props =
Props(new ReadTextualFileHandler(timeout, fileManager))
def props(
timeout: FiniteDuration,
bufferRegistry: ActorRef,
fileManager: ActorRef
): Props =
Props(new ReadTextualFileHandler(timeout, bufferRegistry, fileManager))
}

View File

@ -38,6 +38,8 @@ import org.enso.languageserver.text.TextProtocol.{
FileSaved,
OpenBuffer,
OpenFile,
ReadCollaborativeBuffer,
ReadCollaborativeBufferResult,
SaveFailed,
SaveFile
}
@ -156,6 +158,13 @@ class BufferRegistry(
context.become(running(registry + (path -> bufferRef)))
}
case msg @ ReadCollaborativeBuffer(path) =>
if (registry.contains(path)) {
registry(path).forward(msg)
} else {
sender() ! ReadCollaborativeBufferResult(None)
}
case Terminated(bufferRef) =>
context.become(running(registry.filter(_._2 != bufferRef)))

View File

@ -357,6 +357,9 @@ class CollaborativeBuffer(
failure
)
}
case ReadCollaborativeBuffer(_) =>
sender() ! ReadCollaborativeBufferResult(Some(buffer))
}
private def waitingOnReloadedContent(

View File

@ -202,4 +202,16 @@ object TextProtocol {
/** Signals that the file is modified on disk. */
case object SaveFailedFileModifiedOnDisk extends SaveFileResult
/** Read the contents of a collaborative buffer.
*
* @param path the file path
*/
case class ReadCollaborativeBuffer(path: Path)
/** The data carried by a successful read operation.
*
* @param buffer file contents
*/
case class ReadCollaborativeBufferResult(buffer: Option[Buffer])
}

View File

@ -408,6 +408,126 @@ class TextOperationsTest
}""")
}
"allow file/read to read contents of a buffer" in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "file/write",
"id": 0,
"params": {
"path": {
"rootId": $testContentRootId,
"segments": [ "file_read_test.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": [ "file_read_test.txt" ]
}
}
}
""")
receiveAndReplyToOpenFile("file_read_test.txt")
client.expectJson(json"""
{
"jsonrpc" : "2.0",
"id" : 1,
"result" : {
"writeCapability" : {
"method" : "text/canEdit",
"registerOptions" : {
"path" : {
"rootId" : $testContentRootId,
"segments" : [
"file_read_test.txt"
]
}
}
},
"content" : "123456789",
"currentVersion" : "5795c3d628fd638c9835a4c79a55809f265068c88729a1a3fcdf8522"
}
}
""")
// apply edit
client.send(json"""
{ "jsonrpc": "2.0",
"method": "text/applyEdit",
"id": 2,
"params": {
"edit": {
"path": {
"rootId": $testContentRootId,
"segments": [ "file_read_test.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"
}
]
}
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 2,
"result": null
}
""")
// file/read
client.send(json"""
{ "jsonrpc": "2.0",
"method": "file/read",
"id": 3,
"params": {
"path": {
"rootId": $testContentRootId,
"segments": [ "file_read_test.txt" ]
}
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 3,
"result": { "contents": "bar123456789foo" }
}
""")
}
}
"text/openFile" must {