Skip to content

Commit

Permalink
Notify clients about a successful auto-save
Browse files Browse the repository at this point in the history
  • Loading branch information
hubertp committed Aug 9, 2022
1 parent 134fc50 commit 9bc75e4
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ class JsonConnectionController(
case TextProtocol.TextDidChange(changes) =>
webActor ! Notification(TextDidChange, TextDidChange.Params(changes))

case TextProtocol.FileAutoSaved(path) =>
webActor ! Notification(FileAutoSaved, FileAutoSaved.Params(path))

case PathWatcherProtocol.FileEventResult(event) =>
webActor ! Notification(
EventFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ object JsonRpc {
.registerNotification(ForceReleaseCapability)
.registerNotification(GrantCapability)
.registerNotification(TextDidChange)
.registerNotification(FileAutoSaved)
.registerNotification(EventFile)
.registerNotification(ContentRootAdded)
.registerNotification(ContentRootRemoved)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class CollaborativeBuffer(
path,
client.clientId
)
readFile(client, path)
readFile(client, path, Map.empty)

case OpenBuffer(client, path) =>
context.system.eventStream.publish(BufferOpened(path))
Expand All @@ -82,16 +82,17 @@ class CollaborativeBuffer(
path,
client.clientId
)
openBuffer(client, path)
openBuffer(client, path, Map.empty)
}

private def waitingForFileContent(
rpcSession: JsonSession,
replyTo: ActorRef,
timeoutCancellable: Cancellable
timeoutCancellable: Cancellable,
autoSave: Map[ClientId, Cancellable]
): Receive = {
case ReadTextualFileResult(Right(content)) =>
handleFileContent(rpcSession, replyTo, content)
handleFileContent(rpcSession, replyTo, content, autoSave)
unstashAll()
timeoutCancellable.cancel()

Expand All @@ -111,7 +112,7 @@ class CollaborativeBuffer(
buffer: Buffer,
clients: Map[ClientId, JsonSession],
lockHolder: Option[JsonSession],
autoSave: Option[Cancellable]
autoSave: Map[ClientId, Cancellable]
): Receive = {
case OpenFile(client, _) =>
openFile(buffer, clients, lockHolder, client, autoSave)
Expand Down Expand Up @@ -175,14 +176,15 @@ class CollaborativeBuffer(
lockHolder,
clientId,
clientVersion,
None,
autoSave.removed(clientId),
Some(replyTo)
)
}

private def saving(
buffer: Buffer,
clients: Map[ClientId, JsonSession],
autoSave: Map[ClientId, Cancellable],
lockHolder: Option[JsonSession],
replyTo: ActorRef,
timeoutCancellable: Cancellable,
Expand All @@ -191,20 +193,28 @@ class CollaborativeBuffer(
case IOTimeout =>
replyTo ! SaveFailed(OperationTimeout, triggeredByAutoSave)
unstashAll()
context.become(collaborativeEditing(buffer, clients, lockHolder, None))
context.become(
collaborativeEditing(buffer, clients, lockHolder, autoSave)
)

case WriteFileResult(Left(failure)) =>
replyTo ! SaveFailed(failure, triggeredByAutoSave)

unstashAll()
timeoutCancellable.cancel()
context.become(collaborativeEditing(buffer, clients, lockHolder, None))
context.become(
collaborativeEditing(buffer, clients, lockHolder, autoSave)
)

case WriteFileResult(Right(())) =>
replyTo ! FileSaved(triggeredByAutoSave)
if (triggeredByAutoSave)
clients.values.foreach { _.rpcController ! FileAutoSaved(bufferPath) }
unstashAll()
timeoutCancellable.cancel()
context.become(collaborativeEditing(buffer, clients, lockHolder, None))
context.become(
collaborativeEditing(buffer, clients, lockHolder, autoSave)
)

case _ =>
stash()
Expand All @@ -216,7 +226,7 @@ class CollaborativeBuffer(
lockHolder: Option[JsonSession],
clientId: ClientId,
clientVersion: ContentVersion,
currentAutoSave: Option[Cancellable],
currentAutoSaves: Map[ClientId, Cancellable],
autoSaveReplyTo: Option[ActorRef]
): Unit = {
val hasLock = lockHolder.exists(_.clientId == clientId)
Expand All @@ -227,14 +237,15 @@ class CollaborativeBuffer(
bufferPath,
buffer.contents.toString
)
currentAutoSave.foreach(_.cancel())
currentAutoSaves.get(clientId).foreach(_.cancel())

val timeoutCancellable = context.system.scheduler
.scheduleOnce(timeout, self, IOTimeout)
context.become(
saving(
buffer,
clients,
currentAutoSaves.removed(clientId),
lockHolder,
autoSaveReplyTo.getOrElse(sender()),
timeoutCancellable,
Expand All @@ -254,16 +265,26 @@ class CollaborativeBuffer(
}

private def upsertAutoSaveTimer(
currentAutoSave: Option[Cancellable],
currentAutoSave: Map[ClientId, Cancellable],
replyTo: ActorRef,
clientId: ClientId,
clientVersion: ContentVersion
): Option[Cancellable] = {
currentAutoSave.foreach(_.cancel())
timingsConfig.autoSaveDelay.map(
context.system.scheduler
.scheduleOnce(_, self, AutoSave(replyTo, clientId, clientVersion))
)
): Map[ClientId, Cancellable] = {
currentAutoSave.get(clientId).foreach(_.cancel())
val updatedAutoSave = currentAutoSave.removed(clientId)
timingsConfig.autoSaveDelay
.map(delay =>
updatedAutoSave.updated(
clientId,
context.system.scheduler
.scheduleOnce(
delay,
self,
AutoSave(replyTo, clientId, clientVersion)
)
)
)
.getOrElse(updatedAutoSave)
}

private def editExpressionValue(
Expand All @@ -274,7 +295,7 @@ class CollaborativeBuffer(
change: FileEdit,
expressionId: ExpressionId,
expressionValue: String,
autoSave: Option[Cancellable]
autoSave: Map[ClientId, Cancellable]
): Unit = {
applyEdits(buffer, lockHolder, clientId, change) match {
case Left(failure) =>
Expand All @@ -292,7 +313,7 @@ class CollaborativeBuffer(
expressionValue
)
)
val newAutoSave: Option[Cancellable] = upsertAutoSaveTimer(
val newAutoSave: Map[ClientId, Cancellable] = upsertAutoSaveTimer(
autoSave,
sender(),
clientId,
Expand All @@ -311,7 +332,7 @@ class CollaborativeBuffer(
clientId: ClientId,
change: FileEdit,
execute: Boolean,
autoSave: Option[Cancellable]
autoSave: Map[ClientId, Cancellable]
): Unit = {
applyEdits(buffer, lockHolder, clientId, change) match {
case Left(failure) =>
Expand All @@ -324,7 +345,7 @@ class CollaborativeBuffer(
runtimeConnector ! Api.Request(
Api.EditFileNotification(buffer.file, change.edits, execute)
)
val newAutoSave: Option[Cancellable] = upsertAutoSaveTimer(
val newAutoSave: Map[ClientId, Cancellable] = upsertAutoSaveTimer(
autoSave,
sender(),
clientId,
Expand Down Expand Up @@ -401,28 +422,37 @@ class CollaborativeBuffer(
TextEditValidationFailed(s"Invalid position: $position")
}

private def readFile(rpcSession: JsonSession, path: Path): Unit = {
private def readFile(
rpcSession: JsonSession,
path: Path,
autoSave: Map[ClientId, Cancellable]
): Unit = {
fileManager ! FileManagerProtocol.ReadFile(path)
val timeoutCancellable = context.system.scheduler
.scheduleOnce(timeout, self, IOTimeout)
context.become(
waitingForFileContent(rpcSession, sender(), timeoutCancellable)
waitingForFileContent(rpcSession, sender(), timeoutCancellable, autoSave)
)
}

private def openBuffer(rpcSession: JsonSession, path: Path): Unit = {
private def openBuffer(
rpcSession: JsonSession,
path: Path,
autoSave: Map[ClientId, Cancellable]
): Unit = {
fileManager ! FileManagerProtocol.OpenBuffer(path)
val timeoutCancellable = context.system.scheduler
.scheduleOnce(timeout, self, IOTimeout)
context.become(
waitingForFileContent(rpcSession, sender(), timeoutCancellable)
waitingForFileContent(rpcSession, sender(), timeoutCancellable, autoSave)
)
}

private def handleFileContent(
rpcSession: JsonSession,
originalSender: ActorRef,
file: TextualFileContent
file: TextualFileContent,
autoSave: Map[ClientId, Cancellable]
): Unit = {
val buffer = Buffer(file.path, file.content)
val cap = CapabilityRegistration(CanEdit(bufferPath))
Expand All @@ -437,7 +467,7 @@ class CollaborativeBuffer(
buffer,
Map(rpcSession.clientId -> rpcSession),
Some(rpcSession),
None
autoSave
)
)
}
Expand All @@ -447,7 +477,7 @@ class CollaborativeBuffer(
clients: Map[ClientId, JsonSession],
lockHolder: Option[JsonSession],
rpcSession: JsonSession,
autoSave: Option[Cancellable]
autoSave: Map[ClientId, Cancellable]
): Unit = {
val writeCapability =
if (lockHolder.isEmpty)
Expand All @@ -470,22 +500,27 @@ class CollaborativeBuffer(
clients: Map[ClientId, JsonSession],
lockHolder: Option[JsonSession],
clientId: ClientId,
autoSave: Option[Cancellable]
autoSave: Map[ClientId, Cancellable]
): Unit = {
val newLock =
lockHolder.flatMap {
case holder if holder.clientId == clientId => None
case holder => Some(holder)
}

autoSave.foreach(_.cancel())
autoSave.get(clientId).foreach(_.cancel())
val newClientMap = clients - clientId
if (newClientMap.isEmpty) {
runtimeConnector ! Api.Request(Api.CloseFileNotification(buffer.file))
stop()
} else {
context.become(
collaborativeEditing(buffer, newClientMap, newLock, None)
collaborativeEditing(
buffer,
newClientMap,
newLock,
autoSave.removed(clientId)
)
)
}
}
Expand All @@ -495,7 +530,7 @@ class CollaborativeBuffer(
clients: Map[ClientId, JsonSession],
lockHolder: Option[JsonSession],
clientId: ClientId,
autoSave: Option[Cancellable]
autoSave: Map[ClientId, Cancellable]
): Unit = {
lockHolder match {
case None =>
Expand All @@ -522,7 +557,7 @@ class CollaborativeBuffer(
lockHolder: Option[JsonSession],
clientId: JsonSession,
path: Path,
autoSave: Option[Cancellable]
autoSave: Map[ClientId, Cancellable]
): Unit = {
lockHolder match {
case None =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ object TextApi {
}
}

case object FileAutoSaved extends Method("text/autoSave") {
case class Params(path: Path)
implicit val hasParams = new HasParams[this.type] {
type Params = FileAutoSaved.Params
}
}

case object FileNotOpenedError extends Error(3001, "File not opened")

case class TextEditValidationError(msg: String) extends Error(3002, msg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ object TextProtocol {
*/
case class TextDidChange(changes: List[FileEdit])

/** A notification sent by the Language Server, notifying a client about
* a successful auto-save action.
*
* @param path path to the saved file
*/
case class FileAutoSaved(path: Path)

/** Requests the language server to save a file on behalf of a given user.
*
* @param clientId the client closing the file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2306,6 +2306,17 @@ class TextOperationsTest extends BaseServerTest with FlakySpec {
""")
Thread.sleep(8.seconds.toMillis)
// No explicit file save
client.expectJson(json"""
{ "jsonrpc": "2.0",
"method":"text/autoSave",
"params": {
"path": {
"rootId": $testContentRootId,
"segments": [ "foo.txt" ]
}
}
}
""")
client.send(json"""
{ "jsonrpc": "2.0",
"method": "file/read",
Expand Down

0 comments on commit 9bc75e4

Please sign in to comment.