From d209a78382a656727d0fd9af49d59b36cc9ae29e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 7 Oct 2023 12:35:19 +0200 Subject: [PATCH] Introduce FacetReader 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 --- src/README.md | 2 ++ src/facet.ts | 29 +++++++++++++++++++++++++++-- src/index.ts | 2 +- src/state.ts | 5 +++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/README.md b/src/README.md index a2684d1..112ba10 100644 --- a/src/README.md +++ b/src/README.md @@ -104,6 +104,8 @@ extensions for the editor state. @Facet +@FacetReader + @Prec @Compartment diff --git a/src/facet.ts b/src/facet.ts index bb7f899..4d43032 100644 --- a/src/facet.ts +++ b/src/facet.ts @@ -37,7 +37,10 @@ type FacetConfig = { /// size](#state.EditorState^tabSize), [editor /// attributes](#view.EditorView^editorAttributes), and [update /// listeners](#view.EditorView^updateListener). -export class Facet { +/// +/// Note that `Facet` instances can be used anywhere where +/// [`FacetReader`](#state.FacetReader) is expected. +export class Facet implements FacetReader { /// @internal readonly id = nextID++ /// @internal @@ -59,6 +62,10 @@ export class Facet { 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 { return this } + /// Define a new facet. static define(config: FacetConfig = {}) { return new Facet(config.combine || ((a: any) => a) as any, @@ -102,13 +109,31 @@ export class Facet { 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 = { + /// @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(a: readonly T[], b: readonly T[]) { return a == b || a.length == b.length && a.every((e, i) => e === b[i]) } -type Slot = Facet | StateField | "doc" | "selection" +type Slot = FacetReader | StateField | "doc" | "selection" const enum Provider { Static, Single, Multi } diff --git a/src/index.ts b/src/index.ts index 62091f2..cddff67 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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" diff --git a/src/state.ts b/src/state.ts index 6d2fc8c..e88e4ea 100644 --- a/src/state.ts +++ b/src/state.ts @@ -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 @@ -189,7 +190,7 @@ export class EditorState { } /// Get the value of a state [facet](#state.Facet). - facet(facet: Facet): Output { + facet(facet: FacetReader): Output { let addr = this.config.address[facet.id] if (addr == null) return facet.default ensureAddr(this, addr)