#shell/workspace

    Backend-neutral readonly source, document, filesystem, and tree contracts for host packages.

    #Contracts

    • SourcePath normalizes root-relative paths and rejects absolute paths, traversal, Windows drive paths, and NUL bytes. infer_language_id maps common source extensions.
    • FileSystemProvider exposes scheme-owned text reads and watches; read_document/watch_file adapt it to structured results and snapshots.
    • DocumentProvider exposes document-level read, watch, and close. Watches may invalidate, update, delete, or fail a URI.
    • DocumentSnapshot is immutable provider/transport data: URI, metadata, revision, text, UTF-16 offsets, LF/CRLF/lone-CR line splitting, and 1-based Position conversion.
    • WorkspaceTreeProvider exposes a root URI and pull-based one-level resolve; unresolved directory stats carry children: None.

    Exact public types, error codes, helpers, and trait signatures are in pkg.generated.mbti.

    #SourcePath

    SourcePath normalizes root-relative paths and rejects absolute paths, traversal, Windows drive paths, and NUL bytes. It is the package's containment boundary: a value of this type cannot name anything outside the workspace root.

    ///|
    fn source_path(raw : String) -> String {
    match @workspace.SourcePath::normalize_root_relative_path(raw) {
    ParsedSourcePath(path) => "ok:" + path.to_string()
    SourcePathError(_) => "rejected"
    }
    }

    ///|
    test "traversal, absolute, and drive paths are rejected" {
    debug_inspect(
    [
    "src/main.mbt", "./src/main.mbt", "src//main.mbt", "../escape.mbt", "/etc/passwd",
    "C:\\Windows\\system.ini", "src/\u{0}evil.mbt",
    ].map(source_path),
    content=(
    #|[
    #| "ok:src/main.mbt",
    #| "ok:src/main.mbt",
    #| "ok:src/main.mbt",
    #| "rejected",
    #| "rejected",
    #| "rejected",
    #| "rejected",
    #|]
    ),
    )
    }

    infer_language_id maps common source extensions. Two results are worth knowing before you rely on it:

    • an unrecognized name falls back to moonbit rather than to plaintext, so a moonbit language id is not evidence that the host recognized the file;
    • notes.mbt.md infers moonbit, not markdown, because the .mbt extension matches first.

    The second does not break Markdown presentation: the Viewer selects the Markdown root from the URI's lowercase .md suffix (including .mbt.md), not from this language id. The two mechanisms are independent on purpose.

    ///|
    test "language ids are inferred from the extension" {
    debug_inspect(
    ["main.mbt", "data.json", "app.js", "README.md", "notes.mbt.md", "LICENSE"].map(
    @workspace.infer_language_id,
    ),
    content=(
    #|["moonbit", "json", "javascript", "markdown", "moonbit", "moonbit"]
    ),
    )
    }

    #DocumentSnapshot

    DocumentSnapshot is immutable provider/transport data: URI, metadata, revision, text, UTF-16 offsets, LF/CRLF/lone-CR line splitting, and 1-based Position conversion.

    It is emphatically not the editor model. It is what the transport carries; an adapter turns it into a viewer/common/model.TextModel. Notably, it splits CRLF and lone CR for line addressing but does not rewrite the stored text the way the editor model does, so offsets here index the bytes the host sent.

    ///|
    fn snapshot(text : String) -> @workspace.DocumentSnapshot raise {
    DocumentSnapshot(
    @base_common.Uri::parse("file:///workspace/src/main.mbt"),
    "main.mbt",
    "moonbit",
    "rev-1",
    text,
    )
    }

    ///|
    test "line addressing handles all three terminators" {
    let doc = snapshot("a\r\nbb\rccc\ndddd")
    debug_inspect(
    (
    doc.line_count(),
    [
    for index in 0..<doc.line_count() => doc.line_text(index)
    ],
    doc.length(),
    ),
    content=(
    #|(4, ["a", "bb", "ccc", "dddd"], 14)
    ),
    )
    }

    Offsets and 1-based positions convert in both directions, which is the conversion the remote protocol relies on at the wire boundary.

    ///|
    test "offsets and 1-based positions convert both ways" {
    let doc = snapshot("let x = 1\nlet y = 2\n")
    let position = doc.position_at_offset(12)
    debug_inspect(
    (
    position,
    doc.offset_at_position(position),
    doc.line_at_offset(12),
    doc.slice(OffsetRange(10, 13)),
    ),
    content=(
    #|({ line_number: 2, column: 3 }, 12, 1, "let")
    ),
    )
    }

    revision_from_text derives the revision a watch compares against, so two identical reads produce the same revision and a changed read does not.

    ///|
    test "the revision is derived from the text" {
    let first = @workspace.revision_from_text("let x = 1\n")
    let same = @workspace.revision_from_text("let x = 1\n")
    let different = @workspace.revision_from_text("let x = 2\n")
    debug_inspect(
    (first == same, first == different),
    content=(
    #|(true, false)
    ),
    )
    }

    #Boundary and validation

    This package may depend only on base/common and JSON support. It owns no DOM, browser, native, server-routing, or process effects. DocumentSnapshot is host data: adapters create viewer/common/model.TextModel; viewer core must not import this package.

    Run moon test shell/workspace --target js, then the read-only check gate: moon check --target all --warn-list +73 --deny-warn and moon fmt --check. See docs/quality.md for the complete required checks.

    DocumentProvider

    Document source contract for the readonly viewer. Providers may coalesce watch updates and can either invalidate a URI or provide a fresh snapshot.

    FileSystemProvider

    pub(open) trait FileSystemProvider {
    fn scheme(Self) -> String
    async fn read_file(Self,
    Uri
    ) -> FileReadResult
    fn watch(Self,
    Uri
    , WatchOptions, (Array[FileChange]) -> Unit) -> FileWatch
    }

    Host filesystem abstraction for readonly document loading and file-change notifications.

    WorkspaceTreeProvider

    pub(open) trait WorkspaceTreeProvider {
    fn root(Self) ->
    Uri

    async fn resolve(Self,
    Uri
    ) -> WorkspaceResolveResult
    }

    The IFileService.resolve analog. root names the workspace root URI; resolve returns the stat for a directory URI with exactly one level of children resolved. Requests are pull-based; providers never push tree data.

    DocumentChange

    pub(all) enum DocumentChange {
    DocumentInvalidated(uri~ :
    Uri
    , known_revision~ : String?)
    DocumentUpdated(DocumentSnapshot)
    DocumentDeleted(
    Uri
    )
    DocumentChangeFailed(uri~ :
    Uri
    , error~ : DocumentError)
    } derive(
    Debug
    )

    Push notification from a provider watch or host-owned watcher.

    DocumentContent

    type DocumentContent derive(
    Debug
    )

    Raw file text plus metadata needed to construct a DocumentSnapshot.

    DocumentContent::DocumentContent

    fn DocumentContent::DocumentContent(text : String, display_name? : String, language_id? : String, revision? : String) -> DocumentContent

    Builds file content while allowing providers to omit derived metadata.

    DocumentError

    pub struct DocumentError {
    code : FileSystemProviderErrorCode
    message : String
    uri :
    Uri
    ?
    } derive(
    Debug
    )

    Document-level failure with optional URI context.

    DocumentError::DocumentError

    Builds a document error, defaulting an empty message to the code label.

    DocumentError::to_file_system_error

    fn DocumentError::to_file_system_error(self : DocumentError) -> FileSystemProviderError

    Converts a document error back to the lower-level error shape when protocol adapters need the existing provider-code JSON format.

    DocumentError::to_json

    fn DocumentError::to_json(self : DocumentError) -> Json

    DocumentReadResult

    pub(all) enum DocumentReadResult {
    DocumentLoaded(DocumentSnapshot)
    DocumentNotFound(
    Uri
    )
    DocumentReadFailed(DocumentError)
    } derive(
    Debug
    )

    Provider read result without exceptions.

    DocumentReadResult::to_json

    DocumentSnapshot

    pub struct DocumentSnapshot {
    uri :
    Uri

    display_name : String
    language_id : String
    revision : String
    text : String
    // private fields
    } derive(
    Debug
    )

    Provider-owned immutable document content for one URI/revision.

    DocumentSnapshot::DocumentSnapshot

    fn DocumentSnapshot::DocumentSnapshot(uri :
    Uri
    , display_name : String, language_id : String, revision : String, text : String) -> DocumentSnapshot

    Builds an immutable raw provider snapshot. EOL bytes/code units are kept exactly as supplied; normalization begins only when a Viewer TextModel constructs its TextSnapshot.

    DocumentSnapshot::from_content

    Derives a snapshot from provider content, filling display name, language id, and revision when they are absent.

    DocumentSnapshot::length

    fn DocumentSnapshot::length(self : DocumentSnapshot) -> Int

    Snapshot length in the repo's JavaScript-compatible UTF-16 indexing model.

    DocumentSnapshot::line_at_offset

    fn DocumentSnapshot::line_at_offset(self : DocumentSnapshot, offset : Int) -> Int

    DocumentSnapshot::line_count

    fn DocumentSnapshot::line_count(self : DocumentSnapshot) -> Int

    Number of logical lines, including a final empty line after a newline.

    DocumentSnapshot::line_end_offset

    fn DocumentSnapshot::line_end_offset(self : DocumentSnapshot, line : Int) -> Int

    DocumentSnapshot::line_start_offset

    fn DocumentSnapshot::line_start_offset(self : DocumentSnapshot, line : Int) -> Int

    DocumentSnapshot::line_text

    fn DocumentSnapshot::line_text(self : DocumentSnapshot, line : Int) -> String

    Text of one logical line without its line break.

    DocumentSnapshot::offset_at_position

    DocumentSnapshot::position_at_offset

    DocumentSnapshot::slice

    DocumentSnapshot::to_json

    fn DocumentSnapshot::to_json(self : DocumentSnapshot) -> Json

    FileChange

    pub(all) struct FileChange {
    uri :
    Uri

    change_type : FileChangeType
    } derive(
    Debug
    )

    One changed document URI reported by a file watch.

    FileChange::to_json

    fn FileChange::to_json(self : FileChange) -> Json

    FileChangeType

    pub(all) enum FileChangeType {
    Created
    Changed
    Deleted
    } derive(Eq,
    Debug
    )

    File-watch event kind. Providers may coalesce events before delivering them.

    FileChangeType::equal

    FileChangeType::not_equal

    fn FileChangeType::not_equal(x : FileChangeType, y : FileChangeType) -> Bool

    FileReadResult

    pub(all) enum FileReadResult {
    FileReadOk(DocumentContent)
    FileReadError(FileSystemProviderError)
    } derive(
    Debug
    )

    Provider read result without exceptions, so browser and native providers can share the same contract.

    FileSystemProviderError

    pub struct FileSystemProviderError {
    code : FileSystemProviderErrorCode
    message : String
    uri :
    Uri
    ?
    } derive(
    Debug
    )

    File-provider failure with optional URI context.

    FileSystemProviderError::FileSystemProviderError

    Builds a provider error, defaulting an empty message to the stable code label.

    FileSystemProviderError::to_json

    FileSystemProviderErrorCode

    pub(all) enum FileSystemProviderErrorCode {
    FileNotFound
    FileExists
    FileNotADirectory
    IsDirectory
    NoPermissions
    Unavailable
    InvalidUri
    InvalidData
    } derive(Eq,
    Debug
    )

    Host-neutral error codes used by file providers and remote workspace responses.

    FileSystemProviderErrorCode::from_string

    Decodes both this package's labels and browser File System Access API names.

    FileSystemProviderErrorCode::not_equal

    FileSystemProviderErrorCode::to_string

    Stable string label for wire format and JSON diagnostics.

    FileWatch

    pub(all) struct FileWatch {
    dispose : () -> Unit
    }

    Disposable watch handle. Calling dispose must stop future callbacks for that watch when the provider can do so.

    FileWatch::dispose

    fn FileWatch::dispose(self : FileWatch) -> Unit

    Stops the watch through the provider-supplied disposer.

    SourceError

    pub(all) enum SourceError {
    MissingPath
    InvalidPath
    } derive(Eq,
    Debug
    )

    Validation failures for user-facing source paths and document URIs.

    SourceError::equal

    fn SourceError::equal(SourceError, SourceError) -> Bool

    SourceError::not_equal

    fn SourceError::not_equal(x : SourceError, y : SourceError) -> Bool

    SourceError::to_string

    fn SourceError::to_string(self : SourceError) -> String

    Stable protocol/debug label for source validation failures.

    SourcePath

    type SourcePath derive(Eq,
    Debug
    )

    Root-relative source path accepted from UI or protocol input after normalization rejects absolute paths, parent traversal, and NUL bytes.

    SourcePath::display_name

    fn SourcePath::display_name(self : SourcePath) -> String

    Display label for a source path; currently the normalized path itself.

    SourcePath::equal

    fn SourcePath::equal(SourcePath, SourcePath) -> Bool

    SourcePath::normalize_root_relative_path

    fn SourcePath::normalize_root_relative_path(raw_path : String) -> SourcePathResult

    Normalizes a user-provided workspace path into a safe root-relative path.

    SourcePath::not_equal

    fn SourcePath::not_equal(x : SourcePath, y : SourcePath) -> Bool

    SourcePath::to_string

    fn SourcePath::to_string(self : SourcePath) -> String

    Returns the normalized root-relative path string.

    SourcePathResult

    pub enum SourcePathResult {
    ParsedSourcePath(SourcePath)
    SourcePathError(SourceError)
    } derive(Eq,
    Debug
    )

    Result wrapper used instead of raising while normalizing source paths.

    SourcePathResult::equal

    SourcePathResult::not_equal

    fn SourcePathResult::not_equal(x : SourcePathResult, y : SourcePathResult) -> Bool

    WatchOptions

    pub struct WatchOptions {
    recursive : Bool
    excludes : Array[String]
    } derive(
    Debug
    )

    Watch behavior requested from a provider.

    WatchOptions::default

    fn WatchOptions::default() -> WatchOptions

    Default non-recursive watch with no excludes.

    WorkspaceEntryKind

    pub(all) enum WorkspaceEntryKind {
    File
    Directory
    } derive(Eq,
    Debug
    )

    Kind of a workspace tree entry, in place of IFileStat's isFile/isDirectory booleans.

    WorkspaceEntryKind::equal

    WorkspaceEntryKind::from_string

    fn WorkspaceEntryKind::from_string(kind : String) -> WorkspaceEntryKind?

    Decodes a workspace tree entry kind from its wire label.

    WorkspaceEntryKind::not_equal

    WorkspaceEntryKind::to_string

    fn WorkspaceEntryKind::to_string(self : WorkspaceEntryKind) -> String

    Wire label for workspace tree entry kinds.

    WorkspaceResolveResult

    pub(all) enum WorkspaceResolveResult {
    WorkspaceResolved(WorkspaceStat)
    WorkspaceResolveError(FileSystemProviderError)
    } derive(
    Debug
    )

    Result of resolving a workspace tree node through a tree provider.

    WorkspaceStat

    pub(all) struct WorkspaceStat {
    uri :
    Uri

    name : String
    kind : WorkspaceEntryKind
    children : Array[WorkspaceStat]?
    } derive(
    Debug
    )

    One node of the workspace tree, mirroring VS Code's IFileStat: every stat carries its full provider-minted uri, and children is None until a provider resolves that directory level (the IFileStat.children: undefined convention). Depth is tree structure, not an annotation.

    infer_language_id

    fn infer_language_id(path_or_name : String) -> String

    Maps common source extensions to the language ids used by tokenizer and feature registries.

    read_document

    Document-provider adapters retained as the documented host-neutral workspace contract.

    revision_from_text

    fn revision_from_text(text : String) -> String

    Stable content-derived revision retained for provider implementations.

    watch_file

    fn watch_file(provider : &FileSystemProvider, uri :
    Uri
    , listener : (Array[FileChange]) -> Unit) -> FileWatch