text: define([
    'base/js/namespace',
    'notebook/js/codecell',
    'services/kernels/kernel'
  ], function(Jupyter, codecell, kernel){
    return {onload: function(){
  
  // CodeMirror mode taken from PMunch https://github.com/PMunch/nim-playground-frontend
  CodeMirror.defineMode("nim", function(conf, parserConf) {
    var ERRORCLASS = 'error';
  
    function wordRegexp(words) {
      return new RegExp("^((" + words.join(")|(") + "))\\b");
    }
  
    var operators = new RegExp("\\=\\+\\-\\*\\/\\<\\>\\@\\$\\~\\&\\%\\|\\!\\?\\^\\:\\\\");
    var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
  
    var commonkeywords = ['addr', 'asm',
                          'bind', 'block', 'break', 'case', 'cast',
                          'concept', 'const', 'continue', 'converter',
                          'defer', 'discard', 'distinct', 'do',
                          'elif', 'else', 'end', 'enum', 'except', 'export',
                          'finally', 'for', 'from', 'func',
                          'if', 'import', 'include', 'interface', 'iterator',
                          'let',
                          'macro', 'method', 'mixin', 'nil',
                          'object', 'out',
                          'proc', 'ptr',
                          'raise', 'ref', 'return',
                          'shared', 'static',
                          'template', 'try', 'tuple', 'type',
                          'using',
                          'var',
                          'when', 'while', 'with', 'without',
                          'yield',
  
                          // keyword operators
                          'shl', 'shr', 'and', 'or', 'xor', 'not',
                          'div', 'mod', 'is', 'isnot', 'in', 'as', 'of'];
  
    var commonBuiltins = ["int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16",
                          "uint32", "uint64", "float", "float32", "float64", "bool", "char",
                          "string", "cstring", "pointer", "range", "array", "openarray",
                          "seq", "set", "byte", "Natural", "Positive", "RootObj", "RootRef",
                          "Conversion", "ByteAddress", "BiggestUInt", "BiggestInt", "BiggestFloat",
                          "cchar", "cschar", "cshort", "cint", "csize", "cuchar", "cushort",
                          "clong", "clonglong", "cfloat", "cdouble", "clongdouble", "cuint",
                          "culong", "culonglong", "cchar", "cstringArray", "Endianness", "PFloat32",
                          "PFloat64", "PInt64", "PInt32", "GC_Strategy", "File", "FileMode",
                          "FileHandle", "isMainModule", "CompileDate", "CompileTime", "NimVersion",
                          "NimMajor", "NimMinor", "NimPatch", "cpuEndian", "hostOS", "hostCPU",
                          "Inf", "NegInf", "NaN", "QuitSuccess", "QuitFailure", "dbgLineHook", "stdin",
                          "stdout", "stderr", "defined", "new", "high", "low", "sizeof", "succ", "pred",
                          "inc", "dec", "newSeq", "len", "incl", "excl", "card", "ord", "chr", "ze",
                          "ze64", "toU8", "toU16", "toU32", "abs", "min", "max", "add", "repr", "contains",
                          "toFloat", "toBiggestFloat", "toInt", "toBiggestInt", "addQuitProc", "copy",
                          "setLen", "newString", "zeroMem", "copyMem", "moveMem", "equalMem", "alloc",
                          "alloc0", "realloc", "dealloc", "setLen", "assert", "swap", "getRefcount",
                          "getCurrentException", "Msg", "getOccupiedMem", "getFreeMem", "getTotalMem",
                          "isNil", "seqToPtr", "find", "pop", "GC_disable", "GC_enable", "GC_fullCollect",
                          "GC_setStrategy", "GC_enableMarkAnd", "Sweep", "GC_disableMarkAnd", "Sweep",
                          "GC_getStatistics", "GC_ref", "GC_ref", "GC_ref", "GC_unref", "GC_unref",
                          "GC_unref", "quit", "OpenFile", "OpenFile", "CloseFile", "EndOfFile",
                          "readChar", "FlushFile", "readFile", "write", "readLine", "writeln",
                          "writeln", "getFileSize", "ReadBytes", "ReadChars", "readBuffer",
                          "writeBytes", "writeChars", "writeBuffer", "setFilePos", "getFilePos",
                          "fileHandle", "countdown", "countup", "items", "lines",
                          "ashr", "owned", "TaintedString", "NimNode", "ForLoopStmt",
                          "declared", "declaredInScope", "unsafeAddr", "reset", "typeof",
                          "toOpenArray", "cmp", "compileOption", "Ordinal", "SomeOrdinal",
                          "SomeFloat", "SomeInteger", "SomeSignedInt", "SomeUnsignedInt",
                          "SomeNumber", "typeOfProc", "typeOfIter", "HSlice", "Slice",
                          "sink", "lent", "typedesc", "void", "auto", "any", "untyped", "typed",
                          "true", "false",
  
                          // exceptions
                          "Exception", "Defect", "CatchableError", "IOError", "EOFError", "OSError",
                          "LibraryError", "ResourceExhaustedError", "ArithmeticError", "DivByZeroError",
                          "OverflowError", "AccessViolationError", "AssertionError", "ValueError",
                          "KeyError", "OutOfMemError", "IndexError", "FieldError", "RangeError",
                          "StackOverflowError", "ReraiseError", "ObjectAssignmentError",
                          "ObjectConversionError", "FloatingPointError", "FloatInvalidOpError",
                          "FloatDivByZeroError", "FloatOverflowError", "FloatInexactError",
                          "FloatUnderflowError", "DeadThreadError", "NilAccessError"];
  
    if(parserConf.extra_keywords != undefined)
      commonkeywords = commonkeywords.concat(parserConf.extra_keywords);
  
    if(parserConf.extra_builtins != undefined)
      commonBuiltins = commonBuiltins.concat(parserConf.extra_builtins);
  
    var keywords = wordRegexp(commonkeywords);
    var builtins = wordRegexp(commonBuiltins);
  
    var indentInfo = null;
  
    var stringPrefixes = new RegExp("^(('{3}|\"{3}|['\"]))", "i");
  
    // tokenizers
    function tokenBase(stream, state) {
      // Handle scope changes
      if (stream.sol()) {
        var scopeOffset = state.scopes[0].offset;
        if (stream.eatSpace()) {
          var lineOffset = stream.indentation();
          if (lineOffset > scopeOffset) {
            indentInfo = 'indent';
          } else if (lineOffset < scopeOffset) {
            indentInfo = 'dedent';
          }
          return null;
        } else {
          if (scopeOffset > 0) {
            dedent(stream, state);
          }
        }
      }
  
      if (stream.eatSpace())
        return null;
  
      var ch = stream.peek();
  
      // Handle Comments
      if (ch === '#') {
        stream.next();
        if (stream.eat("[")) {
          state.tokenize = tokenComment;
          return tokenComment(stream, state);
        } else {
          stream.skipToEnd();
          return "comment";
        }
      }
  
      // Handle Number Literals
      if (stream.match(/^[0-9\.]/, false)) {
        var floatLiteral = false;
        // Floats
        if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
        if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
        if (stream.match(/^\.\d+/)) { floatLiteral = true; }
        if (floatLiteral) {
            // Float literals may be "imaginary"
            stream.eat(/J/i);
            return 'number';
        }
        // Integers
        var intLiteral = false;
        // Hex
        if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
        // Binary
        if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
        // Octal
        if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
        // Decimal
        if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
          // Decimal literals may be "imaginary"
          stream.eat(/J/i);
          // TODO - Can you have imaginary longs?
          intLiteral = true;
        }
        // Zero by itself with no other piece of number.
        if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
        if (intLiteral) {
          // Integer literals may be "long"
          stream.eat(/L/i);
          return 'number';
        }
      }
  
      // Handle Strings
      if (stream.match(stringPrefixes)) {
        state.tokenize = tokenStringFactory(stream.current());
        return state.tokenize(stream, state);
      }
  
      if (stream.match(operators))
        return 'operator';
  
      if (stream.match(keywords))
        return 'keyword';
  
      if (stream.match(builtins))
        return 'builtin';
  
      if (stream.match(identifiers)) {
        if (state.lastToken != null &&
            state.lastToken.match(/proc|iterator|macro|template|class|converter|func/)) {
          return 'def';
        }
  
        return 'variable';
      }
  
      // Handle non-detected items
      stream.next();
      return ERRORCLASS;
    }
  
    function tokenStringFactory(delimiter) {
      var singleline = delimiter.length == 1;
      var OUTCLASS = 'string';
  
      function tokenString(stream, state) {
        while (!stream.eol()) {
          stream.eatWhile(/[^'"\\]/);
          if (stream.eat('\\')) {
            stream.next();
            if (singleline && stream.eol()) {
                return OUTCLASS;
            }
          } else if (stream.match(delimiter)) {
            state.tokenize = tokenBase;
            return OUTCLASS;
          } else {
            stream.eat(/['"]/);
          }
        }
        if (singleline) {
          if (parserConf.singleLineStringErrors) {
            return ERRORCLASS;
          } else {
            state.tokenize = tokenBase;
          }
        }
        return OUTCLASS;
      }
  
      tokenString.isString = true;
      return tokenString;
    }
  
    function indent(stream, state, type) {
      type = type || 'nim';
      var indentUnit = 0;
      if (type === 'nim') {
        if (state.scopes[0].type !== 'nim') {
          state.scopes[0].offset = stream.indentation();
          return;
        }
        for (var i = 0; i < state.scopes.length; ++i) {
          if (state.scopes[i].type === 'nim') {
            indentUnit = state.scopes[i].offset + conf.indentUnit;
            break;
          }
        }
      } else {
        indentUnit = stream.column() + stream.current().length;
      }
  
      state.scopes.unshift({
        offset: indentUnit,
        type: type
      });
    }
  
    function dedent(stream, state, type) {
        type = type || 'nim';
        if (state.scopes.length == 1) return;
        if (state.scopes[0].type === 'nim') {
            var _indent = stream.indentation();
            var _indent_index = -1;
            for (var i = 0; i < state.scopes.length; ++i) {
                if (_indent === state.scopes[i].offset) {
                    _indent_index = i;
                    break;
                }
            }
            if (_indent_index === -1) {
                return true;
            }
            while (state.scopes[0].offset !== _indent) {
                state.scopes.shift();
            }
            return false;
        } else {
            if (type === 'nim') {
                state.scopes[0].offset = stream.indentation();
                return false;
            } else {
                if (state.scopes[0].type != type) {
                    return true;
                }
                state.scopes.shift();
                return false;
            }
        }
    }
  
    function tokenComment(stream, state) {
      var maybeEnd = false, ch;
      while (ch = stream.next()) {
        if (ch == "#" && maybeEnd) {
          state.tokenize = tokenBase;
          break;
        }
        maybeEnd = (ch == "]");
      }
      return "comment";
    }
  
    function tokenLexer(stream, state) {
      indentInfo = null;
      var style = state.tokenize(stream, state);
      var current = stream.current();
  
      // Handle '.' connected identifiers
      if (current === '.') {
        style = stream.match(identifiers, false) ? null : ERRORCLASS;
        if (style === null && state.lastStyle === 'meta') {
          // Apply 'meta' style to '.' connected identifiers when
          // appropriate.
          style = 'meta';
        }
        return style;
      }
  
      if ((style === 'variable' || style === 'builtin')
          && state.lastStyle === 'meta') {
        style = 'meta';
      }
  
      // Handle scope changes.
      if (current.match(/return|break|continue|raise/) ||
          (current === 'discard' && stream.eol()))
        state.dedent += 1;
  
      if (current === 'lambda' || current === 'proc')
        state.lambda = true;
  
      var delimiter_index = '[({'.indexOf(current);
  
      if (delimiter_index !== -1) {
        indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
      }
      else if(stream.eol() && current.match(/\=|\:|import|include|type|const|var|let/)) {
        indent(stream, state);
      }
  
      if (indentInfo === 'dedent') {
        if (dedent(stream, state)) {
          return ERRORCLASS;
        }
      }
  
      delimiter_index = '])}'.indexOf(current);
      if (delimiter_index !== -1) {
        if (dedent(stream, state, current)) {
          return ERRORCLASS;
        }
      }
  
      if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'nim') {
        if (state.scopes.length > 1) state.scopes.shift();
        state.dedent -= 1;
      }
  
      return style;
    }
  
    var external = {
      startState: function(basecolumn) {
        return {
          tokenize: tokenBase,
          scopes: [{offset:basecolumn || 0, type:'nim'}],
          lastStyle: null,
          lastToken: null,
          lambda: false,
          dedent: 0
        };
      },
  
      token: function(stream, state) {
        var style = tokenLexer(stream, state);
  
        state.lastStyle = style;
  
        var current = stream.current();
        if (current && style)
          state.lastToken = current;
  
        if (stream.eol() && state.lambda)
          state.lambda = false;
  
        return style;
      },
  
      indent: function(state) {
        if (state.tokenize != tokenBase)
          return state.tokenize.isString ? CodeMirror.Pass : 0;
  
        return state.scopes[0].offset;
      },
  
      lineComment: "#",
      blockCommentStart: "#[",
      blockCommentEnd: "]#",
      fold: "indent"
    };
  
    return external;
  });
  
  CodeMirror.defineMIME("text/x-nim", "nim");
  
  console.log('[patch_execute] adding cell metadata to execute request');
  // Need to keep this current with notebook
  kernel.Kernel.prototype.execute = function (code, callbacks, options, metadata) {
    // the patch is just the addition of the "metadata" field to the args 
    // and passing it to shell_message
    var content = {
        code : code,
        silent : true,
        store_history : false,
        user_expressions : {},
        allow_stdin : false
    };
    callbacks = callbacks || {};
    if (callbacks.input !== undefined) {
        content.allow_stdin = true;
    }
    $.extend(true, content, options);
    this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
    return this.send_shell_message("execute_request", content, callbacks, metadata);
  };
  
  codecell.CodeCell.prototype.execute = function (stop_on_error) {
    if (!this.kernel) {
        console.log(i18n.msg._("Can't execute cell since kernel is not set."));
        return;
    }
  
    if (stop_on_error === undefined) {
        if (this.metadata !== undefined && 
                this.metadata.tags !== undefined) {
            if (this.metadata.tags.indexOf('raises-exception') !== -1) {
                stop_on_error = false;
            } else {
                stop_on_error = true;
            }
        } else {
           stop_on_error = true;
        }
    }
  
    this.clear_output(false, true);
    var old_msg_id = this.last_msg_id;
    if (old_msg_id) {
        this.kernel.clear_callbacks_for_msg(old_msg_id);
        delete codecell.CodeCell.msg_cells[old_msg_id];
        this.last_msg_id = null;
    }
    if (this.get_text().trim().length === 0) {
        // nothing to do
        this.set_input_prompt(null);
        return;
    }
    this.set_input_prompt('*');
    this.element.addClass("running");
    var callbacks = this.get_callbacks();
  
    // Patch: add cellId to metadata to match JupyterLab
    var split_uuid = i=>i.substr(0,8)+"-"+i.substr(8,4)+"-"+i.substr(12,4)+"-"+i.substr(16,4)+"-"+i.substr(20);
    this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
        stop_on_error : stop_on_error}, {cellId: split_uuid(this.cell_id) });
    // end patch
    codecell.CodeCell.msg_cells[this.last_msg_id] = this;
    this.render();
    this.events.trigger('execute.CodeCell', {cell: this});
    var that = this;
    function handleFinished(evt, data) {
        if (that.kernel.id === data.kernel.id && that.last_msg_id === data.msg_id) {
                that.events.trigger('finished_execute.CodeCell', {cell: that});
            that.events.off('finished_iopub.Kernel', handleFinished);
          }
    }
    this.events.on('finished_iopub.Kernel', handleFinished);
  };
  
  console.log('[patch_execute] done');
  
  }}});
  