Spaces:
Sleeping
Sleeping
A state field is an [[Editor extensions|editor extension]] that lets you manage custom editor state. This page walks you through building a state field by implementing a calculator extension. | |
The calculator should be able to add and subtract a number from the current state, and to reset the state when you want to start over. | |
By the end of this page, you'll understand the basic concepts of building a state field. | |
> [!note] | |
> This page aims to distill the official CodeMirror 6 documentation for Obsidian plugin developers. For more detailed information on state fields, refer to [State Fields](https://codemirror.net/docs/guide/#state-fields). | |
## Prerequisites | |
- Basic understanding of [[State management]]. | |
## Defining state effects | |
State effects describe the state change you'd like to make. You may think of them as methods on a class. | |
In the calculator example, you'd define a state effect for each of the calculator operations: | |
```ts | |
const addEffect = StateEffect.define<number>(); | |
const subtractEffect = StateEffect.define<number>(); | |
const resetEffect = StateEffect.define(); | |
``` | |
The type between the angle brackets, `<>`, defines the input type for the effect. For example, the number you want to add or subtract. The reset effect doesn't need any input, so you can leave it out. | |
## Defining a state field | |
Contrary to what one might think, state fields don't actually _store_ state. They _manage_ it. State fields take the current state, applies any state effects, and returns the new state. | |
The state field contains the calculator logic to apply the mathematical operations depending on the effects in a transaction. Since a transaction can contain multiple effects, for example two additions, the state field needs to apply them all one after another. | |
```ts | |
export const calculatorField = StateField.define<number>({ | |
create(state: EditorState): number { | |
return 0; | |
}, | |
update(oldState: number, transaction: Transaction): number { | |
let newState = oldState; | |
for (let effect of transaction.effects) { | |
if (effect.is(addEffect)) { | |
newState += effect.value; | |
} else if (effect.is(subtractEffect)) { | |
newState -= effect.value; | |
} else if (effect.is(resetEffect)) { | |
newState = 0; | |
} | |
} | |
return newState; | |
}, | |
}); | |
``` | |
- `create` returns the value the calculator starts with. | |
- `update` contains the logic for applying the effects. | |
- `effect.is()` lets you check the type of the effect before you apply it. | |
## Dispatching state effects | |
To apply a state effect to a state field, you need to dispatch it to the editor view as part of a transaction. | |
```ts | |
view.dispatch({ | |
effects: [addEffect.of(num)], | |
}); | |
``` | |
You can even define a set of helper functions that provide a more familiar API: | |
```ts | |
export function add(view: EditorView, num: number) { | |
view.dispatch({ | |
effects: [addEffect.of(num)], | |
}); | |
} | |
export function subtract(view: EditorView, num: number) { | |
view.dispatch({ | |
effects: [subtractEffect.of(num)], | |
}); | |
} | |
export function reset(view: EditorView) { | |
view.dispatch({ | |
effects: [resetEffect.of(null)], | |
}); | |
} | |
``` | |
## Next steps | |
Provide [[Decorations]] from your state fields to change how to display the document. | |