Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor the LS to Support Multiple Content Roots #1800

Merged
merged 8 commits into from
Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ jobs:
run: |
sleep 1
sbt --no-colors "launcher/assembly"
sbt --no-colors --mem 1536 "launcher-manager/buildNativeImage"
sbt --no-colors --mem 1536 "launcher/buildNativeImage"

- name: Build the PM Native Image
working-directory: repo
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,15 @@ jobs:
run: |
echo "CI_TEST_TIMEFACTOR=2" >> $GITHUB_ENV
echo "CI_TEST_FLAKY_ENABLE=true" >> $GITHUB_ENV
- name: Build the Launcher

- name: Build the Launcher Native Image
run: |
sleep 1
sbt --no-colors "launcher/assembly"
sbt --no-colors --mem 1536 "launcher/buildNativeImage"
- name: Build the PM Native Image
run: |
sbt --no-colors "project-manager/assembly"
sbt --no-colors --mem 1536 "project-manager/buildNativeImage"

- name: Build the Runner & Runtime Uberjars
run: |
Expand Down
3 changes: 3 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
- Added support for reading and writing byte ranges in files remotely
([#1795](https://github.com/enso-org/enso/pull/1795)). This allows the IDE to
transfer files to a remote back-end in a streaming fashion.
- Added support for multiple content roots in the language server
([#1800](https://github.com/enso-org/enso/pull/1800/)). It is not yet exposed
to the IDE, as this will be done as part of future work.

## Libraries

Expand Down
44 changes: 40 additions & 4 deletions docs/language-server/protocol-language-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -1035,11 +1035,11 @@ A representation of the contents of a file.
#### Format

```typescript
interface FileContents[T] {
interface FileContents<T> {
contents: T;
}

class TextFileContents extends FileContents[String];
class TextFileContents extends FileContents<String> {}
```

### `FileSystemObject`
Expand Down Expand Up @@ -1147,6 +1147,42 @@ table FileSegment {
The `byteOffset` property is zero-indexed, so the last byte in the file is at
index `file.length - 1`.

### `ContentRoot`

A representation of a content root for use in the IDE. A content root represents
a location on a real file-system that has been virtualised for use in the Enso
VFS.

```typescript
interface ContentRoot {
// A unique identifier for the content root.
id: UUID;
// The type of content root.
type: ContentRootType;

// The name of the content root.
name: String;
}
```

### `ContentRootType`

The type of the annotated content root.

```typescript
type ContentRootType = Project | Root | Home | Library | Custom;
```

These represent:

- `Project`: This content root points to the project home.
- `Root`: This content root points to the system root (`/`) on unix systems, or
to a drive root on Windows. In Windows' case, there may be multiple `Root`
entries corresponding to the various drives.
- `Home`: The user's home directory.
- `Library`: An Enso library location.
- `Custom`: A content root that has been added by the IDE (unused for now).

## Connection Management

In order to properly set-up and tear-down the language server connection, we
Expand Down Expand Up @@ -1469,7 +1505,7 @@ must fail.
```typescript
{
path: Path;
contents: FileContents[T];
contents: FileContents<T>;
}
```

Expand Down Expand Up @@ -1513,7 +1549,7 @@ return the contents from the in-memory buffer rather than the file on disk.

```typescript
{
contents: FileContents[T];
contents: FileContents<T>;
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package org.enso.languageserver.boot

import java.io.File
import java.net.URI

import akka.actor.ActorSystem
import org.enso.jsonrpc.JsonRpcServer
import org.enso.languageserver.boot.DeploymentType.{Azure, Desktop}
import org.enso.languageserver.capability.CapabilityRouter
import org.enso.languageserver.data._
import org.enso.languageserver.effect.ZioExec
import org.enso.languageserver.filemanager.{
ContentRootType,
ContentRootWithFile,
FileManager,
FileSystem,
ReceivesTreeUpdatesHandler
Expand Down Expand Up @@ -56,9 +57,15 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
logLevel
)

val directoriesConfig = DirectoriesConfig(serverConfig.contentRootPath)
val directoriesConfig = ProjectDirectoriesConfig(serverConfig.contentRootPath)
private val contentRoot = ContentRootWithFile(
serverConfig.contentRootUuid,
ContentRootType.Project,
"Project",
new File(serverConfig.contentRootPath)
)
val languageServerConfig = Config(
Map(serverConfig.contentRootUuid -> new File(serverConfig.contentRootPath)),
Map(serverConfig.contentRootUuid -> contentRoot),
FileManagerConfig(timeout = 3.seconds),
PathWatcherConfig(),
ExecutionContextConfig(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.enso.languageserver.boot.resource.{
SequentialResourcesInitialization,
TruffleContextInitialization
}
import org.enso.languageserver.data.DirectoriesConfig
import org.enso.languageserver.data.ProjectDirectoriesConfig
import org.enso.searcher.sql.{SqlSuggestionsRepo, SqlVersionsRepo}
import org.graalvm.polyglot.Context

Expand All @@ -29,11 +29,11 @@ object ResourcesInitialization {
* @return the initialization component
*/
def apply(
eventStream: EventStream,
directoriesConfig: DirectoriesConfig,
suggestionsRepo: SqlSuggestionsRepo,
versionsRepo: SqlVersionsRepo,
truffleContext: Context
eventStream: EventStream,
directoriesConfig: ProjectDirectoriesConfig,
suggestionsRepo: SqlSuggestionsRepo,
versionsRepo: SqlVersionsRepo,
truffleContext: Context
)(implicit ec: ExecutionContext): InitializationComponent = {
val resources = Seq(
new DirectoriesInitialization(directoriesConfig),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package org.enso.languageserver.boot.resource

import com.typesafe.scalalogging.LazyLogging
import org.enso.languageserver.data.DirectoriesConfig
import org.enso.languageserver.data.ProjectDirectoriesConfig

import scala.concurrent.{ExecutionContext, Future}

/** Directories initialization.
*
* @param directoriesConfig the directories config
*/
class DirectoriesInitialization(directoriesConfig: DirectoriesConfig)(implicit
ec: ExecutionContext
class DirectoriesInitialization(directoriesConfig: ProjectDirectoriesConfig)(implicit
ec: ExecutionContext
) extends InitializationComponent
with LazyLogging {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import java.nio.file.{FileSystemException, Files, NoSuchFileException}
import akka.event.EventStream
import com.typesafe.scalalogging.LazyLogging
import org.apache.commons.io.FileUtils
import org.enso.languageserver.data.DirectoriesConfig
import org.enso.languageserver.data.ProjectDirectoriesConfig
import org.enso.languageserver.event.InitializedEvent
import org.enso.logger.masking.MaskedPath
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
Expand All @@ -23,10 +23,10 @@ import scala.util.{Failure, Success}
* @param versionsRepo the versions repo
*/
class RepoInitialization(
directoriesConfig: DirectoriesConfig,
eventStream: EventStream,
suggestionsRepo: SqlSuggestionsRepo,
versionsRepo: SqlVersionsRepo
directoriesConfig: ProjectDirectoriesConfig,
eventStream: EventStream,
suggestionsRepo: SqlSuggestionsRepo,
versionsRepo: SqlVersionsRepo
)(implicit ec: ExecutionContext)
extends InitializationComponent
with LazyLogging {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package org.enso.languageserver.data
import java.io.File
import java.nio.file.Files
import java.util.UUID

import org.enso.languageserver.filemanager.{
ContentRootNotFound,
ContentRootWithFile,
FileSystemFailure,
Path
}
Expand Down Expand Up @@ -78,15 +78,15 @@ object ExecutionContextConfig {
*
* @param root the root directory path
*/
case class DirectoriesConfig(root: File) extends ToLogString {
case class ProjectDirectoriesConfig(root: File) extends ToLogString {

/** The data directory path. */
val dataDirectory: File =
new File(root, DirectoriesConfig.DataDirectory)
new File(root, ProjectDirectoriesConfig.DataDirectory)

/** The suggestions database file path. */
val suggestionsDatabaseFile: File =
new File(dataDirectory, DirectoriesConfig.SuggestionsDatabaseFile)
new File(dataDirectory, ProjectDirectoriesConfig.SuggestionsDatabaseFile)

/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String = {
Expand All @@ -101,21 +101,21 @@ case class DirectoriesConfig(root: File) extends ToLogString {
Files.createDirectories(dataDirectory.toPath)
}

object DirectoriesConfig {
object ProjectDirectoriesConfig {

val DataDirectory: String = ".enso"
val SuggestionsDatabaseFile: String = "suggestions.db"

def apply(root: String): DirectoriesConfig =
new DirectoriesConfig(new File(root))
def apply(root: String): ProjectDirectoriesConfig =
new ProjectDirectoriesConfig(new File(root))

/** Create default data directory config, creating directories if not exist.
*
* @param root the root directory path
* @return data directory config
*/
def initialize(root: File): DirectoriesConfig = {
val config = new DirectoriesConfig(root)
def initialize(root: File): ProjectDirectoriesConfig = {
val config = new ProjectDirectoriesConfig(root)
config.createDirectories()
config
}
Expand All @@ -131,11 +131,11 @@ object DirectoriesConfig {
* @param directories the configuration of internal directories
*/
case class Config(
contentRoots: Map[UUID, File],
contentRoots: Map[UUID, ContentRootWithFile],
fileManager: FileManagerConfig,
pathWatcher: PathWatcherConfig,
executionContext: ExecutionContextConfig,
directories: DirectoriesConfig
directories: ProjectDirectoriesConfig
) extends ToLogString {

/** @inheritdoc */
Expand All @@ -144,7 +144,7 @@ case class Config(
if (shouldMask) {
contentRoots
.map { case (k, v) =>
k -> MaskingUtils.toMaskedPath(v.toPath)
k -> MaskingUtils.toMaskedPath(v.file.toPath)
}
} else {
contentRoots
Expand All @@ -158,15 +158,17 @@ case class Config(
s")"
}

def findContentRoot(rootId: UUID): Either[FileSystemFailure, File] =
def findContentRoot(
rootId: UUID
): Either[FileSystemFailure, ContentRootWithFile] =
contentRoots
.get(rootId)
.toRight(ContentRootNotFound)

def findRelativePath(path: File): Option[Path] =
contentRoots.view.flatMap { case (id, root) =>
if (path.toPath.startsWith(root.toPath)) {
Some(Path(id, root.toPath.relativize(path.toPath)))
if (path.toPath.startsWith(root.file.toPath)) {
Some(Path(id, root.file.toPath.relativize(path.toPath)))
} else {
None
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.enso.languageserver.filemanager

import enumeratum._

import java.io.File
import java.util.UUID

/** A representation of a content root.
*
* @param id the unique identifier of the content root
* @param type the type of the content root
* @param name The name of the content root
*/
case class ContentRoot(id: UUID, `type`: ContentRootType, name: String)

/** The type of entity that the content root represents.
*/
sealed trait ContentRootType extends EnumEntry
object ContentRootType extends Enum[ContentRootType] with CirceEnum[ContentRootType] {

/** The content root represents the root of the current Enso project.
*/
case object Project extends ContentRootType

/** The content root represents a system root (`/` on unix, drives on
* windows).
*
* There may be multiple of this type of root sent by default.
*/
case object Root extends ContentRootType

/** The content root represents the user's home directory.
*/
case object Home extends ContentRootType

/** The content root represents an Enso library.
*/
case object Library extends ContentRootType

/** The content root was a custom location added by the IDE.
*/
case object Custom extends ContentRootType

/** Necessary for Enumeratum and Circe. */
override val values = findValues
}

/** A representation of a content root.
*
* @param id the unique identifier of the content root
* @param `type` the type of the content root
* @param name The name of the content root
* @param file the file on the filesystem that is the content root
*/
case class ContentRootWithFile(
id: UUID,
`type`: ContentRootType,
name: String,
file: File
) {

/** Convert this to a content root for use in the protocol.
*
* @return a protocol content root
*/
def toContentRoot: ContentRoot = {
ContentRoot(id, `type`, name)
}
}