Skip to content

Commit

Permalink
Introduce FacetReader
Browse files Browse the repository at this point in the history
FEATURE: The new `FacetReader` type provides a way to export a read-only handle
to a `Facet`.

See https://discuss.codemirror.net/t/read-only-facets/7175
  • Loading branch information
marijnh committed Oct 7, 2023
1 parent 4083520 commit d209a78
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ extensions for the editor state.

@Facet

@FacetReader

@Prec

@Compartment
Expand Down
29 changes: 27 additions & 2 deletions src/facet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ type FacetConfig<Input, Output> = {
/// size](#state.EditorState^tabSize), [editor
/// attributes](#view.EditorView^editorAttributes), and [update
/// listeners](#view.EditorView^updateListener).
export class Facet<Input, Output = readonly Input[]> {
///
/// Note that `Facet` instances can be used anywhere where
/// [`FacetReader`](#state.FacetReader) is expected.
export class Facet<Input, Output = readonly Input[]> implements FacetReader<Output> {
/// @internal
readonly id = nextID++
/// @internal
Expand All @@ -59,6 +62,10 @@ export class Facet<Input, Output = readonly Input[]> {
this.extensions = typeof enables == "function" ? enables(this) : enables
}

/// Returns a facet reader for this facet, which can be used to
/// [read](#state.EditorState.facet) it but not to define values for it.
get reader(): FacetReader<Output> { return this }

/// Define a new facet.
static define<Input, Output = readonly Input[]>(config: FacetConfig<Input, Output> = {}) {
return new Facet<Input, Output>(config.combine || ((a: any) => a) as any,
Expand Down Expand Up @@ -102,13 +109,31 @@ export class Facet<Input, Output = readonly Input[]> {
if (!get) get = x => x as any
return this.compute([field], state => get!(state.field(field)))
}

tag!: typeof FacetTag
}

declare const FacetTag: unique symbol

/// A facet reader can be used to fetch the value of a facet, though
/// [`EditorState.facet`](#state.EditorState.facet) or as a dependency
/// in [`Facet.compute`](#state.Facet.compute), but not to define new
/// values for the facet.
export type FacetReader<Output> = {
/// @internal
id: number
/// @internal
default: Output
/// Dummy tag that makes sure TypeScript doesn't consider all object
/// types as conforming to this type.
tag: typeof FacetTag
}

function sameArray<T>(a: readonly T[], b: readonly T[]) {
return a == b || a.length == b.length && a.every((e, i) => e === b[i])
}

type Slot<T> = Facet<any, T> | StateField<T> | "doc" | "selection"
type Slot<T> = FacetReader<T> | StateField<T> | "doc" | "selection"

const enum Provider { Static, Single, Multi }

Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export {EditorStateConfig, EditorState} from "./state"
export {StateCommand} from "./extension"
export {Facet, StateField, Extension, Prec, Compartment} from "./facet"
export {Facet, FacetReader, StateField, Extension, Prec, Compartment} from "./facet"
export {EditorSelection, SelectionRange} from "./selection"
export {Transaction, TransactionSpec, Annotation, AnnotationType, StateEffect, StateEffectType} from "./transaction"
export {combineConfig} from "./config"
Expand Down
5 changes: 3 additions & 2 deletions src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {EditorSelection, SelectionRange, checkSelection} from "./selection"
import {Transaction, TransactionSpec, resolveTransaction, asArray, StateEffect} from "./transaction"
import {allowMultipleSelections, changeFilter, transactionFilter, transactionExtender,
lineSeparator, languageData, readOnly} from "./extension"
import {Configuration, Facet, Extension, StateField, SlotStatus, ensureAddr, getAddr, Compartment, DynamicSlot} from "./facet"
import {Configuration, Facet, FacetReader, Extension, StateField, SlotStatus, ensureAddr, getAddr,
Compartment, DynamicSlot} from "./facet"
import {CharCategory, makeCategorizer} from "./charcategory"

/// Options passed when [creating](#state.EditorState^create) an
Expand Down Expand Up @@ -189,7 +190,7 @@ export class EditorState {
}

/// Get the value of a state [facet](#state.Facet).
facet<Output>(facet: Facet<any, Output>): Output {
facet<Output>(facet: FacetReader<Output>): Output {
let addr = this.config.address[facet.id]
if (addr == null) return facet.default
ensureAddr(this, addr)
Expand Down

0 comments on commit d209a78

Please sign in to comment.