Search code examples
javascriptcodemirroradd-oncode-folding

I keep getting "CodeMirror.foldCode is not a function". Does the code folding addon not work with custom/simple modes?


I'm writing a very basic website to help me edit .filter files for Path of Exile. I'm using CodeMirro and have written a custom mode to handle syntax highlighting using the mode/simple.js addon. Now I'm trying to implement code folding based on indentation.

I've looked through the examples and documentation provided on CodeMirror's site. https://codemirror.net/doc/manual.html#overview

I've searched here on SO and found very few results that were even tangentially related to my problem. I've double checked my work based on the solutions provided by the one close match found here. CodeMirror foldCode method not working

Yet I'm still getting the error. I've tried looking into whether the addon simply doesn't work with custom/simple modes but it seems the internet is severely lacking in both the quality and quantity of information about CodeMirror.

Any help resolving this issue would be much appreciated. Also, if anyone knows where I could find some quality tutorials that would be amazing.

html:

<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name='viewport' content='width=device-width, initial-scale=1'>

  <title>Filter Editor</title>
  <meta name="description" content="HTML Editor for PoE Filters">
  <meta name="author" content="author">

  <link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="css/styles.css">
  <link rel="stylesheet" href="codemirror/lib/codemirror.css">
  <link rel="stylesheet" href="codemirror/theme/twilight.css">
  <link rel="stylesheet" href="codemirror/addon/fold/foldgutter.css">

</head>

<body>
    <div id='app-container'>
        <div id='display-pane'>
            <div id='preview'>
                <div id='fog'></div>
                <div id='item'>Item Text</div>
            </div>
            <div id='settings'>
                <div id='item-text'>
                    <input id='item-text-input' type='text' placeholder='Item Text' value='Item Text'>
                </div>
                <h3>Sounds</h3>
                <div id='sound-examples'>
                    <div class='sound-row'>
                        <div class='sound'>1<audio src='audio/AlertSound1.mp3'></audio></div>
                        <div class='sound'>2<audio src='audio/AlertSound2.mp3'></audio></div>
                        <div class='sound'>3<audio src='audio/AlertSound3.mp3'></audio></div>
                        <div class='sound'>4<audio src='audio/AlertSound4.mp3'></audio></div>
                    </div>
                    <div class='sound-row'>
                        <div class='sound'>5<audio src='audio/AlertSound5.mp3'></audio></div>
                        <div class='sound'>6<audio src='audio/AlertSound6.mp3'></audio></div>
                        <div class='sound'>7<audio src='audio/AlertSound7.mp3'></audio></div>
                        <div class='sound'>8<audio src='audio/AlertSound8.mp3'></audio></div>
                    </div>
                    <div class='sound-row'>
                        <div class='sound'>9<audio src='audio/AlertSound9.mp3'></audio></div>
                        <div class='sound'>10<audio src='audio/AlertSound10.mp3'></audio></div>
                        <div class='sound'>11<audio src='audio/AlertSound11.mp3'></audio></div>
                        <div class='sound'>12<audio src='audio/AlertSound12.mp3'></audio></div>
                    </div>
                    <div class='sound-row'>
                        <div class='sound'>13<audio src='audio/AlertSound13.mp3'></audio></div>
                        <div class='sound'>14<audio src='audio/AlertSound14.mp3'></audio></div>
                        <div class='sound'>15<audio src='audio/AlertSound15.mp3'></audio></div>
                        <div class='sound'>16<audio src='audio/AlertSound16.mp3'></audio></div>
                    </div>
                </div>
                <h3>Icons</h3>
                <div id='icon-examples'>
                    <div id='icon-sizes'>
                        <h4>Sizes</h4>
                        <div>
                            <div class='icon icon-blue icon-circle'>0</div>
                            <div class='icon-medium icon-blue-med icon-circle-med'>1</div>
                            <div class='icon-small icon-blue-sm icon-circle-sm'>2</div>
                        </div>
                    </div>
                    <div id='icon-shapes'>
                        <h4>Shapes</h4>
                        <div>
                            <div><div class='icon-blue-sm icon-circle-sm icon-small'></div>Circle</div>
                            <div><div class='icon-blue-sm icon-square-sm icon-small'></div>Square</div>
                            <div><div class='icon-blue-sm icon-hexagon-sm icon-small'></div>Hexagon</div>
                            <div><div class='icon-blue-sm icon-triangle-sm icon-small'></div>Triangle</div>
                            <div><div class='icon-blue-sm icon-diamond-sm icon-small'></div>Diamond</div>
                            <div><div class='icon-blue-sm icon-star-sm icon-small'></div>Star</div>
                        </div>
                    </div>
                    <div id='icon-colors'>
                        <h4>Colors</h4>
                        <div>
                            <div><div class='icon-red-sm icon-square-sm icon-small'></div>Red</div>
                            <div><div class='icon-green-sm icon-square-sm icon-small'></div>Green</div>
                            <div><div class='icon-blue-sm icon-square-sm icon-small'></div>Blue</div>
                            <div><div class='icon-yellow-sm icon-square-sm icon-small'></div>Yellow</div>
                            <div><div class='icon-brown-sm icon-square-sm icon-small'></div>Brown</div>
                            <div><div class='icon-white-sm icon-square-sm icon-small'></div>White</div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div id='editor-pane'>
            <textarea id='code-mirror'></textarea>
        </div>
    </div>



    <script src="js/jquery.min.js"></script>
    <script src="codemirror/lib/codemirror.js"></script>
    <script src="codemirror/addon/fold/foldcode.js"></script>
    <script src="codemirror/addon/fold/foldgutter.js"></script>
    <script src="codemirror/addon/fold/indent-fold.js"></script>
    <script src="codemirror/addon/mode/simple.js"></script>
    <script src="codemirror/mode/filter/poefilter.js"></script>
    <script src="js/scripts.js"></script>

