@@ -3,8 +3,9 @@ import { BaseElement } from "../../core/base-element.ts";
33import { styles } from "./styles.ts" ;
44import { basicSetup } from "codemirror" ;
55import { EditorView , keymap , placeholder } from "@codemirror/view" ;
6+ import { indentWithTab } from "@codemirror/commands" ;
67import { Compartment , EditorState , Extension } from "@codemirror/state" ;
7- import { LanguageSupport } from "@codemirror/language" ;
8+ import { indentUnit , LanguageSupport } from "@codemirror/language" ;
89import { javascript as createJavaScript } from "@codemirror/lang-javascript" ;
910import { markdown as createMarkdown } from "@codemirror/lang-markdown" ;
1011import { css as createCss } from "@codemirror/lang-css" ;
@@ -82,6 +83,12 @@ const getLangExtFromMimeType = (mime: MimeType) => {
8283 * @attr {number} timingDelay - Delay in milliseconds for debounce/throttle (default: 500)
8384 * @attr {Array} mentionable - Array of mentionable items with Charm structure for backlink autocomplete
8485 * @attr {Array} mentioned - Optional Cell of live Charms mentioned in content
86+ * @attr {boolean} wordWrap - Enable soft line wrapping (default: true)
87+ * @attr {boolean} lineNumbers - Show line numbers gutter (default: false)
88+ * @attr {number} maxLineWidth - Optional max line width in ch units
89+ * (default: undefined)
90+ * @attr {number} tabSize - Tab size (spaces shown for a tab, default: 2)
91+ * @attr {boolean} tabIndent - Indent on Tab key (default: true)
8592 *
8693 * @fires ct-change - Fired when content changes with detail: { value, oldValue, language }
8794 * @fires ct-focus - Fired on focus
@@ -104,6 +111,12 @@ export class CTCodeEditor extends BaseElement {
104111 timingDelay : { type : Number } ,
105112 mentionable : { type : Array } ,
106113 mentioned : { type : Array } ,
114+ // New editor configuration props
115+ wordWrap : { type : Boolean } ,
116+ lineNumbers : { type : Boolean } ,
117+ maxLineWidth : { type : Number } ,
118+ tabSize : { type : Number } ,
119+ tabIndent : { type : Boolean } ,
107120 } ;
108121
109122 declare value : Cell < string > | string ;
@@ -115,10 +128,21 @@ export class CTCodeEditor extends BaseElement {
115128 declare timingDelay : number ;
116129 declare mentionable : Cell < Charm [ ] > ;
117130 declare mentioned ?: Cell < Charm [ ] > ;
131+ declare wordWrap : boolean ;
132+ declare lineNumbers : boolean ;
133+ declare maxLineWidth ?: number ;
134+ declare tabSize : number ;
135+ declare tabIndent : boolean ;
118136
119137 private _editorView : EditorView | undefined ;
120138 private _lang = new Compartment ( ) ;
121139 private _readonly = new Compartment ( ) ;
140+ private _wrap = new Compartment ( ) ;
141+ private _gutters = new Compartment ( ) ;
142+ private _tabSizeComp = new Compartment ( ) ;
143+ private _tabIndentComp = new Compartment ( ) ;
144+ private _maxLineWidthComp = new Compartment ( ) ;
145+ private _indentUnitComp = new Compartment ( ) ;
122146 private _cleanupFns : Array < ( ) => void > = [ ] ;
123147 private _cellController = createStringCellController ( this , {
124148 timing : {
@@ -145,6 +169,12 @@ export class CTCodeEditor extends BaseElement {
145169 this . placeholder = "" ;
146170 this . timingStrategy = "debounce" ;
147171 this . timingDelay = 500 ;
172+ // Defaults for new props
173+ this . wordWrap = true ;
174+ this . lineNumbers = false ;
175+ this . maxLineWidth = undefined ;
176+ this . tabSize = 2 ;
177+ this . tabIndent = true ;
148178 }
149179
150180 /**
@@ -472,6 +502,61 @@ export class CTCodeEditor extends BaseElement {
472502 } ) ;
473503 }
474504
505+ // Update word wrap
506+ if ( changedProperties . has ( "wordWrap" ) && this . _editorView ) {
507+ this . _editorView . dispatch ( {
508+ effects : this . _wrap . reconfigure (
509+ this . wordWrap ? EditorView . lineWrapping : [ ] ,
510+ ) ,
511+ } ) ;
512+ }
513+
514+ // Update line numbers visibility (hide gutters when false)
515+ if ( changedProperties . has ( "lineNumbers" ) && this . _editorView ) {
516+ const hideGutters = ! this . lineNumbers ;
517+ const ext = hideGutters
518+ ? EditorView . theme ( {
519+ ".cm-gutters" : { display : "none" } ,
520+ ".cm-content" : { paddingLeft : "0px" } ,
521+ } )
522+ : [ ] as unknown as Extension ;
523+ this . _editorView . dispatch ( {
524+ effects : this . _gutters . reconfigure ( ext ) ,
525+ } ) ;
526+ }
527+
528+ // Update tab size
529+ if ( changedProperties . has ( "tabSize" ) && this . _editorView ) {
530+ const size = this . tabSize ?? 2 ;
531+ this . _editorView . dispatch ( {
532+ effects : [
533+ this . _tabSizeComp . reconfigure ( EditorState . tabSize . of ( size ) ) ,
534+ this . _indentUnitComp . reconfigure ( indentUnit . of ( " " . repeat ( size ) ) ) ,
535+ ] ,
536+ } ) ;
537+ }
538+
539+ // Update tab indent keymap
540+ if ( changedProperties . has ( "tabIndent" ) && this . _editorView ) {
541+ const ext = this . tabIndent ? keymap . of ( [ indentWithTab ] ) : [ ] ;
542+ this . _editorView . dispatch ( {
543+ effects : this . _tabIndentComp . reconfigure ( ext ) ,
544+ } ) ;
545+ }
546+
547+ // Update max line width theme
548+ if ( changedProperties . has ( "maxLineWidth" ) && this . _editorView ) {
549+ const n = this . maxLineWidth ;
550+ const ext = typeof n === "number" && n > 0
551+ ? EditorView . theme ( {
552+ ".cm-content" : { maxWidth : `${ n } ch` } ,
553+ } )
554+ : [ ] as unknown as Extension ;
555+ this . _editorView . dispatch ( {
556+ effects : this . _maxLineWidthComp . reconfigure ( ext ) ,
557+ } ) ;
558+ }
559+
475560 // Update timing controller if timing options changed
476561 if (
477562 changedProperties . has ( "timingStrategy" ) ||
@@ -525,9 +610,35 @@ export class CTCodeEditor extends BaseElement {
525610 // Create editor extensions
526611 const extensions : Extension [ ] = [
527612 basicSetup ,
613+ // Tab indentation keymap (toggleable)
614+ this . _tabIndentComp . of ( this . tabIndent ? keymap . of ( [ indentWithTab ] ) : [ ] ) ,
528615 oneDark ,
529616 this . _lang . of ( getLangExtFromMimeType ( this . language ) ) ,
530617 this . _readonly . of ( EditorState . readOnly . of ( this . readonly ) ) ,
618+ // Word wrapping
619+ this . _wrap . of ( this . wordWrap ? EditorView . lineWrapping : [ ] ) ,
620+ // Hide gutters when line numbers are disabled
621+ this . _gutters . of (
622+ ! this . lineNumbers
623+ ? EditorView . theme ( {
624+ ".cm-gutters" : { display : "none" } ,
625+ ".cm-content" : { paddingLeft : "0px" } ,
626+ } )
627+ : [ ] as unknown as Extension ,
628+ ) ,
629+ // Tab size
630+ this . _tabSizeComp . of ( EditorState . tabSize . of ( this . tabSize ?? 2 ) ) ,
631+ this . _indentUnitComp . of (
632+ indentUnit . of ( " " . repeat ( this . tabSize ?? 2 ) ) ,
633+ ) ,
634+ // Optional max line width (in ch)
635+ this . _maxLineWidthComp . of (
636+ typeof this . maxLineWidth === "number" && this . maxLineWidth > 0
637+ ? EditorView . theme ( {
638+ ".cm-content" : { maxWidth : `${ this . maxLineWidth } ch` } ,
639+ } )
640+ : [ ] as unknown as Extension ,
641+ ) ,
531642 EditorView . updateListener . of ( ( update ) => {
532643 if ( update . docChanged && ! this . readonly ) {
533644 const value = update . state . doc . toString ( ) ;
0 commit comments