HTA Hack and CodeMirror

I believe there is a small group of TW5 users who run their TiddlyWiki on Windows in HTA mode/hack per

I am such a user due to a restrictive corporate computing environment running on TW v5.1.17 and CodeMirror 5.37.0 (according to the readme page).

However when I tried to upgrade to TW v5.2.2, the CodeMirror (CM) plugin no longer worked when running as HTA (see TiddlyWiki v5.2.2 CodeMirror plugin not working in HTA mode · Issue #6702 · Jermolene/TiddlyWiki5 · GitHub). While we agreed this is not something that the TW team should be fixing, I’d like to explore this topic further.

I’m currently testing a copy of TW v5.2.7+CM5.58.3 and hope those versed in TW5 development can help to clarify the following: given that CM5.58.3 was released 3 years ago and the latest CM5 version is v5.65.13, Is it possible to upgrade the CM plugin in TW5 to the latest and see if that has improved matters? I believe going to the latest version will be more likely to get responses if we are to ask the CM developers for assistance.

I saw in a comment that GitHub - jeremyben/HTAConsole: HTA Console is a basic but practical Javascript Console Log to help debugging HTML Applications. can assist in troubleshooting HTA issues, can someone share some code snippet on how to do so? A naive attempt as follows didn’t seem to work:

<!--~~ Raw markup ~~-->
<script src="./htaconsole.js"></script>
var iosDragDropShim = { enableEnterLeave: true, holdToDrag: 300 };

Also if you are aware of any free/open source tools that can assist in debugging HTA issues, please do share.

Also, where can I get a list of TW5 versions along with its corresponding CM versions so that I can also explore when did CM stopped working so that we can maybe narrow down the changes that broke it.

Can you detail what actualy breaks? What can or cant you see compared to a html file?

Have you tried a private tiddlyhost or tiddlystow?

Hi @whjou there is a pre-built archive of previous versions of TiddlyWiki here:

I am afraid the build does not include the CodeMirror plugin, but the appropriate version can be installed from the plugin library.

You can see the history of the codemirror plugin here:

It would be nice to be able to point to URLS like:

While the last two work, GitHub Pages doesn’t seem to offer a way to generate index pages for the others. Could we add a build step to create simple index files on publication?

Hi @Scott_Sauyet that is the plan but not done yet. There has been some discussion on GitHub, and then @pmario kindly prepared a PR.

One problem is that the repo is very unwieldy to work with because it has the accreted history of a dozen years of releases and so it takes an absurd amount of time to clone the repo.

When I started to look at this, I did wonder whether the archive might be easier to handle if it was in its own subdomain (eg, which would mean having its own GitHub Pages repo.

I did create a shallow clone with: git clone --depth=1 <url> which just clones the latest version of everything. So it makes it much easier to handle.

I just found out, that some of the full -versions have “filesystem” plugins installed. I’ll create a new PR soon. There must have been something wrong with the CLI commands :confused:

Great, glad to hear it!

Ouch, at 2.55 GB, and only 9%; quitting now. That can be a major headache.

Actually, no. Steps to reproduce (with v5.2.7)

  1. Download empty edition as empty.html

  2. Rename to empty.hta

  3. Open as HTA.

  4. Install CodeMirror plugin.

  5. Save and reload.

  6. Error encountered: hta-error The line 61 above do not seem to refer to actual line 61 in empty.hta as the actual line 61 in empty.hta is just a HTML comment.

  7. Selecting Yes will lead to the following screen:

  8. Selecting “close” will close the Internal JavaScript Error.

  9. After that, none of the buttons work except the edit button on the GettingStarted tiddler: it will popup the standard warning about editing a ShadowTiddler but after selecting OK, nothing happens either.

So to answer your questions,

  • I think everything broke.
  • What you can see is comparable to a normal html file.
  • I have not tried private tiddlyhost or tiddlystow, but if it is relevant, I will try ad let you know.

wrt tiddlyhost or tiddlystow, I like my TWs local and in a single file (without referencing another file or browser extensions etc.), so I’ve read about them but do not intend to try, but thanks for pointing them out as alternatives to file saving. GitHub - slaymaker1907/TW5-browser-nativesaver works for me, but nevertheless I’m pursuing the HTA option as an nice option to have, if it is possible to be had.

The hta wikis have access to the local file system but they use a deprecated feature of IE yes, internet explorer, left behind in Windows 11 because some legacy apps used it, in part, for help files.

  • You can get similar results with TiddlyDesktop but that needs to be installed
  • You could possibly use a USB to run a portable node/TiddlyDesktop instance, unless your USB drives are locked down
  • I believe it is even less likely to work if you upgrade codemirror as every change you move further from the legacy hta method.

It can be difficult to help when locked down environments get in your way, they can be so specific or bespoke. If tiddlyhost works I recommend that. But be careful about saving business content anywhere offsite. If you have SharePoint, search for how we have done it there.

Perhaps you could add local storage and use the manual download save occasionally?

I’ve done some experimentation:

TW v5.2.0 is the last version that works with CodeMirror plugin in HTA mode.
TW v5.2.1 failed to run in HTA mode after importing CodeMirror plugin.

Per plugin info, both versions of CodeMirror plugin is based on upstream CodeMirror version 5.58.3. Additional experiments:

TW v5.2.0 failed with $:/plugins/tiddlywiki/codemirror exported from TW v5.2.1.
TW v5.2.1 worked with $:/plugins/tiddlywiki/codemirror exported from TW v5.2.0.

I hope this implies the change in the CodeMirror plugin is the source of incompatibility rather than TW or upstream CM. Comparing the two $:/plugins/tiddlywiki/codemirror (after expanding the "\n"s and the "\t"s), there are a few differences between them:

Difference 1
self.widget.invokeActionString(self.widget.editInputActions,this,event,{actionValue: this.getText()});

Difference 2
exports.CodeMirrorEngine = CodeMirrorEngine;
self.widget.invokeActionString(self.widget.editInputActions,this,event,{actionValue: this.getText()});

Difference 3


// Detect if Chrome has added a pseudo File object to the dataTransfer
if(!$tw.utils.dragEventContainsFiles(event) && event.dataTransfer.files.length) {
        //Make codemirror ignore the event as we will handle the drop ourselves
        event.codemirrorIgnore = true;

        // from
        function posFromMouse(cm, e, liberal, forRect) {
                let display = cm.display
                if (!liberal && e_target(e).getAttribute(\"cm-not-content\") == \"true\") return null

                let x, y, space = display.lineSpace.getBoundingClientRect()
                // Fails unpredictably on IE[67] when mouse is dragged around quickly.
                try { x = e.clientX - space.left; y = e.clientY - }
                catch (e) { return null }
                let coords = cm.coordsChar(cm, x, y), line
                if (forRect && coords.xRel > 0 && (line = cm.getLine(cm.doc, coords.line).text).length == {
                        let colDiff = window.CodeMirror.countColumn(line, line.length, cm.options.tabSize) - line.length
                        coords = window.CodeMirror.Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff))
                return coords

        var pos = posFromMouse(cm,event,true);
        if(!pos || cm.isReadOnly()) {
        // Don't do a replace if the drop happened inside of the selected text.
        if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
                // Ensure the editor is re-focused
                setTimeout(() => cm.display.input.focus(), 20);
        try {
                var text = event.dataTransfer.getData(\"Text\");
                if (text) {
                        var selected;
                        if (cm.state.draggingText && !cm.state.draggingText.copy) {
                                selected = cm.listSelections();
                        if (selected) {
                                for (var i = 0; i < selected.length; ++i) {
                                        replaceRange(cm.doc, \"\", selected[i].anchor, selected[i].head, \"drag\");
                        cm.replaceSelection(text, \"around\", \"paste\");

I have also installed CM plugin in TW v5.2.7 and extracted $:/plugins/tiddlywiki/codemirror which is also based on upstream CodeMirror version 5.58.3 . The same set of codes are present with minor changes related to “keydown” and “paste” events and additional syntaxes(?).

Here is where I am not able to proceed further. Can anyone comment if any of the above difference in code will cause issues when running as HTA and if there is any way they can be rewritten to play nice with Microsoft’s Trident MSHTML engine? I believe since the bulk of CM plugin is unchanged, if a solution can be found, then it will likely be applicable to the current version of TW and CM plugin too.

Hi, That’s a very detailed research.

You could try to replace the let with var in the Difference 3 code and try if it’s possible to execute the code with HTA …

Edit: I just checked the code. … It also uses “arrow functions” … So replacing let with var alone will not help you out. … more investigation will be needed.

Just for testing! You could change:

		if(!$tw.utils.dragEventContainsFiles(event) && event.dataTransfer.files.length) {


		if(!$tw.browser.isIE && !$tw.utils.dragEventContainsFiles(event) && event.dataTransfer.files.length) {

the !$tw.browser.isIE is a global check if the browser is IE … So if that the HTA hack sets this info right, the new code should be ignored when used with HTA.

Just a “shot in the dark”, but should be relatively straight forward to test.

@pmario I believe the intent of that extra code you suggested is to skip a chunk of code (Difference 3 above) when running in HTA mode. I tried it but that didn’t work. I’ve also narrowed down to the specific line that triggered the error in Fix for CodeMirror issues with drop events in Chrome 96 (#6278) · Jermolene/TiddlyWiki5@75aabcc · GitHub to the “arrow function” you mentioned(?):

            setTimeout(() => cm.display.input.focus(), 20);

and that is indeed the line that caused the error (CM plugin for TW v5.2.7 works when that line is commented out). Taking references from other setTimeout()s, I replaced it with

            setTimeout(function() {cm.display.input.focus();}, 20);

and CM (in HTA mode) seems to work as well. I have no idea if the above is equivalent to the “arrow function” syntax?

Disclaimer: my basic tests for determining if CM works is to enable line numbers and highlight active line and creating a new tiddler and see if the editor does indeed show line numbers and highlights the active line: I didn’t do any file dropping…