</body>
</html>

And the JavaScript where I create the CodeMirror instance:

$(document).ready(function() {

    // CodeMirror Stuff
    var code = $("#code-mirror")[0];
    var editor = CodeMirror.fromTextArea(code, {
        mode: "poefilter",
        theme: "twilight",
        lineNumbers: true,
        lineWrapping: true,
        viewportMargin: 30,
        // saveFunction: "saveFunction",
        indentUnit: 4,
        minHeight: "100%",
        extraKeys: {"Ctrl-Q": function(cm){     CodeMirror.foldCode(cm.getCursor()); }},
        foldGutter: true,
        gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
    });

    editor.foldCode(CodeMirror.Pos(0, 0));
    editor.setSize("100%", "100%");

Here's the mode I wrote (poefilter.js):

var test = {
        block: /(?:Show|Hide)/,
        condition: /(?:ItemLevel|DropLevel|Quality|Rarity|Class|Sockets|LinkedSockets|SocketGroup|Height|Width|AnyEnchantment|StackSize|GemLevel|Identified|Corrupted|ElderItem|ShaperItem|FracturedItem|SynthesisedItem|ShapedMap|BlightedMap|MapTier)/,
        opencond: /BaseType|Prophecy|HasExplicitMod|HasEnchantment|CustomAlertSound/,
        action: /(?:SetBorderColor|SetTextColor|SetBackgroundColor|SetFontSize|PlayAlertSoundPositional|PlayAlertSound|DisableDropSound|MinimapIcon|PlayEffect)/,
        operators: /(?:<|<=|=|>|>=)/,
        number: /[0-9]+/,
        literal: /(?:True|False|Normal|Magic|Rare|Unique|Temp|Red|Green|Blue|Yellow|Brown|White|Triangle|Square|Circle|Diamond|Hexagon|R|G|B)/,
        itemclass: /(?:"Life Flasks"|"Mana Flasks"|"Hybrid Flasks"|"Utility Flasks"|"Critical Utility Flasks"|Flasks|"Stackable Currency"|"Delve Stackable Socketable Currency"|"Socketable Currency"|Currency|Amulets|Rings|Claws|"Rune Dagger"|Daggers|Wands|"One Hand Swords"|"Thrusting One Hand Swords"|"Two Hand Swords"|Swords|"One Hand Axes"|"Two Hand Axes"|Axes|"One Hand Maces"|"Two Hand Maces"|Maces|Bows|Quivers|Staves|Warstaff|"Active Skill Gems"|"Support Skill Gems"|Gems|Belts|Gloves|Boots|"Body Armours"|Body|Helmets|Shields|"Quest Items"|Sceptres|Unarmed|"Fishing Rods"|"Map Fragments"|"Hideout Doodads"|Microtransactions|"Abyss Jewel"|Jewel|"Divination Card"|"Labyrinth Item"|"Labyrinth Trinket"|"Labyrinth Map Item"|Labyrinth|"Misc Map Items"|Leaguestones|"Pantheon Soul"|Piece|"Incursion Item"|Incubator|"Shard Heart"|Shard|Maps|Map)/,
        comment: /#.*/,
        custom: /.*/
    }

CodeMirror.defineSimpleMode("poefilter", {
    // The start state contains the rules that are intially used
    start: [
        // blocks
        {regex: test.comment, token: ["comment"]},
        {regex: test.block, token: "header"},
        {regex: test.condition, token: "attribute"},
        {regex: test.opencond, token: "attribute", next: "custom"},
        {regex: test.action, token: "def"},
        {regex: test.operators, token: "operator"},
        {regex: test.number, token: "number"},
        {regex: test.itemclass, token: "string-2"},
        {regex: test.literal, token: "number"}
    ],
    custom: [
        {regex: test.custom, token: "string", next: "start"}
    ],
    fold: "poefilter"
});

I expect to see little arrows in the gutter that allow me to click on them to fold-open and fold-close the code. Instead, I see no arrows and I get an error in my developer console saying: scripts.js:19 Uncaught TypeError: CodeMirror.foldcode is not a function


Solution

  • Ok, I finally solved this issue.

    The last line in the mode file, fold: "poefilter" had a few problems.

    First, "poefilter" should have been "indent" since that is the name of the fold type I was trying to apply with the foldcode addon (via indent-fold.js).

    Second, since I was using a simple mode, the option needed to be wrapped inside the meta property like so:

    meta: {
        fold: "indent"
    }
    

    Once I fixed those two issues everything works beautifully.