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

Add File System Path to the Content Roots #1827

Merged
merged 4 commits into from
Jul 5, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions docs/language-server/protocol-language-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ transport formats, please look [here](./protocol-architecture).
- [`WorkspaceEdit`](#workspaceedit)
- [`EnsoDigest`](#ensodigest)
- [`FileSegment`](#filesegment)
- [`ContentRoot`](#contentroot)
- [Connection Management](#connection-management)
- [`session/initProtocolConnection`](#sessioninitprotocolconnection)
- [`session/initBinaryConnection`](#sessioninitbinaryconnection)
Expand Down Expand Up @@ -1156,34 +1157,60 @@ a location on a real file-system that has been virtualised for use in the Enso
VFS.

```typescript
interface ContentRoot {
type ContentRoot = Project | FileSystemRoot | Home | Library | Custom;
```

```typescript
/** This content root points to the project home. */
interface Project {
// A unique identifier for the content root.
id: UUID;
// The type of content root.
type: ContentRootType;
}

// The name of the content root.
name: String;
/**
* 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.
*/
interface FileSystemRoot {
// A unique identifier for the content root.
id: UUID;

// The absolute filesystem path of the content root.
path: String;
}
```

### `ContentRootType`
/** The user's home directory. */
interface Home {
// A unique identifier for the content root.
id: UUID;
}

The type of the annotated content root.
/** An Enso library location. */
interface Library {
// A unique identifier for the content root.
id: UUID;

```typescript
type ContentRootType = Project | Root | Home | Library | Custom;
```
// The namespace of the library.
namespace: String;

These represent:
// The name of the library.
name: String;

/**
* The version of the library.
*
* It is either a semver version of the library or the string "local".
*/
version: String;
}

- `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).
/** A content root that has been added by the IDE (unused for now). */
interface Custom {
// A unique identifier for the content root.
id: UUID;
}
```

## Connection Management

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import org.enso.languageserver.capability.CapabilityRouter
import org.enso.languageserver.data._
import org.enso.languageserver.effect.ZioExec
import org.enso.languageserver.filemanager.{
ContentRoot,
ContentRootManager,
ContentRootManagerActor,
ContentRootManagerWrapper,
ContentRootType,
ContentRootWithFile,
FileManager,
FileSystem,
Expand Down Expand Up @@ -62,9 +62,7 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {

val directoriesConfig = ProjectDirectoriesConfig(serverConfig.contentRootPath)
private val contentRoot = ContentRootWithFile(
serverConfig.contentRootUuid,
ContentRootType.Project,
"Project",
ContentRoot.Project(serverConfig.contentRootUuid),
new File(serverConfig.contentRootPath)
)
val languageServerConfig = Config(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,69 +1,128 @@
package org.enso.languageserver.filemanager

import enumeratum._
import io.circe.syntax.EncoderOps
import io.circe.{Encoder, Json}

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)
/** A representation of a content root. */
sealed trait ContentRoot {

/** The type of entity that the content root represents.
*/
sealed trait ContentRootType extends EnumEntry
object ContentRootType extends Enum[ContentRootType] with CirceEnum[ContentRootType] {
/** The unique identifier of the content root. */
def id: UUID
}

/** The content root represents the root of the current Enso project.
object ContentRoot {

/** A filesystem root.
*
* @param id the unique identifier of the content root
* @param path absolute path of the content root
*/
case object Project extends ContentRootType
case class FileSystemRoot(override val id: UUID, path: String)
extends ContentRoot

/** The content root represents a system root (`/` on unix, drives on
* windows).
/** A root representing user's home on the filesystem.
*
* There may be multiple of this type of root sent by default.
* @param id the unique identifier of the content root
*/
case object Root extends ContentRootType
case class Home(override val id: UUID) extends ContentRoot

/** The content root represents the user's home directory.
/** Main project root.
*
* @param id the unique identifier of the content root
*/
case object Home extends ContentRootType
case class Project(override val id: UUID) extends ContentRoot

/** The content root represents an Enso library.
/** A root of an imported library.
*
* @param id the unique identifier of the content root
* @param namespace namespace of the library
* @param name name of the library
* @param version version of the library
*/
case object Library extends ContentRootType
case class Library(
override val id: UUID,
namespace: String,
name: String,
version: String
) extends ContentRoot

/** The content root was a custom location added by the IDE.
/** A custom root, currently not used.
*
* @param id the unique identifier of the content root
*/
case object Custom extends ContentRootType
case class Custom(override val id: UUID) extends ContentRoot

private object CodecField {
val Id = "id"
val Type = "type"
val Namespace = "namespace"
val Name = "name"
val Version = "version"
val Path = "path"
}

private object CodecType {
val FileSystemRoot = "FileSystemRoot"
val Home = "Home"
val Project = "Project"
val Library = "Library"
val Custom = "Custom"
}

/** Necessary for Enumeratum and Circe. */
override val values = findValues
/** An [[Encoder]] instance for [[ContentRoot]]. */
implicit val encoder: Encoder[ContentRoot] = {
case FileSystemRoot(id, path) =>
Json.obj(
CodecField.Type -> CodecType.FileSystemRoot.asJson,
CodecField.Id -> id.asJson,
CodecField.Path -> path.asJson
)
case Home(id) =>
Json.obj(
CodecField.Type -> CodecType.Home.asJson,
CodecField.Id -> id.asJson
)
case Project(id) =>
Json.obj(
CodecField.Type -> CodecType.Project.asJson,
CodecField.Id -> id.asJson
)
case Library(id, namespace, name, version) =>
Json.obj(
CodecField.Type -> CodecType.Library.asJson,
CodecField.Id -> id.asJson,
CodecField.Namespace -> namespace.asJson,
CodecField.Name -> name.asJson,
CodecField.Version -> version.asJson
)
case Custom(id) =>
Json.obj(
CodecField.Type -> CodecType.Custom.asJson,
CodecField.Id -> id.asJson
)
}
}

/** A representation of a content root.
/** A representation of a content root with a file that represents its
* filesystem location
*
* @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 contentRoot the raw content root
* @param file the file on the filesystem that is the content root
*/
case class ContentRootWithFile(
id: UUID,
`type`: ContentRootType,
name: String,
contentRoot: ContentRoot,
file: File
) {

/** The unique identifier of the content root. */
def id: UUID = contentRoot.id

/** Convert this to a content root for use in the protocol.
*
* @return a protocol content root
*/
def toContentRoot: ContentRoot = {
ContentRoot(id, `type`, name)
}
def toContentRoot: ContentRoot = contentRoot
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@ class ContentRootManagerActor(config: Config)
context.become(mainStage(contentRoots, subscribers + sender()))

case Api.LibraryLoaded(libraryName, libraryVersion, rootPath) =>
val rootName = s"$libraryName:$libraryVersion"

val libraryRoot = ContentRootWithFile(
id = UUID.randomUUID(),
`type` = ContentRootType.Library,
name = rootName,
file = rootPath.getCanonicalFile
ContentRoot.Library(
id = UUID.randomUUID(),
namespace = libraryName.namespace,
name = libraryName.name,
version = libraryVersion.toString
),
file = rootPath.getCanonicalFile
)

subscribers.foreach { subscriber =>
Expand Down Expand Up @@ -109,7 +110,7 @@ object ContentRootManagerActor {
private case class ContentRoots(
projectRoot: ContentRootWithFile,
librariesRoots: List[ContentRootWithFile],
homeRoots: List[ContentRootWithFile],
homeRoot: Option[ContentRootWithFile],
filesystemRoots: List[ContentRootWithFile]
) {
def addLibraryRoot(contentRoot: ContentRootWithFile): ContentRoots =
Expand All @@ -122,7 +123,7 @@ object ContentRootManagerActor {
* roots will take precedence.
*/
lazy val toList: List[ContentRootWithFile] =
List(projectRoot) ++ librariesRoots ++ homeRoots ++ filesystemRoots
List(projectRoot) ++ librariesRoots ++ homeRoot.toList ++ filesystemRoots

/** Resolves the path as relative to one of the registered content roots.
*
Expand All @@ -146,30 +147,27 @@ object ContentRootManagerActor {
val fsRoots = FileSystems.getDefault.getRootDirectories.asScala.map {
path =>
val absolutePath = path.toAbsolutePath.normalize
val name =
Option(absolutePath.getRoot).map(_.toString).getOrElse("<root>")
ContentRootWithFile(
id = UUID.randomUUID(),
`type` = ContentRootType.Root,
name = name,
file = absolutePath.toFile
ContentRoot.FileSystemRoot(
id = UUID.randomUUID(),
path = absolutePath.toString
),
file = absolutePath.toFile
)
}

val homeRoot = for {
homeProp <- sys.props.get("user.home")
homePath <- Try(JPath.of(homeProp)).toOption
} yield ContentRootWithFile(
id = UUID.randomUUID(),
`type` = ContentRootType.Home,
name = "Home",
file = homePath.toAbsolutePath.normalize.toFile
ContentRoot.Home(UUID.randomUUID()),
file = homePath.toAbsolutePath.normalize.toFile
)

ContentRoots(
projectRoot = config.projectContentRoot,
librariesRoots = Nil,
homeRoots = homeRoot.toList,
homeRoot = homeRoot,
filesystemRoots = fsRoots.toList
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,11 @@ class JsonConnectionController(
): Receive = {
case ContentRootManagerProtocol.ContentRootsAddedNotification(roots) =>
val allRoots = roots ++ rootsSoFar
if (roots.exists(_.`type` == ContentRootType.Project)) {
val hasProject = roots.exists {
case ContentRootWithFile(ContentRoot.Project(_), _) => true
case _ => false
}
if (hasProject) {
cancellable.cancel()
unstashAll()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import akka.testkit._
import org.apache.commons.io.FileUtils
import org.enso.languageserver.data._
import org.enso.languageserver.event.InitializedEvent
import org.enso.languageserver.filemanager.{
ContentRootType,
ContentRootWithFile
}
import org.enso.languageserver.filemanager.{ContentRoot, ContentRootWithFile}
import org.enso.searcher.sql.{
SchemaVersion,
SqlDatabase,
Expand Down Expand Up @@ -222,9 +219,7 @@ class RepoInitializationSpec
sys.addShutdownHook(FileUtils.deleteQuietly(testContentRoot.toFile))
val config = newConfig(
ContentRootWithFile(
UUID.randomUUID(),
ContentRootType.Project,
"Project",
ContentRoot.Project(UUID.randomUUID()),
testContentRoot.toFile
)
)
Expand Down
Loading