Skip to content

Commit

Permalink
Add File System Path to the Content Roots (#1827)
Browse files Browse the repository at this point in the history
  • Loading branch information
radeusgd authored and iamrecursion committed Jul 9, 2021
1 parent 32b459e commit ba741a4
Show file tree
Hide file tree
Showing 17 changed files with 293 additions and 160 deletions.
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

0 comments on commit ba741a4

Please sign in to comment.