diff --git a/src/components/editors/ScriptEditor/index.tsx b/src/components/editors/ScriptEditor/index.tsx index 3c6fdcd0..51971ac0 100644 --- a/src/components/editors/ScriptEditor/index.tsx +++ b/src/components/editors/ScriptEditor/index.tsx @@ -23,6 +23,7 @@ export function ScriptEditor() { const publish = useScriptEditor((s) => s.publish) const onDidScrollChange = useScriptEditor((s) => s.onDidScrollChange) const jumpCursorOnLineClick = useScriptEditor((s) => s.jumpCursorOnLineClick) + const highlightElements = useScriptEditor((s) => s.highlightElements) const scrollHeight = useScriptEditor((s) => s.scrollHeight) @@ -36,7 +37,9 @@ export function ScriptEditor() { if (!standaloneCodeEditor) { return } + // let's do something basic for now: we disable the + // timeline-to-editor scroll sync when the user is // hovering the editor if (useScriptEditor.getState().mouseIsInside) { @@ -48,6 +51,7 @@ export function ScriptEditor() { scrollTop: horizontalTimelineRatio, }) } + // various things we can do here! // move the scroll: // editor.setScrollPosition({ scrollTop: horizontalTimelineRatio }) @@ -105,6 +109,7 @@ export function ScriptEditor() { } ) */ + highlightElements() } const setMonaco = useScriptEditor((s) => s.setMonaco) @@ -125,6 +130,14 @@ export function ScriptEditor() { inherit: true, // Inherit the default rules rules: [ // You can define token-specific styles here if needed + { token: 'scene.int', foreground: '#4EC9B0' }, + { token: 'scene.ext', foreground: '#9CDCFE' }, + { token: 'character', foreground: '#DCDCAA' }, + { token: 'dialog', foreground: '#D4D4D4' }, + { token: 'parenthetical', foreground: '#B5CEA8' }, + { token: 'transition', foreground: '#C586C0' }, + { token: 'shot', foreground: '#CE9178' }, + { token: 'action', foreground: '#D4D4D4' }, ], colors: { 'editor.background': @@ -149,9 +162,60 @@ export function ScriptEditor() { const textModel: MonacoEditor.editor.ITextModel = monaco.editor.createModel( current || '', - 'plaintext' + 'fountain' ) setTextModel(textModel) + + // Register fountain language + monaco.languages.register({ id: 'fountain' }) + monaco.languages.setMonarchTokensProvider('fountain', { + tokenizer: { + root: [ + [/^(INT|I\/E)(.+)/, 'scene.int'], + [/^(EXT)(.+)/, 'scene.ext'], + [/^[A-Z][A-Z\s]+$/, 'character'], + [/^\(.+\)$/, 'parenthetical'], + [/^>.+<$/, 'transition'], + [/^(?:FADE (?:IN|OUT|TO)|CUT TO:)$/, 'transition'], + [/^(?:ANGLE ON|CLOSE ON|PAN|TRACKING|MOVING):.+$/, 'shot'], + [/^(?![A-Z]+$|\(|\>).+/, 'action'], + ], + }, + }) + + // Setup code folding + monaco.languages.registerFoldingRangeProvider('fountain', { + provideFoldingRanges: function (model, context, token) { + const lines = model.getLinesContent() + const ranges = [] + let sceneStart = -1 + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim() + if (/^(INT|EXT|I\/E)/.test(line)) { + if (sceneStart !== -1) { + ranges.push({ + start: sceneStart + 1, + end: i, + kind: monaco.languages.FoldingRangeKind.Region, + }) + } + sceneStart = i + } + } + + // Add the last scene if there is one + if (sceneStart !== -1 && sceneStart < lines.length - 1) { + ranges.push({ + start: sceneStart + 1, + end: lines.length, + kind: monaco.languages.FoldingRangeKind.Region, + }) + } + + return ranges + }, + }) } return ( @@ -169,6 +233,9 @@ export function ScriptEditor() { onChange={setCurrent} options={{ fontSize: editorFontSize, + language: 'fountain', + folding: true, + foldingStrategy: 'auto', }} />