Creating an Editable Textarea That Supports Syntax-Highlighted Code (2021)

  • Brilliant and excellent write up.

  • But what is the real problem ? (getting editing and feedback in a simple way)

    Textarea or contenteditable provide support for editing, but not for syntax highliting or fancy functions - or to have them, the usual is to write all needed for editing from the ground - because it's not possible to control content of editable in other way than by operating ranges of selection (like startOfsset and endOffset, regarding to startContainer and endContainer - and if they are equal, that's the cursor).

    But it's very convinient to get both: contenteditable and syntax just by aligning precisely one over another, then get rid of few corner cases - as you can see below.

    I made it once in Firefox 23+- in very minimal way. I applied it by userContent.css with @-moz-document regexp("^.*#?\\.(css|js|jsm)$") { .. }, it was working with view-source: as well, without making any changes to the original document (half transparent anonymous content over it), very little was needed to keep it in sync (or to have a handy feature to highligt same words as under cursor when Ctrl key is pressed) and.. you could just save the document after editing.

        @-moz-document  regexp("^.\*#?\\.(css|js|jsm)$") {
      html>body>pre { -moz-binding: url(hilite.xml#hilite) !important; }
      div { position:absolute; pointer-events:none; }
      div, pre, span{ white-space:pre !important; background:transparent; .. }
      ::-moz-selection { background: #d0e0d0 !important;}
     }
    
    - - -

        <binding id="hilite" bindToUntrustedContent="true">
         <content  
          ><xht:div> </xht:div
          ><children /></content>
          <implementation><constructor><![CDATA[
            .. // "http://softwaremaniacs.org/soft/highlight/en/", version: 7.3.0 ..
     this.sync =function(){
      var t=this.replace(/<br>/g,'\n').replace(/&nbsp;/g,' ').replace(/&lt;/g,'<<>')
                .replace(/&gt;/g,'<>>').replace(/&amp;/g,'<&>');
      t=hilite(t, this.word).replace(/<<>/g,'<span><</span>')
                .replace(/<>>/g,'<span>></span>').replace(/<&>/g,'<span>&</span>')
      this.e0.innerHTML= t;
     };
     this.keydownt = function(e){          // filtering keycodes
       if ((e.keyCode == 9)&&(!e.ctrlKey)){
         document.execCommand('insertHTML',false,'\t'); e.preventDefault(); } ..  } //tab 
     this.keydownf = function(e){ 
       if(e.ctrlKey) getWord();  setTimeout(function() {sync();}, 0); ..  }
     setTimeout(function(){  
       document.body.setAttribute('spellcheck',"false"); this.contentEditable = true;
       this.e0=document.getAnonymousNodes(this)[0];
       this.textContent=this.innerHTML.replace(/&nbsp;/g,' ').replace(/&amp;/g,'&')
                                      .replace(/&lt;/g,'<').replace(/&gt;/g,'>');
       sync();  this.focus();     }, 0, false);
     ]]></constructor></implementation></binding></bindings>
    
    Orthogonally to that, I had userContent/Chrome.css opened in the sidebar and on every key up I unregister and loadAndRegisterSheet with it :) (the styles were applied live to documents or browser when the sidebar was open and saved on unload).