Skip to content

Commit

Permalink
Support Autosave for open buffers (#3637)
Browse files Browse the repository at this point in the history
This change adds Autosave action for open buffers. The action is scheduled
after every edit request and is cancelled by every explicit save file request, if
necessary. Successful autosave also notifies any active clients of the buffer.

Related to https://www.pivotaltracker.com/story/show/182721656

# Important Notes
WIP
  • Loading branch information
hubertp authored Aug 11, 2022
1 parent a7bc3c6 commit 3fa78af
Show file tree
Hide file tree
Showing 13 changed files with 592 additions and 77 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@
- [Enable caching in visualisation functions][3618]
- [Update Scala compiler and libraries][3631]
- [Support importing module methods][3633]
- [Support Autosave for open buffers][3637]

[3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248
Expand Down Expand Up @@ -347,6 +348,7 @@
[3608]: https://github.com/enso-org/enso/pull/3608
[3631]: https://github.com/enso-org/enso/pull/3631
[3633]: https://github.com/enso-org/enso/pull/3633
[3637]: https://github.com/enso-org/enso/pull/3637

# Enso 2.0.0-alpha.18 (2021-10-12)

Expand Down
27 changes: 27 additions & 0 deletions docs/language-server/protocol-language-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ transport formats, please look [here](./protocol-architecture).
- [`text/applyEdit`](#textapplyedit)
- [`text/applyExpressionValue`](#textapplyexpressionvalue)
- [`text/didChange`](#textdidchange)
- [`text/autoSave`](#textautosave)
- [Workspace Operations](#workspace-operations)
- [`workspace/projectInfo`](#workspaceprojectinfo)
- [Monitoring](#monitoring)
Expand Down Expand Up @@ -2876,6 +2877,32 @@ This notification must _only_ be sent for files that the client has open.
null;
```

### `text/autoSave`

This is a notification sent from the server to the clients to inform them of any
successful auto-save action.

- **Type:** Notification
- **Direction:** Server -> Client
- **Connection:** Protocol
- **Visibility:** Public

This notification must _only_ be sent for files that the client has open.

#### Parameters

```typescript
{
path: Path;
}
```

#### Errors

```typescript
null;
```

## Workspace Operations

The language server also has a set of operations useful for managing the client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,11 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {

lazy val bufferRegistry =
system.actorOf(
BufferRegistry.props(fileManager, runtimeConnector),
BufferRegistry.props(
fileManager,
runtimeConnector,
TimingsConfig.default().withAutoSave(6.seconds)
),
"buffer-registry"
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.enso.languageserver.boot

import scala.concurrent.duration._

/** TimingsConfig encapsulates information about timings or delays in messages being sent between services.
*
* @param autoSave a request timeout
* @param autoSave if non-empty value, determines the delay when auto-save should be triggered
*/
class TimingsConfig(
private[this] val timeout: FiniteDuration,
private[this] var autoSave: Option[FiniteDuration]
) {
def this(timeout: FiniteDuration) = {
this(timeout, None)
}

/** A request timeout.
*
* @return a duration to wait for the request to be handled
*/
def requestTimeout: FiniteDuration = timeout

/** Auto-save delay.
*
* @return if non-empty, determines the delay when auto-save should be triggered after the last edit
*/
def autoSaveDelay: Option[FiniteDuration] = autoSave

/** Sets the delay for auto-save action that should be triggered after the last edit action.
*
* @param delay delay for auto-save action
* @return updated config
*/
def withAutoSave(delay: FiniteDuration): TimingsConfig = {
autoSave = Some(delay)
this
}
}

object TimingsConfig {
def default(): TimingsConfig = new TimingsConfig(10.seconds)
}
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 @@ -9,35 +9,50 @@ import org.enso.text.buffer.Rope
*
* @param file the file linked to the buffer.
* @param contents the contents of the buffer.
* @param inMemory determines if the buffer is in-memory
* @param version the current version of the buffer contents.
*/
case class Buffer(file: File, contents: Rope, version: ContentVersion)
case class Buffer(
file: File,
contents: Rope,
inMemory: Boolean,
version: ContentVersion
)

object Buffer {

/** Creates a new buffer with a freshly generated version.
*
* @param file the file linked to the buffer.
* @param contents the contents of this buffer.
* @param inMemory determines if the buffer is in-memory
* @param versionCalculator a digest calculator for content based versioning.
* @return a new buffer instance.
*/
def apply(
file: File,
contents: Rope
contents: Rope,
inMemory: Boolean
)(implicit versionCalculator: ContentBasedVersioning): Buffer =
Buffer(file, contents, versionCalculator.evalVersion(contents.toString))
Buffer(
file,
contents,
inMemory,
versionCalculator.evalVersion(contents.toString)
)

/** Creates a new buffer with a freshly generated version.
*
* @param file the file linked to the buffer.
* @param contents the contents of this buffer.
* @param inMemory determines if the buffer is in-memory
* @param versionCalculator a digest calculator for content based versioning.
* @return a new buffer instance.
*/
def apply(
file: File,
contents: String
contents: String,
inMemory: Boolean
)(implicit versionCalculator: ContentBasedVersioning): Buffer =
Buffer(file, Rope(contents))
Buffer(file, Rope(contents), inMemory)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.enso.languageserver.text

import akka.actor.{Actor, ActorRef, Props, Stash, Terminated}
import com.typesafe.scalalogging.LazyLogging
import org.enso.languageserver.boot.TimingsConfig
import org.enso.languageserver.capability.CapabilityProtocol.{
AcquireCapability,
CapabilityAcquisitionBadRequest,
Expand Down Expand Up @@ -58,10 +59,12 @@ import org.enso.text.ContentBasedVersioning
* @param fileManager a file manager
* @param runtimeConnector a gateway to the runtime
* @param versionCalculator a content based version calculator
* @param timingsConfig a config with timeout/delay values
*/
class BufferRegistry(
fileManager: ActorRef,
runtimeConnector: ActorRef
runtimeConnector: ActorRef,
timingsConfig: TimingsConfig
)(implicit
versionCalculator: ContentBasedVersioning
) extends Actor
Expand Down Expand Up @@ -100,7 +103,8 @@ class BufferRegistry(
CollaborativeBuffer.props(
path,
fileManager,
runtimeConnector
runtimeConnector,
timingsConfig = timingsConfig
)
)
context.watch(bufferRef)
Expand All @@ -117,7 +121,8 @@ class BufferRegistry(
CollaborativeBuffer.props(
path,
fileManager,
runtimeConnector
runtimeConnector,
timingsConfig = timingsConfig
)
)
context.watch(bufferRef)
Expand Down Expand Up @@ -180,14 +185,16 @@ object BufferRegistry {
* @param fileManager a file manager actor
* @param runtimeConnector a gateway to the runtime
* @param versionCalculator a content based version calculator
* @param timingsConfig a config with timout/delay values
* @return a configuration object
*/
def props(
fileManager: ActorRef,
runtimeConnector: ActorRef
runtimeConnector: ActorRef,
timingsConfig: TimingsConfig
)(implicit
versionCalculator: ContentBasedVersioning
): Props =
Props(new BufferRegistry(fileManager, runtimeConnector))
Props(new BufferRegistry(fileManager, runtimeConnector, timingsConfig))

}
Loading

0 comments on commit 3fa78af

Please sign in to comment.