// CodeMirror, copyright (c) by Marijn Haverbeke and others

// Distributed under an MIT license: https://codemirror.net/LICENSE



/***

    |''Name''|tiddlywiki.js|

    |''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror|

    |''Author''|PMario|

    |''Version''|0.1.7|

    |''Status''|''stable''|

    |''Source''|[[GitHub|https://github.com/pmario/CodeMirror2/blob/tw-syntax/mode/tiddlywiki]]|

    |''Documentation''|https://codemirror.tiddlyspace.com/|

    |''License''|[[MIT License|http://www.opensource.org/licenses/mit-license.php]]|

    |''CoreVersion''|2.5.0|

    |''Requires''|codemirror.js|

    |''Keywords''|syntax highlighting color code mirror codemirror|

    ! Info

    CoreVersion parameter is needed for TiddlyWiki only!

***/



(function(mod) {

  if (typeof exports == "object" && typeof module == "object") // CommonJS

    mod(require("../../lib/codemirror"));

  else if (typeof define == "function" && define.amd) // AMD

    define(["../../lib/codemirror"], mod);

  else // Plain browser env

    mod(CodeMirror);

})(function(CodeMirror) {

"use strict";



CodeMirror.defineMode("tiddlywiki", function () {

  // Tokenizer

  var textwords = {};



  var keywords = {

    "allTags": true, "closeAll": true, "list": true,

    "newJournal": true, "newTiddler": true,

    "permaview": true, "saveChanges": true,

    "search": true, "slider": true, "tabs": true,

    "tag": true, "tagging": true, "tags": true,

    "tiddler": true, "timeline": true,

    "today": true, "version": true, "option": true,

    "with": true, "filter": true

  };



  var isSpaceName = /[\w_\-]/i,

      reHR = /^\-\-\-\-+$/,                                 // <hr>

      reWikiCommentStart = /^\/\*\*\*$/,            // /***

      reWikiCommentStop = /^\*\*\*\/$/,             // ***/

      reBlockQuote = /^<<<$/,



      reJsCodeStart = /^\/\/\{\{\{$/,                       // //{{{ js block start

      reJsCodeStop = /^\/\/\}\}\}$/,                        // //}}} js stop

      reXmlCodeStart = /^<!--\{\{\{-->$/,           // xml block start

      reXmlCodeStop = /^<!--\}\}\}-->$/,            // xml stop



      reCodeBlockStart = /^\{\{\{$/,                        // {{{ TW text div block start

      reCodeBlockStop = /^\}\}\}$/,                 // }}} TW text stop



      reUntilCodeStop = /.*?\}\}\}/;



  function chain(stream, state, f) {

    state.tokenize = f;

    return f(stream, state);

  }



  function tokenBase(stream, state) {

    var sol = stream.sol(), ch = stream.peek();



    state.block = false;        // indicates the start of a code block.



    // check start of  blocks

    if (sol && /[<\/\*{}\-]/.test(ch)) {

      if (stream.match(reCodeBlockStart)) {

        state.block = true;

        return chain(stream, state, twTokenCode);

      }

      if (stream.match(reBlockQuote))

        return 'quote';

      if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop))

        return 'comment';

      if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop))

        return 'comment';

      if (stream.match(reHR))

        return 'hr';

    }



    stream.next();

    if (sol && /[\/\*!#;:>|]/.test(ch)) {

      if (ch == "!") { // tw header

        stream.skipToEnd();

        return "header";

      }

      if (ch == "*") { // tw list

        stream.eatWhile('*');

        return "comment";

      }

      if (ch == "#") { // tw numbered list

        stream.eatWhile('#');

        return "comment";

      }

      if (ch == ";") { // definition list, term

        stream.eatWhile(';');

        return "comment";

      }

      if (ch == ":") { // definition list, description

        stream.eatWhile(':');

        return "comment";

      }

      if (ch == ">") { // single line quote

        stream.eatWhile(">");

        return "quote";

      }

      if (ch == '|')

        return 'header';

    }



    if (ch == '{' && stream.match(/\{\{/))

      return chain(stream, state, twTokenCode);



    // rudimentary html:// file:// link matching. TW knows much more ...

    if (/[hf]/i.test(ch) &&

        /[ti]/i.test(stream.peek()) &&

        stream.match(/\b(ttps?|tp|ile):\/\/[\-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i))

      return "link";



    // just a little string indicator, don't want to have the whole string covered

    if (ch == '"')

      return 'string';



    if (ch == '~')    // _no_ CamelCase indicator should be bold

      return 'brace';



    if (/[\[\]]/.test(ch) && stream.match(ch)) // check for [[..]]

      return 'brace';



    if (ch == "@") {    // check for space link. TODO fix @@...@@ highlighting

      stream.eatWhile(isSpaceName);

      return "link";

    }



    if (/\d/.test(ch)) {        // numbers

      stream.eatWhile(/\d/);

      return "number";

    }



    if (ch == "/") { // tw invisible comment

      if (stream.eat("%")) {

        return chain(stream, state, twTokenComment);

      } else if (stream.eat("/")) { //

        return chain(stream, state, twTokenEm);

      }

    }



    if (ch == "_" && stream.eat("_")) // tw underline

        return chain(stream, state, twTokenUnderline);



    // strikethrough and mdash handling

    if (ch == "-" && stream.eat("-")) {

      // if strikethrough looks ugly, change CSS.

      if (stream.peek() != ' ')

        return chain(stream, state, twTokenStrike);

      // mdash

      if (stream.peek() == ' ')

        return 'brace';

    }



    if (ch == "'" && stream.eat("'")) // tw bold

      return chain(stream, state, twTokenStrong);



    if (ch == "<" && stream.eat("<")) // tw macro

      return chain(stream, state, twTokenMacro);



    // core macro handling

    stream.eatWhile(/[\w\$_]/);

    return textwords.propertyIsEnumerable(stream.current()) ? "keyword" : null

  }



  // tw invisible comment

  function twTokenComment(stream, state) {

    var maybeEnd = false, ch;

    while (ch = stream.next()) {

      if (ch == "/" && maybeEnd) {

        state.tokenize = tokenBase;

        break;

      }

      maybeEnd = (ch == "%");

    }

    return "comment";

  }



  // tw strong / bold

  function twTokenStrong(stream, state) {

    var maybeEnd = false,

    ch;

    while (ch = stream.next()) {

      if (ch == "'" && maybeEnd) {

        state.tokenize = tokenBase;

        break;

      }

      maybeEnd = (ch == "'");

    }

    return "strong";

  }



  // tw code

  function twTokenCode(stream, state) {

    var sb = state.block;



    if (sb && stream.current()) {

      return "comment";

    }



    if (!sb && stream.match(reUntilCodeStop)) {

      state.tokenize = tokenBase;

      return "comment";

    }



    if (sb && stream.sol() && stream.match(reCodeBlockStop)) {

      state.tokenize = tokenBase;

      return "comment";

    }



    stream.next();

    return "comment";

  }



  // tw em / italic

  function twTokenEm(stream, state) {

    var maybeEnd = false,

    ch;

    while (ch = stream.next()) {

      if (ch == "/" && maybeEnd) {

        state.tokenize = tokenBase;

        break;

      }

      maybeEnd = (ch == "/");

    }

    return "em";

  }



  // tw underlined text

  function twTokenUnderline(stream, state) {

    var maybeEnd = false,

    ch;

    while (ch = stream.next()) {

      if (ch == "_" && maybeEnd) {

        state.tokenize = tokenBase;

        break;

      }

      maybeEnd = (ch == "_");

    }

    return "underlined";

  }



  // tw strike through text looks ugly

  // change CSS if needed

  function twTokenStrike(stream, state) {

    var maybeEnd = false, ch;



    while (ch = stream.next()) {

      if (ch == "-" && maybeEnd) {

        state.tokenize = tokenBase;

        break;

      }

      maybeEnd = (ch == "-");

    }

    return "strikethrough";

  }



  // macro

  function twTokenMacro(stream, state) {

    if (stream.current() == '<<') {

      return 'macro';

    }



    var ch = stream.next();

    if (!ch) {

      state.tokenize = tokenBase;

      return null;

    }

    if (ch == ">") {

      if (stream.peek() == '>') {

        stream.next();

        state.tokenize = tokenBase;

        return "macro";

      }

    }



    stream.eatWhile(/[\w\$_]/);

    return keywords.propertyIsEnumerable(stream.current()) ? "keyword" : null

  }



  // Interface

  return {

    startState: function () {

      return {tokenize: tokenBase};

    },



    token: function (stream, state) {

      if (stream.eatSpace()) return null;

      var style = state.tokenize(stream, state);

      return style;

    }

  };

});



CodeMirror.defineMIME("text/x-tiddlywiki", "tiddlywiki");

});

