From deb2084fc5da975b1d571f26412bb351c39a42d6 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 7 Mar 2016 12:00:45 +0100 Subject: [PATCH 01/65] Add the socket connection checker --- www/common/netflux.js | 35 +++++++++---------------- www/common/realtime-input.js | 51 ++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 47 deletions(-) diff --git a/www/common/netflux.js b/www/common/netflux.js index 1d0e9c809..6919a72cd 100644 --- a/www/common/netflux.js +++ b/www/common/netflux.js @@ -1025,12 +1025,6 @@ return /******/ (function(modules) { // webpackBootstrap this.protocol = _ServiceProvider2.default.get(cs.EXCHANGEPROTOCOL_SERVICE); this.defaults = { signaling: 'ws://localhost:9000', - /** - * If an error is encountered but it is recoverable, do not immediately fail - * but if it keeps firing errors over and over, do fail. - */ - recoverableErrorCount: 0, - MAX_RECOVERABLE_ERRORS: 15, // Maximum number of milliseconds of lag before we fail the connection. MAX_LAG_BEFORE_DISCONNECT: 20000 }; @@ -1040,6 +1034,8 @@ return /******/ (function(modules) { // webpackBootstrap _createClass(WebSocketService, [{ key: 'join', value: function join(key) { + var _this = this; + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var settings = Object.assign({}, this.settings, options); @@ -1051,24 +1047,19 @@ return /******/ (function(modules) { // webpackBootstrap resolve(socket); }; socket.onerror = reject; - }); - } - - // Check the status of the socket connection - /*var isSocketDisconnected = function (realtime) { - let sock = ws._socket; - return sock.readyState === sock.CLOSING - || sock.readyState === sock.CLOSED - || (realtime.getLag().waiting && realtime.getLag().lag > MAX_LAG_BEFORE_DISCONNECT); - } - var checkSocket = module.exports.checkSocket = function (realtime) { - if (isSocketDisconnected(realtime) && !socket.intentionallyClosing) { + // Check the status of the socket connection + var isSocketDisconnected = function isSocketDisconnected(realtime, sock) { + return sock.readyState === sock.CLOSING || sock.readyState === sock.CLOSED || realtime.getLag().waiting && realtime.getLag().lag > _this.settings.MAX_LAG_BEFORE_DISCONNECT; + }; + socket.checkSocket = function (realtime) { + if (isSocketDisconnected(realtime, socket) && !socket.intentionallyClosing) { return true; - } else { + } else { return false; - } - };*/ - + } + }; + }); + } }]); return WebSocketService; diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index ecd19e015..08473e3f8 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -31,7 +31,7 @@ define([ var debug = function (x) { console.log(x); }, warn = function (x) { console.error(x); }, verbose = function (x) { console.log(x); }; - // verbose = function () {}; // comment out to enable verbose logging + verbose = function () {}; // comment out to enable verbose logging // ------------------ Trapping Keyboard Events ---------------------- // @@ -130,7 +130,7 @@ define([ }); // Check the connection to the channel - //checkConnection(wc); + checkConnection(wc); bindAllEvents(textarea, doc, onEvent, false); @@ -202,31 +202,32 @@ define([ } var checkConnection = function(wc) { - //TODO - /*var socketChecker = setInterval(function () { - if (netflux.checkSocket(realtime)) { - warn("Socket disconnected!"); - - recoverableErrorCount += 1; - - if (recoverableErrorCount >= MAX_RECOVERABLE_ERRORS) { - warn("Giving up!"); - realtime.abort(); - wc.leave() - .then(null, function(err) { - warn(err); - }); - if (config.onAbort) { - config.onAbort({ - socket: socket - }); + if(wc.channels && wc.channels.size > 0) { + var channels = Array.from(wc.channels); + var channel = channels[0]; + + var socketChecker = setInterval(function () { + if (channel.checkSocket(realtime)) { + warn("Socket disconnected!"); + + recoverableErrorCount += 1; + + if (recoverableErrorCount >= MAX_RECOVERABLE_ERRORS) { + warn("Giving up!"); + realtime.abort(); + try { channel.close(); } catch (e) { warn(e); } + if (config.onAbort) { + config.onAbort({ + socket: channel + }); + } + if (socketChecker) { clearInterval(socketChecker); } } - if (socketChecker) { clearInterval(socketChecker); } + } else { + // it's working as expected, continue } - } else { - // it's working as expected, continue - } - }, 200);*/ + }, 200); + } } return { From 60c3aceb0dc1a4887d92a9bde6bb3befc4b657f3 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 7 Mar 2016 17:18:47 +0100 Subject: [PATCH 02/65] Add chatflux server --- www/common/netflux.js | 112 ++++++++++++++++------------------- www/common/realtime-input.js | 84 +++++++++++++++++++------- 2 files changed, 113 insertions(+), 83 deletions(-) diff --git a/www/common/netflux.js b/www/common/netflux.js index 6919a72cd..5764ffd7c 100644 --- a/www/common/netflux.js +++ b/www/common/netflux.js @@ -129,9 +129,6 @@ return /******/ (function(modules) { // webpackBootstrap webChannel.onopen = function () { resolve(webChannel); }; - if (settings.openWebChannel && settings.openWebChannel === true) { - webChannel.onopen(); - } }); }); } @@ -215,8 +212,6 @@ return /******/ (function(modules) { // webpackBootstrap this.protocol = cs.EXCHANGEPROTOCOL_SERVICE; // Public attributes - this.topology = this.settings.topology; - this.topologyService = _ServiceProvider2.default.get(this.topology); this.id; this.myID = this._generateID(); this.channels = new Set(); @@ -290,7 +285,7 @@ return /******/ (function(modules) { // webpackBootstrap this.topologyService = _ServiceProvider2.default.get(topologyServiceName); }, get: function get() { - return this.settings.topology; + return this.settigns.topology; } }]); @@ -619,7 +614,8 @@ return /******/ (function(modules) { // webpackBootstrap for (var _iterator = webChannel.channels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var c = _step.value; - c.send(data); + var msg = JSON.stringify([c.seq++, data.type, webChannel.id, data.msg]); + c.send(msg); } } catch (err) { _didIteratorError = true; @@ -647,7 +643,8 @@ return /******/ (function(modules) { // webpackBootstrap for (var _iterator2 = webChannel.channels[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var c = _step2.value; - c.send(data); + var msg = JSON.stringify([c.seq++, data.type, id, data.msg]); + c.send(msg); } } catch (err) { _didIteratorError2 = true; @@ -1024,9 +1021,7 @@ return /******/ (function(modules) { // webpackBootstrap this.NAME = this.constructor.name; this.protocol = _ServiceProvider2.default.get(cs.EXCHANGEPROTOCOL_SERVICE); this.defaults = { - signaling: 'ws://localhost:9000', - // Maximum number of milliseconds of lag before we fail the connection. - MAX_LAG_BEFORE_DISCONNECT: 20000 + signaling: 'ws://localhost:9000' }; this.settings = Object.assign({}, this.defaults, options); } @@ -1034,30 +1029,23 @@ return /******/ (function(modules) { // webpackBootstrap _createClass(WebSocketService, [{ key: 'join', value: function join(key) { - var _this = this; - var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var settings = Object.assign({}, this.settings, options); return new Promise(function (resolve, reject) { var connection = undefined; var socket = new window.WebSocket(settings.signaling); + socket.seq = 1; socket.facade = options.facade || null; socket.onopen = function () { - resolve(socket); - }; - socket.onerror = reject; - // Check the status of the socket connection - var isSocketDisconnected = function isSocketDisconnected(realtime, sock) { - return sock.readyState === sock.CLOSING || sock.readyState === sock.CLOSED || realtime.getLag().waiting && realtime.getLag().lag > _this.settings.MAX_LAG_BEFORE_DISCONNECT; - }; - socket.checkSocket = function (realtime) { - if (isSocketDisconnected(realtime, socket) && !socket.intentionallyClosing) { - return true; + if (key && key !== '') { + socket.send(JSON.stringify([socket.seq++, 'JOIN', key])); } else { - return false; + socket.send(JSON.stringify([socket.seq++, 'JOIN'])); } + resolve(socket); }; + socket.onerror = reject; }); } }]); @@ -1202,18 +1190,13 @@ return /******/ (function(modules) { // webpackBootstrap _createClass(WebSocketProtocolService, [{ key: 'onmessage', value: function onmessage(e) { - var msg = e.data; + var msg = JSON.parse(e.data); var socket = e.currentTarget; var webChannel = socket.webChannel; var topology = cs.STAR_SERVICE; - webChannel.topology = topology; - webChannel.topologyService = _ServiceProvider2.default.get(topology); - webChannel.onMessage('', msg); - - /* - let topology = cs.STAR_SERVICE - let topologyService = ServiceProvider.get(topology) - if (msg[0] !== 0) { + var topologyService = _ServiceProvider2.default.get(topology); + + if (msg[0] !== 0) { return; } if (msg[1] === 'IDENT') { @@ -1227,56 +1210,65 @@ return /******/ (function(modules) { // webpackBootstrap socket.send(JSON.stringify(msg)); return; } - if (msg[2] === 'MSG') { - } + if (msg[2] === 'MSG') {} // We have received a new direct message from another user if (msg[2] === 'MSG' && msg[3] === socket.uid) { // Find the peer exists in one of our channels or create a new one - if(typeof socket.facade._onPeerMessage === "function") - socket.facade._onPeerMessage(msg[1], msg); + if (typeof socket.facade._onPeerMessage === "function") socket.facade._onPeerMessage(msg[1], msg); } if (msg[2] === 'JOIN' && (webChannel.id == null || webChannel.id === msg[3])) { - if(!webChannel.id) { // New unnamed channel : get its name from the first "JOIN" message - var chanName = window.location.hash = msg[3]; - webChannel.id = chanName; + if (!webChannel.id) { + // New unnamed channel : get its name from the first "JOIN" message + if (!window.location.hash) { + var chanName = window.location.hash = msg[3]; + } + webChannel.id = msg[3]; } - if (msg[1] === socket.uid) { // If the user catches himself registering, he is synchronized with the server + + if (msg[1] === socket.uid) { + // If the user catches himself registering, he is synchronized with the server webChannel.onopen(); - } - else { // Trigger onJoining() when another user is joining the channel - // Register the user in the list of peers in the channel - var linkQuality = (msg[1] === '_HISTORY_KEEPER_') ? 1000 : 0; - var sendToPeer = function(data) { - topologyService.sendTo(msg[1], webChannel, {type : 'MSG', msg: data}); - } - var peer = {id: msg[1], connector: socket, linkQuality: linkQuality, send: sendToPeer}; - if(webChannel.peers.indexOf(peer) === -1) { + } else { + // Trigger onJoining() when another user is joining the channel + + // Register the user in the list of peers in the channel + var linkQuality = msg[1] === '_HISTORY_KEEPER_' ? 1000 : 0; + var sendToPeer = function sendToPeer(data) { + topologyService.sendTo(msg[1], webChannel, { type: 'MSG', msg: data }); + }; + var peer = { id: msg[1], connector: socket, linkQuality: linkQuality, send: sendToPeer }; + if (webChannel.peers.indexOf(peer) === -1) { webChannel.peers.push(peer); } - if(typeof webChannel.onJoining === "function") - webChannel.onJoining(msg[1]); + + if (typeof webChannel.onJoining === "function") webChannel.onJoining(msg[1]); } } - // We have received a new message in that channel + // We have received a new message in that channel from another peer if (msg[2] === 'MSG' && msg[3] === webChannel.id) { // Find the peer who sent the message and display it //TODO Use Peer instead of peer.id (msg[1]) : - if(typeof webChannel.onMessage === "function") - + if (typeof webChannel.onMessage === "function") webChannel.onMessage(msg[1], msg[4]); } // Someone else has left the channel, remove him from the list of peers if (msg[2] === 'LEAVE' && msg[3] === webChannel.id) { //TODO Use Peer instead of peer.id (msg[1]) : - if(typeof webChannel.onLeaving === "function") - webChannel.onLeaving(msg[1], webChannel); + if (typeof webChannel.onLeaving === "function") webChannel.onLeaving(msg[1], webChannel); } - */ } }, { key: 'message', value: function message(code, data) { - // The message is already prepared and encrypted - return data.data; + var type = undefined; + switch (code) { + case cs.USER_DATA: + type = 'MSG'; + break; + case cs.JOIN_START: + type = 'JOIN'; + break; + } + return { type: type, msg: data.data }; } }]); diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index 08473e3f8..0f77f421c 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -101,9 +101,20 @@ define([ var initializing = true; var bump = function () {}; + + var onPeerMessage = function (peer, msg) { + if(peer === '_HISTORY_KEEPER_') { + var msgHistory = JSON.parse(msg[4]); + onMessage(msgHistory[1], msgHistory[4]); + } + else { + warn('Illegal direct message'); + } + }; var options = { - signaling: websocketUrl, + // signaling: websocketUrl, + signaling: 'ws://localhost:9000', topology: 'StarTopologyService', protocol: 'WebSocketProtocolService', connector: 'WebSocketService', @@ -111,26 +122,42 @@ define([ }; var realtime; + // Add the Facade's peer messages handler + Netflux._onPeerMessage = onPeerMessage; // Connect to the WebSocket server Netflux.join(channel, options).then(function(wc) { + wc.onMessage = onMessage; // On receiving message wc.onJoining = onJoining; // On user joining the session // Open a Chainpad session realtime = createRealtime(); - realtime.onUserListChange(function (userList) { - var opt = {userList : userList}; - // TODO : onJoining should only a a "newPeer" parameter - wc.onJoining(opt); - }); + + // we're fully synced + initializing = false; + + // execute an onReady callback if one was supplied + if (config.onReady) { + config.onReady(); + } + // On sending message realtime.onMessage(function(message) { + // Do not send authentication messages since it is handled by Netflux + var parsed = parseMessage(message); + if (parsed.content[0] !== 0) { message = Crypto.encrypt(message, cryptKey); wc.send(message); + } }); + + // Get the channel history + var hc; + wc.peers.forEach(function (p) { if (!hc || p.linkQuality > hc.linkQuality) { hc = p; } }); + hc.send(JSON.stringify(['GET_HISTORY', wc.id])); // Check the connection to the channel - checkConnection(wc); + //checkConnection(wc); bindAllEvents(textarea, doc, onEvent, false); @@ -156,8 +183,11 @@ define([ return '\\' +c; })); - var onMessage = function(user, message) { + var onMessage = function(peer, msg) { + // remove the password + var passLen = msg.substring(0,msg.indexOf(':')); + var message = msg.substring(passLen.length+1 + Number(passLen)); message = Crypto.decrypt(message, cryptKey); verbose(message); @@ -183,22 +213,13 @@ define([ } } } - var onJoining = function(optionnalData) { - var userList = optionnalData.userList || []; - if (!initializing || userList.indexOf(userName) === -1) { - return; - } - // if we spot ourselves being added to the document, we'll switch - // 'initializing' off because it means we're fully synced. - initializing = false; + + var onJoining = function(peer, channel) { + + } + + var onLeaving = function(peer, channel) { - // execute an onReady callback if one was supplied - // pass an object so we can extend this later - if (config.onReady) { - config.onReady({ - userList: userList - }); - } } var checkConnection = function(wc) { @@ -230,6 +251,23 @@ define([ } } + var parseMessage = function (msg) { + var res ={}; + // two or more? use a for + ['pass','user','channelId','content'].forEach(function(attr){ + var len=msg.slice(0,msg.indexOf(':')), + // taking an offset lets us slice out the prop + // and saves us one string copy + o=len.length+1, + prop=res[attr]=msg.slice(o,Number(len)+o); + // slice off the property and its descriptor + msg = msg.slice(prop.length+o); + }); + // content is the only attribute that's not a string + res.content=JSON.parse(res.content); + return res; + }; + return { onEvent: function () { onEvent(); From 09a06a8bc5d26ef4f7c96ada279a2acd9bcc8c49 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 7 Mar 2016 17:35:31 +0100 Subject: [PATCH 03/65] Replace the chainpad server by the netflux server --- NetFluxWebsocketServer.js | 186 +++++++++++++++++++++++++++++++++++ server.js | 4 +- www/common/realtime-input.js | 4 +- 3 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 NetFluxWebsocketServer.js diff --git a/NetFluxWebsocketServer.js b/NetFluxWebsocketServer.js new file mode 100644 index 000000000..3ffb96fe7 --- /dev/null +++ b/NetFluxWebsocketServer.js @@ -0,0 +1,186 @@ +;(function () { 'use strict'; +let Crypto = require('crypto'); +let WebSocket = require('ws'); + +let LAG_MAX_BEFORE_DISCONNECT = 30000; +let LAG_MAX_BEFORE_PING = 15000; +let HISTORY_KEEPER_ID = "_HISTORY_KEEPER_"; + +let dropUser; + +let now = function () { return (new Date()).getTime(); }; + +let sendMsg = function (ctx, user, msg) { + try { + console.log('<' + JSON.stringify(msg)); + user.socket.send(JSON.stringify(msg)); + } catch (e) { + console.log(e.stack); + dropUser(ctx, user); + } +}; + +let sendChannelMessage = function (ctx, channel, msgStruct) { + msgStruct.unshift(0); + channel.forEach(function (user) { sendMsg(ctx, user, msgStruct); }); + if (msgStruct[2] === 'MSG') { + ctx.store.message(channel.id, JSON.stringify(msgStruct), function () { }); + } +}; + +dropUser = function (ctx, user) { + if (user.socket.readyState !== WebSocket.CLOSING + && user.socket.readyState !== WebSocket.CLOSED) + { + try { + user.socket.close(); + } catch (e) { + console.log("Failed to disconnect ["+user.id+"], attempting to terminate"); + try { + user.socket.terminate(); + } catch (ee) { + console.log("Failed to terminate ["+user.id+"] *shrug*"); + } + } + } + delete ctx.users[user.id]; + Object.keys(ctx.channels).forEach(function (chanName) { + let chan = ctx.channels[chanName]; + let idx = chan.indexOf(user); + if (idx < 0) { return; } + console.log("Removing ["+user.id+"] from channel ["+chanName+"]"); + chan.splice(idx, 1); + if (chan.length === 0) { + console.log("Removing empty channel ["+chanName+"]"); + delete ctx.channels[chanName]; + } else { + sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chanName, 'Quit: [ dropUser() ]']); + } + }); +}; + +let getHistory = function (ctx, channelName, handler) { + ctx.store.getMessages(channelName, function (msgStr) { handler(JSON.parse(msgStr)); }); +}; + +let randName = function () { return Crypto.randomBytes(16).toString('hex'); }; + +let handleMessage = function (ctx, user, msg) { + let json = JSON.parse(msg); + let seq = json.shift(); + let cmd = json[0]; + let obj = json[1]; + + user.timeOfLastMessage = now(); + user.pingOutstanding = false; + + if (cmd === 'JOIN') { + /*if (obj && obj.length !== 32) { + sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]); + return; + }*/ + let chanName = obj || randName(); + let chan = ctx.channels[chanName] = ctx.channels[chanName] || []; + chan.id = chanName; + sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'JOIN', chanName]); + chan.forEach(function (u) { sendMsg(ctx, user, [0, u.id, 'JOIN', chanName]); }); + chan.push(user); + sendChannelMessage(ctx, chan, [user.id, 'JOIN', chanName]); + return; + } + if (cmd === 'MSG') { + if (obj === HISTORY_KEEPER_ID) { + let parsed; + try { parsed = JSON.parse(json[2]); } catch (err) { return; } + if (parsed[0] === 'GET_HISTORY') { + console.log('getHistory ' + parsed[1]); + getHistory(ctx, parsed[1], function (msg) { + sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)]); + }); + } + return; + } + if (obj && !ctx.channels[obj] && !ctx.users[obj]) { + sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]); + return; + } + let target; + json.unshift(user.id); + if ((target = ctx.channels[obj])) { + sendChannelMessage(ctx, target, json); + return; + } + if ((target = ctx.users[obj])) { + json.unshift(0); + sendMsg(ctx, target, json); + return; + } + } + if (cmd === 'LEAVE') { + let err; + let chan; + let idx; + if (!obj) { err = 'EINVAL'; } + if (!err && !(chan = ctx.channels[obj])) { err = 'ENOENT'; } + if (!err && (idx = chan.indexOf(user)) === -1) { err = 'NOT_IN_CHAN'; } + if (err) { + sendMsg(ctx, user, [seq, 'ERROR', err]); + return; + } + json.unshift(user.id); + sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chan.id]); + chan.splice(idx, 1); + } + if (cmd === 'PING') { + sendMsg(ctx, user, [seq, 'PONG', obj]); + return; + } +}; + +let run = module.exports.run = function (storage, socketServer) { + let ctx = { + users: {}, + channels: {}, + store: storage + }; + setInterval(function () { + Object.keys(ctx.users).forEach(function (userId) { + let u = ctx.users[userId]; + if (now() - u.timeOfLastMessage > LAG_MAX_BEFORE_DISCONNECT) { + dropUser(ctx, u); + } else if (!u.pingOutstanding && now() - u.timeOfLastMessage > LAG_MAX_BEFORE_PING) { + sendMsg(ctx, u, [0, 'PING', now()]); + u.pingOutstanding = true; + } + }); + }, 5000); + socketServer.on('connection', function(socket) { + let conn = socket.upgradeReq.connection; + let user = { + addr: conn.remoteAddress + '|' + conn.remotePort, + socket: socket, + id: randName(), + timeOfLastMessage: now(), + pingOutstanding: false + }; + ctx.users[user.id] = user; + sendMsg(ctx, user, [0, 'IDENT', user.id]); + socket.on('message', function(message) { + console.log('>'+message); + try { + handleMessage(ctx, user, message); + } catch (e) { + console.log(e.stack); + dropUser(ctx, user); + } + }); + socket.on('close', function (evt) { + for (let userId in ctx.users) { + if (ctx.users[userId].socket === socket) { + dropUser(ctx, ctx.users[userId]); + } + } + }); + }); +}; +}()); diff --git a/server.js b/server.js index 3a7c49af4..3bfacb792 100644 --- a/server.js +++ b/server.js @@ -7,6 +7,7 @@ var Https = require('https'); var Fs = require('fs'); var WebSocketServer = require('ws').Server; var ChainPadSrv = require('./ChainPadSrv'); +var NetfluxSrv = require('./NetFluxWebsocketServer'); var config = require('./config'); config.websocketPort = config.websocketPort || config.httpPort; @@ -79,5 +80,6 @@ if (config.websocketPort !== config.httpPort) { var wsSrv = new WebSocketServer(wsConfig); Storage.create(config, function (store) { console.log('DB connected'); - ChainPadSrv.create(wsSrv, store); + // ChainPadSrv.create(wsSrv, store); + NetfluxSrv.run(store, wsSrv); }); diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index 0f77f421c..6599512b8 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -113,8 +113,8 @@ define([ }; var options = { - // signaling: websocketUrl, - signaling: 'ws://localhost:9000', + signaling: websocketUrl, + // signaling: 'ws://localhost:9000', topology: 'StarTopologyService', protocol: 'WebSocketProtocolService', connector: 'WebSocketService', From c4f62fb8124f5107eb623948a12560202997793a Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Tue, 8 Mar 2016 11:25:37 +0100 Subject: [PATCH 04/65] First try with WebRTC --- www/common/netflux.js | 9 ++- www/common/realtime-input.js | 118 +++++++++++++++++++++-------------- 2 files changed, 79 insertions(+), 48 deletions(-) diff --git a/www/common/netflux.js b/www/common/netflux.js index 5764ffd7c..5252a4767 100644 --- a/www/common/netflux.js +++ b/www/common/netflux.js @@ -254,7 +254,7 @@ return /******/ (function(modules) { // webpackBootstrap _this.topologyService.broadcast(_this, protocol.message(cs.JOIN_FINISH, id)); _this.onJoining(id); }); - }).then(function (data) { + }, settings).then(function (data) { return data; }); } @@ -747,6 +747,7 @@ return /******/ (function(modules) { // webpackBootstrap return new Promise(function (resolve, reject) { var connections = []; + console.log(settings); var socket = new window.WebSocket(settings.signaling); socket.onopen = function () { socket.send(JSON.stringify({ key: settings.key })); @@ -804,8 +805,10 @@ return /******/ (function(modules) { // webpackBootstrap return new Promise(function (resolve, reject) { var connection = undefined; var socket = new window.WebSocket(settings.signaling); + console.log('Socket created'); socket.onopen = function () { connection = new _this2.RTCPeerConnection(settings.webRTCOptions); + console.log('RTC created'); connection.onicecandidate = function (e) { if (e.candidate !== null) { var candidate = { @@ -816,6 +819,8 @@ return /******/ (function(modules) { // webpackBootstrap } }; var dc = connection.createDataChannel(key); + console.log('data channel created'); + console.log(dc); dc.onopen = function () { resolve(dc); }; @@ -827,6 +832,8 @@ return /******/ (function(modules) { // webpackBootstrap }; socket.onmessage = function (e) { var msg = JSON.parse(e.data); + console.log('message'); + console.log(msg); if (Reflect.has(msg, 'data')) { if (Reflect.has(msg.data, 'answer')) { var sd = Object.assign(new _this2.RTCSessionDescription(), msg.data.answer); diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index 6599512b8..787e66acb 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -113,61 +113,85 @@ define([ }; var options = { - signaling: websocketUrl, - // signaling: 'ws://localhost:9000', - topology: 'StarTopologyService', - protocol: 'WebSocketProtocolService', - connector: 'WebSocketService', - openWebChannel: true + // signaling: websocketUrl, + signaling: 'ws://localhost:8000', + key: channel + // topology: 'StarTopologyService', + // protocol: 'WebSocketProtocolService', + // connector: 'WebSocketService', + // openWebChannel: true }; + console.log(options); var realtime; // Add the Facade's peer messages handler Netflux._onPeerMessage = onPeerMessage; - // Connect to the WebSocket server - Netflux.join(channel, options).then(function(wc) { - - wc.onMessage = onMessage; // On receiving message - wc.onJoining = onJoining; // On user joining the session - - // Open a Chainpad session - realtime = createRealtime(); - - // we're fully synced - initializing = false; - - // execute an onReady callback if one was supplied - if (config.onReady) { - config.onReady(); - } - - // On sending message - realtime.onMessage(function(message) { - // Do not send authentication messages since it is handled by Netflux - var parsed = parseMessage(message); - if (parsed.content[0] !== 0) { - message = Crypto.encrypt(message, cryptKey); - wc.send(message); + + var webchannel = Netflux.create(); + webchannel.openForJoining(options).then(function(data) { + console.log('keys'); + console.log(channel); + console.log(data); + webchannel.onmessage = onMessage; // On receiving message + webchannel.onJoining = onJoining; // On user joining the session + webchannel.onLeaving = onLeaving; // On user leaving the session + + // console.log('resolved'); + + onOpen(); + + }, function(err) { + console.log('rejected'); + console.error(err); + }); + + var onOpen = function() { + // Connect to the WebSocket server + Netflux.join(channel, options).then(function(wc) { + + wc.onmessage = onMessage; // On receiving message + wc.onJoining = onJoining; // On user joining the session + wc.onLeaving = onLeaving; // On user leaving the session + + // Open a Chainpad session + realtime = createRealtime(); + + // we're fully synced + initializing = false; + + // execute an onReady callback if one was supplied + if (config.onReady) { + config.onReady(); } - }); - - // Get the channel history - var hc; - wc.peers.forEach(function (p) { if (!hc || p.linkQuality > hc.linkQuality) { hc = p; } }); - hc.send(JSON.stringify(['GET_HISTORY', wc.id])); + + // On sending message + realtime.onMessage(function(message) { + // Do not send authentication messages since it is handled by Netflux + var parsed = parseMessage(message); + if (parsed.content[0] !== 0) { + message = Crypto.encrypt(message, cryptKey); + wc.send(message); + } + }); + + // Get the channel history + // var hc; + // wc.peers.forEach(function (p) { if (!hc || p.linkQuality > hc.linkQuality) { hc = p; } }); + // hc.send(JSON.stringify(['GET_HISTORY', wc.id])); - // Check the connection to the channel - //checkConnection(wc); + // Check the connection to the channel + //checkConnection(wc); - bindAllEvents(textarea, doc, onEvent, false); + bindAllEvents(textarea, doc, onEvent, false); - sharejs.attach(textarea, realtime); - bump = realtime.bumpSharejs; + sharejs.attach(textarea, realtime); + bump = realtime.bumpSharejs; - realtime.start(); - }, function(error) { - warn(error); - }); + realtime.start(); + }, function(error) { + warn(error); + }); + } var createRealtime = function() { return ChainPad.create(userName, @@ -215,11 +239,11 @@ define([ } var onJoining = function(peer, channel) { - + console.log('Someone joined : '+peer) } var onLeaving = function(peer, channel) { - + console.log('Someone left : '+peer) } var checkConnection = function(wc) { From 9f682a985b395e46c1d861fdd57fd46db4e46d9e Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Tue, 8 Mar 2016 11:43:25 +0100 Subject: [PATCH 05/65] lvl should not fail on non-existant channels --- storage/lvl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/lvl.js b/storage/lvl.js index 84ff57862..1ddfbe97c 100644 --- a/storage/lvl.js +++ b/storage/lvl.js @@ -38,7 +38,7 @@ var getMessages = function (db, channelName, msgHandler) { if (i < index) { again(i+1); } })); }; - again(0); + if (index > -1) { again(0); } }); }; From 870b2dbb7eaa021851cbadcd2e3448b931c532a1 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Tue, 8 Mar 2016 11:45:03 +0100 Subject: [PATCH 06/65] Add the WebRTC server in Cryptpad --- WebRTCSrv.js | 68 ++++++++++++++++++++++++++++++++++++ server.js | 4 ++- www/common/realtime-input.js | 4 +-- 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 WebRTCSrv.js diff --git a/WebRTCSrv.js b/WebRTCSrv.js new file mode 100644 index 000000000..06dfd61f4 --- /dev/null +++ b/WebRTCSrv.js @@ -0,0 +1,68 @@ +'use strict' +let WebSocketServer = require('ws').Server +const PORT = 8000 +const UNSUPPORTED_DATA = 1007 +const POLICY_VIOLATION = 1008 +const CLOSE_UNSUPPORTED = 1003 + +// let server = new WebSocketServer({port: PORT}, () => { + // console.log('Server runs on: ws://localhost:' + PORT) +// }) + +var run = module.exports.run = function(storage, server) { + server.on('connection', (socket) => { + socket.on('message', (data) => { + try { + let msg = JSON.parse(data) + console.log(msg); + if (msg.hasOwnProperty('key')) { + for (let master of server.clients) { + if (master.key === msg.key) { + socket.close(POLICY_VIOLATION, 'The key already exists') + console.log('ERROR key exists'); + return + } + } + socket.key = msg.key + socket.joiningClients = [] + } else if (msg.hasOwnProperty('id')) { + for (let index in socket.joiningClients) { + if (index == msg.id) { + socket.joiningClients[index].send(JSON.stringify({data: msg.data})) + return + } + } + socket.close(POLICY_VIOLATION, 'Unknown id') + } else if (msg.hasOwnProperty('join')) { + for (let master of server.clients) { + if (master.key === msg.join) { + console.log('joined'); + socket.master = master + master.joiningClients.push(socket) + let id = master.joiningClients.length - 1 + master.send(JSON.stringify({id, data: msg.data})) + return + } + } + console.log('ERROR unknown key'); + socket.close(POLICY_VIOLATION, 'Unknown key') + } else if (msg.hasOwnProperty('data') && socket.hasOwnProperty('master')) { + let id = socket.master.joiningClients.indexOf(socket) + socket.master.send(JSON.stringify({id, data: msg.data})) + } else { + socket.close(UNSUPPORTED_DATA, 'Unsupported message format') + } + } catch (event) { + socket.close(CLOSE_UNSUPPORTED, 'Server accepts only JSON') + } + }) + + socket.on('close', (event) => { + if (socket.hasOwnProperty('joiningClients')) { + for (let client of socket.joiningClients) { + client.close(POLICY_VIOLATION, 'The peer is no longer available') + } + } + }) + }) +} \ No newline at end of file diff --git a/server.js b/server.js index 3bfacb792..538efce28 100644 --- a/server.js +++ b/server.js @@ -8,6 +8,7 @@ var Fs = require('fs'); var WebSocketServer = require('ws').Server; var ChainPadSrv = require('./ChainPadSrv'); var NetfluxSrv = require('./NetFluxWebsocketServer'); +var WebRTCSrv = require('./WebRTCSrv'); var config = require('./config'); config.websocketPort = config.websocketPort || config.httpPort; @@ -81,5 +82,6 @@ var wsSrv = new WebSocketServer(wsConfig); Storage.create(config, function (store) { console.log('DB connected'); // ChainPadSrv.create(wsSrv, store); - NetfluxSrv.run(store, wsSrv); + // NetfluxSrv.run(store, wsSrv); + WebRTCSrv.run(store, wsSrv); }); diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index 787e66acb..17eb724f5 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -113,8 +113,8 @@ define([ }; var options = { - // signaling: websocketUrl, - signaling: 'ws://localhost:8000', + signaling: websocketUrl, + // signaling: 'ws://localhost:8000', key: channel // topology: 'StarTopologyService', // protocol: 'WebSocketProtocolService', From c536ecbc1ce01b69f7692bd97c77d2b84c311ca4 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Tue, 8 Mar 2016 15:13:57 +0100 Subject: [PATCH 07/65] Temp commit --- WebRTCSrv.js | 4 --- www/common/netflux.js | 2 ++ www/common/realtime-input.js | 53 +++++++++++++++++++++++++++--------- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/WebRTCSrv.js b/WebRTCSrv.js index 06dfd61f4..e3e0a3c42 100644 --- a/WebRTCSrv.js +++ b/WebRTCSrv.js @@ -5,10 +5,6 @@ const UNSUPPORTED_DATA = 1007 const POLICY_VIOLATION = 1008 const CLOSE_UNSUPPORTED = 1003 -// let server = new WebSocketServer({port: PORT}, () => { - // console.log('Server runs on: ws://localhost:' + PORT) -// }) - var run = module.exports.run = function(storage, server) { server.on('connection', (socket) => { socket.on('message', (data) => { diff --git a/www/common/netflux.js b/www/common/netflux.js index 5252a4767..d63470bba 100644 --- a/www/common/netflux.js +++ b/www/common/netflux.js @@ -500,6 +500,7 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: 'broadcast', value: function broadcast(webChannel, data) { + console.log(data); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; @@ -508,6 +509,7 @@ return /******/ (function(modules) { // webpackBootstrap for (var _iterator = webChannel.channels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var c = _step.value; + console.log(c); c.send(data); } } catch (err) { diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index 17eb724f5..ebddde624 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -127,27 +127,46 @@ define([ // Add the Facade's peer messages handler Netflux._onPeerMessage = onPeerMessage; + function getParameterByName(name, url) { + if (!url) url = window.location.href; + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); + } + + if(getParameterByName("server")) { + console.log('SERVER'); + console.log(channel); var webchannel = Netflux.create(); webchannel.openForJoining(options).then(function(data) { - console.log('keys'); - console.log(channel); - console.log(data); - webchannel.onmessage = onMessage; // On receiving message - webchannel.onJoining = onJoining; // On user joining the session - webchannel.onLeaving = onLeaving; // On user leaving the session // console.log('resolved'); - onOpen(); + onOpen(webchannel); }, function(err) { console.log('rejected'); console.error(err); }); - - var onOpen = function() { + } + else { + console.log('CLIENT'); + console.log(channel); // Connect to the WebSocket server Netflux.join(channel, options).then(function(wc) { + onOpen(wc); + }, function(error) { + warn(error); + }); + } + + var onOpen = function(wc) { + + console.log('joined the channel'); + console.log(wc.myID); wc.onmessage = onMessage; // On receiving message wc.onJoining = onJoining; // On user joining the session @@ -166,12 +185,16 @@ define([ // On sending message realtime.onMessage(function(message) { + // TODO: put in ChaindpadAdapter // Do not send authentication messages since it is handled by Netflux var parsed = parseMessage(message); if (parsed.content[0] !== 0) { + console.log('ENVOI '+message); message = Crypto.encrypt(message, cryptKey); wc.send(message); + onMessage('', message); } + // END-TODO }); // Get the channel history @@ -188,9 +211,7 @@ define([ bump = realtime.bumpSharejs; realtime.start(); - }, function(error) { - warn(error); - }); + } var createRealtime = function() { @@ -208,12 +229,18 @@ define([ })); var onMessage = function(peer, msg) { + + // TODO : put in ChainpadAdapter // remove the password var passLen = msg.substring(0,msg.indexOf(':')); var message = msg.substring(passLen.length+1 + Number(passLen)); + message = Crypto.decrypt(message, cryptKey); - + console.log('RECOIS '+message); + + // END-TODO ChainpadAdapter + verbose(message); allMessages.push(message); if (!initializing) { From b7885eb5396831f686a1f157c29da365f94fed26 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Wed, 9 Mar 2016 18:35:39 +0100 Subject: [PATCH 08/65] Fix channel history --- WebRTCSrv.js | 27 ++++++- www/common/netflux.js | 139 +++++++++++++++++++++++------------ www/common/realtime-input.js | 42 +++++++---- 3 files changed, 145 insertions(+), 63 deletions(-) diff --git a/WebRTCSrv.js b/WebRTCSrv.js index e3e0a3c42..8b0c78321 100644 --- a/WebRTCSrv.js +++ b/WebRTCSrv.js @@ -36,6 +36,7 @@ var run = module.exports.run = function(storage, server) { socket.master = master master.joiningClients.push(socket) let id = master.joiningClients.length - 1 + console.log(id); master.send(JSON.stringify({id, data: msg.data})) return } @@ -54,10 +55,30 @@ var run = module.exports.run = function(storage, server) { }) socket.on('close', (event) => { - if (socket.hasOwnProperty('joiningClients')) { - for (let client of socket.joiningClients) { - client.close(POLICY_VIOLATION, 'The peer is no longer available') + console.log('someone has closed'); + // If not master + if (socket.hasOwnProperty('master')) { + let masterClients = socket.master.joiningClients + for (let client of masterClients) { + if(client.id === socket.id) { + console.log('close client '+client.key) + client.close(POLICY_VIOLATION, 'The peer is no longer available') + //masterClients.splice(masterClients.indexOf(client),1); + } + } + } + else if (socket.hasOwnProperty('joiningClients')) { + let firstClient + let masterClients = socket.joiningClients + for (let client of masterClients) { + firstClient = client + break; } + firstClient.close(POLICY_VIOLATION, 'The master is no longer available') + //masterClients.splice(masterClients.indexOf(firstClient),1); + firstClient.joiningClients = masterClients + console.log('change master from '+socket.key+' to '+firstClient.key) + socket = firstClient } }) }) diff --git a/www/common/netflux.js b/www/common/netflux.js index d63470bba..1c434e46c 100644 --- a/www/common/netflux.js +++ b/www/common/netflux.js @@ -226,8 +226,35 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: 'send', value: function send(data) { - var protocol = _ServiceProvider2.default.get(this.settings.protocol); - this.topologyService.broadcast(this, protocol.message(cs.USER_DATA, { id: this.myID, data: data })); + var channel = this; + return new Promise(function (resolve, reject) { + if (channel.channels.size === 0) { + console.log('sizenull');resolve(); + } + var protocol = _ServiceProvider2.default.get(channel.settings.protocol); + channel.topologyService.broadcast(channel, protocol.message(cs.USER_DATA, { id: channel.myID, data: data })).then(resolve, reject); + }); + } + }, { + key: 'getHistory', + value: function getHistory(historyKeeperID) { + var channel = this; + return new Promise(function (resolve, reject) { + console.log(channel); + console.log('Je veux history ' + channel.myID); + var protocol = _ServiceProvider2.default.get(channel.settings.protocol); + channel.topologyService.sendTo(historyKeeperID, channel, protocol.message(cs.GET_HISTORY, { id: channel.myID, data: '' })).then(resolve, reject); + }); + } + }, { + key: 'sendTo', + value: function sendTo(id, msg) { + var channel = this; + return new Promise(function (resolve, reject) { + var protocol = _ServiceProvider2.default.get(channel.settings.protocol); + console.log('WCsendTo ' + id); + channel.topologyService.sendTo(id, channel, protocol.message(cs.USER_DATA, { id: channel.myID, data: msg })).then(resolve, reject); + }); } }, { key: 'openForJoining', @@ -305,6 +332,7 @@ return /******/ (function(modules) { // webpackBootstrap }); // API user's message var USER_DATA = exports.USER_DATA = 0; + var GET_HISTORY = exports.GET_HISTORY = 6; // Internal messages var JOIN_START = exports.JOIN_START = 2; @@ -379,7 +407,7 @@ return /******/ (function(modules) { // webpackBootstrap value: function get(code) { var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - var service = undefined; + var service = void 0; switch (code) { case cs.WEBRTC_SERVICE: service = new _WebRTCService2.default(options); @@ -441,6 +469,8 @@ return /******/ (function(modules) { // webpackBootstrap var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; _classCallCheck(this, FullyConnectedService); + + console.log('SERVICE FULLY CONNECTED CONSTRUCTED'); } _createClass(FullyConnectedService, [{ @@ -500,62 +530,70 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: 'broadcast', value: function broadcast(webChannel, data) { - console.log(data); - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = webChannel.channels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var c = _step.value; + return new Promise(function (resolve, reject) { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; - console.log(c); - c.send(data); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); + for (var _iterator = webChannel.channels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var c = _step.value; + + c.send(data); } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; } finally { - if (_didIteratorError) { - throw _iteratorError; + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } } } - } + + resolve(); + }); } }, { key: 'sendTo', value: function sendTo(id, webChannel, data) { - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; + console.log('sending to ' + id); - try { - for (var _iterator2 = webChannel.channels[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var c = _step2.value; + return new Promise(function (resolve, reject) { + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; - if (c.peerID == id) { - c.send(data); - } - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); + for (var _iterator2 = webChannel.channels[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var c = _step2.value; + + if (c.peerID == id) { + c.send(data); + } } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; } finally { - if (_didIteratorError2) { - throw _iteratorError2; + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } } } - } + + resolve(); + }); } }, { key: 'leave', @@ -805,7 +843,7 @@ return /******/ (function(modules) { // webpackBootstrap var settings = Object.assign({}, this.settings, options); return new Promise(function (resolve, reject) { - var connection = undefined; + var connection = void 0; var socket = new window.WebSocket(settings.signaling); console.log('Socket created'); socket.onopen = function () { @@ -928,8 +966,8 @@ return /******/ (function(modules) { // webpackBootstrap }, function () {}); })(); } else if (msg.sdp.type === 'answer') { - var sd = Object.assign(new this.RTCSessionDescription(), msg.sdp); - webChannel.connections.get(msg.senderPeerID).setRemoteDescription(sd, function () {}, function () {}); + var _sd = Object.assign(new this.RTCSessionDescription(), msg.sdp); + webChannel.connections.get(msg.senderPeerID).setRemoteDescription(_sd, function () {}, function () {}); } } else if (Reflect.has(msg, 'candidate')) { webChannel.connections.get(msg.senderPeerID).addIceCandidate(new this.RTCIceCandidate(msg.candidate)); @@ -1042,7 +1080,7 @@ return /******/ (function(modules) { // webpackBootstrap var settings = Object.assign({}, this.settings, options); return new Promise(function (resolve, reject) { - var connection = undefined; + var connection = void 0; var socket = new window.WebSocket(settings.signaling); socket.seq = 1; socket.facade = options.facade || null; @@ -1108,6 +1146,10 @@ return /******/ (function(modules) { // webpackBootstrap case cs.USER_DATA: webChannel.onmessage(msg.id, msg.data); break; + case cs.GET_HISTORY: + console.log("SOMEONE WANTS HISTORY"); + webChannel.onPeerMessage(msg.id, msg.code); + break; case cs.SERVICE_DATA: var service = _ServiceProvider2.default.get(msg.service); service.onmessage(channel, msg.data); @@ -1139,6 +1181,9 @@ return /******/ (function(modules) { // webpackBootstrap msg.id = data.id; msg.data = data.data; break; + case cs.GET_HISTORY: + msg.id = data.id; + break; case cs.SERVICE_DATA: msg.service = data.service; msg.data = Object.assign({}, data.data); @@ -1268,7 +1313,7 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: 'message', value: function message(code, data) { - var type = undefined; + var type = void 0; switch (code) { case cs.USER_DATA: type = 'MSG'; diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index ebddde624..6ba825f53 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -102,16 +102,8 @@ define([ var bump = function () {}; - var onPeerMessage = function (peer, msg) { - if(peer === '_HISTORY_KEEPER_') { - var msgHistory = JSON.parse(msg[4]); - onMessage(msgHistory[1], msgHistory[4]); - } - else { - warn('Illegal direct message'); - } - }; - + var messagesHistory = []; + var options = { signaling: websocketUrl, // signaling: 'ws://localhost:8000', @@ -125,7 +117,7 @@ define([ var realtime; // Add the Facade's peer messages handler - Netflux._onPeerMessage = onPeerMessage; + // Netflux._onPeerMessage = onPeerMessage; function getParameterByName(name, url) { if (!url) url = window.location.href; @@ -171,6 +163,9 @@ define([ wc.onmessage = onMessage; // On receiving message wc.onJoining = onJoining; // On user joining the session wc.onLeaving = onLeaving; // On user leaving the session + wc.onPeerMessage = function(peerId, type) { + onPeerMessage(peerId, wc); // On user leaving the session + } // Open a Chainpad session realtime = createRealtime(); @@ -191,12 +186,21 @@ define([ if (parsed.content[0] !== 0) { console.log('ENVOI '+message); message = Crypto.encrypt(message, cryptKey); - wc.send(message); - onMessage('', message); + wc.send(message).then(function() { + onMessage('', message); + }); } // END-TODO }); + var hc; + for (let c of wc.channels) { hc = c; break; } + if(hc) { + console.log('history keeper :'); + console.log(hc); + console.log('onPeer '+hc.peerID) + wc.getHistory(hc.peerID); + } // Get the channel history // var hc; // wc.peers.forEach(function (p) { if (!hc || p.linkQuality > hc.linkQuality) { hc = p; } }); @@ -227,16 +231,28 @@ define([ var whoami = new RegExp(userName.replace(/[\/\+]/g, function (c) { return '\\' +c; })); + + var onPeerMessage = function(peerID, wc) { + console.log(messagesHistory); + console.log('RTsendTo '+peerID); + messagesHistory.forEach(function(msg) { + console.log(msg); + //var message = Crypto.encrypt('1:y'+msg, cryptKey); + wc.sendTo(peerID, msg); + }); + }; var onMessage = function(peer, msg) { // TODO : put in ChainpadAdapter // remove the password + messagesHistory.push(msg); var passLen = msg.substring(0,msg.indexOf(':')); var message = msg.substring(passLen.length+1 + Number(passLen)); message = Crypto.decrypt(message, cryptKey); + console.log('RECOIS '+message); // END-TODO ChainpadAdapter From ae8f6f7f2c5cebee3ef8939a7fbb75c5ac127210 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Thu, 10 Mar 2016 14:03:31 +0100 Subject: [PATCH 09/65] Ability to choose which protocol to use (Websocket or WebRTC) with Netflux --- WebRTCSrv.js | 35 +------ config.js.dist | 2 + customize.dist/index.html | 6 +- server.js | 43 +++++--- www/common/netflux.js | 119 +++++++++++---------- www/common/realtime-input.js | 194 +++++++++++++++++------------------ www/hack/main.js | 1 + www/vdom/main.js | 1 + 8 files changed, 196 insertions(+), 205 deletions(-) diff --git a/WebRTCSrv.js b/WebRTCSrv.js index 8b0c78321..c97c18b79 100644 --- a/WebRTCSrv.js +++ b/WebRTCSrv.js @@ -1,11 +1,10 @@ 'use strict' let WebSocketServer = require('ws').Server -const PORT = 8000 const UNSUPPORTED_DATA = 1007 const POLICY_VIOLATION = 1008 const CLOSE_UNSUPPORTED = 1003 -var run = module.exports.run = function(storage, server) { +var run = module.exports.run = function(server) { server.on('connection', (socket) => { socket.on('message', (data) => { try { @@ -15,7 +14,6 @@ var run = module.exports.run = function(storage, server) { for (let master of server.clients) { if (master.key === msg.key) { socket.close(POLICY_VIOLATION, 'The key already exists') - console.log('ERROR key exists'); return } } @@ -32,16 +30,13 @@ var run = module.exports.run = function(storage, server) { } else if (msg.hasOwnProperty('join')) { for (let master of server.clients) { if (master.key === msg.join) { - console.log('joined'); socket.master = master master.joiningClients.push(socket) let id = master.joiningClients.length - 1 - console.log(id); master.send(JSON.stringify({id, data: msg.data})) return } } - console.log('ERROR unknown key'); socket.close(POLICY_VIOLATION, 'Unknown key') } else if (msg.hasOwnProperty('data') && socket.hasOwnProperty('master')) { let id = socket.master.joiningClients.indexOf(socket) @@ -55,31 +50,11 @@ var run = module.exports.run = function(storage, server) { }) socket.on('close', (event) => { - console.log('someone has closed'); - // If not master - if (socket.hasOwnProperty('master')) { - let masterClients = socket.master.joiningClients - for (let client of masterClients) { - if(client.id === socket.id) { - console.log('close client '+client.key) - client.close(POLICY_VIOLATION, 'The peer is no longer available') - //masterClients.splice(masterClients.indexOf(client),1); - } - } - } - else if (socket.hasOwnProperty('joiningClients')) { - let firstClient - let masterClients = socket.joiningClients - for (let client of masterClients) { - firstClient = client - break; + if (socket.hasOwnProperty('joiningClients')) { + for (let client of socket.joiningClients) { + client.close(POLICY_VIOLATION, 'The peer is no longer available') } - firstClient.close(POLICY_VIOLATION, 'The master is no longer available') - //masterClients.splice(masterClients.indexOf(firstClient),1); - firstClient.joiningClients = masterClients - console.log('change master from '+socket.key+' to '+firstClient.key) - socket = firstClient } - }) + }); }) } \ No newline at end of file diff --git a/config.js.dist b/config.js.dist index f61828e4c..56d7a1fc8 100644 --- a/config.js.dist +++ b/config.js.dist @@ -11,6 +11,8 @@ module.exports = { httpPort: 3000, // the port used for websockets websocketPort: 3001, + // the port used for webrtc (uncomment to use the WebRTC server) + // webrtcPort: 3002, // You now have a choice of storage engines diff --git a/customize.dist/index.html b/customize.dist/index.html index b652894e6..69e2eaa9f 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -123,9 +123,12 @@

- diff --git a/www/vdom/inner.html b/www/_pad/inner.html similarity index 100% rename from www/vdom/inner.html rename to www/_pad/inner.html diff --git a/www/_pad/main.js b/www/_pad/main.js new file mode 100644 index 000000000..6925d4150 --- /dev/null +++ b/www/_pad/main.js @@ -0,0 +1,58 @@ +define([ + '/api/config?cb=' + Math.random().toString(16).substring(2), + '/pad/realtime-wysiwyg.js', + '/common/messages.js', + '/common/crypto.js', + '/bower_components/jquery/dist/jquery.min.js', + '/customize/pad.js' +], function (Config, RTWysiwyg, Messages, Crypto) { + var $ = window.jQuery; + var ifrw = $('#pad-iframe')[0].contentWindow; + var Ckeditor = ifrw.CKEDITOR; + + var andThen = function (Ckeditor) { + $(window).on('hashchange', function() { + window.location.reload(); + }); + if (window.location.href.indexOf('#') === -1) { + window.location.href = window.location.href + '#' + Crypto.genKey(); + return; + } + var key = Crypto.parseKey(window.location.hash.substring(1)); + var editor = Ckeditor.replace('editor1', { + removeButtons: 'Source,Maximize', + // magicline plugin inserts html crap into the document which is not part of the + // document itself and causes problems when it's sent across the wire and reflected back + removePlugins: 'magicline,resize' + }); + editor.on('instanceReady', function () { + editor.execCommand('maximize'); + + // (contenteditable) iframe in an iframe + ifrw.$('iframe')[0].contentDocument.body.innerHTML = Messages.initialState; + + var rtw = + RTWysiwyg.start(ifrw, // window + Config.websocketURL, // websocketUrl + Crypto.rand64(8), // userName + key.channel, // channel + key.cryptKey); // cryptKey + editor.on('change', function () { rtw.onEvent(); }); + }); + window.editor = editor; + window.RTWysiwyg = RTWysiwyg; + }; + + var interval = 100; + var first = function () { + Ckeditor = ifrw.CKEDITOR; + if (Ckeditor) { + andThen(Ckeditor); + } else { + console.log("Ckeditor was not defined. Trying again in %sms",interval); + setTimeout(first, interval); + } + }; + + $(first); +}); diff --git a/www/pad/rangy.js b/www/_pad/rangy.js similarity index 100% rename from www/pad/rangy.js rename to www/_pad/rangy.js diff --git a/www/pad/realtime-wysiwyg.js b/www/_pad/realtime-wysiwyg.js similarity index 100% rename from www/pad/realtime-wysiwyg.js rename to www/_pad/realtime-wysiwyg.js diff --git a/www/common/toolbar.js b/www/common/toolbar.js index ac7d61d5c..c4692bcfe 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -127,16 +127,20 @@ define([ }; var getOtherUsers = function(myUserName, userList, userData) { - var length = userList.length; - var list = (length > 1) ? ' : ' : ''; + var i = 0; + var list = ''; userList.forEach(function(user) { if(user !== myUserName) { var data = (userData) ? (userData[user] || null) : null; - var userName = (data) ? data.name : user; - list += userName + ', '; + var userName = (data) ? data.name : null; + if(userName) { + if(i === 0) list = ' : '; + list += userName + ', '; + i++; + } } }); - return (length > 1) ? list.slice(0, -2) : list; + return (i > 0) ? list.slice(0, -2) : list; } var createChangeName = function($container, userList, buttonID) { diff --git a/www/pad/index.html b/www/pad/index.html index 685f73398..2116d14c9 100644 --- a/www/pad/index.html +++ b/www/pad/index.html @@ -4,6 +4,10 @@ +
+ + diff --git a/www/pad/main.js b/www/pad/main.js index 6925d4150..68682ef68 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -1,14 +1,25 @@ define([ '/api/config?cb=' + Math.random().toString(16).substring(2), - '/pad/realtime-wysiwyg.js', '/common/messages.js', '/common/crypto.js', + '/common/realtime-input.js', + '/common/convert.js', + '/common/toolbar.js', + '/common/cursor.js', + '/common/json-ot.js', + '/bower_components/diff-dom/diffDOM.js', '/bower_components/jquery/dist/jquery.min.js', '/customize/pad.js' -], function (Config, RTWysiwyg, Messages, Crypto) { +], function (Config, Messages, Crypto, realtimeInput, Convert, Toolbar, Cursor, JsonOT) { var $ = window.jQuery; var ifrw = $('#pad-iframe')[0].contentWindow; - var Ckeditor = ifrw.CKEDITOR; + var Ckeditor; // to be initialized later... + var DiffDom = window.diffDOM; + + window.Toolbar = Toolbar; + + var userName = Crypto.rand64(8), + toolbar; var andThen = function (Ckeditor) { $(window).on('hashchange', function() { @@ -18,29 +29,238 @@ define([ window.location.href = window.location.href + '#' + Crypto.genKey(); return; } + + var fixThings = false; var key = Crypto.parseKey(window.location.hash.substring(1)); - var editor = Ckeditor.replace('editor1', { + var editor = window.editor = Ckeditor.replace('editor1', { + // https://dev.ckeditor.com/ticket/10907 + needsBrFiller: fixThings, + needsNbspFiller: fixThings, removeButtons: 'Source,Maximize', // magicline plugin inserts html crap into the document which is not part of the // document itself and causes problems when it's sent across the wire and reflected back removePlugins: 'magicline,resize' }); - editor.on('instanceReady', function () { + + editor.on('instanceReady', function (Ckeditor) { editor.execCommand('maximize'); + var documentBody = ifrw.$('iframe')[0].contentDocument.body; + + documentBody.innerHTML = Messages.initialState; + + var inner = window.inner = documentBody; + var cursor = window.cursor = Cursor(inner); + + var $textarea = $('#feedback'); + + var setEditable = function (bool) { + inner.setAttribute('contenteditable', + (typeof (bool) !== 'undefined'? bool : true)); + }; + + // don't let the user edit until the pad is ready + setEditable(false); + + var diffOptions = { + preDiffApply: function (info) { + + // no use trying to recover the cursor if it doesn't exist + if (!cursor.exists()) { return; } + + /* frame is either 0, 1, 2, or 3, depending on which + cursor frames were affected: none, first, last, or both + */ + var frame = info.frame = cursor.inNode(info.node); + + if (!frame) { return; } + + var debug = info.debug = { + frame: frame, + action: info.diff.action, + cursorLength: cursor.getLength(), + node: info.node + }; + + if (info.diff.oldValue) { debug.oldValue = info.diff.oldValue; } + if (info.diff.newValue) { debug.newValue = info.diff.newValue; } + if (typeof info.diff.oldValue === 'string' && typeof info.diff.newValue === 'string') { + var pushes = cursor.pushDelta(info.diff.oldValue, info.diff.newValue); + debug.commonStart = pushes.commonStart; + debug.commonEnd = pushes.commonEnd; + debug.insert = pushes.insert; + debug.remove = pushes.remove; + + if (frame & 1) { + // push cursor start if necessary + if (pushes.commonStart < cursor.Range.start.offset) { + cursor.Range.start.offset += pushes.delta; + } + } + if (frame & 2) { + // push cursor end if necessary + if (pushes.commonStart < cursor.Range.end.offset) { + cursor.Range.end.offset += pushes.delta; + } + } + } + console.log("###################################"); + console.log(debug); + }, + postDiffApply: function (info) { + if (info.frame) { + if (info.node) { + if (info.frame & 1) { cursor.fixStart(info.node); } + if (info.frame & 2) { cursor.fixEnd(info.node); } + } else { console.error("info.node did not exist"); } + + var sel = cursor.makeSelection(); + var range = cursor.makeRange(); + + cursor.fixSelection(sel, range); + } + } + }; + + var initializing = true; + var userList = {}; // List of pretty name of all users (mapped with their server ID) + var toolbarList; // List of users still connected to the channel (server IDs) + var addToUserList = function(data) { + for (var attrname in data) { userList[attrname] = data[attrname]; } + if(toolbarList && typeof toolbarList.onChange === "function") { + toolbarList.onChange(userList); + } + }; + + var myData = {}; + var myUserName = ''; // My "pretty name" + var myID; // My server ID + + var setMyID = function(info) { + myID = info.myID || null; + myUserName = myID; + }; + + var createChangeName = function(id, $container) { + var buttonElmt = $container.find('#'+id)[0]; + buttonElmt.addEventListener("click", function() { + var newName = prompt("Change your name :", myUserName) + if (newName && newName.trim()) { + myUserName = newName.trim(); + myData[myID] = { + name: myUserName + }; + addToUserList(myData); + editor.fire( 'change' ); + } + }); + }; + + // apply patches, and try not to lose the cursor in the process! + var applyHjson = function (shjson) { + var hjson = JSON.parse(shjson); + var peerUserList = hjson[hjson.length-1]; + if(peerUserList.metadata) { + var userData = peerUserList.metadata; + addToUserList(userData); + delete hjson[hjson.length-1]; + } + var userDocStateDom = Convert.hjson.to.dom(hjson); + userDocStateDom.setAttribute("contenteditable", "true"); // lol wtf + var DD = new DiffDom(diffOptions); + var patch = (DD).diff(inner, userDocStateDom); + (DD).apply(inner, patch); + }; + + var onRemote = function (shjson) { + if (initializing) { return; } + + // remember where the cursor is + cursor.update(); + + // build a dom from HJSON, diff, and patch the editor + applyHjson(shjson); + }; + + var onInit = function (info) { + var $bar = $('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox'); + toolbarList = info.userList; + var config = { + userData: userList, + changeNameID: 'cryptpad-changeName' + }; + toolbar = info.realtime.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.webChannel, info.userList, config); + createChangeName('cryptpad-changeName', $bar); + /* TODO handle disconnects and such*/ + }; + + var onReady = function (info) { + console.log("Unlocking editor"); + initializing = false; + setEditable(true); + applyHjson($textarea.val()); + $textarea.trigger('keyup'); + }; + + var onAbort = function (info) { + console.log("Aborting the session!"); + // stop the user from continuing to edit + setEditable(false); + // TODO inform them that the session was torn down + toolbar.failed(); + }; + + + + var realtimeOptions = { + // the textarea that we will sync + textarea: $textarea[0], + + // the websocket URL (deprecated?) + websocketURL: Config.websocketURL, + webrtcURL: Config.webrtcURL, + + // our username + userName: userName, + + // the channel we will communicate over + channel: key.channel, + + // our encryption key + cryptKey: key.cryptKey, + + // configuration :D + doc: inner, + // first thing called + onInit: onInit, + + onReady: onReady, + + setMyID: setMyID, + + // when remote changes occur + onRemote: onRemote, + + // handle aborts + onAbort: onAbort, + + // really basic operational transform + transformFunction : JsonOT.validate + // pass in websocket/netflux object TODO + }; + + var rti = window.rti = realtimeInput.start(realtimeOptions); - // (contenteditable) iframe in an iframe - ifrw.$('iframe')[0].contentDocument.body.innerHTML = Messages.initialState; + $textarea.val(JSON.stringify(Convert.dom.to.hjson(inner))); - var rtw = - RTWysiwyg.start(ifrw, // window - Config.websocketURL, // websocketUrl - Crypto.rand64(8), // userName - key.channel, // channel - key.cryptKey); // cryptKey - editor.on('change', function () { rtw.onEvent(); }); + editor.on('change', function () { + var hjson = Convert.core.hyperjson.fromDOM(inner); + if(myData !== {}) { + hjson[hjson.length] = {metadata: userList}; + } + $textarea.val(JSON.stringify(hjson)); + rti.bumpSharejs(); + }); }); - window.editor = editor; - window.RTWysiwyg = RTWysiwyg; }; var interval = 100; diff --git a/www/vdom/main.js b/www/vdom/main.js deleted file mode 100644 index 9f055462d..000000000 --- a/www/vdom/main.js +++ /dev/null @@ -1,279 +0,0 @@ -define([ - '/api/config?cb=' + Math.random().toString(16).substring(2), - '/common/messages.js', - '/common/crypto.js', - '/common/realtime-input.js', - '/common/convert.js', - '/common/toolbar.js', - '/common/cursor.js', - '/common/json-ot.js', - '/bower_components/diff-dom/diffDOM.js', - '/bower_components/jquery/dist/jquery.min.js', - '/customize/pad.js' -], function (Config, Messages, Crypto, realtimeInput, Convert, Toolbar, Cursor, JsonOT) { - var $ = window.jQuery; - var ifrw = $('#pad-iframe')[0].contentWindow; - var Ckeditor; // to be initialized later... - var DiffDom = window.diffDOM; - - window.Toolbar = Toolbar; - - var userName = Crypto.rand64(8), - toolbar; - - var andThen = function (Ckeditor) { - $(window).on('hashchange', function() { - window.location.reload(); - }); - if (window.location.href.indexOf('#') === -1) { - window.location.href = window.location.href + '#' + Crypto.genKey(); - return; - } - - var fixThings = false; - var key = Crypto.parseKey(window.location.hash.substring(1)); - var editor = window.editor = Ckeditor.replace('editor1', { - // https://dev.ckeditor.com/ticket/10907 - needsBrFiller: fixThings, - needsNbspFiller: fixThings, - removeButtons: 'Source,Maximize', - // magicline plugin inserts html crap into the document which is not part of the - // document itself and causes problems when it's sent across the wire and reflected back - removePlugins: 'magicline,resize' - }); - - editor.on('instanceReady', function (Ckeditor) { - editor.execCommand('maximize'); - var documentBody = ifrw.$('iframe')[0].contentDocument.body; - - documentBody.innerHTML = Messages.initialState; - - var inner = window.inner = documentBody; - var cursor = window.cursor = Cursor(inner); - - var $textarea = $('#feedback'); - - var setEditable = function (bool) { - inner.setAttribute('contenteditable', - (typeof (bool) !== 'undefined'? bool : true)); - }; - - // don't let the user edit until the pad is ready - setEditable(false); - - var diffOptions = { - preDiffApply: function (info) { - - // no use trying to recover the cursor if it doesn't exist - if (!cursor.exists()) { return; } - - /* frame is either 0, 1, 2, or 3, depending on which - cursor frames were affected: none, first, last, or both - */ - var frame = info.frame = cursor.inNode(info.node); - - if (!frame) { return; } - - var debug = info.debug = { - frame: frame, - action: info.diff.action, - cursorLength: cursor.getLength(), - node: info.node - }; - - if (info.diff.oldValue) { debug.oldValue = info.diff.oldValue; } - if (info.diff.newValue) { debug.newValue = info.diff.newValue; } - if (typeof info.diff.oldValue === 'string' && typeof info.diff.newValue === 'string') { - var pushes = cursor.pushDelta(info.diff.oldValue, info.diff.newValue); - debug.commonStart = pushes.commonStart; - debug.commonEnd = pushes.commonEnd; - debug.insert = pushes.insert; - debug.remove = pushes.remove; - - if (frame & 1) { - // push cursor start if necessary - if (pushes.commonStart < cursor.Range.start.offset) { - cursor.Range.start.offset += pushes.delta; - } - } - if (frame & 2) { - // push cursor end if necessary - if (pushes.commonStart < cursor.Range.end.offset) { - cursor.Range.end.offset += pushes.delta; - } - } - } - console.log("###################################"); - console.log(debug); - }, - postDiffApply: function (info) { - if (info.frame) { - if (info.node) { - if (info.frame & 1) { cursor.fixStart(info.node); } - if (info.frame & 2) { cursor.fixEnd(info.node); } - } else { console.error("info.node did not exist"); } - - var sel = cursor.makeSelection(); - var range = cursor.makeRange(); - - cursor.fixSelection(sel, range); - } - } - }; - - var initializing = true; - var userList = []; // List of pretty name of all users (mapped with their server ID) - var toolbarList; // List of users still connected to the channel (server IDs) - var addToUserList = function(data) { - for (var attrname in data) { userList[attrname] = data[attrname]; } - if(toolbarList && typeof toolbarList.onChange === "function") { - toolbarList.onChange(userList); - } - }; - - var myData = {}; - var myUserName = ''; // My "pretty name" - var myID; // My server ID - - var setMyID = function(info) { - myID = info.myID || null; - myUserName = myID; - }; - - var createChangeName = function(id, $container) { - var buttonElmt = $container.find('#'+id)[0]; - buttonElmt.addEventListener("click", function() { - var newName = prompt("Change your name :", myUserName) - if (newName && newName.trim()) { - myUserName = newName.trim(); - myData[myID] = { - name: myUserName - }; - addToUserList(myData); - editor.fire( 'change' ); - } - }); - }; - - // apply patches, and try not to lose the cursor in the process! - var applyHjson = function (shjson) { - var hjson = JSON.parse(shjson); - var peerUserList = hjson[hjson.length-1]; - if(peerUserList.mydata) { - var userData = peerUserList.mydata; - addToUserList(userData); - delete hjson[hjson.length-1]; - } - var userDocStateDom = Convert.hjson.to.dom(hjson); - userDocStateDom.setAttribute("contenteditable", "true"); // lol wtf - var DD = new DiffDom(diffOptions); - var patch = (DD).diff(inner, userDocStateDom); - (DD).apply(inner, patch); - }; - - var onRemote = function (shjson) { - if (initializing) { return; } - - // remember where the cursor is - cursor.update(); - - // build a dom from HJSON, diff, and patch the editor - applyHjson(shjson); - }; - - var onInit = function (info) { - var $bar = $('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox'); - toolbarList = info.userList; - var config = { - userList: userList, - changeNameID: 'cryptpad-changeName' - }; - toolbar = info.realtime.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.webChannel, info.userList, config); - createChangeName('cryptpad-changeName', $bar); - /* TODO handle disconnects and such*/ - }; - - var onReady = function (info) { - console.log("Unlocking editor"); - initializing = false; - setEditable(true); - applyHjson($textarea.val()); - $textarea.trigger('keyup'); - }; - - var onAbort = function (info) { - console.log("Aborting the session!"); - // stop the user from continuing to edit - setEditable(false); - // TODO inform them that the session was torn down - toolbar.failed(); - }; - - - - var realtimeOptions = { - // the textarea that we will sync - textarea: $textarea[0], - - // the websocket URL (deprecated?) - websocketURL: Config.websocketURL, - webrtcURL: Config.webrtcURL, - - // our username - userName: userName, - - // the channel we will communicate over - channel: key.channel, - - // our encryption key - cryptKey: key.cryptKey, - - // configuration :D - doc: inner, - // first thing called - onInit: onInit, - - onReady: onReady, - - setMyID: setMyID, - - // when remote changes occur - onRemote: onRemote, - - // handle aborts - onAbort: onAbort, - - // really basic operational transform - transformFunction : JsonOT.validate - // pass in websocket/netflux object TODO - }; - - var rti = window.rti = realtimeInput.start(realtimeOptions); - - $textarea.val(JSON.stringify(Convert.dom.to.hjson(inner))); - - editor.on('change', function () { - var hjson = Convert.core.hyperjson.fromDOM(inner); - if(myData !== {}) { - hjson[hjson.length] = {mydata: myData}; - myData = {}; - } - $textarea.val(JSON.stringify(hjson)); - rti.bumpSharejs(); - }); - }); - }; - - var interval = 100; - var first = function () { - Ckeditor = ifrw.CKEDITOR; - if (Ckeditor) { - andThen(Ckeditor); - } else { - console.log("Ckeditor was not defined. Trying again in %sms",interval); - setTimeout(first, interval); - } - }; - - $(first); -}); From 38d797a4f13d356eb883b23e108d2b87e0dd30e1 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Wed, 16 Mar 2016 17:38:26 +0100 Subject: [PATCH 26/65] Fix issue with Chrome and WebRTC --- www/common/realtime-input.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index d39bae228..9bc3ebc62 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -14,6 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +window.Reflect = { has: (x,y) => { return (y in x); } }; define([ '/common/messages.js', '/common/netflux.js', From de6db0285c0ff9c1a73c84fca4cbd877ccfc17e5 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Wed, 16 Mar 2016 17:48:27 +0100 Subject: [PATCH 27/65] Limit the size of the usernames to 32 chars --- www/pad/main.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/pad/main.js b/www/pad/main.js index 68682ef68..3c8787ea8 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -145,7 +145,11 @@ define([ buttonElmt.addEventListener("click", function() { var newName = prompt("Change your name :", myUserName) if (newName && newName.trim()) { - myUserName = newName.trim(); + var myUserNameTemp = newName.trim(); + if(newName.trim().length > 32) { + myUserNameTemp = myUserNameTemp.substr(0, 31); + } + myUserName = myUserNameTemp; myData[myID] = { name: myUserName }; From fbe6225681b796daaf9d2f3240a024ba9943d1a1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 22 Mar 2016 10:06:42 +0100 Subject: [PATCH 28/65] Don't attempt to use the cursor selection when it has length 0 RTWYSIWYG-20 RTWYSIWYG-24 --- www/common/cursor.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/common/cursor.js b/www/common/cursor.js index 583a8860b..069f896c7 100644 --- a/www/common/cursor.js +++ b/www/common/cursor.js @@ -136,10 +136,9 @@ define([ verbose("cursor.update"); root = root || inner; sel = sel || Rangy.getSelection(root); - // FIXME under what circumstances are no ranges found? if (!sel.rangeCount) { error('[cursor.update] no ranges found'); - //return 'no ranges found'; + return; } var range = sel.getRangeAt(0); From 0ff4906f0e571555bce4113fdfdf30102045c100 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 22 Mar 2016 10:16:14 +0100 Subject: [PATCH 29/65] implement better serialization of class names RTWYSIWYG-27 : poorly formed yet valid HTML caused hyperjson to produce element selectors which hyperscript could not parse. --- www/common/hyperjson.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/www/common/hyperjson.js b/www/common/hyperjson.js index db6d3f20f..2f0badbd4 100644 --- a/www/common/hyperjson.js +++ b/www/common/hyperjson.js @@ -17,7 +17,7 @@ define([], function () { var callOnHyperJSON = function (hj, cb) { var children; - + if (hj && hj[2]) { children = hj[2].map(function (child) { if (isArray(child)) { @@ -39,6 +39,14 @@ define([], function () { return cb(hj[0], hj[1], children); }; + var prependDot = function (token) { + return '.' + token; + }; + + var isTruthy = function (x) { + return x; + }; + var DOM2HyperJSON = function(el){ if(!el.tagName && el.nodeType === Node.TEXT_NODE){ return el.textContent; @@ -73,7 +81,14 @@ define([], function () { delete attributes.id; } if(attributes.class){ - sel = sel +'.'+ attributes.class.replace(/ /g,"."); + /* TODO this can be done with RegExps alone, and it will be faster + but this works and is a little less error prone, albeit slower + come back and speed it up when it comes time to optimize */ + sel = sel +'.'+ attributes.class + .split(/\s+/) + .split(isTruthy) + .map(prependDot) + .join(''); delete attributes.class; } result.push(sel); From 4f3fcb08a41622535a650f8fa52756dc7f633d77 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 22 Mar 2016 10:19:13 +0100 Subject: [PATCH 30/65] better error reporting when the operational transform fails to parse JSON --- www/common/json-ot.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/www/common/json-ot.js b/www/common/json-ot.js index 167e3d11a..238ccc18a 100644 --- a/www/common/json-ot.js +++ b/www/common/json-ot.js @@ -12,7 +12,12 @@ define([ JSON.parse(text3); return resultOp; } catch (e) { - console.log(e); + console.error(e); + console.log({ + resultOp: resultOp, + text2: text2, + text3: text3 + }); } // returning **null** breaks out of the loop From f98fda76040268624cf86a3cd6eec4b79b7cd010 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 22 Mar 2016 19:28:50 +0100 Subject: [PATCH 31/65] fix string manipulation off-by-one --- www/common/hyperjson.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/hyperjson.js b/www/common/hyperjson.js index 2f0badbd4..35975aede 100644 --- a/www/common/hyperjson.js +++ b/www/common/hyperjson.js @@ -84,7 +84,7 @@ define([], function () { /* TODO this can be done with RegExps alone, and it will be faster but this works and is a little less error prone, albeit slower come back and speed it up when it comes time to optimize */ - sel = sel +'.'+ attributes.class + sel = sel + attributes.class .split(/\s+/) .split(isTruthy) .map(prependDot) From 475ca9bea8f3efa17a750ccbf571025091ec2af7 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 23 Mar 2016 12:31:16 +0100 Subject: [PATCH 32/65] hyperjson.js : used split instead of filter... oops --- www/common/hyperjson.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/hyperjson.js b/www/common/hyperjson.js index 35975aede..660c62194 100644 --- a/www/common/hyperjson.js +++ b/www/common/hyperjson.js @@ -84,9 +84,10 @@ define([], function () { /* TODO this can be done with RegExps alone, and it will be faster but this works and is a little less error prone, albeit slower come back and speed it up when it comes time to optimize */ + sel = sel + attributes.class .split(/\s+/) - .split(isTruthy) + .filter(isTruthy) .map(prependDot) .join(''); delete attributes.class; From c047d5310fb0efca14b7a9dd72fd57fc385b3fe0 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 24 Mar 2016 12:11:31 +0100 Subject: [PATCH 33/65] implement optional filtering in hyperjson Implemented via callback, return falsey if you want to filter an element and all of its children from the serialized result. Conflicts: www/common/convert.js --- www/common/convert.js | 24 +++++++++++++++++++++--- www/common/hyperjson.js | 15 ++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/www/common/convert.js b/www/common/convert.js index d37e98539..973881c80 100644 --- a/www/common/convert.js +++ b/www/common/convert.js @@ -1,14 +1,16 @@ define([ + '/common/virtual-dom.js', '/common/hyperjson.js', '/common/hyperscript.js' -], function (hyperjson, hyperscript) { +], function (vdom, hyperjson, hyperscript) { // complain if you don't find the required APIs - if (!(hyperjson && hyperscript)) { throw new Error(); } + if (!(vdom && hyperjson && hyperscript)) { throw new Error(); } // Generate a matrix of conversions /* convert.dom.to.hjson, convert.hjson.to.dom, convert.dom.to.vdom, convert.vdom.to.dom, + convert.vdom.to.hjson, convert.hjson.to.vdom and of course, identify functions in case you try to convert a datatype to itself @@ -20,13 +22,28 @@ define([ methods = { dom:{ dom: Self, - hjson: hyperjson.fromDOM + hjson: hyperjson.fromDOM, + vdom: function (D) { + return hyperjson.callOn(hyperjson.fromDOM(D), vdom.h); + } }, hjson:{ hjson: Self, dom: function (H) { // hyperjson.fromDOM, return hyperjson.callOn(H, hyperscript); + }, + vdom: function (H) { + return hyperjson.callOn(H, vdom.h); + } + }, + vdom:{ + vdom: Self, + dom: function (V) { + return vdom.create(V); + }, + hjson: function (V) { + return hyperjson.fromDOM(vdom.create(V)); } } }, @@ -38,6 +55,7 @@ define([ }()); convert.core = { + vdom: vdom, hyperjson: hyperjson, hyperscript: hyperscript }; diff --git a/www/common/hyperjson.js b/www/common/hyperjson.js index 660c62194..31a2caf08 100644 --- a/www/common/hyperjson.js +++ b/www/common/hyperjson.js @@ -47,13 +47,20 @@ define([], function () { return x; }; - var DOM2HyperJSON = function(el){ + var DOM2HyperJSON = function(el, predicate){ if(!el.tagName && el.nodeType === Node.TEXT_NODE){ return el.textContent; } if(!el.attributes){ return; } + + if (predicate) { + if (!predicate(el)) { + // shortcircuit + return; + } + } var attributes = {}; var i = 0; @@ -102,10 +109,12 @@ define([], function () { // js hint complains if we use 'var' here i = 0; + for(; i < el.childNodes.length; i++){ - children.push(DOM2HyperJSON(el.childNodes[i])); + children.push(DOM2HyperJSON(el.childNodes[i], predicate)); } - result.push(children); + + result.push(children.filter(isTruthy)); return result; }; From cd462ed8724337521a6753fc0806e83dfb0dbc2f Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 24 Mar 2016 12:28:45 +0100 Subject: [PATCH 34/65] Merge legacy websocket version --- www/socket/index.html | 41 +++++ www/socket/inner.html | 12 ++ www/socket/main.js | 288 ++++++++++++++++++++++++++++++ www/socket/realtime-input.js | 329 +++++++++++++++++++++++++++++++++++ www/socket/toolbar.js | 233 +++++++++++++++++++++++++ 5 files changed, 903 insertions(+) create mode 100644 www/socket/index.html create mode 100644 www/socket/inner.html create mode 100644 www/socket/main.js create mode 100644 www/socket/realtime-input.js create mode 100644 www/socket/toolbar.js diff --git a/www/socket/index.html b/www/socket/index.html new file mode 100644 index 000000000..6ee2a8596 --- /dev/null +++ b/www/socket/index.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + diff --git a/www/socket/inner.html b/www/socket/inner.html new file mode 100644 index 000000000..bf79dcd0d --- /dev/null +++ b/www/socket/inner.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/www/socket/main.js b/www/socket/main.js new file mode 100644 index 000000000..cbb7f66ed --- /dev/null +++ b/www/socket/main.js @@ -0,0 +1,288 @@ +define([ + '/api/config?cb=' + Math.random().toString(16).substring(2), + '/common/messages.js', + '/common/crypto.js', + '/socket/realtime-input.js', + '/common/convert.js', + '/socket/toolbar.js', + '/common/cursor.js', + '/common/json-ot.js', + '/bower_components/diff-dom/diffDOM.js', + '/bower_components/jquery/dist/jquery.min.js', + '/customize/pad.js' +], function (Config, Messages, Crypto, realtimeInput, Convert, Toolbar, Cursor, JsonOT) { + var $ = window.jQuery; + var ifrw = $('#pad-iframe')[0].contentWindow; + var Ckeditor; // to be initialized later... + var DiffDom = window.diffDOM; + + window.Convert = Convert; + + window.Toolbar = Toolbar; + + var userName = Crypto.rand64(8), + toolbar; + + var module = {}; + + var andThen = function (Ckeditor) { + $(window).on('hashchange', function() { + window.location.reload(); + }); + if (window.location.href.indexOf('#') === -1) { + window.location.href = window.location.href + '#' + Crypto.genKey(); + return; + } + + var fixThings = false; + var key = Crypto.parseKey(window.location.hash.substring(1)); + var editor = window.editor = Ckeditor.replace('editor1', { + // https://dev.ckeditor.com/ticket/10907 + needsBrFiller: fixThings, + needsNbspFiller: fixThings, + removeButtons: 'Source,Maximize', + // magicline plugin inserts html crap into the document which is not part of the + // document itself and causes problems when it's sent across the wire and reflected back + // but we filter it now, so that's ok. + removePlugins: 'resize' + }); + + editor.on('instanceReady', function (Ckeditor) { + editor.execCommand('maximize'); + var documentBody = ifrw.$('iframe')[0].contentDocument.body; + + documentBody.innerHTML = Messages.initialState; + + var inner = window.inner = documentBody; + var cursor = window.cursor = Cursor(inner); + + var $textarea = $('#feedback'); + + var setEditable = function (bool) { + // inner.style.backgroundColor = bool? 'unset': 'grey'; + inner.setAttribute('contenteditable', bool); + }; + + // don't let the user edit until the pad is ready + setEditable(false); + + var diffOptions = { + preDiffApply: function (info) { + + // no use trying to recover the cursor if it doesn't exist + if (!cursor.exists()) { return; } + + /* frame is either 0, 1, 2, or 3, depending on which + cursor frames were affected: none, first, last, or both + */ + var frame = info.frame = cursor.inNode(info.node); + + if (!frame) { return; } + + if (typeof info.diff.oldValue === 'string' && typeof info.diff.newValue === 'string') { + var pushes = cursor.pushDelta(info.diff.oldValue, info.diff.newValue); + + if (frame & 1) { + // push cursor start if necessary + if (pushes.commonStart < cursor.Range.start.offset) { + cursor.Range.start.offset += pushes.delta; + } + } + if (frame & 2) { + // push cursor end if necessary + if (pushes.commonStart < cursor.Range.end.offset) { + cursor.Range.end.offset += pushes.delta; + } + } + } + }, + postDiffApply: function (info) { + if (info.frame) { + if (info.node) { + if (info.frame & 1) { cursor.fixStart(info.node); } + if (info.frame & 2) { cursor.fixEnd(info.node); } + } else { console.error("info.node did not exist"); } + + var sel = cursor.makeSelection(); + var range = cursor.makeRange(); + + cursor.fixSelection(sel, range); + } + } + }; + + var initializing = true; + + var assertStateMatches = function () { + var userDocState = module.realtimeInput.realtime.getUserDoc(); + var currentState = $textarea.val(); + if (currentState !== userDocState) { + console.log({ + userDocState: userDocState, + currentState: currentState + }); + throw new Error("currentState !== userDocState"); + } + }; + + // apply patches, and try not to lose the cursor in the process! + var applyHjson = function (shjson) { + setEditable(false); + var userDocStateDom = Convert.hjson.to.dom(JSON.parse(shjson)); + userDocStateDom.setAttribute("contenteditable", "true"); // lol wtf + var DD = new DiffDom(diffOptions); + + assertStateMatches(); + + var patch = (DD).diff(inner, userDocStateDom); + (DD).apply(inner, patch); + + // push back to the textarea so we get a userDocState + setEditable(true); + }; + + var onRemote = function (shjson) { + if (initializing) { return; } + + // remember where the cursor is + cursor.update(); + + // TODO call propogate + + // build a dom from HJSON, diff, and patch the editor + applyHjson(shjson); + }; + + var onInit = function (info) { + var $bar = $('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox'); + toolbar = info.realtime.toolbar = Toolbar.create($bar, userName, info.realtime); + /* TODO handle disconnects and such*/ + }; + + var onReady = function (info) { + console.log("Unlocking editor"); + initializing = false; + setEditable(true); + applyHjson($textarea.val()); + $textarea.trigger('keyup'); + }; + + var onAbort = function (info) { + console.log("Aborting the session!"); + // stop the user from continuing to edit + setEditable(false); + // TODO inform them that the session was torn down + toolbar.failed(); + }; + + var realtimeOptions = { + // configuration :D + doc: inner, + // first thing called + onInit: onInit, + + onReady: onReady, + + // when remote changes occur + onRemote: onRemote, + + // handle aborts + onAbort: onAbort, + + // really basic operational transform + transformFunction : JsonOT.validate + // pass in websocket/netflux object TODO + }; + + var rti = module.realtimeInput = window.rti = realtimeInput.start($textarea[0], // synced element + Config.websocketURL, // websocketURL, ofc + userName, // userName + key.channel, // channelName + key.cryptKey, // key + realtimeOptions); + + $textarea.val(JSON.stringify(Convert.dom.to.hjson(inner))); + + var isNotMagicLine = function (el) { + // factor as: + // return !(el.tagName === 'SPAN' && el.contentEditable === 'false'); + var filter = (el.tagName === 'SPAN' && el.contentEditable === 'false'); + if (filter) { + console.log("[hyperjson.serializer] prevented an element" + + "from being serialized:", el); + return false; + } + return true; + }; + + var propogate = function () { + var hjson = Convert.core.hyperjson.fromDOM(inner, isNotMagicLine); + + $textarea.val(JSON.stringify(hjson)); + rti.bumpSharejs(); + }; + + var testInput = window.testInput = function (el, offset) { + var i = 0, + j = offset, + input = "The quick red fox jumped over the lazy brown dog. ", + l = input.length, + errors = 0, + max_errors = 15, + interval; + var cancel = function () { + if (interval) { window.clearInterval(interval); } + }; + + interval = window.setInterval(function () { + propogate(); + try { + el.replaceData(j, 0, input.charAt(i)); + } catch (err) { + errors++; + if (errors >= max_errors) { + console.log("Max error number exceeded"); + cancel(); + } + + console.error(err); + var next = document.createTextNode(""); + el.parentNode.appendChild(next); + el = next; + j = 0; + } + i = (i + 1) % l; + j++; + }, 200); + + return { + cancel: cancel + }; + }; + + var easyTest = window.easyTest = function () { + cursor.update(); + var start = cursor.Range.start; + var test = testInput(start.el, start.offset); + //window.rti.bumpSharejs(); + propogate(); + return test; + }; + + editor.on('change', propogate); + }); + }; + + var interval = 100; + var first = function () { + Ckeditor = ifrw.CKEDITOR; + if (Ckeditor) { + andThen(Ckeditor); + } else { + console.log("Ckeditor was not defined. Trying again in %sms",interval); + setTimeout(first, interval); + } + }; + + $(first); +}); diff --git a/www/socket/realtime-input.js b/www/socket/realtime-input.js new file mode 100644 index 000000000..556a4b3c8 --- /dev/null +++ b/www/socket/realtime-input.js @@ -0,0 +1,329 @@ +/* + * Copyright 2014 XWiki SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +define([ + '/common/messages.js', + '/bower_components/reconnectingWebsocket/reconnecting-websocket.js', + '/common/crypto.js', + '/common/sharejs_textarea.js', + '/common/chainpad.js', + '/bower_components/jquery/dist/jquery.min.js', +], function (Messages, ReconnectingWebSocket, Crypto, sharejs) { + var $ = window.jQuery; + var ChainPad = window.ChainPad; + var PARANOIA = true; + var module = { exports: {} }; + + /** + * If an error is encountered but it is recoverable, do not immediately fail + * but if it keeps firing errors over and over, do fail. + */ + var MAX_RECOVERABLE_ERRORS = 15; + + /** Maximum number of milliseconds of lag before we fail the connection. */ + var MAX_LAG_BEFORE_DISCONNECT = 20000; + + var debug = function (x) { console.log(x); }, + warn = function (x) { console.error(x); }, + verbose = function (x) { /*console.log(x);*/ }; + + // ------------------ Trapping Keyboard Events ---------------------- // + + var bindEvents = function (element, events, callback, unbind) { + for (var i = 0; i < events.length; i++) { + var e = events[i]; + if (element.addEventListener) { + if (unbind) { + element.removeEventListener(e, callback, false); + } else { + element.addEventListener(e, callback, false); + } + } else { + if (unbind) { + element.detachEvent('on' + e, callback); + } else { + element.attachEvent('on' + e, callback); + } + } + } + }; + + var bindAllEvents = function (textarea, docBody, onEvent, unbind) + { + /* + we use docBody for the purposes of CKEditor. + because otherwise special keybindings like ctrl-b and ctrl-i + would open bookmarks and info instead of applying bold/italic styles + */ + if (docBody) { + bindEvents(docBody, + ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste'], + onEvent, + unbind); + } + bindEvents(textarea, + ['mousedown','mouseup','click','change'], + onEvent, + unbind); + }; + + /* websocket stuff */ + var isSocketDisconnected = function (socket, realtime) { + var sock = socket._socket; + return sock.readyState === sock.CLOSING + || sock.readyState === sock.CLOSED + || (realtime.getLag().waiting && realtime.getLag().lag > MAX_LAG_BEFORE_DISCONNECT); + }; + + // this differs from other functions with similar names in that + // you are expected to pass a socket into it. + var checkSocket = function (socket) { + if (isSocketDisconnected(socket, socket.realtime) && + !socket.intentionallyClosing) { + return true; + } else { + return false; + } + }; + + // TODO before removing websocket implementation + // bind abort to onLeaving + var abort = function (socket, realtime) { + realtime.abort(); + try { socket._socket.close(); } catch (e) { warn(e); } + }; + + var handleError = function (socket, realtime, err, docHTML, allMessages) { + // var internalError = createDebugInfo(err, realtime, docHTML, allMessages); + abort(socket, realtime); + }; + + var makeWebsocket = function (url) { + var socket = new ReconnectingWebSocket(url); + var out = { + onOpen: [], + onClose: [], + onError: [], + onMessage: [], + send: function (msg) { socket.send(msg); }, + close: function () { socket.close(); }, + _socket: socket + }; + var mkHandler = function (name) { + return function (evt) { + for (var i = 0; i < out[name].length; i++) { + if (out[name][i](evt) === false) { + console.log(name +"Handler"); + return; + } + } + }; + }; + socket.onopen = mkHandler('onOpen'); + socket.onclose = mkHandler('onClose'); + socket.onerror = mkHandler('onError'); + socket.onmessage = mkHandler('onMessage'); + return out; + }; + /* end websocket stuff */ + + var start = module.exports.start = + function (textarea, websocketUrl, userName, channel, cryptKey, config) + { + + var passwd = 'y'; + + // make sure configuration is defined + config = config || {}; + + var doc = config.doc || null; + + // trying to deprecate onRemote, prefer loading it via the conf + var onRemote = config.onRemote || null; + + var transformFunction = config.transformFunction || null; + + var socket; + + if (config.socketAdaptor) { + // do netflux stuff + } else { + socket = makeWebsocket(websocketUrl); + } + // define this in case it gets called before the rest of our stuff is ready. + var onEvent = function () { }; + + var allMessages = []; + var isErrorState = false; + var initializing = true; + var recoverableErrorCount = 0; + + var $textarea = $(textarea); + + var bump = function () {}; + + var toReturn = { + socket: socket + }; + + socket.onOpen.push(function (evt) { + if (!initializing) { + console.log("Starting"); + // realtime is passed around as an attribute of the socket + // FIXME?? + socket.realtime.start(); + return; + } + + var realtime = toReturn.realtime = socket.realtime = ChainPad.create(userName, + passwd, + channel, + $(textarea).val(), + { + transformFunction: config.transformFunction + }); + + if (config.onInit) { + // extend as you wish + config.onInit({ + realtime: realtime + }); + } + + onEvent = function () { + // This looks broken + if (isErrorState || initializing) { return; } + }; + + realtime.onUserListChange(function (userList) { + if (!initializing || userList.indexOf(userName) === -1) { + return; + } + // if we spot ourselves being added to the document, we'll switch + // 'initializing' off because it means we're fully synced. + initializing = false; + + // execute an onReady callback if one was supplied + // pass an object so we can extend this later + if (config.onReady) { + // extend as you wish + config.onReady({ + userList: userList + }); + } + }); + + var whoami = new RegExp(userName.replace(/[\/\+]/g, function (c) { + return '\\' +c; + })); + + // when you receive a message... + socket.onMessage.push(function (evt) { + verbose(evt.data); + if (isErrorState) { return; } + + var message = Crypto.decrypt(evt.data, cryptKey); + verbose(message); + allMessages.push(message); + if (!initializing) { + if (PARANOIA) { + // FIXME this is out of sync with the application logic + onEvent(); + } + } + realtime.message(message); + if (/\[5,/.test(message)) { verbose("pong"); } + + if (!initializing) { + if (/\[2,/.test(message)) { + //verbose("Got a patch"); + if (whoami.test(message)) { + //verbose("Received own message"); + } else { + //verbose("Received remote message"); + // obviously this is only going to get called if + if (onRemote) { onRemote(realtime.getUserDoc()); } + } + } + } + }); + + // when a message is ready to send + realtime.onMessage(function (message) { + if (isErrorState) { return; } + message = Crypto.encrypt(message, cryptKey); + try { + socket.send(message); + } catch (e) { + warn(e); + } + }); + + // actual socket bindings + socket.onmessage = function (evt) { + for (var i = 0; i < socket.onMessage.length; i++) { + if (socket.onMessage[i](evt) === false) { return; } + } + }; + socket.onclose = function (evt) { + for (var i = 0; i < socket.onMessage.length; i++) { + if (socket.onClose[i](evt) === false) { return; } + } + }; + + socket.onerror = warn; + + // TODO confirm that we can rely on netflux API + var socketChecker = setInterval(function () { + if (checkSocket(socket)) { + warn("Socket disconnected!"); + + recoverableErrorCount += 1; + + if (recoverableErrorCount >= MAX_RECOVERABLE_ERRORS) { + warn("Giving up!"); + abort(socket, realtime); + if (config.onAbort) { + config.onAbort({ + socket: socket + }); + } + if (socketChecker) { clearInterval(socketChecker); } + } + } else { + // it's working as expected, continue + } + }, 200); + + bindAllEvents(textarea, doc, onEvent, false); + + // attach textarea + // NOTE: should be able to remove the websocket without damaging this + sharejs.attach(textarea, realtime); + + realtime.start(); + debug('started'); + + bump = realtime.bumpSharejs; + }); + + toReturn.onEvent = function () { onEvent(); }; + toReturn.bumpSharejs = function () { bump(); }; + + return toReturn; + }; + return module.exports; +}); diff --git a/www/socket/toolbar.js b/www/socket/toolbar.js new file mode 100644 index 000000000..4159ea0ac --- /dev/null +++ b/www/socket/toolbar.js @@ -0,0 +1,233 @@ +define([ + '/common/messages.js' +], function (Messages) { + + /** Id of the element for getting debug info. */ + var DEBUG_LINK_CLS = 'rtwysiwyg-debug-link'; + + /** Id of the div containing the user list. */ + var USER_LIST_CLS = 'rtwysiwyg-user-list'; + + /** Id of the div containing the lag info. */ + var LAG_ELEM_CLS = 'rtwysiwyg-lag'; + + /** The toolbar class which contains the user list, debug link and lag. */ + var TOOLBAR_CLS = 'rtwysiwyg-toolbar'; + + /** Key in the localStore which indicates realtime activity should be disallowed. */ + var LOCALSTORAGE_DISALLOW = 'rtwysiwyg-disallow'; + + var SPINNER_DISAPPEAR_TIME = 3000; + var SPINNER = [ '-', '\\', '|', '/' ]; + + var uid = function () { + return 'rtwysiwyg-uid-' + String(Math.random()).substring(2); + }; + + var createRealtimeToolbar = function ($container) { + var id = uid(); + $container.prepend( + '
' + + '
' + + '
' + + '
' + ); + var toolbar = $container.find('#'+id); + toolbar.append([ + '' + ].join('\n')); + return toolbar; + }; + + var createEscape = function ($container) { + var id = uid(); + $container.append('
⇐ Back
'); + var $ret = $container.find('#'+id); + $ret.on('click', function () { + window.location.href = '/'; + }); + return $ret[0]; + }; + + var createSpinner = function ($container) { + var id = uid(); + $container.append('
'); + return $container.find('#'+id)[0]; + }; + + var kickSpinner = function (spinnerElement, reversed) { + var txt = spinnerElement.textContent || '-'; + var inc = (reversed) ? -1 : 1; + spinnerElement.textContent = SPINNER[(SPINNER.indexOf(txt) + inc) % SPINNER.length]; + if (spinnerElement.timeout) { clearTimeout(spinnerElement.timeout); } + spinnerElement.timeout = setTimeout(function () { + spinnerElement.textContent = ''; + }, SPINNER_DISAPPEAR_TIME); + }; + + var createUserList = function ($container) { + var id = uid(); + $container.append('
'); + return $container.find('#'+id)[0]; + }; + + var updateUserList = function (myUserName, listElement, userList) { + var meIdx = userList.indexOf(myUserName); + if (meIdx === -1) { + listElement.textContent = Messages.synchronizing; + return; + } + if (userList.length === 1) { + listElement.textContent = Messages.editingAlone; + } else if (userList.length === 2) { + listElement.textContent = Messages.editingWithOneOtherPerson; + } else { + listElement.textContent = Messages.editingWith + ' ' + (userList.length - 1) + ' ' + Messages.otherPeople; + } + }; + + var createLagElement = function ($container) { + var id = uid(); + $container.append('
'); + return $container.find('#'+id)[0]; + }; + + var checkLag = function (realtime, lagElement) { + var lag = realtime.getLag(); + var lagSec = lag.lag/1000; + var lagMsg = Messages.lag + ' '; + if (lag.waiting && lagSec > 1) { + lagMsg += "?? " + Math.floor(lagSec); + } else { + lagMsg += lagSec; + } + lagElement.textContent = lagMsg; + }; + + // this is a little hack, it should go in it's own file. + // FIXME ok, so let's put it in its own file then + // TODO there should also be a 'clear recent pads' button + var rememberPad = function () { + // FIXME, this is overly complicated, use array methods + var recentPadsStr = localStorage['CryptPad_RECENTPADS']; + var recentPads = []; + if (recentPadsStr) { recentPads = JSON.parse(recentPadsStr); } + // TODO use window.location.hash or something like that + if (window.location.href.indexOf('#') === -1) { return; } + var now = new Date(); + var out = []; + for (var i = recentPads.length; i >= 0; i--) { + if (recentPads[i] && + // TODO precompute this time value, maybe make it configurable? + // FIXME precompute the date too, why getTime every time? + now.getTime() - recentPads[i][1] < (1000*60*60*24*30) && + recentPads[i][0] !== window.location.href) + { + out.push(recentPads[i]); + } + } + out.push([window.location.href, now.getTime()]); + localStorage['CryptPad_RECENTPADS'] = JSON.stringify(out); + }; + + var create = function ($container, myUserName, realtime) { + var toolbar = createRealtimeToolbar($container); + createEscape(toolbar.find('.rtwysiwyg-toolbar-leftside')); + var userListElement = createUserList(toolbar.find('.rtwysiwyg-toolbar-leftside')); + var spinner = createSpinner(toolbar.find('.rtwysiwyg-toolbar-rightside')); + var lagElement = createLagElement(toolbar.find('.rtwysiwyg-toolbar-rightside')); + + rememberPad(); + + var connected = false; + + realtime.onUserListChange(function (userList) { + if (userList.indexOf(myUserName) !== -1) { connected = true; } + if (!connected) { return; } + updateUserList(myUserName, userListElement, userList); + }); + + var ks = function () { + if (connected) { kickSpinner(spinner, false); } + }; + + realtime.onPatch(ks); + // Try to filter out non-patch messages, doesn't have to be perfect this is just the spinner + realtime.onMessage(function (msg) { if (msg.indexOf(':[2,') > -1) { ks(); } }); + + setInterval(function () { + if (!connected) { return; } + checkLag(realtime, lagElement); + }, 3000); + + return { + failed: function () { + connected = false; + userListElement.textContent = ''; + lagElement.textContent = ''; + }, + reconnecting: function () { + connected = false; + userListElement.textContent = Messages.reconnecting; + lagElement.textContent = ''; + }, + connected: function () { + connected = true; + } + }; + }; + + return { create: create }; +}); From e066730b6835bf1645d9d67302fe590267bd1f1d Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 24 Mar 2016 12:46:35 +0100 Subject: [PATCH 35/65] add notes about how hyperjson is to be used --- www/common/hyperjson.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/www/common/hyperjson.js b/www/common/hyperjson.js index 31a2caf08..880a7fe3e 100644 --- a/www/common/hyperjson.js +++ b/www/common/hyperjson.js @@ -47,6 +47,13 @@ define([], function () { return x; }; + /* DOM2HyperJSON accepts a DOM element/node + and converts it into Hyperjson. It optionally accepts a predicate + which is used for filtering out subtrees of the DOM from the result. + + The function, if provided, must return true for elements which + should be preserved, and 'false' for elements which should be removed. + */ var DOM2HyperJSON = function(el, predicate){ if(!el.tagName && el.nodeType === Node.TEXT_NODE){ return el.textContent; From 42c972116b6c497ee333053557008f55805feeb8 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 24 Mar 2016 12:50:04 +0100 Subject: [PATCH 36/65] leave TODO re: diffDOM and magicline Hyperjson guarantees that magicline elements are not sent across the wire. DiffDOM must guarantee that magicline elements will not be removed on remote edits. --- www/socket/main.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/www/socket/main.js b/www/socket/main.js index cbb7f66ed..a4c60afa8 100644 --- a/www/socket/main.js +++ b/www/socket/main.js @@ -68,6 +68,12 @@ define([ var diffOptions = { preDiffApply: function (info) { + /* TODO DiffDOM will filter out magicline plugin elements + in practice this will make it impossible to use it + while someone else is typing, which could be annoying + + we should check when such an element is going to be + removed, and prevent that from happening. */ // no use trying to recover the cursor if it doesn't exist if (!cursor.exists()) { return; } @@ -190,8 +196,8 @@ define([ onAbort: onAbort, // really basic operational transform + // reject patch if it results in invalid JSON transformFunction : JsonOT.validate - // pass in websocket/netflux object TODO }; var rti = module.realtimeInput = window.rti = realtimeInput.start($textarea[0], // synced element From 5ef7e29a9bf4966a078c98e483602fff22c0a7b1 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Fri, 1 Apr 2016 17:58:33 +0200 Subject: [PATCH 37/65] Add the latest changes from _socket into the netflux pad --- www/common/realtime-input.js | 78 ++++------- www/pad/index.html | 6 + www/pad/main.js | 253 ++++++++++++++++++++++++----------- 3 files changed, 204 insertions(+), 133 deletions(-) diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index 9bc3ebc62..635af830a 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -20,10 +20,10 @@ define([ '/common/netflux.js', '/common/crypto.js', '/common/toolbar.js', - '/common/sharejs_textarea.js', + '/_socket/text-patcher.js', '/common/chainpad.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Messages, Netflux, Crypto, Toolbar, sharejs) { +], function (Messages, Netflux, Crypto, Toolbar, TextPatcher) { var $ = window.jQuery; var ChainPad = window.ChainPad; var PARANOIA = true; @@ -61,25 +61,6 @@ define([ } }; - var bindAllEvents = function (textarea, docBody, onEvent, unbind) - { - /* - we use docBody for the purposes of CKEditor. - because otherwise special keybindings like ctrl-b and ctrl-i - would open bookmarks and info instead of applying bold/italic styles - */ - if (docBody) { - bindEvents(docBody, - ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste'], - onEvent, - unbind); - } - bindEvents(textarea, - ['mousedown','mouseup','click','change'], - onEvent, - unbind); - }; - var getParameterByName = function (name, url) { if (!url) { url = window.location.href; } name = name.replace(/[\[\]]/g, "\\$&"); @@ -93,7 +74,6 @@ define([ var start = module.exports.start = function (config) { - var textarea = config.textarea; var websocketUrl = config.websocketURL; var webrtcUrl = config.webrtcURL; var userName = config.userName; @@ -106,19 +86,16 @@ define([ var doc = config.doc || null; - // trying to deprecate onRemote, prefer loading it via the conf - var onRemote = config.onRemote || null; - - // define this in case it gets called before the rest of our stuff is ready. - var onEvent = function () { }; - var allMessages = []; var initializing = true; var recoverableErrorCount = 0; - var bump = function () {}; + var toReturn = {}; var messagesHistory = []; var chainpadAdapter = {}; var realtime; + + // define this in case it gets called before the rest of our stuff is ready. + var onEvent = toReturn.onEvent = function (newText) { }; var parseMessage = function (msg) { var res ={}; @@ -167,11 +144,11 @@ define([ verbose(message); allMessages.push(message); - if (!initializing) { - if (PARANOIA) { - onEvent(); - } - } + // if (!initializing) { + // if (toReturn.onLocal) { + // toReturn.onLocal(); + // } + // } realtime.message(message); if (/\[5,/.test(message)) { verbose("pong"); } @@ -183,7 +160,11 @@ define([ } else { //verbose("Received remote message"); // obviously this is only going to get called if - if (onRemote) { onRemote(realtime.getUserDoc()); } + if (config.onRemote) { + config.onRemote({ + realtime: realtime + }); + } } } } @@ -263,7 +244,7 @@ define([ return ChainPad.create(userName, passwd, channel, - $(textarea).val(), + config.initialState || {}, { transformFunction: config.transformFunction }); @@ -286,7 +267,9 @@ define([ // execute an onReady callback if one was supplied if (config.onReady) { - config.onReady(); + config.onReady({ + realtime: realtime + }); } } @@ -334,16 +317,10 @@ define([ hc.send(JSON.stringify(['GET_HISTORY', wc.id])); } - // Check the connection to the channel - if(!rtc) { - // TODO - // checkConnection(wc); - } - - bindAllEvents(textarea, doc, onEvent, false); - - sharejs.attach(textarea, realtime); - bump = realtime.bumpSharejs; + + toReturn.patchText = TextPatcher.create({ + realtime: realtime + }); realtime.start(); }; @@ -401,12 +378,7 @@ define([ } }; - return { - onEvent: function () { - onEvent(); - }, - bumpSharejs: function () { bump(); } - }; + return toReturn; }; return module.exports; }); diff --git a/www/pad/index.html b/www/pad/index.html index 2116d14c9..fccdfff02 100644 --- a/www/pad/index.html +++ b/www/pad/index.html @@ -38,6 +38,12 @@ right: 0px; top: 70px; } + #debug button { + visibility: hidden; + } + #debug:hover button { + visibility: visible; + } diff --git a/www/pad/main.js b/www/pad/main.js index 3c8787ea8..02d3006e5 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -3,24 +3,46 @@ define([ '/common/messages.js', '/common/crypto.js', '/common/realtime-input.js', - '/common/convert.js', + '/common/hyperjson.js', + '/common/hyperscript.js', '/common/toolbar.js', '/common/cursor.js', '/common/json-ot.js', '/bower_components/diff-dom/diffDOM.js', '/bower_components/jquery/dist/jquery.min.js', '/customize/pad.js' -], function (Config, Messages, Crypto, realtimeInput, Convert, Toolbar, Cursor, JsonOT) { +], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT) { var $ = window.jQuery; var ifrw = $('#pad-iframe')[0].contentWindow; var Ckeditor; // to be initialized later... var DiffDom = window.diffDOM; window.Toolbar = Toolbar; + window.Hyperjson = Hyperjson; + + var hjsonToDom = function (H) { + return Hyperjson.callOn(H, Hyperscript); + }; + + var module = window.REALTIME_MODULE = { + localChangeInProgress: 0 + }; var userName = Crypto.rand64(8), toolbar; + var isNotMagicLine = function (el) { + // factor as: + // return !(el.tagName === 'SPAN' && el.contentEditable === 'false'); + var filter = (el.tagName === 'SPAN' && el.contentEditable === 'false'); + if (filter) { + console.log("[hyperjson.serializer] prevented an element" + + "from being serialized:", el); + return false; + } + return true; + }; + var andThen = function (Ckeditor) { $(window).on('hashchange', function() { window.location.reload(); @@ -39,7 +61,7 @@ define([ removeButtons: 'Source,Maximize', // magicline plugin inserts html crap into the document which is not part of the // document itself and causes problems when it's sent across the wire and reflected back - removePlugins: 'magicline,resize' + removePlugins: 'resize' }); editor.on('instanceReady', function (Ckeditor) { @@ -51,8 +73,6 @@ define([ var inner = window.inner = documentBody; var cursor = window.cursor = Cursor(inner); - var $textarea = $('#feedback'); - var setEditable = function (bool) { inner.setAttribute('contenteditable', (typeof (bool) !== 'undefined'? bool : true)); @@ -63,6 +83,31 @@ define([ var diffOptions = { preDiffApply: function (info) { + /* DiffDOM will filter out magicline plugin elements + in practice this will make it impossible to use it + while someone else is typing, which could be annoying. + + we should check when such an element is going to be + removed, and prevent that from happening. */ + if (info.node && info.node.tagName === 'SPAN' && + info.node.contentEditable === "true") { + // it seems to be a magicline plugin element... + if (info.diff.action === 'removeElement') { + // and you're about to remove it... + // this probably isn't what you want + + /* + I have never seen this in the console, but the + magic line is still getting removed on remote + edits. This suggests that it's getting removed + by something other than diffDom. + */ + console.log("preventing removal of the magic line!"); + + // return true to prevent diff application + return true; + } + } // no use trying to recover the cursor if it doesn't exist if (!cursor.exists()) { return; } @@ -74,21 +119,8 @@ define([ if (!frame) { return; } - var debug = info.debug = { - frame: frame, - action: info.diff.action, - cursorLength: cursor.getLength(), - node: info.node - }; - - if (info.diff.oldValue) { debug.oldValue = info.diff.oldValue; } - if (info.diff.newValue) { debug.newValue = info.diff.newValue; } if (typeof info.diff.oldValue === 'string' && typeof info.diff.newValue === 'string') { var pushes = cursor.pushDelta(info.diff.oldValue, info.diff.newValue); - debug.commonStart = pushes.commonStart; - debug.commonEnd = pushes.commonEnd; - debug.insert = pushes.insert; - debug.remove = pushes.remove; if (frame & 1) { // push cursor start if necessary @@ -103,8 +135,6 @@ define([ } } } - console.log("###################################"); - console.log(debug); }, postDiffApply: function (info) { if (info.frame) { @@ -121,6 +151,8 @@ define([ } }; + var now = function () { return new Date().getTime(); }; + var initializing = true; var userList = {}; // List of pretty name of all users (mapped with their server ID) var toolbarList; // List of users still connected to the channel (server IDs) @@ -130,11 +162,11 @@ define([ toolbarList.onChange(userList); } }; - + var myData = {}; var myUserName = ''; // My "pretty name" var myID; // My server ID - + var setMyID = function(info) { myID = info.myID || null; myUserName = myID; @@ -147,7 +179,7 @@ define([ if (newName && newName.trim()) { var myUserNameTemp = newName.trim(); if(newName.trim().length > 32) { - myUserNameTemp = myUserNameTemp.substr(0, 31); + myUserNameTemp = myUserNameTemp.substr(0, 32); } myUserName = myUserNameTemp; myData[myID] = { @@ -159,33 +191,87 @@ define([ }); }; + var DD = new DiffDom(diffOptions); + // apply patches, and try not to lose the cursor in the process! var applyHjson = function (shjson) { - var hjson = JSON.parse(shjson); - var peerUserList = hjson[hjson.length-1]; - if(peerUserList.metadata) { - var userData = peerUserList.metadata; - addToUserList(userData); - delete hjson[hjson.length-1]; - } - var userDocStateDom = Convert.hjson.to.dom(hjson); + // var hjson = JSON.parse(shjson); + // var peerUserList = hjson[hjson.length-1]; + // if(peerUserList.metadata) { + // var userData = peerUserList.metadata; + // addToUserList(userData); + // delete hjson[hjson.length-1]; + // } + var userDocStateDom = hjsonToDom(JSON.parse(shjson)); userDocStateDom.setAttribute("contenteditable", "true"); // lol wtf - var DD = new DiffDom(diffOptions); var patch = (DD).diff(inner, userDocStateDom); (DD).apply(inner, patch); }; - var onRemote = function (shjson) { + var realtimeOptions = { + // provide initialstate... + initialState: JSON.stringify(Hyperjson.fromDOM(inner, isNotMagicLine)), + + // the websocket URL (deprecated?) + websocketURL: Config.websocketURL, + webrtcURL: Config.webrtcURL, + + // our username + userName: userName, + + // the channel we will communicate over + channel: key.channel, + + // our encryption key + cryptKey: key.cryptKey, + + // configuration :D + doc: inner, + + setMyID: setMyID, + + // really basic operational transform + transformFunction : JsonOT.validate + // pass in websocket/netflux object TODO + }; + + var onRemote = realtimeOptions.onRemote = function (info) { if (initializing) { return; } + var shjson = info.realtime.getUserDoc(); + // remember where the cursor is cursor.update(); + // Extract the user list (metadata) from the hyperjson + var hjson = JSON.parse(shjson); + var peerUserList = hjson[hjson.length-1]; + if(peerUserList.metadata) { + var userData = peerUserList.metadata; + // Update the local user data + userList = userData; + // Send the new data to the toolbar + if(toolbarList && typeof toolbarList.onChange === "function") { + toolbarList.onChange(userList); + } + hjson.pop(); + } + // build a dom from HJSON, diff, and patch the editor applyHjson(shjson); + + // Build a new stringified Chainpad hyperjson without metadata to compare with the one build from the dom + shjson = JSON.stringify(hjson); + + var hjson2 = Hyperjson.fromDOM(inner); + var shjson2 = JSON.stringify(hjson2); + if (shjson2 !== shjson) { + console.error("shjson2 !== shjson"); + module.realtimeInput.patchText(shjson2); + } }; - var onInit = function (info) { + var onInit = realtimeOptions.onInit = function (info) { var $bar = $('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox'); toolbarList = info.userList; var config = { @@ -197,15 +283,15 @@ define([ /* TODO handle disconnects and such*/ }; - var onReady = function (info) { + var onReady = realtimeOptions.onReady = function (info) { console.log("Unlocking editor"); initializing = false; setEditable(true); - applyHjson($textarea.val()); - $textarea.trigger('keyup'); + var shjson = info.realtime.getUserDoc(); + applyHjson(shjson); }; - var onAbort = function (info) { + var onAbort = realtimeOptions.onAbort = function (info) { console.log("Aborting the session!"); // stop the user from continuing to edit setEditable(false); @@ -213,57 +299,64 @@ define([ toolbar.failed(); }; - - - var realtimeOptions = { - // the textarea that we will sync - textarea: $textarea[0], - // the websocket URL (deprecated?) - websocketURL: Config.websocketURL, - webrtcURL: Config.webrtcURL, - // our username - userName: userName, - // the channel we will communicate over - channel: key.channel, - - // our encryption key - cryptKey: key.cryptKey, - - // configuration :D - doc: inner, - // first thing called - onInit: onInit, - - onReady: onReady, - - setMyID: setMyID, - - // when remote changes occur - onRemote: onRemote, - // handle aborts - onAbort: onAbort, + var rti = module.realtimeInput = realtimeInput.start(realtimeOptions); - // really basic operational transform - transformFunction : JsonOT.validate - // pass in websocket/netflux object TODO + /* catch `type="_moz"` before it goes over the wire */ + var brFilter = function (hj) { + if (hj[1].type === '_moz') { hj[1].type = undefined; } + return hj; }; - var rti = window.rti = realtimeInput.start(realtimeOptions); - - $textarea.val(JSON.stringify(Convert.dom.to.hjson(inner))); - - editor.on('change', function () { - var hjson = Convert.core.hyperjson.fromDOM(inner); - if(myData !== {}) { + // $textarea.val(JSON.stringify(Convert.dom.to.hjson(inner))); + + /* It's incredibly important that you assign 'rti.onLocal' + It's used inside of realtimeInput to make sure that all changes + make it into chainpad. + + It's being assigned this way because it can't be passed in, and + and can't be easily returned from realtime input without making + the code less extensible. + */ + var propogate = rti.onLocal = function () { + /* if the problem were a matter of external patches being + applied while a local patch were in progress, then we would + expect to be able to check and find + 'module.localChangeInProgress' with a non-zero value while + we were applying a remote change. + */ + var hjson = Hyperjson.fromDOM(inner, isNotMagicLine, brFilter); + if(Object.keys(myData).length > 0) { hjson[hjson.length] = {metadata: userList}; } - $textarea.val(JSON.stringify(hjson)); - rti.bumpSharejs(); - }); + var shjson = JSON.stringify(hjson); + if (!rti.patchText(shjson)) { + return; + } + rti.onEvent(shjson); + }; + + /* hitting enter makes a new line, but places the cursor inside + of the
instead of the

. This makes it such that you + cannot type until you click, which is rather unnacceptable. + If the cursor is ever inside such a
, you probably want + to push it out to the parent element, which ought to be a + paragraph tag. This needs to be done on keydown, otherwise + the first such keypress will not be inserted into the P. */ + inner.addEventListener('keydown', cursor.brFix); + + editor.on('change', propogate); + // editor.on('change', function () { + // var hjson = Convert.core.hyperjson.fromDOM(inner); + // if(myData !== {}) { + // hjson[hjson.length] = {metadata: userList}; + // } + // $textarea.val(JSON.stringify(hjson)); + // rti.bumpSharejs(); + // }); }); }; From ba4faea939935e434abf49fe882f4697b4edc92a Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Tue, 5 Apr 2016 12:17:43 +0200 Subject: [PATCH 38/65] Update the pads to run with the latest improvements to the websocket server --- NetfluxWebsocketSrv.js | 198 +++++++++++++++++++++++++++++++++++ customize.dist/index.html | 4 +- server.js | 3 +- www/common/netflux.js | 42 ++++++-- www/common/realtime-input.js | 21 ++-- www/pad/main.js | 24 +++-- 6 files changed, 261 insertions(+), 31 deletions(-) create mode 100644 NetfluxWebsocketSrv.js diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js new file mode 100644 index 000000000..4d59f25d2 --- /dev/null +++ b/NetfluxWebsocketSrv.js @@ -0,0 +1,198 @@ +;(function () { 'use strict'; +const Crypto = require('crypto'); +const LogStore = require('./storage/LogStore'); + +const LAG_MAX_BEFORE_DISCONNECT = 30000; +const LAG_MAX_BEFORE_PING = 15000; +const HISTORY_KEEPER_ID = Crypto.randomBytes(8).toString('hex'); + +const USE_HISTORY_KEEPER = true; + +let dropUser; + +const now = function () { return (new Date()).getTime(); }; + +const sendMsg = function (ctx, user, msg) { + try { + console.log('<' + JSON.stringify(msg)); + user.socket.send(JSON.stringify(msg)); + } catch (e) { + console.log(e.stack); + dropUser(ctx, user); + } +}; + +const sendChannelMessage = function (ctx, channel, msgStruct) { + msgStruct.unshift(0); + channel.forEach(function (user) { + if(msgStruct[2] !== 'MSG' || user.id !== msgStruct[1]) { // We don't want to send back a message to its sender, in order to save bandwidth + sendMsg(ctx, user, msgStruct); + } + }); + if (USE_HISTORY_KEEPER && msgStruct[2] === 'MSG') { + ctx.store.message(channel.id, JSON.stringify(msgStruct), function () { }); + } +}; + +dropUser = function (ctx, user) { + if (user.socket.readyState !== 2 /* WebSocket.CLOSING */ + && user.socket.readyState !== 3 /* WebSocket.CLOSED */) + { + try { + user.socket.close(); + } catch (e) { + console.log("Failed to disconnect ["+user.id+"], attempting to terminate"); + try { + user.socket.terminate(); + } catch (ee) { + console.log("Failed to terminate ["+user.id+"] *shrug*"); + } + } + } + delete ctx.users[user.id]; + Object.keys(ctx.channels).forEach(function (chanName) { + let chan = ctx.channels[chanName]; + let idx = chan.indexOf(user); + if (idx < 0) { return; } + console.log("Removing ["+user.id+"] from channel ["+chanName+"]"); + chan.splice(idx, 1); + if (chan.length === 0) { + console.log("Removing empty channel ["+chanName+"]"); + delete ctx.channels[chanName]; + } else { + sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chanName, 'Quit: [ dropUser() ]']); + } + }); +}; + +const getHistory = function (ctx, channelName, handler) { + ctx.store.getMessages(channelName, function (msgStr) { handler(JSON.parse(msgStr)); }); +}; + +const randName = function () { return Crypto.randomBytes(16).toString('hex'); }; + +const handleMessage = function (ctx, user, msg) { + let json = JSON.parse(msg); + let seq = json.shift(); + let cmd = json[0]; + let obj = json[1]; + + user.timeOfLastMessage = now(); + user.pingOutstanding = false; + + if (cmd === 'JOIN') { + if (obj && obj.length !== 32) { + sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]); + return; + } + let chanName = obj || randName(); + sendMsg(ctx, user, [seq, 'ACK', chanName]); + let chan = ctx.channels[chanName] = ctx.channels[chanName] || []; + chan.id = chanName; + if (USE_HISTORY_KEEPER) { + sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'JOIN', chanName]); + } + chan.forEach(function (u) { sendMsg(ctx, user, [0, u.id, 'JOIN', chanName]); }); + chan.push(user); + sendChannelMessage(ctx, chan, [user.id, 'JOIN', chanName]); + return; + } + if (cmd === 'MSG') { + if (obj === HISTORY_KEEPER_ID) { + let parsed; + try { parsed = JSON.parse(json[2]); } catch (err) { return; } + if (parsed[0] === 'GET_HISTORY') { + console.log('getHistory ' + parsed[1]); + getHistory(ctx, parsed[1], function (msg) { + sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)]); + }); + sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, 0]); + } + return; + } + if (obj && !ctx.channels[obj] && !ctx.users[obj]) { + sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]); + return; + } + sendMsg(ctx, user, [seq, 'ACK', '']); + let target; + json.unshift(user.id); + if ((target = ctx.channels[obj])) { + sendChannelMessage(ctx, target, json); + return; + } + if ((target = ctx.users[obj])) { + json.unshift(0); + sendMsg(ctx, target, json); + return; + } + } + if (cmd === 'LEAVE') { + let err; + let chan; + let idx; + if (!obj) { err = 'EINVAL'; } + if (!err && !(chan = ctx.channels[obj])) { err = 'ENOENT'; } + if (!err && (idx = chan.indexOf(user)) === -1) { err = 'NOT_IN_CHAN'; } + if (err) { + sendMsg(ctx, user, [seq, 'ERROR', err]); + return; + } + sendMsg(ctx, user, [seq, 'ACK', chan.id]); + json.unshift(user.id); + sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chan.id]); + chan.splice(idx, 1); + } + if (cmd === 'PING') { + sendMsg(ctx, user, [seq, 'ACK', obj]); + return; + } +}; + +let run = module.exports.run = function (storage, socketServer) { + let ctx = { + users: {}, + channels: {}, + store: LogStore.create('messages.log', storage) + }; + setInterval(function () { + Object.keys(ctx.users).forEach(function (userId) { + let u = ctx.users[userId]; + if (now() - u.timeOfLastMessage > LAG_MAX_BEFORE_DISCONNECT) { + dropUser(ctx, u); + } else if (!u.pingOutstanding && now() - u.timeOfLastMessage > LAG_MAX_BEFORE_PING) { + sendMsg(ctx, u, [0, 'PING', now()]); + u.pingOutstanding = true; + } + }); + }, 5000); + socketServer.on('connection', function(socket) { + let conn = socket.upgradeReq.connection; + let user = { + addr: conn.remoteAddress + '|' + conn.remotePort, + socket: socket, + id: randName(), + timeOfLastMessage: now(), + pingOutstanding: false + }; + ctx.users[user.id] = user; + sendMsg(ctx, user, [0, '', 'IDENT', user.id]); + socket.on('message', function(message) { + console.log('>'+message); + try { + handleMessage(ctx, user, message); + } catch (e) { + console.log(e.stack); + dropUser(ctx, user); + } + }); + socket.on('close', function (evt) { + for (let userId in ctx.users) { + if (ctx.users[userId].socket === socket) { + dropUser(ctx, ctx.users[userId]); + } + } + }); + }); +}; +}()); diff --git a/customize.dist/index.html b/customize.dist/index.html index 15c1285f1..60cefe347 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -125,9 +125,9 @@ + + + + +

+ + + + + diff --git a/www/padrtc/inner.html b/www/padrtc/inner.html new file mode 100644 index 000000000..bf79dcd0d --- /dev/null +++ b/www/padrtc/inner.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/www/padrtc/main.js b/www/padrtc/main.js new file mode 100644 index 000000000..ba983e799 --- /dev/null +++ b/www/padrtc/main.js @@ -0,0 +1,356 @@ +define([ + '/api/config?cb=' + Math.random().toString(16).substring(2), + '/common/messages.js', + '/common/crypto.js', + '/padrtc/realtime-input.js', + '/common/hyperjson.js', + '/common/hyperscript.js', + '/common/toolbar.js', + '/common/cursor.js', + '/common/json-ot.js', + '/bower_components/diff-dom/diffDOM.js', + '/bower_components/jquery/dist/jquery.min.js', + '/customize/pad.js' +], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT) { + var $ = window.jQuery; + var ifrw = $('#pad-iframe')[0].contentWindow; + var Ckeditor; // to be initialized later... + var DiffDom = window.diffDOM; + + window.Toolbar = Toolbar; + window.Hyperjson = Hyperjson; + + var hjsonToDom = function (H) { + return Hyperjson.callOn(H, Hyperscript); + }; + + var module = window.REALTIME_MODULE = { + localChangeInProgress: 0 + }; + + var userName = Crypto.rand64(8), + toolbar; + + var isNotMagicLine = function (el) { + // factor as: + // return !(el.tagName === 'SPAN' && el.contentEditable === 'false'); + var filter = (el.tagName === 'SPAN' && el.contentEditable === 'false'); + if (filter) { + console.log("[hyperjson.serializer] prevented an element" + + "from being serialized:", el); + return false; + } + return true; + }; + + var andThen = function (Ckeditor) { + // $(window).on('hashchange', function() { + // window.location.reload(); + // }); + var key; + var channel = ''; + if (window.location.href.indexOf('#') === -1) { + key = Crypto.genKey(); + // window.location.href = window.location.href + '#' + Crypto.genKey(); + // return; + } + else { + var hash = window.location.hash.substring(1); + var sep = hash.indexOf('|'); + channel = hash.substr(0,sep); + key = hash.substr(sep+1); + } + + var fixThings = false; + // var key = Crypto.parseKey(window.location.hash.substring(1)); + var editor = window.editor = Ckeditor.replace('editor1', { + // https://dev.ckeditor.com/ticket/10907 + needsBrFiller: fixThings, + needsNbspFiller: fixThings, + removeButtons: 'Source,Maximize', + // magicline plugin inserts html crap into the document which is not part of the + // document itself and causes problems when it's sent across the wire and reflected back + removePlugins: 'resize' + }); + + editor.on('instanceReady', function (Ckeditor) { + editor.execCommand('maximize'); + var documentBody = ifrw.$('iframe')[0].contentDocument.body; + + documentBody.innerHTML = Messages.initialState; + + var inner = window.inner = documentBody; + var cursor = window.cursor = Cursor(inner); + + var setEditable = function (bool) { + inner.setAttribute('contenteditable', + (typeof (bool) !== 'undefined'? bool : true)); + }; + + // don't let the user edit until the pad is ready + setEditable(false); + + var diffOptions = { + preDiffApply: function (info) { + /* Don't remove local instances of the magicline plugin */ + if (info.node && info.node.tagName === 'SPAN' && + info.node.getAttribute('contentEditable') === 'false') { + return true; + } + + if (!cursor.exists()) { return; } + var frame = info.frame = cursor.inNode(info.node); + if (!frame) { return; } + if (typeof info.diff.oldValue === 'string' && + typeof info.diff.newValue === 'string') { + var pushes = cursor.pushDelta(info.diff.oldValue, + info.diff.newValue); + if (frame & 1) { + if (pushes.commonStart < cursor.Range.start.offset) { + cursor.Range.start.offset += pushes.delta; + } + } + if (frame & 2) { + if (pushes.commonStart < cursor.Range.end.offset) { + cursor.Range.end.offset += pushes.delta; + } + } + } + }, + postDiffApply: function (info) { + if (info.frame) { + if (info.node) { + if (info.frame & 1) { cursor.fixStart(info.node); } + if (info.frame & 2) { cursor.fixEnd(info.node); } + } else { console.log("info.node did not exist"); } + + var sel = cursor.makeSelection(); + var range = cursor.makeRange(); + + cursor.fixSelection(sel, range); + } + } + }; + + var now = function () { return new Date().getTime(); }; + + var initializing = true; + var userList = {}; // List of pretty name of all users (mapped with their server ID) + var toolbarList; // List of users still connected to the channel (server IDs) + var addToUserList = function(data) { + for (var attrname in data) { userList[attrname] = data[attrname]; } + if(toolbarList && typeof toolbarList.onChange === "function") { + toolbarList.onChange(userList); + } + }; + + var myData = {}; + var myUserName = ''; // My "pretty name" + var myID; // My server ID + + var setMyID = function(info) { + myID = info.myID || null; + myUserName = myID; + }; + + var createChangeName = function(id, $container) { + var buttonElmt = $container.find('#'+id)[0]; + buttonElmt.addEventListener("click", function() { + var newName = prompt("Change your name :", myUserName) + if (newName && newName.trim()) { + var myUserNameTemp = newName.trim(); + if(newName.trim().length > 32) { + myUserNameTemp = myUserNameTemp.substr(0, 32); + } + myUserName = myUserNameTemp; + myData[myID] = { + name: myUserName + }; + addToUserList(myData); + editor.fire( 'change' ); + } + }); + }; + + var DD = new DiffDom(diffOptions); + + // apply patches, and try not to lose the cursor in the process! + var applyHjson = function (shjson) { + // var hjson = JSON.parse(shjson); + // var peerUserList = hjson[hjson.length-1]; + // if(peerUserList.metadata) { + // var userData = peerUserList.metadata; + // addToUserList(userData); + // delete hjson[hjson.length-1]; + // } + var userDocStateDom = hjsonToDom(JSON.parse(shjson)); + userDocStateDom.setAttribute("contenteditable", "true"); // lol wtf + var patch = (DD).diff(inner, userDocStateDom); + (DD).apply(inner, patch); + }; + + var realtimeOptions = { + // provide initialstate... + initialState: JSON.stringify(Hyperjson.fromDOM(inner, isNotMagicLine)), + + // the websocket URL (deprecated?) + websocketURL: Config.websocketURL, + webrtcURL: Config.webrtcURL, + + // our username + userName: userName, + + // the channel we will communicate over + channel: channel, + + // our encryption key + cryptKey: key, + + // configuration :D + doc: inner, + + setMyID: setMyID, + + // really basic operational transform + transformFunction : JsonOT.validate + // pass in websocket/netflux object TODO + }; + + var onRemote = realtimeOptions.onRemote = function (info) { + if (initializing) { return; } + + var shjson = info.realtime.getUserDoc(); + + // remember where the cursor is + cursor.update(); + + // Extract the user list (metadata) from the hyperjson + var hjson = JSON.parse(shjson); + var peerUserList = hjson[hjson.length-1]; + if(peerUserList.metadata) { + var userData = peerUserList.metadata; + // Update the local user data + userList = userData; + // Send the new data to the toolbar + if(toolbarList && typeof toolbarList.onChange === "function") { + toolbarList.onChange(userList); + } + hjson.pop(); + } + + // build a dom from HJSON, diff, and patch the editor + applyHjson(shjson); + + // Build a new stringified Chainpad hyperjson without metadata to compare with the one build from the dom + shjson = JSON.stringify(hjson); + + var hjson2 = Hyperjson.fromDOM(inner); + var shjson2 = JSON.stringify(hjson2); + if (shjson2 !== shjson) { + console.error("shjson2 !== shjson"); + module.realtimeInput.patchText(shjson2); + } + }; + + var onInit = realtimeOptions.onInit = function (info) { + var $bar = $('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox'); + toolbarList = info.userList; + var config = { + userData: userList, + changeNameID: 'cryptpad-changeName' + }; + toolbar = info.realtime.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.webChannel, info.userList, config); + createChangeName('cryptpad-changeName', $bar); + /* TODO handle disconnects and such*/ + }; + + var onReady = realtimeOptions.onReady = function (info) { + console.log("Unlocking editor"); + initializing = false; + setEditable(true); + var shjson = info.realtime.getUserDoc(); + applyHjson(shjson); + }; + + var onAbort = realtimeOptions.onAbort = function (info) { + console.log("Aborting the session!"); + // stop the user from continuing to edit + setEditable(false); + // TODO inform them that the session was torn down + toolbar.failed(); + }; + + + + + + var rti = module.realtimeInput = realtimeInput.start(realtimeOptions); + + /* catch `type="_moz"` before it goes over the wire */ + var brFilter = function (hj) { + if (hj[1].type === '_moz') { hj[1].type = undefined; } + return hj; + }; + + // $textarea.val(JSON.stringify(Convert.dom.to.hjson(inner))); + + /* It's incredibly important that you assign 'rti.onLocal' + It's used inside of realtimeInput to make sure that all changes + make it into chainpad. + + It's being assigned this way because it can't be passed in, and + and can't be easily returned from realtime input without making + the code less extensible. + */ + var propogate = rti.onLocal = function () { + /* if the problem were a matter of external patches being + applied while a local patch were in progress, then we would + expect to be able to check and find + 'module.localChangeInProgress' with a non-zero value while + we were applying a remote change. + */ + var hjson = Hyperjson.fromDOM(inner, isNotMagicLine, brFilter); + if(Object.keys(myData).length > 0) { + hjson[hjson.length] = {metadata: userList}; + } + var shjson = JSON.stringify(hjson); + if (!rti.patchText(shjson)) { + return; + } + rti.onEvent(shjson); + }; + + /* hitting enter makes a new line, but places the cursor inside + of the
instead of the

. This makes it such that you + cannot type until you click, which is rather unnacceptable. + If the cursor is ever inside such a
, you probably want + to push it out to the parent element, which ought to be a + paragraph tag. This needs to be done on keydown, otherwise + the first such keypress will not be inserted into the P. */ + inner.addEventListener('keydown', cursor.brFix); + + editor.on('change', propogate); + // editor.on('change', function () { + // var hjson = Convert.core.hyperjson.fromDOM(inner); + // if(myData !== {}) { + // hjson[hjson.length] = {metadata: userList}; + // } + // $textarea.val(JSON.stringify(hjson)); + // rti.bumpSharejs(); + // }); + }); + }; + + var interval = 100; + var first = function () { + Ckeditor = ifrw.CKEDITOR; + if (Ckeditor) { + andThen(Ckeditor); + } else { + console.log("Ckeditor was not defined. Trying again in %sms",interval); + setTimeout(first, interval); + } + }; + + $(first); +}); diff --git a/www/padrtc/netflux.js b/www/padrtc/netflux.js new file mode 100644 index 000000000..d01302980 --- /dev/null +++ b/www/padrtc/netflux.js @@ -0,0 +1,1473 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["nf"] = factory(); + else + root["nf"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _Facade = __webpack_require__(1); + + var _Facade2 = _interopRequireDefault(_Facade); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + module.exports = new _Facade2.default(); + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _WebChannel = __webpack_require__(2); + + var _WebChannel2 = _interopRequireDefault(_WebChannel); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Facade = function () { + function Facade() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, Facade); + + this.defaults = { + webrtc: {} + }; + this.settings = Object.assign({}, this.defaults, options); + } + + _createClass(Facade, [{ + key: 'create', + value: function create() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + return new _WebChannel2.default(); + } + }, { + key: 'join', + value: function join(key) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var defaults = { + connector: 'WebRTCService', + protocol: 'ExchangeProtocolService' + }; + var settings = Object.assign({}, defaults, options); + var connector = _ServiceProvider2.default.get(settings.connector); + var protocol = _ServiceProvider2.default.get(settings.protocol); + var connectorOptions = { signaling: settings.signaling, facade: this }; + return new Promise(function (resolve, reject) { + connector.join(key, connectorOptions).then(function (channel) { + var webChannel = new _WebChannel2.default(options); + channel.webChannel = webChannel; + channel.onmessage = protocol.onmessage; + webChannel.channels.add(channel); + webChannel.onopen = function () { + resolve(webChannel); + }; + }, reject); + }); + } + }, { + key: 'invite', + value: function invite() { + // TODO + } + }, { + key: '_onJoining', + value: function _onJoining() { + // TODO + } + }, { + key: '_onLeaving', + value: function _onLeaving() { + // TODO + } + }, { + key: '_onMessage', + value: function _onMessage() { + // TODO + } + }, { + key: '_onPeerMessage', + value: function _onPeerMessage() { + // TODO + } + }, { + key: '_onInvite', + value: function _onInvite() { + // TODO + } + }]); + + return Facade; + }(); + + exports.default = Facade; + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var WebChannel = function () { + function WebChannel() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, WebChannel); + + this.defaults = { + connector: cs.WEBRTC_SERVICE, + topology: cs.FULLYCONNECTED_SERVICE, + protocol: cs.EXCHANGEPROTOCOL_SERVICE + }; + this.settings = Object.assign({}, this.defaults, options); + + // Private attributes + this.protocol = cs.EXCHANGEPROTOCOL_SERVICE; + + // Public attributes + this.id; + this.myID = this._generateID(); + this.channels = new Set(); + this.onjoining; + this.onleaving; + this.onmessage; + } + + _createClass(WebChannel, [{ + key: 'leave', + value: function leave() {} + }, { + key: 'send', + value: function send(data) { + var channel = this; + return new Promise(function (resolve, reject) { + if (channel.channels.size === 0) { + resolve(); + } + var protocol = _ServiceProvider2.default.get(channel.settings.protocol); + channel.topologyService.broadcast(channel, protocol.message(cs.USER_DATA, { id: channel.myID, data: data })).then(resolve, reject); + }); + } + }, { + key: 'sendPing', + value: function sendPing() { + var channel = this; + return new Promise(function (resolve, reject) { + if (channel.channels.size === 0) { + resolve(); + } + var protocol = _ServiceProvider2.default.get(channel.settings.protocol); + channel.topologyService.broadcast(channel, protocol.message(cs.PING, { data: '' })).then(resolve, reject); + }); + } + }, { + key: 'getHistory', + value: function getHistory(historyKeeperID) { + var channel = this; + return new Promise(function (resolve, reject) { + var protocol = _ServiceProvider2.default.get(channel.settings.protocol); + channel.topologyService.sendTo(historyKeeperID, channel, protocol.message(cs.GET_HISTORY, { id: channel.myID, data: '' })).then(resolve, reject); + }); + } + }, { + key: 'sendTo', + value: function sendTo(id, msg) { + var channel = this; + return new Promise(function (resolve, reject) { + var protocol = _ServiceProvider2.default.get(channel.settings.protocol); + channel.topologyService.sendTo(id, channel, protocol.message(cs.USER_DATA, { id: channel.myID, data: msg })).then(resolve, reject); + }); + } + }, { + key: 'openForJoining', + value: function openForJoining() { + var _this = this; + + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var settings = Object.assign({}, this.settings, options); + var connector = _ServiceProvider2.default.get(settings.connector); + return connector.open(function (channel) { + // 1) New dataChannel connection established. + // NEXT: add it to the network + var protocol = _ServiceProvider2.default.get(_this.protocol); + _this.topologyService = _ServiceProvider2.default.get(_this.settings.topology); + channel.webChannel = _this; + channel.onmessage = protocol.onmessage; + + // 2.1) Send to the new client the webChannel topology name + channel.send(protocol.message(cs.JOIN_START, _this.settings.topology)); + + // 2.2) Ask to topology to add the new client to this webChannel + _this.topologyService.addStart(channel, _this).then(function (id) { + _this.topologyService.broadcast(_this, protocol.message(cs.JOIN_FINISH, id)); + _this.onJoining(id); + }); + }, settings).then(function (data) { + return data; + }); + } + }, { + key: 'closeForJoining', + value: function closeForJoining() {} + }, { + key: 'isInviting', + value: function isInviting() {} + }, { + key: '_generateID', + value: function _generateID() { + var MIN_LENGTH = 10; + var DELTA_LENGTH = 10; + var MASK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + var result = ''; + var length = MIN_LENGTH + Math.round(Math.random() * DELTA_LENGTH); + + for (var i = 0; i < length; i++) { + result += MASK[Math.round(Math.random() * (MASK.length - 1))]; + } + return result; + } + }, { + key: 'topology', + set: function set(topologyServiceName) { + this.settings.topology = topologyServiceName; + this.topologyService = _ServiceProvider2.default.get(topologyServiceName); + }, + get: function get() { + return this.settigns.topology; + } + }]); + + return WebChannel; + }(); + + exports.default = WebChannel; + +/***/ }, +/* 3 */ +/***/ function(module, exports) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + // API user's message + var USER_DATA = exports.USER_DATA = 0; + var GET_HISTORY = exports.GET_HISTORY = 6; + + // Internal messages + var JOIN_START = exports.JOIN_START = 2; + var JOIN_FINISH = exports.JOIN_FINISH = 4; + var YOUR_NEW_ID = exports.YOUR_NEW_ID = 5; + var PING = exports.PING = 7; + + // Internal message to a specific Service + var SERVICE_DATA = exports.SERVICE_DATA = 3; + + var WEBRTC_SERVICE = exports.WEBRTC_SERVICE = 'WebRTCService'; + var WEBSOCKET_SERVICE = exports.WEBSOCKET_SERVICE = 'WebSocketService'; + var FULLYCONNECTED_SERVICE = exports.FULLYCONNECTED_SERVICE = 'FullyConnectedService'; + var STAR_SERVICE = exports.STAR_SERVICE = 'StarTopologyService'; + var EXCHANGEPROTOCOL_SERVICE = exports.EXCHANGEPROTOCOL_SERVICE = 'ExchangeProtocolService'; + var WSPROTOCOL_SERVICE = exports.WSPROTOCOL_SERVICE = 'WebSocketProtocolService'; + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _FullyConnectedService = __webpack_require__(5); + + var _FullyConnectedService2 = _interopRequireDefault(_FullyConnectedService); + + var _StarTopologyService = __webpack_require__(6); + + var _StarTopologyService2 = _interopRequireDefault(_StarTopologyService); + + var _WebRTCService = __webpack_require__(7); + + var _WebRTCService2 = _interopRequireDefault(_WebRTCService); + + var _WebSocketService = __webpack_require__(8); + + var _WebSocketService2 = _interopRequireDefault(_WebSocketService); + + var _ExchangeProtocolService = __webpack_require__(9); + + var _ExchangeProtocolService2 = _interopRequireDefault(_ExchangeProtocolService); + + var _WebSocketProtocolService = __webpack_require__(10); + + var _WebSocketProtocolService2 = _interopRequireDefault(_WebSocketProtocolService); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var services = new Map(); + + var ServiceProvider = function () { + function ServiceProvider() { + _classCallCheck(this, ServiceProvider); + } + + _createClass(ServiceProvider, null, [{ + key: 'get', + value: function get(code) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var service = undefined; + switch (code) { + case cs.WEBRTC_SERVICE: + service = new _WebRTCService2.default(options); + break; + case cs.WEBSOCKET_SERVICE: + service = new _WebSocketService2.default(options); + break; + case cs.FULLYCONNECTED_SERVICE: + service = new _FullyConnectedService2.default(options); + break; + case cs.STAR_SERVICE: + service = new _StarTopologyService2.default(options); + break; + case cs.EXCHANGEPROTOCOL_SERVICE: + service = new _ExchangeProtocolService2.default(options); + break; + case cs.WSPROTOCOL_SERVICE: + service = new _WebSocketProtocolService2.default(options); + break; + } + services.set(code, service); + return service; + } + }]); + + return ServiceProvider; + }(); + + exports.default = ServiceProvider; + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var FullyConnectedService = function () { + function FullyConnectedService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, FullyConnectedService); + } + + _createClass(FullyConnectedService, [{ + key: 'addStart', + value: function addStart(channel, webChannel) { + var _this = this; + + var protocol = _ServiceProvider2.default.get(cs.EXCHANGEPROTOCOL_SERVICE); + return new Promise(function (resolve, reject) { + channel.peerID = _this._generateID(); + channel.send(protocol.message(cs.YOUR_NEW_ID, { + newID: channel.peerID, + myID: webChannel.myID + })); + if (Reflect.has(webChannel, 'aboutToJoin') && webChannel.aboutToJoin instanceof Map) { + webChannel.aboutToJoin.set(channel.peerID, channel); + } else { + webChannel.aboutToJoin = new Map(); + } + + if (webChannel.channels.size === 0) { + webChannel.channels.add(channel); + channel.onclose = function () { + webChannel.onLeaving(channel.peerID); + webChannel.channels.delete(channel); + }; + resolve(channel.peerID); + } else { + (function () { + webChannel.successfullyConnected = new Map(); + webChannel.successfullyConnected.set(channel.peerID, 0); + webChannel.connectionSucceed = function (id, withId) { + var counter = webChannel.successfullyConnected.get(withId); + webChannel.successfullyConnected.set(withId, ++counter); + if (webChannel.successfullyConnected.get(withId) === webChannel.channels.size) { + _this.addFinish(webChannel, withId); + resolve(withId); + } + }; + var connector = _ServiceProvider2.default.get(cs.WEBRTC_SERVICE); + webChannel.channels.forEach(function (c) { + connector.connect(channel.peerID, webChannel, c.peerID); + }); + })(); + } + }); + } + }, { + key: 'addFinish', + value: function addFinish(webChannel, id) { + if (id != webChannel.myID) { + (function () { + var channel = webChannel.aboutToJoin.get(id); + webChannel.channels.add(webChannel.aboutToJoin.get(id)); + channel.onclose = function () { + webChannel.onLeaving(channel.peerID); + webChannel.channels.delete(channel); + }; + //webChannel.aboutToJoin.delete(id) + if (Reflect.has(webChannel, 'successfullyConnected')) { + webChannel.successfullyConnected.delete(id); + } + })(); + } else { + webChannel.onopen(); + } + } + }, { + key: 'broadcast', + value: function broadcast(webChannel, data) { + return new Promise(function (resolve, reject) { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = webChannel.channels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var c = _step.value; + + c.send(data); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + resolve(); + }); + } + }, { + key: 'sendTo', + value: function sendTo(id, webChannel, data) { + return new Promise(function (resolve, reject) { + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = webChannel.channels[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var c = _step2.value; + + if (c.peerID == id) { + c.send(data); + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + resolve(); + }); + } + }, { + key: 'leave', + value: function leave(webChannel) { + this.broadcast(webChannel); + } + }, { + key: '_generateID', + value: function _generateID() { + var MIN_LENGTH = 10; + var DELTA_LENGTH = 10; + var MASK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + var length = MIN_LENGTH + Math.round(Math.random() * DELTA_LENGTH); + var maskLastIndex = MASK.length - 1; + var result = ''; + + for (var i = 0; i < length; i++) { + result += MASK[Math.round(Math.random() * maskLastIndex)]; + } + return result; + } + }]); + + return FullyConnectedService; + }(); + + exports.default = FullyConnectedService; + +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var StarTopologyService = function () { + function StarTopologyService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, StarTopologyService); + } + + _createClass(StarTopologyService, [{ + key: 'broadcast', + value: function broadcast(webChannel, data) { + return new Promise(function (resolve, reject) { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = webChannel.channels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var c = _step.value; + + var msg = undefined; + // Create the string message + var date = new Date().getTime(); + if (data.type === 'PING') { + msg = JSON.stringify([c.seq++, 'PING', date]); + } else { + msg = JSON.stringify([c.seq++, data.type, webChannel.id, data.msg]); + } + // Store the message with his sequence number to know what message has caused the reception of an ACK + // This is used in WebSocketProtocolService + var srvMsg = JSON.parse(msg); + srvMsg.shift(); + srvMsg.unshift(webChannel.myID); + srvMsg.unshift(0); + webChannel.waitingAck[c.seq - 1] = { resolve: resolve, reject: reject, time: date, data: srvMsg }; + // Send the message to the server + c.send(msg); + } + // resolve(); + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + }); + } + }, { + key: 'sendTo', + value: function sendTo(id, webChannel, data) { + return new Promise(function (resolve, reject) { + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = webChannel.channels[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var c = _step2.value; + + var msg = JSON.stringify([c.seq++, data.type, id, data.msg]); + c.send(msg); + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + resolve(); + }); + } + }]); + + return StarTopologyService; + }(); + + exports.default = StarTopologyService; + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var WEBRTC_DATA = 0; + var CONNECT_WITH = 1; + var CONNECT_WITH_SUCCEED = 2; + + var WebRTCService = function () { + function WebRTCService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, WebRTCService); + + this.NAME = this.constructor.name; + this.protocol = _ServiceProvider2.default.get(cs.EXCHANGEPROTOCOL_SERVICE); + this.defaults = { + signaling: 'ws://localhost:8000', + webRTCOptions: { + iceServers: [{ + urls: ['stun:23.21.150.121', 'stun:stun.l.google.com:19302'] + }, { + urls: 'turn:numb.viagenie.ca', + credential: 'webrtcdemo', + username: 'louis%40mozilla.com' + }] + } + }; + this.settings = Object.assign({}, this.defaults, options); + + // Declare WebRTC related global(window) constructors + this.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.msRTCPeerConnection; + + this.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate || window.msRTCIceCandidate; + + this.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription || window.msRTCSessionDescription; + } + + _createClass(WebRTCService, [{ + key: 'connect', + value: function connect(newPeerID, webChannel, peerID) { + webChannel.topologyService.sendTo(peerID, webChannel, this._msg(CONNECT_WITH, { key: newPeerID, intermediaryID: webChannel.myID })); + } + }, { + key: 'open', + value: function open(onchannel) { + var _this = this; + + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var defaults = { + key: this._randomKey() + }; + var settings = Object.assign({}, this.settings, defaults, options); + + return new Promise(function (resolve, reject) { + var connections = []; + var socket = new window.WebSocket(settings.signaling); + socket.onopen = function () { + socket.send(JSON.stringify({ key: settings.key })); + resolve({ url: _this.settings.signaling, key: settings.key }); + }; + socket.onmessage = function (e) { + var msg = JSON.parse(e.data); + if (Reflect.has(msg, 'id') && Reflect.has(msg, 'data')) { + if (Reflect.has(msg.data, 'offer')) { + (function () { + var connection = new _this.RTCPeerConnection(settings.webRTCOptions); + connections.push(connection); + connection.ondatachannel = function (e) { + e.channel.onopen = function () { + onchannel(e.channel); + }; + }; + connection.onicecandidate = function (e) { + if (e.candidate !== null) { + var candidate = { + candidate: e.candidate.candidate, + sdpMLineIndex: e.candidate.sdpMLineIndex + }; + socket.send(JSON.stringify({ id: msg.id, data: { candidate: candidate } })); + } + }; + var sd = Object.assign(new _this.RTCSessionDescription(), msg.data.offer); + connection.setRemoteDescription(sd, function () { + connection.createAnswer(function (answer) { + connection.setLocalDescription(answer, function () { + socket.send(JSON.stringify({ + id: msg.id, + data: { answer: connection.localDescription.toJSON() } })); + }, function () {}); + }, function () {}); + }, function () {}); + })(); + } else if (Reflect.has(msg.data, 'candidate')) { + var candidate = new _this.RTCIceCandidate(msg.data.candidate); + connections[msg.id].addIceCandidate(candidate); + } + } + }; + socket.onerror = reject; + }); + } + }, { + key: 'join', + value: function join(key) { + var _this2 = this; + + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var settings = Object.assign({}, this.settings, options); + return new Promise(function (resolve, reject) { + var connection = undefined; + var socket = new window.WebSocket(settings.signaling); + socket.onopen = function () { + connection = new _this2.RTCPeerConnection(settings.webRTCOptions); + connection.onicecandidate = function (e) { + if (e.candidate !== null) { + var candidate = { + candidate: e.candidate.candidate, + sdpMLineIndex: e.candidate.sdpMLineIndex + }; + socket.send(JSON.stringify({ data: { candidate: candidate } })); + } + }; + var dc = connection.createDataChannel(key); + dc.onopen = function () { + resolve(dc); + }; + connection.createOffer(function (offer) { + connection.setLocalDescription(offer, function () { + socket.send(JSON.stringify({ join: key, data: { offer: connection.localDescription.toJSON() } })); + }, reject); + }, reject); + }; + socket.onclose = function (e) { + reject(e); + }; + socket.onmessage = function (e) { + var msg = JSON.parse(e.data); + if (Reflect.has(msg, 'data')) { + if (Reflect.has(msg.data, 'answer')) { + var sd = Object.assign(new _this2.RTCSessionDescription(), msg.data.answer); + connection.setRemoteDescription(sd, function () {}, reject); + } else if (Reflect.has(msg.data, 'candidate')) { + var candidate = new _this2.RTCIceCandidate(msg.data.candidate); + connection.addIceCandidate(candidate); + } else { + reject(); + } + } else { + reject(); + } + }; + socket.onerror = reject; + }); + } + }, { + key: '_randomKey', + value: function _randomKey() { + var MIN_LENGTH = 10; + var DELTA_LENGTH = 10; + var MASK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + var result = ''; + var length = MIN_LENGTH + Math.round(Math.random() * DELTA_LENGTH); + + for (var i = 0; i < length; i++) { + result += MASK[Math.round(Math.random() * (MASK.length - 1))]; + } + return result; + } + }, { + key: '_msg', + value: function _msg(code, data) { + var msg = { service: this.constructor.name }; + msg.data = {}; + msg.data.code = code; + Object.assign(msg.data, data); + return this.protocol.message(cs.SERVICE_DATA, msg); + } + }, { + key: 'onmessage', + value: function onmessage(channel, msg) { + var _this3 = this; + + var webChannel = channel.webChannel; + if (!Reflect.has(webChannel, 'connections')) { + webChannel.connections = new Map(); + } + switch (msg.code) { + case WEBRTC_DATA: + if (webChannel.myID === msg.recipientPeerID) { + if (Reflect.has(msg, 'sdp')) { + if (msg.sdp.type === 'offer') { + (function () { + var connection = new _this3.RTCPeerConnection(_this3.settings.webRTCOptions); + webChannel.connections.set(msg.senderPeerID, connection); + connection.ondatachannel = function (e) { + e.channel.onopen = function () { + e.channel.peerID = msg.senderPeerID; + e.channel.webChannel = webChannel; + e.channel.onmessage = _this3.protocol.onmessage; + webChannel.channels.add(e.channel); + e.channel.onclose = function () { + webChannel.onLeaving(e.channel.peerID); + webChannel.channels.delete(e.channel); + }; + }; + }; + connection.onicecandidate = function (e) { + if (e.candidate !== null) { + var candidate = { + candidate: e.candidate.candidate, + sdpMLineIndex: e.candidate.sdpMLineIndex + }; + channel.send(_this3._msg(WEBRTC_DATA, { + senderPeerID: webChannel.myID, + recipientPeerID: msg.senderPeerID, + candidate: candidate + })); + } + }; + var sd = Object.assign(new _this3.RTCSessionDescription(), msg.sdp); + connection.setRemoteDescription(sd, function () { + connection.createAnswer(function (answer) { + connection.setLocalDescription(answer, function () { + channel.send(_this3._msg(WEBRTC_DATA, { + senderPeerID: webChannel.myID, + recipientPeerID: msg.senderPeerID, + sdp: connection.localDescription.toJSON() + })); + }, function () {}); + }, function () {}); + }, function () {}); + })(); + } else if (msg.sdp.type === 'answer') { + var sd = Object.assign(new this.RTCSessionDescription(), msg.sdp); + webChannel.connections.get(msg.senderPeerID).setRemoteDescription(sd, function () {}, function () {}); + } + } else if (Reflect.has(msg, 'candidate')) { + webChannel.connections.get(msg.senderPeerID).addIceCandidate(new this.RTCIceCandidate(msg.candidate)); + } + } else { + var data = this._msg(WEBRTC_DATA, msg); + if (webChannel.aboutToJoin.has(msg.recipientPeerID)) { + webChannel.aboutToJoin.get(msg.recipientPeerID).send(data); + } else { + webChannel.topologyService.sendTo(msg.recipientPeerID, webChannel, data); + } + } + break; + case CONNECT_WITH: + var connection = new this.RTCPeerConnection(this.settings.webRTCOptions); + connection.onicecandidate = function (e) { + if (e.candidate !== null) { + var candidate = { + candidate: e.candidate.candidate, + sdpMLineIndex: e.candidate.sdpMLineIndex + }; + webChannel.topologyService.sendTo(msg.intermediaryID, webChannel, _this3._msg(WEBRTC_DATA, { + senderPeerID: webChannel.myID, + recipientPeerID: msg.key, + candidate: candidate + })); + } + }; + var dc = connection.createDataChannel(msg.key); + dc.onopen = function () { + if (!Reflect.has(webChannel, 'aboutToJoin')) { + webChannel.aboutToJoin = new Map(); + } + webChannel.aboutToJoin.set(dc.label, dc); + dc.onmessage = _this3.protocol.onmessage; + dc.peerID = dc.label; + dc.webChannel = webChannel; + webChannel.topologyService.sendTo(msg.intermediaryID, webChannel, _this3._msg(CONNECT_WITH_SUCCEED, { + senderPeerID: webChannel.myID, + recipientPeerID: dc.label + })); + }; + connection.createOffer(function (offer) { + connection.setLocalDescription(offer, function () { + webChannel.topologyService.sendTo(msg.intermediaryID, webChannel, _this3._msg(WEBRTC_DATA, { + senderPeerID: webChannel.myID, + recipientPeerID: msg.key, + sdp: connection.localDescription.toJSON() + })); + webChannel.connections.set(msg.key, connection); + }, function () {}); + }, function () {}); + break; + case CONNECT_WITH_SUCCEED: + webChannel.connectionSucceed(msg.senderPeerID, msg.recipientPeerID); + break; + } + } + }]); + + return WebRTCService; + }(); + + exports.default = WebRTCService; + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var WebSocketService = function () { + function WebSocketService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, WebSocketService); + + this.NAME = this.constructor.name; + this.protocol = _ServiceProvider2.default.get(cs.EXCHANGEPROTOCOL_SERVICE); + this.defaults = { + signaling: 'ws://localhost:9000', + REQUEST_TIMEOUT: 5000 + }; + this.settings = Object.assign({}, this.defaults, options); + } + + _createClass(WebSocketService, [{ + key: 'join', + value: function join(key) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var settings = Object.assign({}, this.settings, options); + return new Promise(function (resolve, reject) { + var connection = undefined; + var socket = new window.WebSocket(settings.signaling); + setInterval(function () { + if (socket.webChannel && socket.webChannel.waitingAck) { + var waitingAck = socket.webChannel.waitingAck; + for (var id in waitingAck) { + var req = waitingAck[id]; + var now = new Date().getTime(); + if (now - req.time > settings.REQUEST_TIMEOUT) { + delete socket.webChannel.waitingAck[id]; + req.reject({ type: 'TIMEOUT', message: 'waited ' + now - req.time + 'ms' }); + } + } + } + }, 5000); + socket.seq = 1; + socket.facade = options.facade || null; + socket.onopen = function () { + if (key && key !== '') { + socket.send(JSON.stringify([socket.seq++, 'JOIN', key])); + } else { + socket.send(JSON.stringify([socket.seq++, 'JOIN'])); + } + resolve(socket); + }; + socket.onerror = reject; + }); + } + }]); + + return WebSocketService; + }(); + + exports.default = WebSocketService; + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var ExchangeProtocolService = function () { + function ExchangeProtocolService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, ExchangeProtocolService); + } + + _createClass(ExchangeProtocolService, [{ + key: 'onmessage', + value: function onmessage(e) { + var msg = JSON.parse(e.data); + var channel = e.currentTarget; + var webChannel = channel.webChannel; + + switch (msg.code) { + case cs.USER_DATA: + webChannel.onmessage(msg.id, msg.data); + break; + case cs.GET_HISTORY: + webChannel.onPeerMessage(msg.id, msg.code); + break; + case cs.SERVICE_DATA: + var service = _ServiceProvider2.default.get(msg.service); + service.onmessage(channel, msg.data); + break; + case cs.YOUR_NEW_ID: + // TODO: change names + webChannel.myID = msg.newID; + channel.peerID = msg.myID; + break; + case cs.JOIN_START: + // 2.1) Send to the new client the webChannel topology + webChannel.topology = msg.topology; + webChannel.topologyService = _ServiceProvider2.default.get(msg.topology); + break; + case cs.JOIN_FINISH: + webChannel.topologyService.addFinish(webChannel, msg.id); + if (msg.id != webChannel.myID) { + // A new user has just registered + webChannel.onJoining(msg.id); + } else { + (function () { + // We're fully synced, trigger onJoining for all existing users + var waitForOnJoining = function waitForOnJoining() { + if (typeof webChannel.onJoining !== "function") { + setTimeout(waitForOnJoining, 500); + return; + } + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = webChannel.channels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var c = _step.value; + + webChannel.onJoining(c.peerID); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + }; + waitForOnJoining(); + })(); + } + break; + } + } + }, { + key: 'message', + value: function message(code, data) { + var msg = { code: code }; + switch (code) { + case cs.USER_DATA: + msg.id = data.id; + msg.data = data.data; + break; + case cs.GET_HISTORY: + msg.id = data.id; + break; + case cs.SERVICE_DATA: + msg.service = data.service; + msg.data = Object.assign({}, data.data); + break; + case cs.YOUR_NEW_ID: + msg.newID = data.newID; + msg.myID = data.myID; + break; + case cs.JOIN_START: + msg.topology = data; + break; + case cs.JOIN_FINISH: + msg.id = data; + break; + } + return JSON.stringify(msg); + } + }]); + + return ExchangeProtocolService; + }(); + + exports.default = ExchangeProtocolService; + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var WebSocketProtocolService = function () { + function WebSocketProtocolService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, WebSocketProtocolService); + } + + _createClass(WebSocketProtocolService, [{ + key: 'onmessage', + value: function onmessage(e) { + var msg = JSON.parse(e.data); + var socket = e.currentTarget; + var webChannel = socket.webChannel; + var topology = cs.STAR_SERVICE; + var topologyService = _ServiceProvider2.default.get(topology); + var history_keeper = webChannel.hc; + + if (msg[0] !== 0 && msg[1] !== 'ACK') { + return; + } + + if (msg[2] === 'IDENT' && msg[1] === '') { + socket.uid = msg[3]; + webChannel.myID = msg[3]; + webChannel.peers = []; + webChannel.waitingAck = []; + webChannel.topology = topology; + return; + } + if (msg[1] === 'PING') { + msg[1] = 'PONG'; + socket.send(JSON.stringify(msg)); + return; + } + if (msg[1] === 'ACK') { + var seq = msg[0]; + if (webChannel.waitingAck[seq]) { + var waitingAck = webChannel.waitingAck[seq]; + waitingAck.resolve(); + var newMsg = waitingAck.data; + if (newMsg[2] === 'PING') { + // PING message : set the lag + var lag = new Date().getTime() - newMsg[3]; + webChannel.getLag = function () { + return lag; + }; + } + delete webChannel.waitingAck[seq]; + } + return; + } + // We have received a new direct message from another user + if (msg[2] === 'MSG' && msg[3] === socket.uid) { + // If it comes from the history keeper, send it to the user + if (msg[1] === history_keeper) { + if (msg[4] === 0) { + webChannel.onmessage(msg[1], msg[4]); + return; + } + var msgHistory = JSON.parse(msg[4]); + webChannel.onmessage(msgHistory[1], msgHistory[4]); + } + return; + } + if (msg[2] === 'JOIN' && (webChannel.id == null || webChannel.id === msg[3])) { + if (!webChannel.id) { + // New unnamed channel : get its name from the first "JOIN" message + if (!window.location.hash) { + var chanName = window.location.hash = msg[3]; + } + webChannel.id = msg[3]; + } + + if (msg[1] === socket.uid) { + // If the user catches himself registering, he is synchronized with the server + webChannel.onopen(); + } else { + // Trigger onJoining() when another user is joining the channel + // Register the user in the list of peers in the channel + if (webChannel.peers.length === 0 && msg[1].length === 16) { + // We've just catched the history keeper (16 characters length name) + history_keeper = msg[1]; + webChannel.hc = history_keeper; + } + var linkQuality = msg[1] === history_keeper ? 1000 : 0; + var sendToPeer = function sendToPeer(data) { + topologyService.sendTo(msg[1], webChannel, { type: 'MSG', msg: data }); + }; + var peer = { id: msg[1], connector: socket, linkQuality: linkQuality, send: sendToPeer }; + if (webChannel.peers.indexOf(peer) === -1) { + webChannel.peers.push(peer); + } + + if (msg[1] !== history_keeper) { + // Trigger onJoining with that peer once the function is loaded (i.e. once the channel is synced) + var waitForOnJoining = function waitForOnJoining() { + if (typeof webChannel.onJoining !== "function") { + setTimeout(waitForOnJoining, 500); + return; + } + webChannel.onJoining(msg[1]); + }; + waitForOnJoining(); + } + } + return; + } + // We have received a new message in that channel from another peer + if (msg[2] === 'MSG' && msg[3] === webChannel.id) { + // Find the peer who sent the message and display it + //TODO Use Peer instead of peer.id (msg[1]) : + if (typeof webChannel.onmessage === "function") webChannel.onmessage(msg[1], msg[4]); + return; + } + // Someone else has left the channel, remove him from the list of peers + if (msg[2] === 'LEAVE' && msg[3] === webChannel.id) { + //TODO Use Peer instead of peer.id (msg[1]) : + if (typeof webChannel.onLeaving === "function") webChannel.onLeaving(msg[1], webChannel); + return; + } + } + }, { + key: 'message', + value: function message(code, data) { + var type = undefined; + switch (code) { + case cs.USER_DATA: + type = 'MSG'; + break; + case cs.JOIN_START: + type = 'JOIN'; + break; + case cs.PING: + type = 'PING'; + break; + } + return { type: type, msg: data.data }; + } + }]); + + return WebSocketProtocolService; + }(); + + exports.default = WebSocketProtocolService; + +/***/ } +/******/ ]) +}); +; \ No newline at end of file diff --git a/www/padrtc/realtime-input.js b/www/padrtc/realtime-input.js new file mode 100644 index 000000000..af0f22a1d --- /dev/null +++ b/www/padrtc/realtime-input.js @@ -0,0 +1,397 @@ +/* + * Copyright 2014 XWiki SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +window.Reflect = { has: (x,y) => { return (y in x); } }; +define([ + '/common/messages.js', + '/padrtc/netflux.js', + '/common/crypto.js', + '/common/toolbar.js', + '/_socket/text-patcher.js', + '/common/es6-promise.min.js', + '/common/chainpad.js', + '/bower_components/jquery/dist/jquery.min.js', +], function (Messages, Netflux, Crypto, Toolbar, TextPatcher) { + var $ = window.jQuery; + var ChainPad = window.ChainPad; + var PARANOIA = true; + var module = { exports: {} }; + + /** + * If an error is encountered but it is recoverable, do not immediately fail + * but if it keeps firing errors over and over, do fail. + */ + var MAX_RECOVERABLE_ERRORS = 15; + + var debug = function (x) { console.log(x); }, + warn = function (x) { console.error(x); }, + verbose = function (x) { console.log(x); }; + verbose = function () {}; // comment out to enable verbose logging + + // ------------------ Trapping Keyboard Events ---------------------- // + + var bindEvents = function (element, events, callback, unbind) { + for (var i = 0; i < events.length; i++) { + var e = events[i]; + if (element.addEventListener) { + if (unbind) { + element.removeEventListener(e, callback, false); + } else { + element.addEventListener(e, callback, false); + } + } else { + if (unbind) { + element.detachEvent('on' + e, callback); + } else { + element.attachEvent('on' + e, callback); + } + } + } + }; + + var getParameterByName = function (name, url) { + if (!url) { url = window.location.href; } + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) { return null; } + if (!results[2]) { return ''; } + return decodeURIComponent(results[2].replace(/\+/g, " ")); + }; + + var start = module.exports.start = + function (config) + { + var websocketUrl = config.websocketURL; + var webrtcUrl = config.webrtcURL; + var userName = config.userName; + var channel = config.channel; + var chanKey = config.cryptKey; + var cryptKey = Crypto.parseKey(chanKey).cryptKey; + var passwd = 'y'; + + // make sure configuration is defined + config = config || {}; + + var doc = config.doc || null; + + var allMessages = []; + var initializing = true; + var recoverableErrorCount = 0; + var toReturn = {}; + var messagesHistory = []; + var chainpadAdapter = {}; + var realtime; + + // define this in case it gets called before the rest of our stuff is ready. + var onEvent = toReturn.onEvent = function (newText) { }; + + var parseMessage = function (msg) { + var res ={}; + // two or more? use a for + ['pass','user','channelId','content'].forEach(function(attr){ + var len=msg.slice(0,msg.indexOf(':')), + // taking an offset lets us slice out the prop + // and saves us one string copy + o=len.length+1, + prop=res[attr]=msg.slice(o,Number(len)+o); + // slice off the property and its descriptor + msg = msg.slice(prop.length+o); + }); + // content is the only attribute that's not a string + res.content=JSON.parse(res.content); + return res; + }; + + var mkMessage = function (user, chan, content) { + content = JSON.stringify(content); + return user.length + ':' + user + + chan.length + ':' + chan + + content.length + ':' + content; + }; + + var onPeerMessage = function(toId, type, wc) { + if(type === 6) { + messagesHistory.forEach(function(msg) { + wc.sendTo(toId, '1:y'+msg); + }); + wc.sendTo(toId, '0'); + } + }; + + var whoami = new RegExp(userName.replace(/[\/\+]/g, function (c) { + return '\\' +c; + })); + + var onMessage = function(peer, msg, wc) { + + if(msg === 0 || msg === '0') { + onReady(wc); + return; + } + var message = chainpadAdapter.msgIn(peer, msg); + + verbose(message); + allMessages.push(message); + // if (!initializing) { + // if (toReturn.onLocal) { + // toReturn.onLocal(); + // } + // } + realtime.message(message); + if (/\[5,/.test(message)) { verbose("pong"); } + + if (!initializing) { + if (/\[2,/.test(message)) { + //verbose("Got a patch"); + if (whoami.test(message)) { + //verbose("Received own message"); + } else { + //verbose("Received remote message"); + // obviously this is only going to get called if + if (config.onRemote) { + config.onRemote({ + realtime: realtime + }); + } + } + } + } + }; + + var userList = { + onChange : function() {}, + users: [] + }; + var onJoining = function(peer) { + var list = userList.users; + if(list.indexOf(peer) === -1) { + userList.users.push(peer); + } + userList.onChange(); + }; + + var onLeaving = function(peer) { + var list = userList.users; + var index = list.indexOf(peer); + if(index !== -1) { + userList.users.splice(index, 1); + } + userList.onChange(); + }; + + chainpadAdapter = { + msgIn : function(peerId, msg) { + var parsed = parseMessage(msg); + // Remove the password from the message + var passLen = msg.substring(0,msg.indexOf(':')); + var message = msg.substring(passLen.length+1 + Number(passLen)); + try { + var decryptedMsg = Crypto.decrypt(message, cryptKey); + messagesHistory.push(decryptedMsg); + return decryptedMsg; + } catch (err) { + return message; + } + + }, + msgOut : function(msg, wc) { + var parsed = parseMessage(msg); + if(parsed.content[0] === 0) { // We're registering : send a REGISTER_ACK to Chainpad + onMessage('', '1:y'+mkMessage('', channel, [1,0])); + return; + } + if(parsed.content[0] === 4) { // PING message from Chainpad + parsed.content[0] = 5; + onMessage('', '1:y'+mkMessage(parsed.user, parsed.channelId, parsed.content)); + wc.sendPing(); + return; + } + return Crypto.encrypt(msg, cryptKey); + } + }; + + var options = {}; + + var rtc = true; + + if(channel.trim().length > 0) { + options.key = channel; + } + if(!webrtcUrl) { + rtc = false; + options.signaling = websocketUrl; + options.topology = 'StarTopologyService'; + options.protocol = 'WebSocketProtocolService'; + options.connector = 'WebSocketService'; + options.openWebChannel = true; + } + else { + options.signaling = webrtcUrl; + } + + var createRealtime = function(chan) { + return ChainPad.create(userName, + passwd, + channel, + config.initialState || {}, + { + transformFunction: config.transformFunction + }); + }; + + var onReady = function(wc) { + if(config.onInit) { + config.onInit({ + myID: wc.myID, + realtime: realtime, + getLag: wc.getLag, + userList: userList + }); + } + // Trigger onJoining with our own Cryptpad username to tell the toolbar that we are synced + onJoining(wc.myID); + + // we're fully synced + initializing = false; + + // execute an onReady callback if one was supplied + if (config.onReady) { + config.onReady({ + realtime: realtime + }); + } + } + + var onOpen = function(wc) { + channel = wc.id; + window.location.hash = channel + '|' + chanKey; + // Add the handlers to the WebChannel + wc.onmessage = function(peer, msg) { // On receiving message + onMessage(peer, msg, wc); + }; + wc.onJoining = onJoining; // On user joining the session + wc.onLeaving = onLeaving; // On user leaving the session + wc.onPeerMessage = function(peerId, type) { + onPeerMessage(peerId, type, wc); + }; + if(config.setMyID) { + config.setMyID({ + myID: wc.myID + }); + } + // Open a Chainpad session + realtime = createRealtime(); + + // Sending a message... + realtime.onMessage(function(message) { + // Filter messages sent by Chainpad to make it compatible with Netflux + message = chainpadAdapter.msgOut(message, wc); + if(message) { + wc.send(message).then(function() { + // Send the message back to Chainpad once it is sent to the recipients. + onMessage(wc.myID, message); + }, function(err) { + // The message has not been sent, display the error. + console.error(err); + }); + } + }); + + // Get the channel history + var hc; + if(rtc) { + wc.channels.forEach(function (c) { if(!hc) { hc = c; } }); + if(hc) { + wc.getHistory(hc.peerID); + } + } + else { + // TODO : Improve WebSocket service to use the latest Netflux's API + wc.peers.forEach(function (p) { if (!hc || p.linkQuality > hc.linkQuality) { hc = p; } }); + hc.send(JSON.stringify(['GET_HISTORY', wc.id])); + } + + + toReturn.patchText = TextPatcher.create({ + realtime: realtime + }); + + realtime.start(); + }; + + var createRTCChannel = function () { + // Check if the WebRTC channel exists and create it if necessary + var webchannel = Netflux.create(); + webchannel.openForJoining(options).then(function(data) { + console.log(data); + webchannel.id = data.key + onOpen(webchannel); + onReady(webchannel); + }, function(error) { + warn(error); + }); + }; + + var joinChannel = function() { + // Connect to the WebSocket/WebRTC channel + Netflux.join(channel, options).then(function(wc) { + if(channel.trim().length > 0) { + wc.id = channel + } + onOpen(wc); + }, function(error) { + if(rtc && error.code === 1008) {// Unexisting RTC channel + createRTCChannel(); + } + else { warn(error); } + }); + }; + joinChannel(); + + var checkConnection = function(wc) { + if(wc.channels && wc.channels.size > 0) { + var channels = Array.from(wc.channels); + var channel = channels[0]; + + var socketChecker = setInterval(function () { + if (channel.checkSocket(realtime)) { + warn("Socket disconnected!"); + + recoverableErrorCount += 1; + + if (recoverableErrorCount >= MAX_RECOVERABLE_ERRORS) { + warn("Giving up!"); + realtime.abort(); + try { channel.close(); } catch (e) { warn(e); } + if (config.onAbort) { + config.onAbort({ + socket: channel + }); + } + if (socketChecker) { clearInterval(socketChecker); } + } + } else { + // it's working as expected, continue + } + }, 200); + } + }; + + return toReturn; + }; + return module.exports; +}); From 692fe24b323c0635b9f2f895e30baa933d44accc Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 11 Apr 2016 14:27:58 +0200 Subject: [PATCH 42/65] Ability to use the old Chainpad server in parallel of the netflux server --- ChainPadSrv.js | 1 + server.js | 1 + 2 files changed, 2 insertions(+) diff --git a/ChainPadSrv.js b/ChainPadSrv.js index 4ed6ff840..e2d7a9ef6 100644 --- a/ChainPadSrv.js +++ b/ChainPadSrv.js @@ -177,6 +177,7 @@ var create = module.exports.create = function (socketServer, store) { }; socketServer.on('connection', function(socket) { + if(socket.upgradeReq.url !== '/cryptpad_websocket_old') { return; } socket.on('message', function(message) { try { handleMessage(ctx, socket, message); diff --git a/server.js b/server.js index ce7d6ec49..f3d472dea 100644 --- a/server.js +++ b/server.js @@ -84,5 +84,6 @@ var wsSrv = new WebSocketServer(wsConfig); Storage.create(config, function (store) { console.log('DB connected'); NetfluxSrv.run(store, wsSrv); + ChainPadSrv.create(wsSrv, store); WebRTCSrv.run(wsSrv); }); From c53baab99dcad689e6a40fe545a74877614960bb Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 11 Apr 2016 14:43:39 +0200 Subject: [PATCH 43/65] Update Codepad with the latest improvements --- www/code/html-patcher.js | 483 --- www/code/main.js | 6 +- www/code/rangy.js | 3743 ---------------------- www/code/realtime-wysiwyg.js | 358 --- www/code/{rtwiki.js => rt_codemirror.js} | 458 +-- www/code/sharejs_textarea.js | 263 -- www/code/toolbar.js | 246 ++ 7 files changed, 253 insertions(+), 5304 deletions(-) delete mode 100644 www/code/html-patcher.js delete mode 100644 www/code/rangy.js delete mode 100644 www/code/realtime-wysiwyg.js rename www/code/{rtwiki.js => rt_codemirror.js} (50%) delete mode 100644 www/code/sharejs_textarea.js create mode 100644 www/code/toolbar.js diff --git a/www/code/html-patcher.js b/www/code/html-patcher.js deleted file mode 100644 index d36552d85..000000000 --- a/www/code/html-patcher.js +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright 2014 XWiki SAS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -define([ - '/bower_components/jquery/dist/jquery.min.js', - '/common/otaml.js' -], function () { - - var $ = window.jQuery; - var Otaml = window.Otaml; - var module = { exports: {} }; - var PARANOIA = true; - - var debug = function (x) { }; - debug = function (x) { console.log(x); }; - - var getNextSiblingDeep = function (node, parent) - { - if (node.firstChild) { return node.firstChild; } - do { - if (node.nextSibling) { return node.nextSibling; } - node = node.parentNode; - } while (node && node !== parent); - }; - - var getOuterHTML = function (node) - { - var html = node.outerHTML; - if (html) { return html; } - if (node.parentNode && node.parentNode.childNodes.length === 1) { - return node.parentNode.innerHTML; - } - var div = document.createElement('div'); - div.appendChild(node.cloneNode(true)); - return div.innerHTML; - }; - - var nodeFromHTML = function (html) - { - var e = document.createElement('div'); - e.innerHTML = html; - return e.childNodes[0]; - }; - - var getInnerHTML = function (node) - { - var html = node.innerHTML; - if (html) { return html; } - var outerHTML = getOuterHTML(node); - var tw = Otaml.tagWidth(outerHTML); - if (!tw) { return outerHTML; } - return outerHTML.substring(tw, outerHTML.lastIndexOf(''; - if (PARANOIA && spanHTML !== span.outerHTML) { throw new Error(); } - - node.parentNode.insertBefore(span, node); - var newDocText = getInnerHTML(dom); - idx = newDocText.lastIndexOf(spanHTML); - if (idx === -1 || idx !== newDocText.indexOf(spanHTML)) { throw new Error(); } - node.parentNode.removeChild(span); - - if (PARANOIA && getInnerHTML(dom) !== docText) { throw new Error(); } - } - - if (PARANOIA && docText.indexOf(content, idx) !== idx) { throw new Error(); } - return idx; - }; - - var patchString = module.exports.patchString = function (oldString, offset, toRemove, toInsert) - { - return oldString.substring(0, offset) + toInsert + oldString.substring(offset + toRemove); - }; - - var getNodeAtOffset = function (docText, offset, dom) - { - if (PARANOIA && dom.childNodes.length && docText !== dom.innerHTML) { throw new Error(); } - if (offset < 0) { throw new Error(); } - - var idx = 0; - for (var i = 0; i < dom.childNodes.length; i++) { - var childOuterHTML = getOuterHTML(dom.childNodes[i]); - if (PARANOIA && docText.indexOf(childOuterHTML, idx) !== idx) { throw new Error(); } - if (i === 0 && idx >= offset) { - return { node: dom, pos: 0 }; - } - if (idx + childOuterHTML.length > offset) { - var childInnerHTML = childOuterHTML; - var tw = Otaml.tagWidth(childOuterHTML); - if (tw) { - childInnerHTML = childOuterHTML.substring(tw, childOuterHTML.lastIndexOf(' docText.length) { throw new Error(); } - var beforeOffset = docText.substring(0, offset); - if (beforeOffset.indexOf('&') > -1) { - var tn = nodeFromHTML(beforeOffset); - offset = tn.data.length; - } - } else { - offset = 0; - } - - return { node: dom, pos: offset }; - }; - - var relocatedPositionInNode = function (newNode, oldNode, offset) - { - if (newNode.nodeName !== '#text' || oldNode.nodeName !== '#text' || offset === 0) { - offset = 0; - } else if (oldNode.data === newNode.data) { - // fallthrough - } else if (offset > newNode.length) { - offset = newNode.length; - } else if (oldNode.data.substring(0, offset) === newNode.data.substring(0, offset)) { - // keep same offset and fall through - } else { - var rOffset = oldNode.length - offset; - if (oldNode.data.substring(offset) === - newNode.data.substring(newNode.length - rOffset)) - { - offset = newNode.length - rOffset; - } else { - offset = 0; - } - } - return { node: newNode, pos: offset }; - }; - - var pushNode = function (list, node) { - if (node.nodeName === '#text') { - list.push.apply(list, node.data.split('')); - } else { - list.push('#' + node.nodeName); - } - }; - - var getChildPath = function (parent) { - var out = []; - for (var next = parent; next; next = getNextSiblingDeep(next, parent)) { - pushNode(out, next); - } - return out; - }; - - var tryFromBeginning = function (oldPath, newPath) { - for (var i = 0; i < oldPath.length; i++) { - if (oldPath[i] !== newPath[i]) { return i; } - } - return oldPath.length; - }; - - var tryFromEnd = function (oldPath, newPath) { - for (var i = 1; i <= oldPath.length; i++) { - if (oldPath[oldPath.length - i] !== newPath[newPath.length - i]) { - return false; - } - } - return true; - }; - - /** - * returns 2 arrays (before and after). - * before is string representations (see nodeId()) of all nodes before the target - * node and after is representations of all nodes which follow. - */ - var getNodePaths = function (parent, node) { - var before = []; - var next = parent; - for (; next && next !== node; next = getNextSiblingDeep(next, parent)) { - pushNode(before, next); - } - - if (next !== node) { throw new Error(); } - - var after = []; - next = getNextSiblingDeep(next, parent); - for (; next; next = getNextSiblingDeep(next, parent)) { - pushNode(after, next); - } - - return { before: before, after: after }; - }; - - var nodeAtIndex = function (parent, idx) { - var node = parent; - for (var i = 0; i < idx; i++) { - if (node.nodeName === '#text') { - if (i + node.data.length > idx) { return node; } - i += node.data.length - 1; - } - node = getNextSiblingDeep(node); - } - return node; - }; - - var getRelocatedPosition = function (newParent, oldParent, oldNode, oldOffset, origText, op) - { - var newPath = getChildPath(newParent); - if (newPath.length === 1) { - return { node: null, pos: 0 }; - } - var oldPaths = getNodePaths(oldParent, oldNode); - - var idx = -1; - var fromBeginning = tryFromBeginning(oldPaths.before, newPath); - if (fromBeginning === oldPaths.before.length) { - idx = oldPaths.before.length; - } else if (tryFromEnd(oldPaths.after, newPath)) { - idx = (newPath.length - oldPaths.after.length - 1); - } else { - idx = fromBeginning; - var id = 'relocate-' + String(Math.random()).substring(2); - $(document.body).append(''); - $('#'+id).val(JSON.stringify([origText, op, newPath, getChildPath(oldParent), oldPaths])); - } - - var out = nodeAtIndex(newParent, idx); - return relocatedPositionInNode(out, oldNode, oldOffset); - }; - - // We can't create a real range until the new parent is installed in the document - // but we need the old range to be in the document so we can do comparisons - // so create a "pseudo" range instead. - var getRelocatedPseudoRange = function (newParent, oldParent, range, origText, op) - { - if (!range.startContainer) { - throw new Error(); - } - if (!newParent) { throw new Error(); } - - // Copy because tinkering in the dom messes up the original range. - var startContainer = range.startContainer; - var startOffset = range.startOffset; - var endContainer = range.endContainer; - var endOffset = range.endOffset; - - var newStart = - getRelocatedPosition(newParent, oldParent, startContainer, startOffset, origText, op); - - if (!newStart.node) { - // there is probably nothing left of the document so just clear the selection. - endContainer = null; - } - - var newEnd = { node: newStart.node, pos: newStart.pos }; - if (endContainer) { - if (endContainer !== startContainer) { - newEnd = getRelocatedPosition(newParent, oldParent, endContainer, endOffset, origText, op); - } else if (endOffset !== startOffset) { - newEnd = { - node: newStart.node, - pos: relocatedPositionInNode(newStart.node, endContainer, endOffset).pos - }; - } else { - newEnd = { node: newStart.node, pos: newStart.pos }; - } - } - - return { start: newStart, end: newEnd }; - }; - - var replaceAllChildren = function (parent, newParent) - { - var c; - while ((c = parent.firstChild)) { - parent.removeChild(c); - } - while ((c = newParent.firstChild)) { - newParent.removeChild(c); - parent.appendChild(c); - } - }; - - var isAncestorOf = function (maybeDecendent, maybeAncestor) { - while ((maybeDecendent = maybeDecendent.parentNode)) { - if (maybeDecendent === maybeAncestor) { return true; } - } - return false; - }; - - var getSelectedRange = function (rangy, ifrWindow, selection) { - selection = selection || rangy.getSelection(ifrWindow); - if (selection.rangeCount === 0) { - return; - } - var range = selection.getRangeAt(0); - range.backward = (selection.rangeCount === 1 && selection.isBackward()); - if (!range.startContainer) { - throw new Error(); - } - - // Occasionally, some browsers *cough* firefox *cough* will attach the range to something - // which has been used in the past but is nolonger part of the dom... - if (range.startContainer && - isAncestorOf(range.startContainer, ifrWindow.document)) - { - return range; - } - - return; - }; - - var applyHTMLOp = function (docText, op, dom, rangy, ifrWindow) - { - var parent = getNodeAtOffset(docText, op.offset, dom).node; - var htmlToRemove = docText.substring(op.offset, op.offset + op.toRemove); - - var parentInnerHTML; - var indexOfInnerHTML; - var localOffset; - for (;;) { - for (;;) { - parentInnerHTML = parent.innerHTML; - if (typeof(parentInnerHTML) !== 'undefined' - && parentInnerHTML.indexOf(htmlToRemove) !== -1) - { - break; - } - if (parent === dom || !(parent = parent.parentNode)) { throw new Error(); } - } - - var indexOfOuterHTML = 0; - var tw = 0; - if (parent !== dom) { - indexOfOuterHTML = offsetOfNodeOuterHTML(docText, parent, dom, ifrWindow); - tw = Otaml.tagWidth(docText.substring(indexOfOuterHTML)); - } - indexOfInnerHTML = indexOfOuterHTML + tw; - - localOffset = op.offset - indexOfInnerHTML; - - if (localOffset >= 0 && localOffset + op.toRemove <= parentInnerHTML.length) { - break; - } - - parent = parent.parentNode; - if (!parent) { throw new Error(); } - } - - if (PARANOIA && - docText.substr(indexOfInnerHTML, parentInnerHTML.length) !== parentInnerHTML) - { - throw new Error(); - } - - var newParentInnerHTML = - patchString(parentInnerHTML, localOffset, op.toRemove, op.toInsert); - - // Create a temp container for holding the children of the parent node. - // Once we've identified the new range, we'll return the nodes to the - // original parent. This is because parent might be the and we - // don't want to destroy all of our event listeners. - var babysitter = ifrWindow.document.createElement('div'); - // give it a uid so that we can prove later that it's not in the document, - // see getSelectedRange() - babysitter.setAttribute('id', uniqueId()); - babysitter.innerHTML = newParentInnerHTML; - - var range = getSelectedRange(rangy, ifrWindow); - - // doesn't intersect at all - if (!range || !range.containsNode(parent, true)) { - replaceAllChildren(parent, babysitter); - return; - } - - var pseudoRange = getRelocatedPseudoRange(babysitter, parent, range, rangy); - range.detach(); - replaceAllChildren(parent, babysitter); - if (pseudoRange.start.node) { - var selection = rangy.getSelection(ifrWindow); - var newRange = rangy.createRange(); - newRange.setStart(pseudoRange.start.node, pseudoRange.start.pos); - newRange.setEnd(pseudoRange.end.node, pseudoRange.end.pos); - selection.setSingleRange(newRange); - } - return; - }; - - var applyHTMLOpHammer = function (docText, op, dom, rangy, ifrWindow) - { - var newDocText = patchString(docText, op.offset, op.toRemove, op.toInsert); - var babysitter = ifrWindow.document.createElement('body'); - // give it a uid so that we can prove later that it's not in the document, - // see getSelectedRange() - babysitter.setAttribute('id', uniqueId()); - babysitter.innerHTML = newDocText; - - var range = getSelectedRange(rangy, ifrWindow); - - // doesn't intersect at all - if (!range) { - replaceAllChildren(dom, babysitter); - return; - } - - var pseudoRange = getRelocatedPseudoRange(babysitter, dom, range, docText, op); - range.detach(); - replaceAllChildren(dom, babysitter); - if (pseudoRange.start.node) { - var selection = rangy.getSelection(ifrWindow); - var newRange = rangy.createRange(); - newRange.setStart(pseudoRange.start.node, pseudoRange.start.pos); - newRange.setEnd(pseudoRange.end.node, pseudoRange.end.pos); - selection.setSingleRange(newRange); - } - return; - }; - - /* Return whether the selection range has been "dirtied" and needs to be reloaded. */ - var applyOp = module.exports.applyOp = function (docText, op, dom, rangy, ifrWindow) - { - if (PARANOIA && docText !== getInnerHTML(dom)) { throw new Error(); } - - if (op.offset + op.toRemove > docText.length) { - throw new Error(); - } - try { - applyHTMLOpHammer(docText, op, dom, rangy, ifrWindow); - var result = patchString(docText, op.offset, op.toRemove, op.toInsert); - var innerHTML = getInnerHTML(dom); - if (result !== innerHTML) { - $(document.body).append(''); - $(document.body).append(''); - var SEP = '\n\n\n\n\n\n\n\n\n\n'; - $('#statebox').val(docText + SEP + result + SEP + innerHTML); - var diff = Otaml.makeTextOperation(result, innerHTML); - $('#errorbox').val(JSON.stringify(op) + '\n' + JSON.stringify(diff)); - throw new Error(); - } - } catch (err) { - if (PARANOIA) { console.log(err.stack); } - // The big hammer - dom.innerHTML = patchString(docText, op.offset, op.toRemove, op.toInsert); - } - }; - - return module.exports; -}); diff --git a/www/code/main.js b/www/code/main.js index 12804cbb0..9f5d1fa1c 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -1,10 +1,10 @@ define([ '/api/config?cb=' + Math.random().toString(16).substring(2), - '/code/rtwiki.js', + '/code/rt_codemirror.js', '/common/messages.js', '/common/crypto.js', '/bower_components/jquery/dist/jquery.min.js' -], function (Config, RTWiki, Messages, Crypto) { +], function (Config, RTCode, Messages, Crypto) { var $ = window.jQuery; var ifrw = $('#pad-iframe')[0].contentWindow; @@ -36,7 +36,7 @@ define([ editor.setValue(Messages.codeInitialState); var rtw = - RTWiki.start(ifrw, + RTCode.start(ifrw, Config.websocketURL, Crypto.rand64(8), key.channel, diff --git a/www/code/rangy.js b/www/code/rangy.js deleted file mode 100644 index 787e2b88e..000000000 --- a/www/code/rangy.js +++ /dev/null @@ -1,3743 +0,0 @@ -/** - * Rangy, a cross-browser JavaScript range and selection library - * http://code.google.com/p/rangy/ - * - * Copyright 2013, Tim Down - * Licensed under the MIT license. - * Version: 1.3alpha.804 - * Build date: 8 December 2013 - */ - -/* - TODO FIXME use www/common/rangy if possible... - -*/ - -(function(global) { - var amdSupported = (typeof global.define == "function" && global.define.amd); - - var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; - - // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START - // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113. - var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", - "commonAncestorContainer"]; - - // Minimal set of methods required for DOM Level 2 Range compliance - var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", - "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", - "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; - - var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; - - // Subset of TextRange's full set of methods that we're interested in - var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select", - "setEndPoint", "getBoundingClientRect"]; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Trio of functions taken from Peter Michaux's article: - // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting - function isHostMethod(o, p) { - var t = typeof o[p]; - return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; - } - - function isHostObject(o, p) { - return !!(typeof o[p] == OBJECT && o[p]); - } - - function isHostProperty(o, p) { - return typeof o[p] != UNDEFINED; - } - - // Creates a convenience function to save verbose repeated calls to tests functions - function createMultiplePropertyTest(testFunc) { - return function(o, props) { - var i = props.length; - while (i--) { - if (!testFunc(o, props[i])) { - return false; - } - } - return true; - }; - } - - // Next trio of functions are a convenience to save verbose repeated calls to previous two functions - var areHostMethods = createMultiplePropertyTest(isHostMethod); - var areHostObjects = createMultiplePropertyTest(isHostObject); - var areHostProperties = createMultiplePropertyTest(isHostProperty); - - function isTextRange(range) { - return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); - } - - function getBody(doc) { - return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; - } - - var modules = {}; - - var api = { - version: "1.3alpha.804", - initialized: false, - supported: true, - - util: { - isHostMethod: isHostMethod, - isHostObject: isHostObject, - isHostProperty: isHostProperty, - areHostMethods: areHostMethods, - areHostObjects: areHostObjects, - areHostProperties: areHostProperties, - isTextRange: isTextRange, - getBody: getBody - }, - - features: {}, - - modules: modules, - config: { - alertOnFail: true, - alertOnWarn: false, - preferTextRange: false - } - }; - - function consoleLog(msg) { - if (isHostObject(window, "console") && isHostMethod(window.console, "log")) { - window.console.log(msg); - } - } - - function alertOrLog(msg, shouldAlert) { - if (shouldAlert) { - window.alert(msg); - } else { - consoleLog(msg); - } - } - - function fail(reason) { - api.initialized = true; - api.supported = false; - alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail); - } - - api.fail = fail; - - function warn(msg) { - alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn); - } - - api.warn = warn; - - // Add utility extend() method - if ({}.hasOwnProperty) { - api.util.extend = function(obj, props, deep) { - var o, p; - for (var i in props) { - if (props.hasOwnProperty(i)) { - o = obj[i]; - p = props[i]; - //if (deep) alert([o !== null, typeof o == "object", p !== null, typeof p == "object"]) - if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") { - api.util.extend(o, p, true); - } - obj[i] = p; - } - } - return obj; - }; - } else { - fail("hasOwnProperty not supported"); - } - - // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not - (function() { - var el = document.createElement("div"); - el.appendChild(document.createElement("span")); - var slice = [].slice; - var toArray; - try { - if (slice.call(el.childNodes, 0)[0].nodeType == 1) { - toArray = function(arrayLike) { - return slice.call(arrayLike, 0); - }; - } - } catch (e) {} - - if (!toArray) { - toArray = function(arrayLike) { - var arr = []; - for (var i = 0, len = arrayLike.length; i < len; ++i) { - arr[i] = arrayLike[i]; - } - return arr; - }; - } - - api.util.toArray = toArray; - })(); - - - // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or - // normalization of event properties - var addListener; - if (isHostMethod(document, "addEventListener")) { - addListener = function(obj, eventType, listener) { - obj.addEventListener(eventType, listener, false); - }; - } else if (isHostMethod(document, "attachEvent")) { - addListener = function(obj, eventType, listener) { - obj.attachEvent("on" + eventType, listener); - }; - } else { - fail("Document does not have required addEventListener or attachEvent method"); - } - - api.util.addListener = addListener; - - var initListeners = []; - - function getErrorDesc(ex) { - return ex.message || ex.description || String(ex); - } - - // Initialization - function init() { - if (api.initialized) { - return; - } - var testRange; - var implementsDomRange = false, implementsTextRange = false; - - // First, perform basic feature tests - - if (isHostMethod(document, "createRange")) { - testRange = document.createRange(); - if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { - implementsDomRange = true; - } - testRange.detach(); - } - - var body = getBody(document); - if (!body || body.nodeName.toLowerCase() != "body") { - fail("No body element found"); - return; - } - - if (body && isHostMethod(body, "createTextRange")) { - testRange = body.createTextRange(); - if (isTextRange(testRange)) { - implementsTextRange = true; - } - } - - if (!implementsDomRange && !implementsTextRange) { - fail("Neither Range nor TextRange are available"); - return; - } - - api.initialized = true; - api.features = { - implementsDomRange: implementsDomRange, - implementsTextRange: implementsTextRange - }; - - // Initialize modules - var module, errorMessage; - for (var moduleName in modules) { - if ( (module = modules[moduleName]) instanceof Module ) { - module.init(module, api); - } - } - - // Call init listeners - for (var i = 0, len = initListeners.length; i < len; ++i) { - try { - initListeners[i](api); - } catch (ex) { - errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex); - consoleLog(errorMessage); - } - } - } - - // Allow external scripts to initialize this library in case it's loaded after the document has loaded - api.init = init; - - // Execute listener immediately if already initialized - api.addInitListener = function(listener) { - if (api.initialized) { - listener(api); - } else { - initListeners.push(listener); - } - }; - - var createMissingNativeApiListeners = []; - - api.addCreateMissingNativeApiListener = function(listener) { - createMissingNativeApiListeners.push(listener); - }; - - function createMissingNativeApi(win) { - win = win || window; - init(); - - // Notify listeners - for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) { - createMissingNativeApiListeners[i](win); - } - } - - api.createMissingNativeApi = createMissingNativeApi; - - function Module(name, dependencies, initializer) { - this.name = name; - this.dependencies = dependencies; - this.initialized = false; - this.supported = false; - this.initializer = initializer; - } - - Module.prototype = { - init: function(api) { - var requiredModuleNames = this.dependencies || []; - for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) { - moduleName = requiredModuleNames[i]; - - requiredModule = modules[moduleName]; - if (!requiredModule || !(requiredModule instanceof Module)) { - throw new Error("required module '" + moduleName + "' not found"); - } - - requiredModule.init(); - - if (!requiredModule.supported) { - throw new Error("required module '" + moduleName + "' not supported"); - } - } - - // Now run initializer - this.initializer(this) - }, - - fail: function(reason) { - this.initialized = true; - this.supported = false; - throw new Error("Module '" + this.name + "' failed to load: " + reason); - }, - - warn: function(msg) { - api.warn("Module " + this.name + ": " + msg); - }, - - deprecationNotice: function(deprecated, replacement) { - api.warn("DEPRECATED: " + deprecated + " in module " + this.name + "is deprecated. Please use " - + replacement + " instead"); - }, - - createError: function(msg) { - return new Error("Error in Rangy " + this.name + " module: " + msg); - } - }; - - function createModule(isCore, name, dependencies, initFunc) { - var newModule = new Module(name, dependencies, function(module) { - if (!module.initialized) { - module.initialized = true; - try { - initFunc(api, module); - module.supported = true; - } catch (ex) { - var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex); - consoleLog(errorMessage); - } - } - }); - modules[name] = newModule; - -/* - // Add module AMD support - if (!isCore && amdSupported) { - global.define(["rangy-core"], function(rangy) { - - }); - } -*/ - } - - api.createModule = function(name) { - // Allow 2 or 3 arguments (second argument is an optional array of dependencies) - var initFunc, dependencies; - if (arguments.length == 2) { - initFunc = arguments[1]; - dependencies = []; - } else { - initFunc = arguments[2]; - dependencies = arguments[1]; - } - createModule(false, name, dependencies, initFunc); - }; - - api.createCoreModule = function(name, dependencies, initFunc) { - createModule(true, name, dependencies, initFunc); - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately - - function RangePrototype() {} - api.RangePrototype = RangePrototype; - api.rangePrototype = new RangePrototype(); - - function SelectionPrototype() {} - api.selectionPrototype = new SelectionPrototype(); - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Wait for document to load before running tests - - var docReady = false; - - var loadHandler = function(e) { - if (!docReady) { - docReady = true; - if (!api.initialized) { - init(); - } - } - }; - - // Test whether we have window and document objects that we will need - if (typeof window == UNDEFINED) { - fail("No window found"); - return; - } - if (typeof document == UNDEFINED) { - fail("No document found"); - return; - } - - if (isHostMethod(document, "addEventListener")) { - document.addEventListener("DOMContentLoaded", loadHandler, false); - } - - // Add a fallback in case the DOMContentLoaded event isn't supported - addListener(window, "load", loadHandler); - - /*----------------------------------------------------------------------------------------------------------------*/ - - // AMD, for those who like this kind of thing - - if (amdSupported) { - // AMD. Register as an anonymous module. - global.define(function() { - api.amd = true; - return api; - }); - } - - // Create a "rangy" property of the global object in any case. Other Rangy modules (which use Rangy's own simple - // module system) rely on the existence of this global property - global.rangy = api; -})(this); - -rangy.createCoreModule("DomUtil", [], function(api, module) { - var UNDEF = "undefined"; - var util = api.util; - - // Perform feature tests - if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { - module.fail("document missing a Node creation method"); - } - - if (!util.isHostMethod(document, "getElementsByTagName")) { - module.fail("document missing getElementsByTagName method"); - } - - var el = document.createElement("div"); - if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || - !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { - module.fail("Incomplete Element implementation"); - } - - // innerHTML is required for Range's createContextualFragment method - if (!util.isHostProperty(el, "innerHTML")) { - module.fail("Element is missing innerHTML property"); - } - - var textNode = document.createTextNode("test"); - if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || - !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || - !util.areHostProperties(textNode, ["data"]))) { - module.fail("Incomplete Text Node implementation"); - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been - // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that - // contains just the document as a single element and the value searched for is the document. - var arrayContains = /*Array.prototype.indexOf ? - function(arr, val) { - return arr.indexOf(val) > -1; - }:*/ - - function(arr, val) { - var i = arr.length; - while (i--) { - if (arr[i] === val) { - return true; - } - } - return false; - }; - - // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI - function isHtmlNamespace(node) { - var ns; - return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); - } - - function parentElement(node) { - var parent = node.parentNode; - return (parent.nodeType == 1) ? parent : null; - } - - function getNodeIndex(node) { - var i = 0; - while( (node = node.previousSibling) ) { - ++i; - } - return i; - } - - function getNodeLength(node) { - switch (node.nodeType) { - case 7: - case 10: - return 0; - case 3: - case 8: - return node.length; - default: - return node.childNodes.length; - } - } - - function getCommonAncestor(node1, node2) { - var ancestors = [], n; - for (n = node1; n; n = n.parentNode) { - ancestors.push(n); - } - - for (n = node2; n; n = n.parentNode) { - if (arrayContains(ancestors, n)) { - return n; - } - } - - return null; - } - - function isAncestorOf(ancestor, descendant, selfIsAncestor) { - var n = selfIsAncestor ? descendant : descendant.parentNode; - while (n) { - if (n === ancestor) { - return true; - } else { - n = n.parentNode; - } - } - return false; - } - - function isOrIsAncestorOf(ancestor, descendant) { - return isAncestorOf(ancestor, descendant, true); - } - - function getClosestAncestorIn(node, ancestor, selfIsAncestor) { - var p, n = selfIsAncestor ? node : node.parentNode; - while (n) { - p = n.parentNode; - if (p === ancestor) { - return n; - } - n = p; - } - return null; - } - - function isCharacterDataNode(node) { - var t = node.nodeType; - return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment - } - - function isTextOrCommentNode(node) { - if (!node) { - return false; - } - var t = node.nodeType; - return t == 3 || t == 8 ; // Text or Comment - } - - function insertAfter(node, precedingNode) { - var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; - if (nextNode) { - parent.insertBefore(node, nextNode); - } else { - parent.appendChild(node); - } - return node; - } - - // Note that we cannot use splitText() because it is bugridden in IE 9. - function splitDataNode(node, index, positionsToPreserve) { - var newNode = node.cloneNode(false); - newNode.deleteData(0, index); - node.deleteData(index, node.length - index); - insertAfter(newNode, node); - - // Preserve positions - if (positionsToPreserve) { - for (var i = 0, position; position = positionsToPreserve[i++]; ) { - // Handle case where position was inside the portion of node after the split point - if (position.node == node && position.offset > index) { - position.node = newNode; - position.offset -= index; - } - // Handle the case where the position is a node offset within node's parent - else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) { - ++position.offset; - } - } - } - return newNode; - } - - function getDocument(node) { - if (node.nodeType == 9) { - return node; - } else if (typeof node.ownerDocument != UNDEF) { - return node.ownerDocument; - } else if (typeof node.document != UNDEF) { - return node.document; - } else if (node.parentNode) { - return getDocument(node.parentNode); - } else { - throw module.createError("getDocument: no document found for node"); - } - } - - function getWindow(node) { - var doc = getDocument(node); - if (typeof doc.defaultView != UNDEF) { - return doc.defaultView; - } else if (typeof doc.parentWindow != UNDEF) { - return doc.parentWindow; - } else { - throw module.createError("Cannot get a window object for node"); - } - } - - function getIframeDocument(iframeEl) { - if (typeof iframeEl.contentDocument != UNDEF) { - return iframeEl.contentDocument; - } else if (typeof iframeEl.contentWindow != UNDEF) { - return iframeEl.contentWindow.document; - } else { - throw module.createError("getIframeDocument: No Document object found for iframe element"); - } - } - - function getIframeWindow(iframeEl) { - if (typeof iframeEl.contentWindow != UNDEF) { - return iframeEl.contentWindow; - } else if (typeof iframeEl.contentDocument != UNDEF) { - return iframeEl.contentDocument.defaultView; - } else { - throw module.createError("getIframeWindow: No Window object found for iframe element"); - } - } - - // This looks bad. Is it worth it? - function isWindow(obj) { - return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document"); - } - - function getContentDocument(obj, module, methodName) { - var doc; - - if (!obj) { - doc = document; - } - - // Test if a DOM node has been passed and obtain a document object for it if so - else if (util.isHostProperty(obj, "nodeType")) { - doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") - ? getIframeDocument(obj) : getDocument(obj); - } - - // Test if the doc parameter appears to be a Window object - else if (isWindow(obj)) { - doc = obj.document; - } - - if (!doc) { - throw module.createError(methodName + "(): Parameter must be a Window object or DOM node"); - } - - return doc; - } - - function getRootContainer(node) { - var parent; - while ( (parent = node.parentNode) ) { - node = parent; - } - return node; - } - - function comparePoints(nodeA, offsetA, nodeB, offsetB) { - // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing - var nodeC, root, childA, childB, n; - if (nodeA == nodeB) { - // Case 1: nodes are the same - return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; - } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { - // Case 2: node C (container B or an ancestor) is a child node of A - return offsetA <= getNodeIndex(nodeC) ? -1 : 1; - } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { - // Case 3: node C (container A or an ancestor) is a child node of B - return getNodeIndex(nodeC) < offsetB ? -1 : 1; - } else { - root = getCommonAncestor(nodeA, nodeB); - if (!root) { - throw new Error("comparePoints error: nodes have no common ancestor"); - } - - // Case 4: containers are siblings or descendants of siblings - childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); - childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); - - if (childA === childB) { - // This shouldn't be possible - throw module.createError("comparePoints got to case 4 and childA and childB are the same!"); - } else { - n = root.firstChild; - while (n) { - if (n === childA) { - return -1; - } else if (n === childB) { - return 1; - } - n = n.nextSibling; - } - } - } - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried - var crashyTextNodes = false; - - function isBrokenNode(node) { - try { - node.parentNode; - return false; - } catch (e) { - return true; - } - } - - (function() { - var el = document.createElement("b"); - el.innerHTML = "1"; - var textNode = el.firstChild; - el.innerHTML = "
"; - crashyTextNodes = isBrokenNode(textNode); - - api.features.crashyTextNodes = crashyTextNodes; - })(); - - /*----------------------------------------------------------------------------------------------------------------*/ - - function inspectNode(node) { - if (!node) { - return "[No node]"; - } - if (crashyTextNodes && isBrokenNode(node)) { - return "[Broken node]"; - } - if (isCharacterDataNode(node)) { - return '"' + node.data + '"'; - } - if (node.nodeType == 1) { - var idAttr = node.id ? ' id="' + node.id + '"' : ""; - return "<" + node.nodeName + idAttr + ">[" + getNodeIndex(node) + "][" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]"; - } - return node.nodeName; - } - - function fragmentFromNodeChildren(node) { - var fragment = getDocument(node).createDocumentFragment(), child; - while ( (child = node.firstChild) ) { - fragment.appendChild(child); - } - return fragment; - } - - var getComputedStyleProperty; - if (typeof window.getComputedStyle != UNDEF) { - getComputedStyleProperty = function(el, propName) { - return getWindow(el).getComputedStyle(el, null)[propName]; - }; - } else if (typeof document.documentElement.currentStyle != UNDEF) { - getComputedStyleProperty = function(el, propName) { - return el.currentStyle[propName]; - }; - } else { - module.fail("No means of obtaining computed style properties found"); - } - - function NodeIterator(root) { - this.root = root; - this._next = root; - } - - NodeIterator.prototype = { - _current: null, - - hasNext: function() { - return !!this._next; - }, - - next: function() { - var n = this._current = this._next; - var child, next; - if (this._current) { - child = n.firstChild; - if (child) { - this._next = child; - } else { - next = null; - while ((n !== this.root) && !(next = n.nextSibling)) { - n = n.parentNode; - } - this._next = next; - } - } - return this._current; - }, - - detach: function() { - this._current = this._next = this.root = null; - } - }; - - function createIterator(root) { - return new NodeIterator(root); - } - - function DomPosition(node, offset) { - this.node = node; - this.offset = offset; - } - - DomPosition.prototype = { - equals: function(pos) { - return !!pos && this.node === pos.node && this.offset == pos.offset; - }, - - inspect: function() { - return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; - }, - - toString: function() { - return this.inspect(); - } - }; - - function DOMException(codeName) { - this.code = this[codeName]; - this.codeName = codeName; - this.message = "DOMException: " + this.codeName; - } - - DOMException.prototype = { - INDEX_SIZE_ERR: 1, - HIERARCHY_REQUEST_ERR: 3, - WRONG_DOCUMENT_ERR: 4, - NO_MODIFICATION_ALLOWED_ERR: 7, - NOT_FOUND_ERR: 8, - NOT_SUPPORTED_ERR: 9, - INVALID_STATE_ERR: 11 - }; - - DOMException.prototype.toString = function() { - return this.message; - }; - - api.dom = { - arrayContains: arrayContains, - isHtmlNamespace: isHtmlNamespace, - parentElement: parentElement, - getNodeIndex: getNodeIndex, - getNodeLength: getNodeLength, - getCommonAncestor: getCommonAncestor, - isAncestorOf: isAncestorOf, - isOrIsAncestorOf: isOrIsAncestorOf, - getClosestAncestorIn: getClosestAncestorIn, - isCharacterDataNode: isCharacterDataNode, - isTextOrCommentNode: isTextOrCommentNode, - insertAfter: insertAfter, - splitDataNode: splitDataNode, - getDocument: getDocument, - getWindow: getWindow, - getIframeWindow: getIframeWindow, - getIframeDocument: getIframeDocument, - getBody: util.getBody, - isWindow: isWindow, - getContentDocument: getContentDocument, - getRootContainer: getRootContainer, - comparePoints: comparePoints, - isBrokenNode: isBrokenNode, - inspectNode: inspectNode, - getComputedStyleProperty: getComputedStyleProperty, - fragmentFromNodeChildren: fragmentFromNodeChildren, - createIterator: createIterator, - DomPosition: DomPosition - }; - - api.DOMException = DOMException; -}); -rangy.createCoreModule("DomRange", ["DomUtil"], function(api, module) { - var dom = api.dom; - var util = api.util; - var DomPosition = dom.DomPosition; - var DOMException = api.DOMException; - - var isCharacterDataNode = dom.isCharacterDataNode; - var getNodeIndex = dom.getNodeIndex; - var isOrIsAncestorOf = dom.isOrIsAncestorOf; - var getDocument = dom.getDocument; - var comparePoints = dom.comparePoints; - var splitDataNode = dom.splitDataNode; - var getClosestAncestorIn = dom.getClosestAncestorIn; - var getNodeLength = dom.getNodeLength; - var arrayContains = dom.arrayContains; - var getRootContainer = dom.getRootContainer; - var crashyTextNodes = api.features.crashyTextNodes; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Utility functions - - function isNonTextPartiallySelected(node, range) { - return (node.nodeType != 3) && - (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer)); - } - - function getRangeDocument(range) { - return range.document || getDocument(range.startContainer); - } - - function getBoundaryBeforeNode(node) { - return new DomPosition(node.parentNode, getNodeIndex(node)); - } - - function getBoundaryAfterNode(node) { - return new DomPosition(node.parentNode, getNodeIndex(node) + 1); - } - - function insertNodeAtPosition(node, n, o) { - var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; - if (isCharacterDataNode(n)) { - if (o == n.length) { - dom.insertAfter(node, n); - } else { - n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o)); - } - } else if (o >= n.childNodes.length) { - n.appendChild(node); - } else { - n.insertBefore(node, n.childNodes[o]); - } - return firstNodeInserted; - } - - function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) { - assertRangeValid(rangeA); - assertRangeValid(rangeB); - - if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) { - throw new DOMException("WRONG_DOCUMENT_ERR"); - } - - var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset), - endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset); - - return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; - } - - function cloneSubtree(iterator) { - var partiallySelected; - for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { - partiallySelected = iterator.isPartiallySelectedSubtree(); - node = node.cloneNode(!partiallySelected); - if (partiallySelected) { - subIterator = iterator.getSubtreeIterator(); - node.appendChild(cloneSubtree(subIterator)); - subIterator.detach(true); - } - - if (node.nodeType == 10) { // DocumentType - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - frag.appendChild(node); - } - return frag; - } - - function iterateSubtree(rangeIterator, func, iteratorState) { - var it, n; - iteratorState = iteratorState || { stop: false }; - for (var node, subRangeIterator; node = rangeIterator.next(); ) { - if (rangeIterator.isPartiallySelectedSubtree()) { - if (func(node) === false) { - iteratorState.stop = true; - return; - } else { - // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of - // the node selected by the Range. - subRangeIterator = rangeIterator.getSubtreeIterator(); - iterateSubtree(subRangeIterator, func, iteratorState); - subRangeIterator.detach(true); - if (iteratorState.stop) { - return; - } - } - } else { - // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its - // descendants - it = dom.createIterator(node); - while ( (n = it.next()) ) { - if (func(n) === false) { - iteratorState.stop = true; - return; - } - } - } - } - } - - function deleteSubtree(iterator) { - var subIterator; - while (iterator.next()) { - if (iterator.isPartiallySelectedSubtree()) { - subIterator = iterator.getSubtreeIterator(); - deleteSubtree(subIterator); - subIterator.detach(true); - } else { - iterator.remove(); - } - } - } - - function extractSubtree(iterator) { - for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { - - if (iterator.isPartiallySelectedSubtree()) { - node = node.cloneNode(false); - subIterator = iterator.getSubtreeIterator(); - node.appendChild(extractSubtree(subIterator)); - subIterator.detach(true); - } else { - iterator.remove(); - } - if (node.nodeType == 10) { // DocumentType - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - frag.appendChild(node); - } - return frag; - } - - function getNodesInRange(range, nodeTypes, filter) { - var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; - var filterExists = !!filter; - if (filterNodeTypes) { - regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); - } - - var nodes = []; - iterateSubtree(new RangeIterator(range, false), function(node) { - if (filterNodeTypes && !regex.test(node.nodeType)) { - return; - } - if (filterExists && !filter(node)) { - return; - } - // Don't include a boundary container if it is a character data node and the range does not contain any - // of its character data. See issue 190. - var sc = range.startContainer; - if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) { - return; - } - - var ec = range.endContainer; - if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) { - return; - } - - nodes.push(node); - }); - return nodes; - } - - function inspect(range) { - var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); - return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + - dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) - - function RangeIterator(range, clonePartiallySelectedTextNodes) { - this.range = range; - this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; - - - if (!range.collapsed) { - this.sc = range.startContainer; - this.so = range.startOffset; - this.ec = range.endContainer; - this.eo = range.endOffset; - var root = range.commonAncestorContainer; - - if (this.sc === this.ec && isCharacterDataNode(this.sc)) { - this.isSingleCharacterDataNode = true; - this._first = this._last = this._next = this.sc; - } else { - this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ? - this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true); - this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ? - this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true); - } - } - } - - RangeIterator.prototype = { - _current: null, - _next: null, - _first: null, - _last: null, - isSingleCharacterDataNode: false, - - reset: function() { - this._current = null; - this._next = this._first; - }, - - hasNext: function() { - return !!this._next; - }, - - next: function() { - // Move to next node - var current = this._current = this._next; - if (current) { - this._next = (current !== this._last) ? current.nextSibling : null; - - // Check for partially selected text nodes - if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { - if (current === this.ec) { - (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); - } - if (this._current === this.sc) { - (current = current.cloneNode(true)).deleteData(0, this.so); - } - } - } - - return current; - }, - - remove: function() { - var current = this._current, start, end; - - if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { - start = (current === this.sc) ? this.so : 0; - end = (current === this.ec) ? this.eo : current.length; - if (start != end) { - current.deleteData(start, end - start); - } - } else { - if (current.parentNode) { - current.parentNode.removeChild(current); - } else { - } - } - }, - - // Checks if the current node is partially selected - isPartiallySelectedSubtree: function() { - var current = this._current; - return isNonTextPartiallySelected(current, this.range); - }, - - getSubtreeIterator: function() { - var subRange; - if (this.isSingleCharacterDataNode) { - subRange = this.range.cloneRange(); - subRange.collapse(false); - } else { - subRange = new Range(getRangeDocument(this.range)); - var current = this._current; - var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current); - - if (isOrIsAncestorOf(current, this.sc)) { - startContainer = this.sc; - startOffset = this.so; - } - if (isOrIsAncestorOf(current, this.ec)) { - endContainer = this.ec; - endOffset = this.eo; - } - - updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); - } - return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); - }, - - detach: function(detachRange) { - if (detachRange) { - this.range.detach(); - } - this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; - } - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Exceptions - - function RangeException(codeName) { - this.code = this[codeName]; - this.codeName = codeName; - this.message = "RangeException: " + this.codeName; - } - - RangeException.prototype = { - BAD_BOUNDARYPOINTS_ERR: 1, - INVALID_NODE_TYPE_ERR: 2 - }; - - RangeException.prototype.toString = function() { - return this.message; - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; - var rootContainerNodeTypes = [2, 9, 11]; - var readonlyNodeTypes = [5, 6, 10, 12]; - var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; - var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; - - function createAncestorFinder(nodeTypes) { - return function(node, selfIsAncestor) { - var t, n = selfIsAncestor ? node : node.parentNode; - while (n) { - t = n.nodeType; - if (arrayContains(nodeTypes, t)) { - return n; - } - n = n.parentNode; - } - return null; - }; - } - - var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); - var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); - var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); - - function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { - if (getDocTypeNotationEntityAncestor(node, allowSelf)) { - throw new RangeException("INVALID_NODE_TYPE_ERR"); - } - } - - function assertNotDetached(range) { - if (!range.startContainer) { - throw new DOMException("INVALID_STATE_ERR"); - } - } - - function assertValidNodeType(node, invalidTypes) { - if (!arrayContains(invalidTypes, node.nodeType)) { - throw new RangeException("INVALID_NODE_TYPE_ERR"); - } - } - - function assertValidOffset(node, offset) { - if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) { - throw new DOMException("INDEX_SIZE_ERR"); - } - } - - function assertSameDocumentOrFragment(node1, node2) { - if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { - throw new DOMException("WRONG_DOCUMENT_ERR"); - } - } - - function assertNodeNotReadOnly(node) { - if (getReadonlyAncestor(node, true)) { - throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); - } - } - - function assertNode(node, codeName) { - if (!node) { - throw new DOMException(codeName); - } - } - - function isOrphan(node) { - return (crashyTextNodes && dom.isBrokenNode(node)) || - !arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true); - } - - function isValidOffset(node, offset) { - return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length); - } - - function isRangeValid(range) { - return (!!range.startContainer && !!range.endContainer - && !isOrphan(range.startContainer) - && !isOrphan(range.endContainer) - && isValidOffset(range.startContainer, range.startOffset) - && isValidOffset(range.endContainer, range.endOffset)); - } - - function assertRangeValid(range) { - assertNotDetached(range); - if (!isRangeValid(range)) { - throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")"); - } - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Test the browser's innerHTML support to decide how to implement createContextualFragment - var styleEl = document.createElement("style"); - var htmlParsingConforms = false; - try { - styleEl.innerHTML = "x"; - htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node - } catch (e) { - // IE 6 and 7 throw - } - - api.features.htmlParsingConforms = htmlParsingConforms; - - var createContextualFragment = htmlParsingConforms ? - - // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See - // discussion and base code for this implementation at issue 67. - // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface - // Thanks to Aleks Williams. - function(fragmentStr) { - // "Let node the context object's start's node." - var node = this.startContainer; - var doc = getDocument(node); - - // "If the context object's start's node is null, raise an INVALID_STATE_ERR - // exception and abort these steps." - if (!node) { - throw new DOMException("INVALID_STATE_ERR"); - } - - // "Let element be as follows, depending on node's interface:" - // Document, Document Fragment: null - var el = null; - - // "Element: node" - if (node.nodeType == 1) { - el = node; - - // "Text, Comment: node's parentElement" - } else if (isCharacterDataNode(node)) { - el = dom.parentElement(node); - } - - // "If either element is null or element's ownerDocument is an HTML document - // and element's local name is "html" and element's namespace is the HTML - // namespace" - if (el === null || ( - el.nodeName == "HTML" - && dom.isHtmlNamespace(getDocument(el).documentElement) - && dom.isHtmlNamespace(el) - )) { - - // "let element be a new Element with "body" as its local name and the HTML - // namespace as its namespace."" - el = doc.createElement("body"); - } else { - el = el.cloneNode(false); - } - - // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." - // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." - // "In either case, the algorithm must be invoked with fragment as the input - // and element as the context element." - el.innerHTML = fragmentStr; - - // "If this raises an exception, then abort these steps. Otherwise, let new - // children be the nodes returned." - - // "Let fragment be a new DocumentFragment." - // "Append all new children to fragment." - // "Return fragment." - return dom.fragmentFromNodeChildren(el); - } : - - // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that - // previous versions of Rangy used (with the exception of using a body element rather than a div) - function(fragmentStr) { - assertNotDetached(this); - var doc = getRangeDocument(this); - var el = doc.createElement("body"); - el.innerHTML = fragmentStr; - - return dom.fragmentFromNodeChildren(el); - }; - - function splitRangeBoundaries(range, positionsToPreserve) { - assertRangeValid(range); - - var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset; - var startEndSame = (sc === ec); - - if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { - splitDataNode(ec, eo, positionsToPreserve); - } - - if (isCharacterDataNode(sc) && so > 0 && so < sc.length) { - sc = splitDataNode(sc, so, positionsToPreserve); - if (startEndSame) { - eo -= so; - ec = sc; - } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) { - eo++; - } - so = 0; - } - range.setStartAndEnd(sc, so, ec, eo); - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", - "commonAncestorContainer"]; - - var s2s = 0, s2e = 1, e2e = 2, e2s = 3; - var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; - - util.extend(api.rangePrototype, { - compareBoundaryPoints: function(how, range) { - assertRangeValid(this); - assertSameDocumentOrFragment(this.startContainer, range.startContainer); - - var nodeA, offsetA, nodeB, offsetB; - var prefixA = (how == e2s || how == s2s) ? "start" : "end"; - var prefixB = (how == s2e || how == s2s) ? "start" : "end"; - nodeA = this[prefixA + "Container"]; - offsetA = this[prefixA + "Offset"]; - nodeB = range[prefixB + "Container"]; - offsetB = range[prefixB + "Offset"]; - return comparePoints(nodeA, offsetA, nodeB, offsetB); - }, - - insertNode: function(node) { - assertRangeValid(this); - assertValidNodeType(node, insertableNodeTypes); - assertNodeNotReadOnly(this.startContainer); - - if (isOrIsAncestorOf(node, this.startContainer)) { - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - - // No check for whether the container of the start of the Range is of a type that does not allow - // children of the type of node: the browser's DOM implementation should do this for us when we attempt - // to add the node - - var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); - this.setStartBefore(firstNodeInserted); - }, - - cloneContents: function() { - assertRangeValid(this); - - var clone, frag; - if (this.collapsed) { - return getRangeDocument(this).createDocumentFragment(); - } else { - if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) { - clone = this.startContainer.cloneNode(true); - clone.data = clone.data.slice(this.startOffset, this.endOffset); - frag = getRangeDocument(this).createDocumentFragment(); - frag.appendChild(clone); - return frag; - } else { - var iterator = new RangeIterator(this, true); - clone = cloneSubtree(iterator); - iterator.detach(); - } - return clone; - } - }, - - canSurroundContents: function() { - assertRangeValid(this); - assertNodeNotReadOnly(this.startContainer); - assertNodeNotReadOnly(this.endContainer); - - // Check if the contents can be surrounded. Specifically, this means whether the range partially selects - // no non-text nodes. - var iterator = new RangeIterator(this, true); - var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || - (iterator._last && isNonTextPartiallySelected(iterator._last, this))); - iterator.detach(); - return !boundariesInvalid; - }, - - surroundContents: function(node) { - assertValidNodeType(node, surroundNodeTypes); - - if (!this.canSurroundContents()) { - throw new RangeException("BAD_BOUNDARYPOINTS_ERR"); - } - - // Extract the contents - var content = this.extractContents(); - - // Clear the children of the node - if (node.hasChildNodes()) { - while (node.lastChild) { - node.removeChild(node.lastChild); - } - } - - // Insert the new node and add the extracted contents - insertNodeAtPosition(node, this.startContainer, this.startOffset); - node.appendChild(content); - - this.selectNode(node); - }, - - cloneRange: function() { - assertRangeValid(this); - var range = new Range(getRangeDocument(this)); - var i = rangeProperties.length, prop; - while (i--) { - prop = rangeProperties[i]; - range[prop] = this[prop]; - } - return range; - }, - - toString: function() { - assertRangeValid(this); - var sc = this.startContainer; - if (sc === this.endContainer && isCharacterDataNode(sc)) { - return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; - } else { - var textParts = [], iterator = new RangeIterator(this, true); - iterateSubtree(iterator, function(node) { - // Accept only text or CDATA nodes, not comments - if (node.nodeType == 3 || node.nodeType == 4) { - textParts.push(node.data); - } - }); - iterator.detach(); - return textParts.join(""); - } - }, - - // The methods below are all non-standard. The following batch were introduced by Mozilla but have since - // been removed from Mozilla. - - compareNode: function(node) { - assertRangeValid(this); - - var parent = node.parentNode; - var nodeIndex = getNodeIndex(node); - - if (!parent) { - throw new DOMException("NOT_FOUND_ERR"); - } - - var startComparison = this.comparePoint(parent, nodeIndex), - endComparison = this.comparePoint(parent, nodeIndex + 1); - - if (startComparison < 0) { // Node starts before - return (endComparison > 0) ? n_b_a : n_b; - } else { - return (endComparison > 0) ? n_a : n_i; - } - }, - - comparePoint: function(node, offset) { - assertRangeValid(this); - assertNode(node, "HIERARCHY_REQUEST_ERR"); - assertSameDocumentOrFragment(node, this.startContainer); - - if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { - return -1; - } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { - return 1; - } - return 0; - }, - - createContextualFragment: createContextualFragment, - - toHtml: function() { - assertRangeValid(this); - var container = this.commonAncestorContainer.parentNode.cloneNode(false); - container.appendChild(this.cloneContents()); - return container.innerHTML; - }, - - // touchingIsIntersecting determines whether this method considers a node that borders a range intersects - // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) - intersectsNode: function(node, touchingIsIntersecting) { - assertRangeValid(this); - assertNode(node, "NOT_FOUND_ERR"); - if (getDocument(node) !== getRangeDocument(this)) { - return false; - } - - var parent = node.parentNode, offset = getNodeIndex(node); - assertNode(parent, "NOT_FOUND_ERR"); - - var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset), - endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset); - - return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; - }, - - isPointInRange: function(node, offset) { - assertRangeValid(this); - assertNode(node, "HIERARCHY_REQUEST_ERR"); - assertSameDocumentOrFragment(node, this.startContainer); - - return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && - (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); - }, - - // The methods below are non-standard and invented by me. - - // Sharing a boundary start-to-end or end-to-start does not count as intersection. - intersectsRange: function(range) { - return rangesIntersect(this, range, false); - }, - - // Sharing a boundary start-to-end or end-to-start does count as intersection. - intersectsOrTouchesRange: function(range) { - return rangesIntersect(this, range, true); - }, - - intersection: function(range) { - if (this.intersectsRange(range)) { - var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), - endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); - - var intersectionRange = this.cloneRange(); - if (startComparison == -1) { - intersectionRange.setStart(range.startContainer, range.startOffset); - } - if (endComparison == 1) { - intersectionRange.setEnd(range.endContainer, range.endOffset); - } - return intersectionRange; - } - return null; - }, - - union: function(range) { - if (this.intersectsOrTouchesRange(range)) { - var unionRange = this.cloneRange(); - if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { - unionRange.setStart(range.startContainer, range.startOffset); - } - if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { - unionRange.setEnd(range.endContainer, range.endOffset); - } - return unionRange; - } else { - throw new RangeException("Ranges do not intersect"); - } - }, - - containsNode: function(node, allowPartial) { - if (allowPartial) { - return this.intersectsNode(node, false); - } else { - return this.compareNode(node) == n_i; - } - }, - - containsNodeContents: function(node) { - return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0; - }, - - containsRange: function(range) { - var intersection = this.intersection(range); - return intersection !== null && range.equals(intersection); - }, - - containsNodeText: function(node) { - var nodeRange = this.cloneRange(); - nodeRange.selectNode(node); - var textNodes = nodeRange.getNodes([3]); - if (textNodes.length > 0) { - nodeRange.setStart(textNodes[0], 0); - var lastTextNode = textNodes.pop(); - nodeRange.setEnd(lastTextNode, lastTextNode.length); - var contains = this.containsRange(nodeRange); - nodeRange.detach(); - return contains; - } else { - return this.containsNodeContents(node); - } - }, - - getNodes: function(nodeTypes, filter) { - assertRangeValid(this); - return getNodesInRange(this, nodeTypes, filter); - }, - - getDocument: function() { - return getRangeDocument(this); - }, - - collapseBefore: function(node) { - assertNotDetached(this); - - this.setEndBefore(node); - this.collapse(false); - }, - - collapseAfter: function(node) { - assertNotDetached(this); - - this.setStartAfter(node); - this.collapse(true); - }, - - getBookmark: function(containerNode) { - var doc = getRangeDocument(this); - var preSelectionRange = api.createRange(doc); - containerNode = containerNode || dom.getBody(doc); - preSelectionRange.selectNodeContents(containerNode); - var range = this.intersection(preSelectionRange); - var start = 0, end = 0; - if (range) { - preSelectionRange.setEnd(range.startContainer, range.startOffset); - start = preSelectionRange.toString().length; - end = start + range.toString().length; - preSelectionRange.detach(); - } - - return { - start: start, - end: end, - containerNode: containerNode - }; - }, - - moveToBookmark: function(bookmark) { - var containerNode = bookmark.containerNode; - var charIndex = 0; - this.setStart(containerNode, 0); - this.collapse(true); - var nodeStack = [containerNode], node, foundStart = false, stop = false; - var nextCharIndex, i, childNodes; - - while (!stop && (node = nodeStack.pop())) { - if (node.nodeType == 3) { - nextCharIndex = charIndex + node.length; - if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) { - this.setStart(node, bookmark.start - charIndex); - foundStart = true; - } - if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) { - this.setEnd(node, bookmark.end - charIndex); - stop = true; - } - charIndex = nextCharIndex; - } else { - childNodes = node.childNodes; - i = childNodes.length; - while (i--) { - nodeStack.push(childNodes[i]); - } - } - } - }, - - getName: function() { - return "DomRange"; - }, - - equals: function(range) { - return Range.rangesEqual(this, range); - }, - - isValid: function() { - return isRangeValid(this); - }, - - inspect: function() { - return inspect(this); - } - }); - - function copyComparisonConstantsToObject(obj) { - obj.START_TO_START = s2s; - obj.START_TO_END = s2e; - obj.END_TO_END = e2e; - obj.END_TO_START = e2s; - - obj.NODE_BEFORE = n_b; - obj.NODE_AFTER = n_a; - obj.NODE_BEFORE_AND_AFTER = n_b_a; - obj.NODE_INSIDE = n_i; - } - - function copyComparisonConstants(constructor) { - copyComparisonConstantsToObject(constructor); - copyComparisonConstantsToObject(constructor.prototype); - } - - function createRangeContentRemover(remover, boundaryUpdater) { - return function() { - assertRangeValid(this); - - var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; - - var iterator = new RangeIterator(this, true); - - // Work out where to position the range after content removal - var node, boundary; - if (sc !== root) { - node = getClosestAncestorIn(sc, root, true); - boundary = getBoundaryAfterNode(node); - sc = boundary.node; - so = boundary.offset; - } - - // Check none of the range is read-only - iterateSubtree(iterator, assertNodeNotReadOnly); - - iterator.reset(); - - // Remove the content - var returnValue = remover(iterator); - iterator.detach(); - - // Move to the new position - boundaryUpdater(this, sc, so, sc, so); - - return returnValue; - }; - } - - function createPrototypeRange(constructor, boundaryUpdater, detacher) { - function createBeforeAfterNodeSetter(isBefore, isStart) { - return function(node) { - assertNotDetached(this); - assertValidNodeType(node, beforeAfterNodeTypes); - assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); - - var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); - (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); - }; - } - - function setRangeStart(range, node, offset) { - var ec = range.endContainer, eo = range.endOffset; - if (node !== range.startContainer || offset !== range.startOffset) { - // Check the root containers of the range and the new boundary, and also check whether the new boundary - // is after the current end. In either case, collapse the range to the new position - if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) { - ec = node; - eo = offset; - } - boundaryUpdater(range, node, offset, ec, eo); - } - } - - function setRangeEnd(range, node, offset) { - var sc = range.startContainer, so = range.startOffset; - if (node !== range.endContainer || offset !== range.endOffset) { - // Check the root containers of the range and the new boundary, and also check whether the new boundary - // is after the current end. In either case, collapse the range to the new position - if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) { - sc = node; - so = offset; - } - boundaryUpdater(range, sc, so, node, offset); - } - } - - // Set up inheritance - var F = function() {}; - F.prototype = api.rangePrototype; - constructor.prototype = new F(); - - util.extend(constructor.prototype, { - setStart: function(node, offset) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - - setRangeStart(this, node, offset); - }, - - setEnd: function(node, offset) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - - setRangeEnd(this, node, offset); - }, - - /** - * Convenience method to set a range's start and end boundaries. Overloaded as follows: - * - Two parameters (node, offset) creates a collapsed range at that position - * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at - * startOffset and ending at endOffset - * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in - * startNode and ending at endOffset in endNode - */ - setStartAndEnd: function() { - assertNotDetached(this); - - var args = arguments; - var sc = args[0], so = args[1], ec = sc, eo = so; - - switch (args.length) { - case 3: - eo = args[2]; - break; - case 4: - ec = args[2]; - eo = args[3]; - break; - } - - boundaryUpdater(this, sc, so, ec, eo); - }, - - setBoundary: function(node, offset, isStart) { - this["set" + (isStart ? "Start" : "End")](node, offset); - }, - - setStartBefore: createBeforeAfterNodeSetter(true, true), - setStartAfter: createBeforeAfterNodeSetter(false, true), - setEndBefore: createBeforeAfterNodeSetter(true, false), - setEndAfter: createBeforeAfterNodeSetter(false, false), - - collapse: function(isStart) { - assertRangeValid(this); - if (isStart) { - boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); - } else { - boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); - } - }, - - selectNodeContents: function(node) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - - boundaryUpdater(this, node, 0, node, getNodeLength(node)); - }, - - selectNode: function(node) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, false); - assertValidNodeType(node, beforeAfterNodeTypes); - - var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); - boundaryUpdater(this, start.node, start.offset, end.node, end.offset); - }, - - extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), - - deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), - - canSurroundContents: function() { - assertRangeValid(this); - assertNodeNotReadOnly(this.startContainer); - assertNodeNotReadOnly(this.endContainer); - - // Check if the contents can be surrounded. Specifically, this means whether the range partially selects - // no non-text nodes. - var iterator = new RangeIterator(this, true); - var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || - (iterator._last && isNonTextPartiallySelected(iterator._last, this))); - iterator.detach(); - return !boundariesInvalid; - }, - - detach: function() { - detacher(this); - }, - - splitBoundaries: function() { - splitRangeBoundaries(this); - }, - - splitBoundariesPreservingPositions: function(positionsToPreserve) { - splitRangeBoundaries(this, positionsToPreserve); - }, - - normalizeBoundaries: function() { - assertRangeValid(this); - - var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; - - var mergeForward = function(node) { - var sibling = node.nextSibling; - if (sibling && sibling.nodeType == node.nodeType) { - ec = node; - eo = node.length; - node.appendData(sibling.data); - sibling.parentNode.removeChild(sibling); - } - }; - - var mergeBackward = function(node) { - var sibling = node.previousSibling; - if (sibling && sibling.nodeType == node.nodeType) { - sc = node; - var nodeLength = node.length; - so = sibling.length; - node.insertData(0, sibling.data); - sibling.parentNode.removeChild(sibling); - if (sc == ec) { - eo += so; - ec = sc; - } else if (ec == node.parentNode) { - var nodeIndex = getNodeIndex(node); - if (eo == nodeIndex) { - ec = node; - eo = nodeLength; - } else if (eo > nodeIndex) { - eo--; - } - } - } - }; - - var normalizeStart = true; - - if (isCharacterDataNode(ec)) { - if (ec.length == eo) { - mergeForward(ec); - } - } else { - if (eo > 0) { - var endNode = ec.childNodes[eo - 1]; - if (endNode && isCharacterDataNode(endNode)) { - mergeForward(endNode); - } - } - normalizeStart = !this.collapsed; - } - - if (normalizeStart) { - if (isCharacterDataNode(sc)) { - if (so == 0) { - mergeBackward(sc); - } - } else { - if (so < sc.childNodes.length) { - var startNode = sc.childNodes[so]; - if (startNode && isCharacterDataNode(startNode)) { - mergeBackward(startNode); - } - } - } - } else { - sc = ec; - so = eo; - } - - boundaryUpdater(this, sc, so, ec, eo); - }, - - collapseToPoint: function(node, offset) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - this.setStartAndEnd(node, offset); - } - }); - - copyComparisonConstants(constructor); - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Updates commonAncestorContainer and collapsed after boundary change - function updateCollapsedAndCommonAncestor(range) { - range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); - range.commonAncestorContainer = range.collapsed ? - range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); - } - - function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { - range.startContainer = startContainer; - range.startOffset = startOffset; - range.endContainer = endContainer; - range.endOffset = endOffset; - range.document = dom.getDocument(startContainer); - - updateCollapsedAndCommonAncestor(range); - } - - function detach(range) { - assertNotDetached(range); - range.startContainer = range.startOffset = range.endContainer = range.endOffset = range.document = null; - range.collapsed = range.commonAncestorContainer = null; - } - - function Range(doc) { - this.startContainer = doc; - this.startOffset = 0; - this.endContainer = doc; - this.endOffset = 0; - this.document = doc; - updateCollapsedAndCommonAncestor(this); - } - - createPrototypeRange(Range, updateBoundaries, detach); - - util.extend(Range, { - rangeProperties: rangeProperties, - RangeIterator: RangeIterator, - copyComparisonConstants: copyComparisonConstants, - createPrototypeRange: createPrototypeRange, - inspect: inspect, - getRangeDocument: getRangeDocument, - rangesEqual: function(r1, r2) { - return r1.startContainer === r2.startContainer && - r1.startOffset === r2.startOffset && - r1.endContainer === r2.endContainer && - r1.endOffset === r2.endOffset; - } - }); - - api.DomRange = Range; - api.RangeException = RangeException; -}); -rangy.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { - var WrappedRange, WrappedTextRange; - var dom = api.dom; - var util = api.util; - var DomPosition = dom.DomPosition; - var DomRange = api.DomRange; - var getBody = dom.getBody; - var getContentDocument = dom.getContentDocument; - var isCharacterDataNode = dom.isCharacterDataNode; - - - /*----------------------------------------------------------------------------------------------------------------*/ - - if (api.features.implementsDomRange) { - // This is a wrapper around the browser's native DOM Range. It has two aims: - // - Provide workarounds for specific browser bugs - // - provide convenient extensions, which are inherited from Rangy's DomRange - - (function() { - var rangeProto; - var rangeProperties = DomRange.rangeProperties; - - function updateRangeProperties(range) { - var i = rangeProperties.length, prop; - while (i--) { - prop = rangeProperties[i]; - range[prop] = range.nativeRange[prop]; - } - // Fix for broken collapsed property in IE 9. - range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); - } - - function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) { - var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset); - var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset); - var nativeRangeDifferent = !range.equals(range.nativeRange); - - // Always set both boundaries for the benefit of IE9 (see issue 35) - if (startMoved || endMoved || nativeRangeDifferent) { - range.setEnd(endContainer, endOffset); - range.setStart(startContainer, startOffset); - } - } - - function detach(range) { - range.nativeRange.detach(); - range.detached = true; - var i = rangeProperties.length; - while (i--) { - range[ rangeProperties[i] ] = null; - } - } - - var createBeforeAfterNodeSetter; - - WrappedRange = function(range) { - if (!range) { - throw module.createError("WrappedRange: Range must be specified"); - } - this.nativeRange = range; - updateRangeProperties(this); - }; - - DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach); - - rangeProto = WrappedRange.prototype; - - rangeProto.selectNode = function(node) { - this.nativeRange.selectNode(node); - updateRangeProperties(this); - }; - - rangeProto.cloneContents = function() { - return this.nativeRange.cloneContents(); - }; - - // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect, - // insertNode() is never delegated to the native range. - - rangeProto.surroundContents = function(node) { - this.nativeRange.surroundContents(node); - updateRangeProperties(this); - }; - - rangeProto.collapse = function(isStart) { - this.nativeRange.collapse(isStart); - updateRangeProperties(this); - }; - - rangeProto.cloneRange = function() { - return new WrappedRange(this.nativeRange.cloneRange()); - }; - - rangeProto.refresh = function() { - updateRangeProperties(this); - }; - - rangeProto.toString = function() { - return this.nativeRange.toString(); - }; - - // Create test range and node for feature detection - - var testTextNode = document.createTextNode("test"); - getBody(document).appendChild(testTextNode); - var range = document.createRange(); - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and - // correct for it - - range.setStart(testTextNode, 0); - range.setEnd(testTextNode, 0); - - try { - range.setStart(testTextNode, 1); - - rangeProto.setStart = function(node, offset) { - this.nativeRange.setStart(node, offset); - updateRangeProperties(this); - }; - - rangeProto.setEnd = function(node, offset) { - this.nativeRange.setEnd(node, offset); - updateRangeProperties(this); - }; - - createBeforeAfterNodeSetter = function(name) { - return function(node) { - this.nativeRange[name](node); - updateRangeProperties(this); - }; - }; - - } catch(ex) { - - rangeProto.setStart = function(node, offset) { - try { - this.nativeRange.setStart(node, offset); - } catch (ex) { - this.nativeRange.setEnd(node, offset); - this.nativeRange.setStart(node, offset); - } - updateRangeProperties(this); - }; - - rangeProto.setEnd = function(node, offset) { - try { - this.nativeRange.setEnd(node, offset); - } catch (ex) { - this.nativeRange.setStart(node, offset); - this.nativeRange.setEnd(node, offset); - } - updateRangeProperties(this); - }; - - createBeforeAfterNodeSetter = function(name, oppositeName) { - return function(node) { - try { - this.nativeRange[name](node); - } catch (ex) { - this.nativeRange[oppositeName](node); - this.nativeRange[name](node); - } - updateRangeProperties(this); - }; - }; - } - - rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore"); - rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter"); - rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore"); - rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter"); - - /*--------------------------------------------------------------------------------------------------------*/ - - // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing - // whether the native implementation can be trusted - rangeProto.selectNodeContents = function(node) { - this.setStartAndEnd(node, 0, dom.getNodeLength(node)); - }; - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for - // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738 - - range.selectNodeContents(testTextNode); - range.setEnd(testTextNode, 3); - - var range2 = document.createRange(); - range2.selectNodeContents(testTextNode); - range2.setEnd(testTextNode, 4); - range2.setStart(testTextNode, 2); - - if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 && - range.compareBoundaryPoints(range.END_TO_START, range2) == 1) { - // This is the wrong way round, so correct for it - - rangeProto.compareBoundaryPoints = function(type, range) { - range = range.nativeRange || range; - if (type == range.START_TO_END) { - type = range.END_TO_START; - } else if (type == range.END_TO_START) { - type = range.START_TO_END; - } - return this.nativeRange.compareBoundaryPoints(type, range); - }; - } else { - rangeProto.compareBoundaryPoints = function(type, range) { - return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range); - }; - } - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107. - - var el = document.createElement("div"); - el.innerHTML = "123"; - var textNode = el.firstChild; - var body = getBody(document); - body.appendChild(el); - - range.setStart(textNode, 1); - range.setEnd(textNode, 2); - range.deleteContents(); - - if (textNode.data == "13") { - // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and - // extractContents() - rangeProto.deleteContents = function() { - this.nativeRange.deleteContents(); - updateRangeProperties(this); - }; - - rangeProto.extractContents = function() { - var frag = this.nativeRange.extractContents(); - updateRangeProperties(this); - return frag; - }; - } else { - } - - body.removeChild(el); - body = null; - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for existence of createContextualFragment and delegate to it if it exists - if (util.isHostMethod(range, "createContextualFragment")) { - rangeProto.createContextualFragment = function(fragmentStr) { - return this.nativeRange.createContextualFragment(fragmentStr); - }; - } - - /*--------------------------------------------------------------------------------------------------------*/ - - // Clean up - getBody(document).removeChild(testTextNode); - range.detach(); - range2.detach(); - - rangeProto.getName = function() { - return "WrappedRange"; - }; - - api.WrappedRange = WrappedRange; - - api.createNativeRange = function(doc) { - doc = getContentDocument(doc, module, "createNativeRange"); - return doc.createRange(); - }; - })(); - } - - if (api.features.implementsTextRange) { - /* - This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() - method. For example, in the following (where pipes denote the selection boundaries): - -

  • | a
  • b |
- - var range = document.selection.createRange(); - alert(range.parentElement().id); // Should alert "ul" but alerts "b" - - This method returns the common ancestor node of the following: - - the parentElement() of the textRange - - the parentElement() of the textRange after calling collapse(true) - - the parentElement() of the textRange after calling collapse(false) - */ - var getTextRangeContainerElement = function(textRange) { - var parentEl = textRange.parentElement(); - var range = textRange.duplicate(); - range.collapse(true); - var startEl = range.parentElement(); - range = textRange.duplicate(); - range.collapse(false); - var endEl = range.parentElement(); - var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); - - return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); - }; - - var textRangeIsCollapsed = function(textRange) { - return textRange.compareEndPoints("StartToEnd", textRange) == 0; - }; - - // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as - // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has - // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling - // for inputs and images, plus optimizations. - var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) { - var workingRange = textRange.duplicate(); - workingRange.collapse(isStart); - var containerElement = workingRange.parentElement(); - - // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so - // check for that - if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) { - containerElement = wholeRangeContainerElement; - } - - - // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and - // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx - if (!containerElement.canHaveHTML) { - var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); - return { - boundaryPosition: pos, - nodeInfo: { - nodeIndex: pos.offset, - containerElement: pos.node - } - }; - } - - var workingNode = dom.getDocument(containerElement).createElement("span"); - - // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5 - // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64 - if (workingNode.parentNode) { - workingNode.parentNode.removeChild(workingNode); - } - - var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; - var previousNode, nextNode, boundaryPosition, boundaryNode; - var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0; - var childNodeCount = containerElement.childNodes.length; - var end = childNodeCount; - - // Check end first. Code within the loop assumes that the endth child node of the container is definitely - // after the range boundary. - var nodeIndex = end; - - while (true) { - if (nodeIndex == childNodeCount) { - containerElement.appendChild(workingNode); - } else { - containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]); - } - workingRange.moveToElementText(workingNode); - comparison = workingRange.compareEndPoints(workingComparisonType, textRange); - if (comparison == 0 || start == end) { - break; - } else if (comparison == -1) { - if (end == start + 1) { - // We know the endth child node is after the range boundary, so we must be done. - break; - } else { - start = nodeIndex; - } - } else { - end = (end == start + 1) ? start : nodeIndex; - } - nodeIndex = Math.floor((start + end) / 2); - containerElement.removeChild(workingNode); - } - - - // We've now reached or gone past the boundary of the text range we're interested in - // so have identified the node we want - boundaryNode = workingNode.nextSibling; - - if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) { - // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the - // node containing the text range's boundary, so we move the end of the working range to the boundary point - // and measure the length of its text to get the boundary's offset within the node. - workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); - - var offset; - - if (/[\r\n]/.test(boundaryNode.data)) { - /* - For the particular case of a boundary within a text node containing rendered line breaks (within a
-                    element, for example), we need a slightly complicated approach to get the boundary's offset in IE. The
-                    facts:
-                    
-                    - Each line break is represented as \r in the text node's data/nodeValue properties
-                    - Each line break is represented as \r\n in the TextRange's 'text' property
-                    - The 'text' property of the TextRange does not contain trailing line breaks
-                    
-                    To get round the problem presented by the final fact above, we can use the fact that TextRange's
-                    moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
-                    the same as the number of characters it was instructed to move. The simplest approach is to use this to
-                    store the characters moved when moving both the start and end of the range to the start of the document
-                    body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
-                    However, this is extremely slow when the document is large and the range is near the end of it. Clearly
-                    doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
-                    problem.
-                    
-                    Another approach that works is to use moveStart() to move the start boundary of the range up to the end
-                    boundary one character at a time and incrementing a counter with the value returned by the moveStart()
-                    call. However, the check for whether the start boundary has reached the end boundary is expensive, so
-                    this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
-                    the range within the document).
-                    
-                    The method below is a hybrid of the two methods above. It uses the fact that a string containing the
-                    TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
-                    text of the TextRange, so the start of the range is moved that length initially and then a character at
-                    a time to make up for any trailing line breaks not contained in the 'text' property. This has good
-                    performance in most situations compared to the previous two methods.
-                    */
-                    var tempRange = workingRange.duplicate();
-                    var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
-
-                    offset = tempRange.moveStart("character", rangeLength);
-                    while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
-                        offset++;
-                        tempRange.moveStart("character", 1);
-                    }
-                } else {
-                    offset = workingRange.text.length;
-                }
-                boundaryPosition = new DomPosition(boundaryNode, offset);
-            } else {
-
-                // If the boundary immediately follows a character data node and this is the end boundary, we should favour
-                // a position within that, and likewise for a start boundary preceding a character data node
-                previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
-                nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
-                if (nextNode && isCharacterDataNode(nextNode)) {
-                    boundaryPosition = new DomPosition(nextNode, 0);
-                } else if (previousNode && isCharacterDataNode(previousNode)) {
-                    boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
-                } else {
-                    boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
-                }
-            }
-
-            // Clean up
-            workingNode.parentNode.removeChild(workingNode);
-
-            return {
-                boundaryPosition: boundaryPosition,
-                nodeInfo: {
-                    nodeIndex: nodeIndex,
-                    containerElement: containerElement
-                }
-            };
-        };
-
-        // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
-        // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
-        // (http://code.google.com/p/ierange/)
-        var createBoundaryTextRange = function(boundaryPosition, isStart) {
-            var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
-            var doc = dom.getDocument(boundaryPosition.node);
-            var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
-            var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
-
-            if (nodeIsDataNode) {
-                boundaryNode = boundaryPosition.node;
-                boundaryParent = boundaryNode.parentNode;
-            } else {
-                childNodes = boundaryPosition.node.childNodes;
-                boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
-                boundaryParent = boundaryPosition.node;
-            }
-
-            // Position the range immediately before the node containing the boundary
-            workingNode = doc.createElement("span");
-
-            // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
-            // element rather than immediately before or after it
-            workingNode.innerHTML = "&#feff;";
-
-            // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
-            // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
-            if (boundaryNode) {
-                boundaryParent.insertBefore(workingNode, boundaryNode);
-            } else {
-                boundaryParent.appendChild(workingNode);
-            }
-
-            workingRange.moveToElementText(workingNode);
-            workingRange.collapse(!isStart);
-
-            // Clean up
-            boundaryParent.removeChild(workingNode);
-
-            // Move the working range to the text offset, if required
-            if (nodeIsDataNode) {
-                workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
-            }
-
-            return workingRange;
-        };
-
-        /*------------------------------------------------------------------------------------------------------------*/
-
-        // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
-        // prototype
-
-        WrappedTextRange = function(textRange) {
-            this.textRange = textRange;
-            this.refresh();
-        };
-
-        WrappedTextRange.prototype = new DomRange(document);
-
-        WrappedTextRange.prototype.refresh = function() {
-            var start, end, startBoundary;
-
-            // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
-            var rangeContainerElement = getTextRangeContainerElement(this.textRange);
-
-            if (textRangeIsCollapsed(this.textRange)) {
-                end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
-                    true).boundaryPosition;
-            } else {
-                startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
-                start = startBoundary.boundaryPosition;
-
-                // An optimization used here is that if the start and end boundaries have the same parent element, the
-                // search scope for the end boundary can be limited to exclude the portion of the element that precedes
-                // the start boundary
-                end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
-                    startBoundary.nodeInfo).boundaryPosition;
-            }
-
-            this.setStart(start.node, start.offset);
-            this.setEnd(end.node, end.offset);
-        };
-
-        WrappedTextRange.prototype.getName = function() {
-            return "WrappedTextRange";
-        };
-
-        DomRange.copyComparisonConstants(WrappedTextRange);
-
-        WrappedTextRange.rangeToTextRange = function(range) {
-            if (range.collapsed) {
-                return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
-            } else {
-                var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
-                var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
-                var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
-                textRange.setEndPoint("StartToStart", startRange);
-                textRange.setEndPoint("EndToEnd", endRange);
-                return textRange;
-            }
-        };
-
-        api.WrappedTextRange = WrappedTextRange;
-
-        // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
-        // implementation to use by default.
-        if (!api.features.implementsDomRange || api.config.preferTextRange) {
-            // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
-            var globalObj = (function() { return this; })();
-            if (typeof globalObj.Range == "undefined") {
-                globalObj.Range = WrappedTextRange;
-            }
-
-            api.createNativeRange = function(doc) {
-                doc = getContentDocument(doc, module, "createNativeRange");
-                return getBody(doc).createTextRange();
-            };
-
-            api.WrappedRange = WrappedTextRange;
-        }
-    }
-
-    api.createRange = function(doc) {
-        doc = getContentDocument(doc, module, "createRange");
-        return new api.WrappedRange(api.createNativeRange(doc));
-    };
-
-    api.createRangyRange = function(doc) {
-        doc = getContentDocument(doc, module, "createRangyRange");
-        return new DomRange(doc);
-    };
-
-    api.createIframeRange = function(iframeEl) {
-        module.deprecationNotice("createIframeRange()", "createRange(iframeEl)");
-        return api.createRange(iframeEl);
-    };
-
-    api.createIframeRangyRange = function(iframeEl) {
-        module.deprecationNotice("createIframeRangyRange()", "createRangyRange(iframeEl)");
-        return api.createRangyRange(iframeEl);
-    };
-
-    api.addCreateMissingNativeApiListener(function(win) {
-        var doc = win.document;
-        if (typeof doc.createRange == "undefined") {
-            doc.createRange = function() {
-                return api.createRange(doc);
-            };
-        }
-        doc = win = null;
-    });
-});
-// This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
-// in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
-rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
-    api.config.checkSelectionRanges = true;
-
-    var BOOLEAN = "boolean";
-    var NUMBER = "number";
-    var dom = api.dom;
-    var util = api.util;
-    var isHostMethod = util.isHostMethod;
-    var DomRange = api.DomRange;
-    var WrappedRange = api.WrappedRange;
-    var DOMException = api.DOMException;
-    var DomPosition = dom.DomPosition;
-    var getNativeSelection;
-    var selectionIsCollapsed;
-    var features = api.features;
-    var CONTROL = "Control";
-    var getDocument = dom.getDocument;
-    var getBody = dom.getBody;
-    var rangesEqual = DomRange.rangesEqual;
-
-
-    // Utility function to support direction parameters in the API that may be a string ("backward" or "forward") or a
-    // Boolean (true for backwards).
-    function isDirectionBackward(dir) {
-        return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
-    }
-
-    function getWindow(win, methodName) {
-        if (!win) {
-            return window;
-        } else if (dom.isWindow(win)) {
-            return win;
-        } else if (win instanceof WrappedSelection) {
-            return win.win;
-        } else {
-            var doc = dom.getContentDocument(win, module, methodName);
-            return dom.getWindow(doc);
-        }
-    }
-
-    function getWinSelection(winParam) {
-        return getWindow(winParam, "getWinSelection").getSelection();
-    }
-
-    function getDocSelection(winParam) {
-        return getWindow(winParam, "getDocSelection").document.selection;
-    }
-    
-    function winSelectionIsBackward(sel) {
-        var backward = false;
-        if (sel.anchorNode) {
-            backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
-        }
-        return backward;
-    }
-
-    // Test for the Range/TextRange and Selection features required
-    // Test for ability to retrieve selection
-    var implementsWinGetSelection = isHostMethod(window, "getSelection"),
-        implementsDocSelection = util.isHostObject(document, "selection");
-
-    features.implementsWinGetSelection = implementsWinGetSelection;
-    features.implementsDocSelection = implementsDocSelection;
-
-    var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
-
-    if (useDocumentSelection) {
-        getNativeSelection = getDocSelection;
-        api.isSelectionValid = function(winParam) {
-            var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
-
-            // Check whether the selection TextRange is actually contained within the correct document
-            return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
-        };
-    } else if (implementsWinGetSelection) {
-        getNativeSelection = getWinSelection;
-        api.isSelectionValid = function() {
-            return true;
-        };
-    } else {
-        module.fail("Neither document.selection or window.getSelection() detected.");
-    }
-
-    api.getNativeSelection = getNativeSelection;
-
-    var testSelection = getNativeSelection();
-    var testRange = api.createNativeRange(document);
-    var body = getBody(document);
-
-    // Obtaining a range from a selection
-    var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
-        ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
-
-    features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
-
-    // Test for existence of native selection extend() method
-    var selectionHasExtend = isHostMethod(testSelection, "extend");
-    features.selectionHasExtend = selectionHasExtend;
-    
-    // Test if rangeCount exists
-    var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
-    features.selectionHasRangeCount = selectionHasRangeCount;
-
-    var selectionSupportsMultipleRanges = false;
-    var collapsedNonEditableSelectionsSupported = true;
-
-    var addRangeBackwardToNative = selectionHasExtend ?
-        function(nativeSelection, range) {
-            var doc = DomRange.getRangeDocument(range);
-            var endRange = api.createRange(doc);
-            endRange.collapseToPoint(range.endContainer, range.endOffset);
-            nativeSelection.addRange(getNativeRange(endRange));
-            nativeSelection.extend(range.startContainer, range.startOffset);
-        } : null;
-
-    if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
-            typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
-
-        (function() {
-            // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
-            // performed on the current document's selection. See issue 109.
-
-            // Note also that if a selection previously existed, it is wiped by these tests. This should usually be fine
-            // because initialization usually happens when the document loads, but could be a problem for a script that
-            // loads and initializes Rangy later. If anyone complains, code could be added to save and restore the
-            // selection.
-            var sel = window.getSelection();
-            if (sel) {
-                // Store the current selection
-                var originalSelectionRangeCount = sel.rangeCount;
-                var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
-                var originalSelectionRanges = [];
-                var originalSelectionBackward = winSelectionIsBackward(sel); 
-                for (var i = 0; i < originalSelectionRangeCount; ++i) {
-                    originalSelectionRanges[i] = sel.getRangeAt(i);
-                }
-                
-                // Create some test elements
-                var body = getBody(document);
-                var testEl = body.appendChild( document.createElement("div") );
-                testEl.contentEditable = "false";
-                var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
-
-                // Test whether the native selection will allow a collapsed selection within a non-editable element
-                var r1 = document.createRange();
-
-                r1.setStart(textNode, 1);
-                r1.collapse(true);
-                sel.addRange(r1);
-                collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
-                sel.removeAllRanges();
-
-                // Test whether the native selection is capable of supporting multiple ranges
-                if (!selectionHasMultipleRanges) {
-                    var r2 = r1.cloneRange();
-                    r1.setStart(textNode, 0);
-                    r2.setEnd(textNode, 3);
-                    r2.setStart(textNode, 2);
-                    sel.addRange(r1);
-                    sel.addRange(r2);
-
-                    selectionSupportsMultipleRanges = (sel.rangeCount == 2);
-                    r2.detach();
-                }
-
-                // Clean up
-                body.removeChild(testEl);
-                sel.removeAllRanges();
-                r1.detach();
-
-                for (i = 0; i < originalSelectionRangeCount; ++i) {
-                    if (i == 0 && originalSelectionBackward) {
-                        if (addRangeBackwardToNative) {
-                            addRangeBackwardToNative(sel, originalSelectionRanges[i]);
-                        } else {
-                            api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because browser does not support Selection.extend");
-                            sel.addRange(originalSelectionRanges[i])
-                        }
-                    } else {
-                        sel.addRange(originalSelectionRanges[i])
-                    }
-                }
-            }
-        })();
-    }
-
-    features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
-    features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
-
-    // ControlRanges
-    var implementsControlRange = false, testControlRange;
-
-    if (body && isHostMethod(body, "createControlRange")) {
-        testControlRange = body.createControlRange();
-        if (util.areHostProperties(testControlRange, ["item", "add"])) {
-            implementsControlRange = true;
-        }
-    }
-    features.implementsControlRange = implementsControlRange;
-
-    // Selection collapsedness
-    if (selectionHasAnchorAndFocus) {
-        selectionIsCollapsed = function(sel) {
-            return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
-        };
-    } else {
-        selectionIsCollapsed = function(sel) {
-            return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
-        };
-    }
-
-    function updateAnchorAndFocusFromRange(sel, range, backward) {
-        var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
-        sel.anchorNode = range[anchorPrefix + "Container"];
-        sel.anchorOffset = range[anchorPrefix + "Offset"];
-        sel.focusNode = range[focusPrefix + "Container"];
-        sel.focusOffset = range[focusPrefix + "Offset"];
-    }
-
-    function updateAnchorAndFocusFromNativeSelection(sel) {
-        var nativeSel = sel.nativeSelection;
-        sel.anchorNode = nativeSel.anchorNode;
-        sel.anchorOffset = nativeSel.anchorOffset;
-        sel.focusNode = nativeSel.focusNode;
-        sel.focusOffset = nativeSel.focusOffset;
-    }
-
-    function updateEmptySelection(sel) {
-        sel.anchorNode = sel.focusNode = null;
-        sel.anchorOffset = sel.focusOffset = 0;
-        sel.rangeCount = 0;
-        sel.isCollapsed = true;
-        sel._ranges.length = 0;
-    }
-
-    function getNativeRange(range) {
-        var nativeRange;
-        if (range instanceof DomRange) {
-            nativeRange = api.createNativeRange(range.getDocument());
-            nativeRange.setEnd(range.endContainer, range.endOffset);
-            nativeRange.setStart(range.startContainer, range.startOffset);
-        } else if (range instanceof WrappedRange) {
-            nativeRange = range.nativeRange;
-        } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
-            nativeRange = range;
-        }
-        return nativeRange;
-    }
-
-    function rangeContainsSingleElement(rangeNodes) {
-        if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
-            return false;
-        }
-        for (var i = 1, len = rangeNodes.length; i < len; ++i) {
-            if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    function getSingleElementFromRange(range) {
-        var nodes = range.getNodes();
-        if (!rangeContainsSingleElement(nodes)) {
-            throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
-        }
-        return nodes[0];
-    }
-
-    // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
-    function isTextRange(range) {
-        return !!range && typeof range.text != "undefined";
-    }
-
-    function updateFromTextRange(sel, range) {
-        // Create a Range from the selected TextRange
-        var wrappedRange = new WrappedRange(range);
-        sel._ranges = [wrappedRange];
-
-        updateAnchorAndFocusFromRange(sel, wrappedRange, false);
-        sel.rangeCount = 1;
-        sel.isCollapsed = wrappedRange.collapsed;
-    }
-
-    function updateControlSelection(sel) {
-        // Update the wrapped selection based on what's now in the native selection
-        sel._ranges.length = 0;
-        if (sel.docSelection.type == "None") {
-            updateEmptySelection(sel);
-        } else {
-            var controlRange = sel.docSelection.createRange();
-            if (isTextRange(controlRange)) {
-                // This case (where the selection type is "Control" and calling createRange() on the selection returns
-                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
-                // ControlRange have been removed from the ControlRange and removed from the document.
-                updateFromTextRange(sel, controlRange);
-            } else {
-                sel.rangeCount = controlRange.length;
-                var range, doc = getDocument(controlRange.item(0));
-                for (var i = 0; i < sel.rangeCount; ++i) {
-                    range = api.createRange(doc);
-                    range.selectNode(controlRange.item(i));
-                    sel._ranges.push(range);
-                }
-                sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
-                updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
-            }
-        }
-    }
-
-    function addRangeToControlSelection(sel, range) {
-        var controlRange = sel.docSelection.createRange();
-        var rangeElement = getSingleElementFromRange(range);
-
-        // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
-        // contained by the supplied range
-        var doc = getDocument(controlRange.item(0));
-        var newControlRange = getBody(doc).createControlRange();
-        for (var i = 0, len = controlRange.length; i < len; ++i) {
-            newControlRange.add(controlRange.item(i));
-        }
-        try {
-            newControlRange.add(rangeElement);
-        } catch (ex) {
-            throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
-        }
-        newControlRange.select();
-
-        // Update the wrapped selection based on what's now in the native selection
-        updateControlSelection(sel);
-    }
-
-    var getSelectionRangeAt;
-
-    if (isHostMethod(testSelection, "getRangeAt")) {
-        // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
-        // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
-        // lesson to us all, especially me.
-        getSelectionRangeAt = function(sel, index) {
-            try {
-                return sel.getRangeAt(index);
-            } catch (ex) {
-                return null;
-            }
-        };
-    } else if (selectionHasAnchorAndFocus) {
-        getSelectionRangeAt = function(sel) {
-            var doc = getDocument(sel.anchorNode);
-            var range = api.createRange(doc);
-            range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
-
-            // Handle the case when the selection was selected backwards (from the end to the start in the
-            // document)
-            if (range.collapsed !== this.isCollapsed) {
-                range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
-            }
-
-            return range;
-        };
-    }
-
-    function WrappedSelection(selection, docSelection, win) {
-        this.nativeSelection = selection;
-        this.docSelection = docSelection;
-        this._ranges = [];
-        this.win = win;
-        this.refresh();
-    }
-
-    WrappedSelection.prototype = api.selectionPrototype;
-
-    function deleteProperties(sel) {
-        sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
-        sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
-        sel.detached = true;
-    }
-
-    var cachedRangySelections = [];
-
-    function actOnCachedSelection(win, action) {
-        var i = cachedRangySelections.length, cached, sel;
-        while (i--) {
-            cached = cachedRangySelections[i];
-            sel = cached.selection;
-            if (action == "deleteAll") {
-                deleteProperties(sel);
-            } else if (cached.win == win) {
-                if (action == "delete") {
-                    cachedRangySelections.splice(i, 1);
-                    return true;
-                } else {
-                    return sel;
-                }
-            }
-        }
-        if (action == "deleteAll") {
-            cachedRangySelections.length = 0;
-        }
-        return null;
-    }
-
-    var getSelection = function(win) {
-        // Check if the parameter is a Rangy Selection object
-        if (win && win instanceof WrappedSelection) {
-            win.refresh();
-            return win;
-        }
-
-        win = getWindow(win, "getNativeSelection");
-
-        var sel = actOnCachedSelection(win);
-        var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
-        if (sel) {
-            sel.nativeSelection = nativeSel;
-            sel.docSelection = docSel;
-            sel.refresh();
-        } else {
-            sel = new WrappedSelection(nativeSel, docSel, win);
-            cachedRangySelections.push( { win: win, selection: sel } );
-        }
-        return sel;
-    };
-
-    api.getSelection = getSelection;
-
-    api.getIframeSelection = function(iframeEl) {
-        module.deprecationNotice("getIframeSelection()", "getSelection(iframeEl)");
-        return api.getSelection(dom.getIframeWindow(iframeEl));
-    };
-
-    var selProto = WrappedSelection.prototype;
-
-    function createControlSelection(sel, ranges) {
-        // Ensure that the selection becomes of type "Control"
-        var doc = getDocument(ranges[0].startContainer);
-        var controlRange = getBody(doc).createControlRange();
-        for (var i = 0, el, len = ranges.length; i < len; ++i) {
-            el = getSingleElementFromRange(ranges[i]);
-            try {
-                controlRange.add(el);
-            } catch (ex) {
-                throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
-            }
-        }
-        controlRange.select();
-
-        // Update the wrapped selection based on what's now in the native selection
-        updateControlSelection(sel);
-    }
-
-    // Selecting a range
-    if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
-        selProto.removeAllRanges = function() {
-            this.nativeSelection.removeAllRanges();
-            updateEmptySelection(this);
-        };
-
-        var addRangeBackward = function(sel, range) {
-            addRangeBackwardToNative(sel.nativeSelection, range);
-            sel.refresh();
-        };
-
-        if (selectionHasRangeCount) {
-            selProto.addRange = function(range, direction) {
-                if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
-                    addRangeToControlSelection(this, range);
-                } else {
-                    if (isDirectionBackward(direction) && selectionHasExtend) {
-                        addRangeBackward(this, range);
-                    } else {
-                        var previousRangeCount;
-                        if (selectionSupportsMultipleRanges) {
-                            previousRangeCount = this.rangeCount;
-                        } else {
-                            this.removeAllRanges();
-                            previousRangeCount = 0;
-                        }
-                        // Clone the native range so that changing the selected range does not affect the selection.
-                        // This is contrary to the spec but is the only way to achieve consistency between browsers. See
-                        // issue 80.
-                        this.nativeSelection.addRange(getNativeRange(range).cloneRange());
-
-                        // Check whether adding the range was successful
-                        this.rangeCount = this.nativeSelection.rangeCount;
-
-                        if (this.rangeCount == previousRangeCount + 1) {
-                            // The range was added successfully
-
-                            // Check whether the range that we added to the selection is reflected in the last range extracted from
-                            // the selection
-                            if (api.config.checkSelectionRanges) {
-                                var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
-                                if (nativeRange && !rangesEqual(nativeRange, range)) {
-                                    // Happens in WebKit with, for example, a selection placed at the start of a text node
-                                    range = new WrappedRange(nativeRange);
-                                }
-                            }
-                            this._ranges[this.rangeCount - 1] = range;
-                            updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
-                            this.isCollapsed = selectionIsCollapsed(this);
-                        } else {
-                            // The range was not added successfully. The simplest thing is to refresh
-                            this.refresh();
-                        }
-                    }
-                }
-            };
-        } else {
-            selProto.addRange = function(range, direction) {
-                if (isDirectionBackward(direction) && selectionHasExtend) {
-                    addRangeBackward(this, range);
-                } else {
-                    this.nativeSelection.addRange(getNativeRange(range));
-                    this.refresh();
-                }
-            };
-        }
-
-        selProto.setRanges = function(ranges) {
-            if (implementsControlRange && ranges.length > 1) {
-                createControlSelection(this, ranges);
-            } else {
-                this.removeAllRanges();
-                for (var i = 0, len = ranges.length; i < len; ++i) {
-                    this.addRange(ranges[i]);
-                }
-            }
-        };
-    } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
-               implementsControlRange && useDocumentSelection) {
-
-        selProto.removeAllRanges = function() {
-            // Added try/catch as fix for issue #21
-            try {
-                this.docSelection.empty();
-
-                // Check for empty() not working (issue #24)
-                if (this.docSelection.type != "None") {
-                    // Work around failure to empty a control selection by instead selecting a TextRange and then
-                    // calling empty()
-                    var doc;
-                    if (this.anchorNode) {
-                        doc = getDocument(this.anchorNode);
-                    } else if (this.docSelection.type == CONTROL) {
-                        var controlRange = this.docSelection.createRange();
-                        if (controlRange.length) {
-                            doc = getDocument( controlRange.item(0) );
-                        }
-                    }
-                    if (doc) {
-                        var textRange = getBody(doc).createTextRange();
-                        textRange.select();
-                        this.docSelection.empty();
-                    }
-                }
-            } catch(ex) {}
-            updateEmptySelection(this);
-        };
-
-        selProto.addRange = function(range) {
-            if (this.docSelection.type == CONTROL) {
-                addRangeToControlSelection(this, range);
-            } else {
-                api.WrappedTextRange.rangeToTextRange(range).select();
-                this._ranges[0] = range;
-                this.rangeCount = 1;
-                this.isCollapsed = this._ranges[0].collapsed;
-                updateAnchorAndFocusFromRange(this, range, false);
-            }
-        };
-
-        selProto.setRanges = function(ranges) {
-            this.removeAllRanges();
-            var rangeCount = ranges.length;
-            if (rangeCount > 1) {
-                createControlSelection(this, ranges);
-            } else if (rangeCount) {
-                this.addRange(ranges[0]);
-            }
-        };
-    } else {
-        module.fail("No means of selecting a Range or TextRange was found");
-        return false;
-    }
-
-    selProto.getRangeAt = function(index) {
-        if (index < 0 || index >= this.rangeCount) {
-            throw new DOMException("INDEX_SIZE_ERR");
-        } else {
-            // Clone the range to preserve selection-range independence. See issue 80.
-            return this._ranges[index].cloneRange();
-        }
-    };
-
-    var refreshSelection;
-
-    if (useDocumentSelection) {
-        refreshSelection = function(sel) {
-            var range;
-            if (api.isSelectionValid(sel.win)) {
-                range = sel.docSelection.createRange();
-            } else {
-                range = getBody(sel.win.document).createTextRange();
-                range.collapse(true);
-            }
-
-            if (sel.docSelection.type == CONTROL) {
-                updateControlSelection(sel);
-            } else if (isTextRange(range)) {
-                updateFromTextRange(sel, range);
-            } else {
-                updateEmptySelection(sel);
-            }
-        };
-    } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
-        refreshSelection = function(sel) {
-            if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
-                updateControlSelection(sel);
-            } else {
-                sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
-                if (sel.rangeCount) {
-                    for (var i = 0, len = sel.rangeCount; i < len; ++i) {
-                        sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
-                    }
-                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
-                    sel.isCollapsed = selectionIsCollapsed(sel);
-                } else {
-                    updateEmptySelection(sel);
-                }
-            }
-        };
-    } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
-        refreshSelection = function(sel) {
-            var range, nativeSel = sel.nativeSelection;
-            if (nativeSel.anchorNode) {
-                range = getSelectionRangeAt(nativeSel, 0);
-                sel._ranges = [range];
-                sel.rangeCount = 1;
-                updateAnchorAndFocusFromNativeSelection(sel);
-                sel.isCollapsed = selectionIsCollapsed(sel);
-            } else {
-                updateEmptySelection(sel);
-            }
-        };
-    } else {
-        module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
-        return false;
-    }
-
-    selProto.refresh = function(checkForChanges) {
-        var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
-        var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
-
-        refreshSelection(this);
-        if (checkForChanges) {
-            // Check the range count first
-            var i = oldRanges.length;
-            if (i != this._ranges.length) {
-                return true;
-            }
-
-            // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
-            // ranges after this
-            if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
-                return true;
-            }
-
-            // Finally, compare each range in turn
-            while (i--) {
-                if (!rangesEqual(oldRanges[i], this._ranges[i])) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    };
-
-    // Removal of a single range
-    var removeRangeManually = function(sel, range) {
-        var ranges = sel.getAllRanges();
-        sel.removeAllRanges();
-        for (var i = 0, len = ranges.length; i < len; ++i) {
-            if (!rangesEqual(range, ranges[i])) {
-                sel.addRange(ranges[i]);
-            }
-        }
-        if (!sel.rangeCount) {
-            updateEmptySelection(sel);
-        }
-    };
-
-    if (implementsControlRange) {
-        selProto.removeRange = function(range) {
-            if (this.docSelection.type == CONTROL) {
-                var controlRange = this.docSelection.createRange();
-                var rangeElement = getSingleElementFromRange(range);
-
-                // Create a new ControlRange containing all the elements in the selected ControlRange minus the
-                // element contained by the supplied range
-                var doc = getDocument(controlRange.item(0));
-                var newControlRange = getBody(doc).createControlRange();
-                var el, removed = false;
-                for (var i = 0, len = controlRange.length; i < len; ++i) {
-                    el = controlRange.item(i);
-                    if (el !== rangeElement || removed) {
-                        newControlRange.add(controlRange.item(i));
-                    } else {
-                        removed = true;
-                    }
-                }
-                newControlRange.select();
-
-                // Update the wrapped selection based on what's now in the native selection
-                updateControlSelection(this);
-            } else {
-                removeRangeManually(this, range);
-            }
-        };
-    } else {
-        selProto.removeRange = function(range) {
-            removeRangeManually(this, range);
-        };
-    }
-
-    // Detecting if a selection is backward
-    var selectionIsBackward;
-    if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
-        selectionIsBackward = winSelectionIsBackward;
-
-        selProto.isBackward = function() {
-            return selectionIsBackward(this);
-        };
-    } else {
-        selectionIsBackward = selProto.isBackward = function() {
-            return false;
-        };
-    }
-
-    // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
-    selProto.isBackwards = selProto.isBackward;
-
-    // Selection stringifier
-    // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
-    // The current spec does not yet define this method.
-    selProto.toString = function() {
-        var rangeTexts = [];
-        for (var i = 0, len = this.rangeCount; i < len; ++i) {
-            rangeTexts[i] = "" + this._ranges[i];
-        }
-        return rangeTexts.join("");
-    };
-
-    function assertNodeInSameDocument(sel, node) {
-        if (sel.win.document != getDocument(node)) {
-            throw new DOMException("WRONG_DOCUMENT_ERR");
-        }
-    }
-
-    // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
-    selProto.collapse = function(node, offset) {
-        assertNodeInSameDocument(this, node);
-        var range = api.createRange(node);
-        range.collapseToPoint(node, offset);
-        this.setSingleRange(range);
-        this.isCollapsed = true;
-    };
-
-    selProto.collapseToStart = function() {
-        if (this.rangeCount) {
-            var range = this._ranges[0];
-            this.collapse(range.startContainer, range.startOffset);
-        } else {
-            throw new DOMException("INVALID_STATE_ERR");
-        }
-    };
-
-    selProto.collapseToEnd = function() {
-        if (this.rangeCount) {
-            var range = this._ranges[this.rangeCount - 1];
-            this.collapse(range.endContainer, range.endOffset);
-        } else {
-            throw new DOMException("INVALID_STATE_ERR");
-        }
-    };
-
-    // The spec is very specific on how selectAllChildren should be implemented so the native implementation is
-    // never used by Rangy.
-    selProto.selectAllChildren = function(node) {
-        assertNodeInSameDocument(this, node);
-        var range = api.createRange(node);
-        range.selectNodeContents(node);
-        this.setSingleRange(range);
-    };
-
-    selProto.deleteFromDocument = function() {
-        // Sepcial behaviour required for IE's control selections
-        if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
-            var controlRange = this.docSelection.createRange();
-            var element;
-            while (controlRange.length) {
-                element = controlRange.item(0);
-                controlRange.remove(element);
-                element.parentNode.removeChild(element);
-            }
-            this.refresh();
-        } else if (this.rangeCount) {
-            var ranges = this.getAllRanges();
-            if (ranges.length) {
-                this.removeAllRanges();
-                for (var i = 0, len = ranges.length; i < len; ++i) {
-                    ranges[i].deleteContents();
-                }
-                // The spec says nothing about what the selection should contain after calling deleteContents on each
-                // range. Firefox moves the selection to where the final selected range was, so we emulate that
-                this.addRange(ranges[len - 1]);
-            }
-        }
-    };
-
-    // The following are non-standard extensions
-    selProto.eachRange = function(func, returnValue) {
-        for (var i = 0, len = this._ranges.length; i < len; ++i) {
-            if ( func( this.getRangeAt(i) ) ) {
-                return returnValue;
-            }
-        }
-    };
-
-    selProto.getAllRanges = function() {
-        var ranges = [];
-        this.eachRange(function(range) {
-            ranges.push(range);
-        });
-        return ranges;
-    };
-
-    selProto.setSingleRange = function(range, direction) {
-        this.removeAllRanges();
-        this.addRange(range, direction);
-    };
-
-    selProto.callMethodOnEachRange = function(methodName, params) {
-        var results = [];
-        this.eachRange( function(range) {
-            results.push( range[methodName].apply(range, params) );
-        } );
-        return results;
-    };
-    
-    function createStartOrEndSetter(isStart) {
-        return function(node, offset) {
-            var range;
-            if (this.rangeCount) {
-                range = this.getRangeAt(0);
-                range["set" + (isStart ? "Start" : "End")](node, offset);
-            } else {
-                range = api.createRange(this.win.document);
-                range.setStartAndEnd(node, offset);
-            }
-            this.setSingleRange(range, this.isBackward());
-        };
-    }
-
-    selProto.setStart = createStartOrEndSetter(true);
-    selProto.setEnd = createStartOrEndSetter(false);
-    
-    // Add select() method to Range prototype. Any existing selection will be removed.
-    api.rangePrototype.select = function(direction) {
-        getSelection( this.getDocument() ).setSingleRange(this, direction);
-    };
-
-    selProto.changeEachRange = function(func) {
-        var ranges = [];
-        var backward = this.isBackward();
-
-        this.eachRange(function(range) {
-            func(range);
-            ranges.push(range);
-        });
-
-        this.removeAllRanges();
-        if (backward && ranges.length == 1) {
-            this.addRange(ranges[0], "backward");
-        } else {
-            this.setRanges(ranges);
-        }
-    };
-
-    selProto.containsNode = function(node, allowPartial) {
-        return this.eachRange( function(range) {
-            return range.containsNode(node, allowPartial);
-        }, true );
-    };
-
-    selProto.getBookmark = function(containerNode) {
-        return {
-            backward: this.isBackward(),
-            rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
-        };
-    };
-
-    selProto.moveToBookmark = function(bookmark) {
-        var selRanges = [];
-        for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
-            range = api.createRange(this.win);
-            range.moveToBookmark(rangeBookmark);
-            selRanges.push(range);
-        }
-        if (bookmark.backward) {
-            this.setSingleRange(selRanges[0], "backward");
-        } else {
-            this.setRanges(selRanges);
-        }
-    };
-
-    selProto.toHtml = function() {
-        return this.callMethodOnEachRange("toHtml").join("");
-    };
-
-    function inspect(sel) {
-        var rangeInspects = [];
-        var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
-        var focus = new DomPosition(sel.focusNode, sel.focusOffset);
-        var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
-
-        if (typeof sel.rangeCount != "undefined") {
-            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
-                rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
-            }
-        }
-        return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
-                ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
-    }
-
-    selProto.getName = function() {
-        return "WrappedSelection";
-    };
-
-    selProto.inspect = function() {
-        return inspect(this);
-    };
-
-    selProto.detach = function() {
-        actOnCachedSelection(this.win, "delete");
-        deleteProperties(this);
-    };
-
-    WrappedSelection.detachAll = function() {
-        actOnCachedSelection(null, "deleteAll");
-    };
-
-    WrappedSelection.inspect = inspect;
-    WrappedSelection.isDirectionBackward = isDirectionBackward;
-
-    api.Selection = WrappedSelection;
-
-    api.selectionPrototype = selProto;
-
-    api.addCreateMissingNativeApiListener(function(win) {
-        if (typeof win.getSelection == "undefined") {
-            win.getSelection = function() {
-                return getSelection(win);
-            };
-        }
-        win = null;
-    });
-});
diff --git a/www/code/realtime-wysiwyg.js b/www/code/realtime-wysiwyg.js
deleted file mode 100644
index 4363c07f7..000000000
--- a/www/code/realtime-wysiwyg.js
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * Copyright 2014 XWiki SAS
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see .
- */
-define([
-    '/code/html-patcher.js',
-    '/code/errorbox.js',
-    '/common/messages.js',
-    '/bower_components/reconnectingWebsocket/reconnecting-websocket.js',
-    '/common/crypto.js',
-    '/common/toolbar.js',
-    '/code/rangy.js',
-    '/common/chainpad.js',
-    '/common/otaml.js',
-    '/bower_components/jquery/dist/jquery.min.js',
-], function (HTMLPatcher, ErrorBox, Messages, ReconnectingWebSocket, Crypto, Toolbar) {
-
-window.ErrorBox = ErrorBox;
-
-    var $ = window.jQuery;
-    var Rangy = window.rangy;
-    Rangy.init();
-    var ChainPad = window.ChainPad;
-    var Otaml = window.Otaml;
-
-    var PARANOIA = true;
-
-    var module = { exports: {} };
-
-    /**
-     * If an error is encountered but it is recoverable, do not immediately fail
-     * but if it keeps firing errors over and over, do fail.
-     */
-    var MAX_RECOVERABLE_ERRORS = 15;
-
-    /** Maximum number of milliseconds of lag before we fail the connection. */
-    var MAX_LAG_BEFORE_DISCONNECT = 20000;
-
-    // ------------------ Trapping Keyboard Events ---------------------- //
-
-    var bindEvents = function (element, events, callback, unbind) {
-        for (var i = 0; i < events.length; i++) {
-            var e = events[i];
-            if (element.addEventListener) {
-                if (unbind) {
-                    element.removeEventListener(e, callback, false);
-                } else {
-                    element.addEventListener(e, callback, false);
-                }
-            } else {
-                if (unbind) {
-                    element.detachEvent('on' + e, callback);
-                } else {
-                    element.attachEvent('on' + e, callback);
-                }
-            }
-        }
-    };
-
-    var bindAllEvents = function (cmDiv, onEvent, unbind)
-    {
-        bindEvents(cmDiv,
-                   ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste', 'mousedown','mouseup','click'],
-                   onEvent,
-                   unbind);
-    };
-
-    var isSocketDisconnected = function (socket, realtime) {
-        var sock = socket._socket;
-        return sock.readyState === sock.CLOSING
-            || sock.readyState === sock.CLOSED
-            || (realtime.getLag().waiting && realtime.getLag().lag > MAX_LAG_BEFORE_DISCONNECT);
-    };
-
-    var abort = function (socket, realtime) {
-        realtime.abort();
-        realtime.toolbar.failed();
-        try { socket._socket.close(); } catch (e) { }
-    };
-
-    var createDebugInfo = function (cause, realtime, docHTML, allMessages) {
-        return JSON.stringify({
-            cause: cause,
-            realtimeUserDoc: realtime.getUserDoc(),
-            realtimeAuthDoc: realtime.getAuthDoc(),
-            docHTML: docHTML,
-            allMessages: allMessages,
-        });
-    };
-
-    var handleError = function (socket, realtime, err, docHTML, allMessages) {
-        var internalError = createDebugInfo(err, realtime, docHTML, allMessages);
-        abort(socket, realtime);
-        ErrorBox.show('error', docHTML, internalError);
-    };
-
-    var getDocHTML = function (doc) {
-        return $(doc).val();
-    };
-
-    var transformCursorCMRemove = function(text, cursor, pos, length) {
-      var newCursor = cursor;
-      var textLines = text.substr(0, pos).split("\n");
-      var removedTextLineNumber = textLines.length-1;
-      var removedTextColumnIndex = textLines[textLines.length-1].length;
-      var removedLines = text.substr(pos, length).split("\n").length - 1;
-      if(cursor.line > (removedTextLineNumber + removedLines)) {
-        newCursor.line -= removedLines;
-      }
-      else if(removedLines > 0 && cursor.line === (removedTextLineNumber+removedLines)) {
-        var lastLineCharsRemoved = text.substr(pos, length).split("\n")[removedLines].length;
-        if(cursor.ch >= lastLineCharsRemoved) {
-          newCursor.line = removedTextLineNumber;
-          newCursor.ch = removedTextColumnIndex + cursor.ch - lastLineCharsRemoved;
-        }
-        else {
-          newCursor.line -= removedLines;
-          newCursor.ch = removedTextColumnIndex;
-        }
-      }
-      else if(cursor.line === removedTextLineNumber && cursor.ch > removedTextLineNumber) {
-        newCursor.ch -= Math.min(length, cursor.ch-removedTextLineNumber);
-      }
-      return newCursor;
-    };
-    var transformCursorCMInsert = function(oldtext, cursor, pos, text) {
-      var newCursor = cursor;
-      var textLines = oldtext.substr(0, pos).split("\n");
-      var addedTextLineNumber = textLines.length-1;
-      var addedTextColumnIndex = textLines[textLines.length-1].length;
-      var addedLines = text.split("\n").length - 1;
-      if(cursor.line > addedTextLineNumber) {
-        newCursor.line += addedLines;
-      }
-      else if(cursor.line === addedTextLineNumber && cursor.ch > addedTextColumnIndex) {
-        newCursor.line += addedLines;
-        if(addedLines > 0) {
-          newCursor.ch = newCursor.ch - addedTextColumnIndex + text.split("\n")[addedLines].length;
-        }
-        else {
-          newCursor.ch += text.split("\n")[addedLines].length;
-        }
-      }
-      return newCursor;
-    };
-
-    var makeWebsocket = function (url) {
-        var socket = new ReconnectingWebSocket(url);
-        var out = {
-            onOpen: [],
-            onClose: [],
-            onError: [],
-            onMessage: [],
-            send: function (msg) { socket.send(msg); },
-            close: function () { socket.close(); },
-            _socket: socket
-        };
-        var mkHandler = function (name) {
-            return function (evt) {
-                for (var i = 0; i < out[name].length; i++) {
-                    if (out[name][i](evt) === false) { return; }
-                }
-            };
-        };
-        socket.onopen = mkHandler('onOpen');
-        socket.onclose = mkHandler('onClose');
-        socket.onerror = mkHandler('onError');
-        socket.onmessage = mkHandler('onMessage');
-        return out;
-    };
-
-    var start = module.exports.start =
-        function (window, websocketUrl, userName, channel, cryptKey)
-    {
-        var passwd = 'y';
-        //var wysiwygDiv = window.document.getElementById('cke_1_contents');
-        var doc = window.document.getElementById('editor1');
-        var cmDiv = window.document.getElementsByClassName('CodeMirror')[0];
-        var cmEditor = cmDiv.CodeMirror;
-        //var ifr = wysiwygDiv.getElementsByTagName('iframe')[0];
-        var socket = makeWebsocket(websocketUrl);
-        var onEvent = function () { };
-
-        var allMessages = [];
-        var isErrorState = false;
-        var initializing = true;
-        var recoverableErrorCount = 0;
-        var error = function (recoverable, err) {
-console.log(new Error().stack);
-            console.log('error: ' + err.stack);
-            if (recoverable && recoverableErrorCount++ < MAX_RECOVERABLE_ERRORS) { return; }
-            var realtime = socket.realtime;
-            var docHtml = getDocHTML(doc);
-            isErrorState = true;
-            handleError(socket, realtime, err, docHtml, allMessages);
-        };
-        var attempt = function (func) {
-            return function () {
-                var e;
-                try { return func.apply(func, arguments); } catch (ee) { e = ee; }
-                if (e) {
-                    console.log(e.stack);
-                    error(true, e);
-                }
-            };
-        };
-        var checkSocket = function () {
-            if (isSocketDisconnected(socket, socket.realtime) && !socket.intentionallyClosing) {
-                //isErrorState = true;
-                //abort(socket, socket.realtime);
-                //ErrorBox.show('disconnected', getDocHTML(doc));
-                return true;
-            }
-            return false;
-        };
-
-        socket.onOpen.push(function (evt) {
-            if (!initializing) {
-                socket.realtime.start();
-                return;
-            }
-
-            var realtime = socket.realtime =
-                ChainPad.create(userName,
-                                passwd,
-                                channel,
-                                getDocHTML(doc),
-                                { transformFunction: Otaml.transform });
-
-            var toolbar = realtime.toolbar =
-                Toolbar.create(window.$('#cme_toolbox'), userName, realtime);
-
-            onEvent = function () {
-                if (isErrorState) { return; }
-                if (initializing) { return; }
-
-                var oldDocText = realtime.getUserDoc();
-                var docText = getDocHTML(doc);
-                var op = attempt(Otaml.makeTextOperation)(oldDocText, docText);
-
-                if (!op) { return; }
-
-                if (op.toRemove > 0) {
-                    attempt(realtime.remove)(op.offset, op.toRemove);
-                }
-                if (op.toInsert.length > 0) {
-                    attempt(realtime.insert)(op.offset, op.toInsert);
-                }
-
-                if (realtime.getUserDoc() !== docText) {
-                    error(false, 'realtime.getUserDoc() !== docText');
-                }
-            };
-
-            var userDocBeforePatch;
-            var incomingPatch = function () {
-                if (isErrorState || initializing) { return; }
-                userDocBeforePatch = userDocBeforePatch || getDocHTML(doc);
-                if (PARANOIA && userDocBeforePatch !== getDocHTML(doc)) {
-                    error(false, "userDocBeforePatch != getDocHTML(doc)");
-                }
-                var op = attempt(Otaml.makeTextOperation)(userDocBeforePatch, realtime.getUserDoc());
-                var oldValue = getDocHTML(doc);
-                var newValue = realtime.getUserDoc();
-                // Fix cursor and/or selection
-                var oldCursor = cmEditor.getCursor();
-                var oldCursorCMStart = cmEditor.getCursor('from');
-                var oldCursorCMEnd = cmEditor.getCursor('to');
-                var newCursor;
-                var newSelection;
-                if(oldCursorCMStart !== oldCursorCMEnd) { // Selection
-                    if (op.toRemove > 0) {
-                        newSelection = [transformCursorCMRemove(oldValue, oldCursorCMStart, op.offset, op.toRemove), transformCursorCMRemove(oldValue, oldCursorCMEnd, op.offset, op.toRemove)];
-                    }
-                    if (op.toInsert.length > 0) {
-                        newSelection = [transformCursorCMInsert(oldValue, oldCursorCMStart, op.offset, op.toInsert), transformCursorCMInsert(oldValue, oldCursorCMEnd, op.offset, op.toInsert)];
-                    }
-                }
-                else { // Cursor
-                    if (op.toRemove > 0) {
-                        newCursor = transformCursorCMRemove(oldValue, oldCursor, op.offset, op.toRemove);
-                    }
-                    if (op.toInsert.length > 0) {
-                        newCursor = transformCursorCMInsert(oldValue, oldCursor, op.offset, op.toInsert);
-                    }
-                }
-                $(doc).val(newValue);
-                cmEditor.setValue(newValue);
-                if(newCursor) {
-                  cmEditor.setCursor(newCursor);
-                }
-                else {
-                  cmEditor.setSelection(newSelection[0], newSelection[1]);
-                }
-            };
-
-            realtime.onUserListChange(function (userList) {
-                if (!initializing || userList.indexOf(userName) === -1) { return; }
-                // if we spot ourselves being added to the document, we'll switch
-                // 'initializing' off because it means we're fully synced.
-                initializing = false;
-                incomingPatch();
-            });
-
-            socket.onMessage.push(function (evt) {
-                if (isErrorState) { return; }
-                var message = Crypto.decrypt(evt.data, cryptKey);
-                allMessages.push(message);
-                if (!initializing) {
-                    if (PARANOIA) { onEvent(); }
-                    userDocBeforePatch = realtime.getUserDoc();
-                }
-                realtime.message(message);
-            });
-            realtime.onMessage(function (message) {
-                if (isErrorState) { return; }
-                message = Crypto.encrypt(message, cryptKey);
-                try {
-                    socket.send(message);
-                } catch (e) {
-                    error(true, e.stack);
-                }
-            });
-
-            realtime.onPatch(incomingPatch);
-
-            bindAllEvents(cmDiv, onEvent, false);
-
-            setInterval(function () {
-                if (isErrorState || checkSocket()) {
-                    toolbar.reconnecting();
-                }
-            }, 200);
-
-            realtime.start();
-            toolbar.connected();
-
-            //console.log('started');
-        });
-        return {
-            onEvent: function () { onEvent(); }
-        };
-    };
-
-    return module.exports;
-});
diff --git a/www/code/rtwiki.js b/www/code/rt_codemirror.js
similarity index 50%
rename from www/code/rtwiki.js
rename to www/code/rt_codemirror.js
index c17b98daa..7135651ae 100644
--- a/www/code/rtwiki.js
+++ b/www/code/rt_codemirror.js
@@ -6,7 +6,7 @@ define([
   '/common/crypto.js',
   '/code/errorbox.js',
   '/common/messages.js',
-  '/common/toolbar.js',
+  '/code/toolbar.js',
   '/common/chainpad.js',
   '/common/otaml.js',
   '/bower_components/jquery/dist/jquery.min.js'
@@ -16,17 +16,6 @@ define([
     var Otaml = window.Otaml;
     var module = { exports: {} };
 
-    var LOCALSTORAGE_DISALLOW = 'rtwiki-disallow';
-
-    // Number for a message type which will not interfere with chainpad.
-    var MESSAGE_TYPE_ISAVED = 5000;
-
-    // how often to check if the document has been saved recently
-    var SAVE_DOC_CHECK_CYCLE = 20000;
-
-    // how often to save the document
-    var SAVE_DOC_TIME = 60000;
-
     // How long to wait before determining that the connection is lost.
     var MAX_LAG_BEFORE_DISCONNECT = 30000;
 
@@ -36,37 +25,6 @@ define([
     var debug = function (x) { };
     //debug = function (x) { console.log(x) };
     warn = function (x) { console.log(x); };
-    var setStyle = function () {
-        $('head').append([
-            ''
-         ].join(''));
-    };
 
     var uid = function () {
         return 'rtwiki-uid-' + String(Math.random()).substring(2);
@@ -151,178 +109,6 @@ define([
         return lagElement;
     };
 
-    var createRealtimeToolbar = function (container) {
-        var id = uid();
-        $(container).prepend(
-            '
' + - '
' + - '
' + - '
' - ); - return $('#'+id); - }; - - var now = function () { return (new Date()).getTime(); }; - - var getFormToken = function () { - return $('meta[name="form_token"]').attr('content'); - }; - - var getDocumentSection = function (sectionNum, andThen) { - debug("getting document section..."); - $.ajax({ - url: window.docediturl, - type: "POST", - async: true, - dataType: 'text', - data: { - xpage: 'editwiki', - section: ''+sectionNum - }, - success: function (jqxhr) { - var content = $(jqxhr).find('#content'); - if (!content || !content.length) { - andThen(new Error("could not find content")); - } else { - andThen(undefined, content.text()); - } - }, - error: function (jqxhr, err, cause) { - andThen(new Error(err)); - } - }); - }; - - var getIndexOfDocumentSection = function (documentContent, sectionNum, andThen) { - getDocumentSection(sectionNum, function (err, content) { - if (err) { - andThen(err); - return; - } - // This is screwed up, XWiki generates the section by rendering the XDOM back to - // XWiki2.0 syntax so it's not possible to find the actual location of a section. - // See: http://jira.xwiki.org/browse/XWIKI-10430 - var idx = documentContent.indexOf(content); - if (idx === -1) { - content = content.split('\n')[0]; - idx = documentContent.indexOf(content); - } - if (idx === -1) { - warn("Could not find section content.."); - } else if (idx !== documentContent.lastIndexOf(content)) { - warn("Duplicate section content.."); - } else { - andThen(undefined, idx); - return; - } - andThen(undefined, 0); - }); - }; - - var seekToSection = function (textArea, andThen) { - var sect = window.location.hash.match(/^#!([\W\w]*&)?section=([0-9]+)/); - if (!sect || !sect[2]) { - andThen(); - return; - } - var text = $(textArea).text(); - getIndexOfDocumentSection(text, Number(sect[2]), function (err, idx) { - if (err) { andThen(err); return; } - if (idx === 0) { - warn("Attempted to seek to a section which could not be found"); - } else { - var heightOne = $(textArea)[0].scrollHeight; - $(textArea).text(text.substring(idx)); - var heightTwo = $(textArea)[0].scrollHeight; - $(textArea).text(text); - $(textArea).scrollTop(heightOne - heightTwo); - } - andThen(); - }); - }; - - var saveDocument = function (textArea, language, andThen) { - debug("saving document..."); - $.ajax({ - url: window.docsaveurl, - type: "POST", - async: true, - dataType: 'text', - data: { - xredirect: '', - content: $(textArea).val(), - xeditaction: 'edit', - comment: 'Auto-Saved by Realtime Session', - action_saveandcontinue: 'Save & Continue', - minorEdit: 1, - ajax: true, - form_token: getFormToken(), - language: language - }, - success: function () { - andThen(); - }, - error: function (jqxhr, err, cause) { - warn(err); - // Don't callback, this way in case of error we will keep trying. - //andThen(); - } - }); - }; - - /** - * If we are editing a page which does not exist and creating it from a template - * then we should not auto-save the document otherwise it will cause RTWIKI-16 - */ - var createPageMode = function () { - return (window.location.href.indexOf('template=') !== -1); - }; - - var createSaver = function (socket, channel, myUserName, textArea, demoMode, language) { - var timeOfLastSave = now(); - socket.onMessage.unshift(function (evt) { - // get the content... - var chanIdx = evt.data.indexOf(channel); - var content = evt.data.substring(evt.data.indexOf(':[', chanIdx + channel.length)+1); - - // parse - var json = JSON.parse(content); - - // not an isaved message - if (json[0] !== MESSAGE_TYPE_ISAVED) { return; } - - timeOfLastSave = now(); - return false; - }); - - var lastSavedState = ''; - var to; - var check = function () { - if (to) { clearTimeout(to); } - debug("createSaver.check"); - to = setTimeout(check, Math.random() * SAVE_DOC_CHECK_CYCLE); - if (now() - timeOfLastSave < SAVE_DOC_TIME) { return; } - var toSave = $(textArea).val(); - if (lastSavedState === toSave) { return; } - if (demoMode) { return; } - saveDocument(textArea, language, function () { - debug("saved document"); - timeOfLastSave = now(); - lastSavedState = toSave; - var saved = JSON.stringify([MESSAGE_TYPE_ISAVED, 0]); - socket.send('1:x' + - myUserName.length + ':' + myUserName + - channel.length + ':' + channel + - saved.length + ':' + saved - ); - }); - }; - check(); - socket.onClose.push(function () { - clearTimeout(to); - }); - }; - var isSocketDisconnected = function (socket, realtime) { return socket.readyState === socket.CLOSING || socket.readyState === socket.CLOSED || @@ -338,243 +124,6 @@ define([ } }; - var startWebSocket = function (textArea, - toolbarContainer, - websocketUrl, - userName, - channel, - messages, - demoMode, - language) - { - debug("Opening websocket"); - localStorage.removeItem(LOCALSTORAGE_DISALLOW); - - var toolbar = createRealtimeToolbar(toolbarContainer); - var socket = new WebSocket(websocketUrl); - socket.onClose = []; - socket.onMessage = []; - var initState = $(textArea).val(); - var realtime = socket.realtime = ChainPad.create(userName, 'x', channel, initState); - // for debugging - window.rtwiki_chainpad = realtime; - - // http://jira.xwiki.org/browse/RTWIKI-21 - var onbeforeunload = window.onbeforeunload || function () { }; - window.onbeforeunload = function (ev) { - socket.intentionallyClosing = true; - return onbeforeunload(ev); - }; - - var isErrorState = false; - var checkSocket = function () { - if (socket.intentionallyClosing || isErrorState) { return false; } - if (isSocketDisconnected(socket, realtime)) { - realtime.abort(); - socket.close(); - ErrorBox.show('disconnected'); - isErrorState = true; - return true; - } - return false; - }; - - socket.onopen = function (evt) { - - var initializing = true; - - var userListElement = createUserList(realtime, - userName, - toolbar.find('.rtwiki-toolbar-leftside'), - messages); - - userListElement.text(messages.initializing); - - createLagElement(socket, - realtime, - toolbar.find('.rtwiki-toolbar-rightside'), - messages); - - setAutosaveHiddenState(true); - - createSaver(socket, channel, userName, textArea, demoMode, language); - - socket.onMessage.push(function (evt) { - debug(evt.data); - realtime.message(evt.data); - }); - realtime.onMessage(function (message) { socket.send(message); }); - - $(textArea).attr("disabled", "disabled"); - - realtime.onUserListChange(function (userList) { - if (initializing && userList.indexOf(userName) > -1) { - initializing = false; - $(textArea).val(realtime.getUserDoc()); - textArea.attach($(textArea)[0], realtime); - $(textArea).removeAttr("disabled"); - } - if (!initializing) { - updateUserList(userName, userListElement, userList, messages); - } - }); - - - debug("Bound websocket"); - realtime.start(); - }; - socket.onclose = function (evt) { - for (var i = 0; i < socket.onClose.length; i++) { - if (socket.onClose[i](evt) === false) { return; } - } - }; - socket.onmessage = function (evt) { - for (var i = 0; i < socket.onMessage.length; i++) { - if (socket.onMessage[i](evt) === false) { return; } - } - }; - socket.onerror = function (err) { - warn(err); - checkSocket(realtime); - }; - - var to = setInterval(function () { - checkSocket(realtime); - }, 500); - socket.onClose.push(function () { - clearTimeout(to); - if (toolbar && typeof toolbar.remove === 'function') { - toolbar.remove(); - } else { - warn("toolbar.remove is not a function"); //why not? - } - setAutosaveHiddenState(false); - }); - - return socket; - }; - - var stopWebSocket = function (socket) { - debug("Stopping websocket"); - socket.intentionallyClosing = true; - if (!socket) { return; } - if (socket.realtime) { socket.realtime.abort(); } - socket.close(); - }; - - var checkSectionEdit = function () { - var href = window.location.href; - if (href.indexOf('#') === -1) { href += '#!'; } - var si = href.indexOf('section='); - if (si === -1 || si > href.indexOf('#')) { return false; } - var m = href.match(/([&]*section=[0-9]+)/)[1]; - href = href.replace(m, ''); - if (m[0] === '&') { m = m.substring(1); } - href = href + '&' + m; - window.location.href = href; - return true; - }; - - var editor = function (websocketUrl, userName, messages, channel, demoMode, language) { - var contentInner = $('#xwikieditcontentinner'); - var textArea = contentInner.find('#content'); - if (!textArea.length) { - warn("WARNING: Could not find textarea to bind to"); - return; - } - - if (createPageMode()) { return; } - - if (checkSectionEdit()) { return; } - - setStyle(); - - var checked = (localStorage.getItem(LOCALSTORAGE_DISALLOW)) ? "" : 'checked="checked"'; - var allowRealtimeCbId = uid(); - $('#mainEditArea .buttons').append( - '
' + - '' + - '
' - ); - - var socket; - var checkboxClick = function (checked) { - if (checked || demoMode) { - socket = startWebSocket(textArea, - contentInner, - websocketUrl, - userName, - channel, - messages, - demoMode, - language); - } else if (socket) { - localStorage.setItem(LOCALSTORAGE_DISALLOW, 1); - stopWebSocket(socket); - socket = undefined; - } - }; - - seekToSection(textArea, function (err) { - if (err) { throw err; } - $('#'+allowRealtimeCbId).click(function () { checkboxClick(this.checked); }); - checkboxClick(checked); - }); - }; - - var main = module.exports.main = function (websocketUrl, - userName, - messages, - channel, - demoMode, - language) - { - - if (!websocketUrl) { - throw new Error("No WebSocket URL, please ensure Realtime Backend is installed."); - } - - // Either we are in edit mode or the document is locked. - // There is no cross-language way that the UI tells us the document is locked - // but we can hunt for the force button. - var forceLink = $('a[href$="&force=1"][href*="/edit/"]'); - - var hasActiveRealtimeSession = function () { - forceLink.text(messages.joinSession); - forceLink.attr('href', forceLink.attr('href') + '&editor=wiki'); - }; - - if (forceLink.length && !localStorage.getItem(LOCALSTORAGE_DISALLOW)) { - // ok it's locked. - var socket = new WebSocket(websocketUrl); - socket.onopen = function (evt) { - socket.onmessage = function (evt) { - debug("Message! " + evt.data); - var regMsgEnd = '3:[0]'; - if (evt.data.indexOf(regMsgEnd) !== evt.data.length - regMsgEnd.length) { - // Not a register message - } else if (evt.data.indexOf(userName.length + ':' + userName) === 0) { - // It's us registering - } else { - // Someone has registered - debug("hasActiveRealtimeSession"); - socket.close(); - hasActiveRealtimeSession(); - } - }; - socket.send('1:x' + userName.length + ':' + userName + - channel.length + ':' + channel + '3:[0]'); - debug("Bound websocket"); - }; - } else if (window.XWiki.editor === 'wiki' || demoMode) { - editor(websocketUrl, userName, messages, channel, demoMode, language); - } - }; - // CodeMirror/RTWiki // Trapping Keyboard Events var bindEvents = function (element, events, callback, unbind) { @@ -760,12 +309,12 @@ define([ var incomingPatch = function () { if (isErrorState || initializing) { return; } var textAreaVal = $(textArea).val(); + console.log($(textArea).val()); userDocBeforePatch = userDocBeforePatch || textAreaVal; if (userDocBeforePatch !== textAreaVal) { //error(false, "userDocBeforePatch !== textAreaVal"); } var op = attempt(Otaml.makeTextOperation)(userDocBeforePatch, realtime.getUserDoc()); - if (typeof op === 'undefined') { warn("TypeError: op is undefined"); return; @@ -796,6 +345,7 @@ define([ } } $(textArea).val(newValue); + userDocBeforePatch = newValue; cmEditor.setValue(newValue); if(newCursor) { cmEditor.setCursor(newCursor); @@ -884,7 +434,7 @@ define([ if (!websocketUrl) { throw new Error("No WebSocket URL, please ensure Realtime Backend is installed."); } - var cme = cmEditor(window, websocketUrl, userName, Messages, channel, cryptkey); + var cme = cmEditor(window, websocketUrl+'_old', userName, Messages, channel, cryptkey); return { onEvent: function () { cme.onEvent(); diff --git a/www/code/sharejs_textarea.js b/www/code/sharejs_textarea.js deleted file mode 100644 index 14076033a..000000000 --- a/www/code/sharejs_textarea.js +++ /dev/null @@ -1,263 +0,0 @@ -define(function () { - -/** - * Licensed under the standard MIT license: - * - * Copyright 2011 Joseph Gentle. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * See: https://github.com/share/ShareJS/blob/master/LICENSE - */ - -/* This contains the textarea binding for ShareJS. This binding is really - * simple, and a bit slow on big documents (Its O(N). However, it requires no - * changes to the DOM and no heavy libraries like ace. It works for any kind of - * text input field. - * - * You probably want to use this binding for small fields on forms and such. - * For code editors or rich text editors or whatever, I recommend something - * heavier. - */ - - -/* applyChange creates the edits to convert oldval -> newval. - * - * This function should be called every time the text element is changed. - * Because changes are always localised, the diffing is quite easy. We simply - * scan in from the start and scan in from the end to isolate the edited range, - * then delete everything that was removed & add everything that was added. - * This wouldn't work for complex changes, but this function should be called - * on keystroke - so the edits will mostly just be single character changes. - * Sometimes they'll paste text over other text, but even then the diff - * generated by this algorithm is correct. - * - * This algorithm is O(N). I suspect you could speed it up somehow using regular expressions. - */ -var applyChange = function(ctx, oldval, newval) { - // Strings are immutable and have reference equality. I think this test is O(1), so its worth doing. - if (oldval === newval) { return; } - - var commonStart = 0; - while (oldval.charAt(commonStart) === newval.charAt(commonStart)) { - commonStart++; - } - - var commonEnd = 0; - while (oldval.charAt(oldval.length - 1 - commonEnd) === newval.charAt(newval.length - 1 - commonEnd) && - commonEnd + commonStart < oldval.length && commonEnd + commonStart < newval.length) { - commonEnd++; - } - - if (oldval.length !== commonStart + commonEnd) { - ctx.remove(commonStart, oldval.length - commonStart - commonEnd); - } - if (newval.length !== commonStart + commonEnd) { - ctx.insert(commonStart, newval.slice(commonStart, newval.length - commonEnd)); - } -}; - -/** - * Fix issues with textarea content which is different per-browser. - */ -var cannonicalize = function (content) { - - return content.replace(/\r\n/g, '\n'); -}; - -// Attach a textarea to a document's editing context. -// -// The context is optional, and will be created from the document if its not -// specified. -var attachTextarea = function(elem, ctx, cmElem) { - - // initial state will always fail the !== check in genop. - var content = {}; - - // Replace the content of the text area with newText, and transform the - // current cursor by the specified function. - var replaceText = function(newText, transformCursor, transformCursorCM) { - var newCursor; - var newSelection; - - if(cmElem) { - // Fix cursor here? - var cursorCM = cmElem.getCursor(); - var cursorCMStart = cmElem.getCursor('from'); - var cursorCMEnd = cmElem.getCursor('to'); - if(cursorCMStart !== cursorCMEnd) { - newSelection = [transformCursorCM(elem.value, cursorCMStart), transformCursorCM(elem.value, cursorCMEnd)]; - } - else { - newCursor = transformCursorCM(elem.value, cursorCM); - } - } - - if (transformCursor && !cmElem) { - newSelection = [transformCursor(elem.selectionStart), transformCursor(elem.selectionEnd)]; - } - - // Fixate the window's scroll while we set the element's value. Otherwise - // the browser scrolls to the element. - var scrollTop = elem.scrollTop; - elem.value = newText; - if(cmElem) { - // Fix cursor here? - cmElem.setValue(newText); - if(newCursor) { - cmElem.setCursor(newCursor); - } - else { - cmElem.setSelection(newSelection[0], newSelection[1]); - } - } - content = elem.value; // Not done on one line so the browser can do newline conversion. - - if(!cmElem) { - if (elem.scrollTop !== scrollTop) { elem.scrollTop = scrollTop; } - - // Setting the selection moves the cursor. We'll just have to let your - // cursor drift if the element isn't active, though usually users don't - // care. - if (newSelection && window.document.activeElement === elem) { - elem.selectionStart = newSelection[0]; - elem.selectionEnd = newSelection[1]; - } - } - }; - - //replaceText(ctx.get()); - - - // *** remote -> local changes - - ctx.onRemove(function(pos, length) { - var transformCursor = function(cursor) { - // If the cursor is inside the deleted region, we only want to move back to the start - // of the region. Hence the Math.min. - return pos < cursor ? cursor - Math.min(length, cursor - pos) : cursor; - }; - var transformCursorCM = function(text, cursor) { - var newCursor = cursor; - var textLines = text.substr(0, pos).split("\n"); - var removedTextLineNumber = textLines.length-1; - var removedTextColumnIndex = textLines[textLines.length-1].length; - var removedLines = text.substr(pos, length).split("\n").length - 1; - if(cursor.line > (removedTextLineNumber + removedLines)) { - newCursor.line -= removedLines; - } - else if(removedLines > 0 && cursor.line === (removedTextLineNumber+removedLines)) { - var lastLineCharsRemoved = text.substr(pos, length).split("\n")[removedLines].length; - if(cursor.ch >= lastLineCharsRemoved) { - newCursor.line = removedTextLineNumber; - newCursor.ch = removedTextColumnIndex + cursor.ch - lastLineCharsRemoved; - } - else { - newCursor.line -= removedLines; - newCursor.ch = removedTextColumnIndex; - } - } - else if(cursor.line === removedTextLineNumber && cursor.ch > removedTextLineNumber) { - newCursor.ch -= Math.min(length, cursor.ch-removedTextLineNumber); - } - return newCursor; - }; - replaceText(ctx.getUserDoc(), transformCursor, transformCursorCM); - }); - - ctx.onInsert(function(pos, text) { - var transformCursor = function(cursor) { - return pos < cursor ? cursor + text.length : cursor; - }; - var transformCursorCM = function(oldtext, cursor) { - var newCursor = cursor; - var textLines = oldtext.substr(0, pos).split("\n"); - var addedTextLineNumber = textLines.length-1; - var addedTextColumnIndex = textLines[textLines.length-1].length; - var addedLines = text.split("\n").length - 1; - if(cursor.line > addedTextLineNumber) { - newCursor.line += addedLines; - } - else if(cursor.line === addedTextLineNumber && cursor.ch > addedTextColumnIndex) { - newCursor.line += addedLines; - if(addedLines > 0) { - newCursor.ch = newCursor.ch - addedTextColumnIndex + text.split("\n")[addedLines].length; - } - else { - newCursor.ch += text.split("\n")[addedLines].length; - } - } - return newCursor; - }; - replaceText(ctx.getUserDoc(), transformCursor, transformCursorCM); - }); - - - // *** local -> remote changes - - // This function generates operations from the changed content in the textarea. - var genOp = function() { - // In a timeout so the browser has time to propogate the event's changes to the DOM. - setTimeout(function() { - var val = elem.value; - if (val !== content) { - applyChange(ctx, ctx.getUserDoc(), cannonicalize(val)); - } - }, 0); - }; - - var eventNames = ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste']; - for (var i = 0; i < eventNames.length; i++) { - var e = eventNames[i]; - if (elem.addEventListener) { - elem.addEventListener(e, genOp, false); - } else { - elem.attachEvent('on' + e, genOp); - } - } - window.setTimeout(function() { - if(cmElem) { - var elem2 = cmElem; - elem2.on('change', function() { - elem2.save(); - genOp(); - }); - } - else { - console.log('CM inexistant'); - } - }, 500); - - - ctx.detach = function() { - for (var i = 0; i < eventNames.length; i++) { - var e = eventNames[i]; - if (elem.removeEventListener) { - elem.removeEventListener(e, genOp, false); - } else { - elem.detachEvent('on' + e, genOp); - } - } - }; - - return ctx; -}; - -return { attach: attachTextarea }; -}); diff --git a/www/code/toolbar.js b/www/code/toolbar.js new file mode 100644 index 000000000..8871833ff --- /dev/null +++ b/www/code/toolbar.js @@ -0,0 +1,246 @@ +define([ + '/common/messages.js' +], function (Messages) { + + /** Id of the element for getting debug info. */ + var DEBUG_LINK_CLS = 'rtwysiwyg-debug-link'; + + /** Id of the div containing the user list. */ + var USER_LIST_CLS = 'rtwysiwyg-user-list'; + + /** Id of the div containing the lag info. */ + var LAG_ELEM_CLS = 'rtwysiwyg-lag'; + + /** The toolbar class which contains the user list, debug link and lag. */ + var TOOLBAR_CLS = 'rtwysiwyg-toolbar'; + + /** Key in the localStore which indicates realtime activity should be disallowed. */ + var LOCALSTORAGE_DISALLOW = 'rtwysiwyg-disallow'; + + var SPINNER_DISAPPEAR_TIME = 3000; + var SPINNER = [ '-', '\\', '|', '/' ]; + + var uid = function () { + return 'rtwysiwyg-uid-' + String(Math.random()).substring(2); + }; + + var createRealtimeToolbar = function ($container) { + var id = uid(); + $container.prepend( + '
' + + '
' + + '
' + + '
' + ); + var toolbar = $container.find('#'+id); + toolbar.append([ + '' + ].join('\n')); + return toolbar; + }; + + var createEscape = function ($container) { + var id = uid(); + $container.append('
⇐ Back
'); + var $ret = $container.find('#'+id); + $ret.on('click', function () { + window.location.href = '/'; + }); + return $ret[0]; + }; + + var createSpinner = function ($container) { + var id = uid(); + $container.append('
'); + return $container.find('#'+id)[0]; + }; + + var kickSpinner = function (spinnerElement, reversed) { + var txt = spinnerElement.textContent || '-'; + var inc = (reversed) ? -1 : 1; + spinnerElement.textContent = SPINNER[(SPINNER.indexOf(txt) + inc) % SPINNER.length]; + if (spinnerElement.timeout) { clearTimeout(spinnerElement.timeout); } + spinnerElement.timeout = setTimeout(function () { + spinnerElement.textContent = ''; + }, SPINNER_DISAPPEAR_TIME); + }; + + var createUserList = function ($container) { + var id = uid(); + $container.append('
'); + return $container.find('#'+id)[0]; + }; + + var getOtherUsers = function(myUserName, userList) { + var i = 0; + var list = ''; + userList.forEach(function(user) { + if(user !== myUserName) { + if(i === 0) list = ' : '; + list += user + ', '; + i++; + } + }); + return (i > 0) ? list.slice(0, -2) : list; + } + + var updateUserList = function (myUserName, listElement, userList) { + var meIdx = userList.indexOf(myUserName); + if (meIdx === -1) { + listElement.textContent = Messages.synchronizing; + return; + } + if (userList.length === 1) { + listElement.innerHTML = Messages.editingAlone; + } else if (userList.length === 2) { + listElement.innerHTML = Messages.editingWithOneOtherPerson + getOtherUsers(myUserName, userList); + } else { + listElement.innerHTML = Messages.editingWith + ' ' + (userList.length - 1) + ' ' + Messages.otherPeople + getOtherUsers(myUserName, userList); + } + }; + + var createLagElement = function ($container) { + var id = uid(); + $container.append('
'); + return $container.find('#'+id)[0]; + }; + + var checkLag = function (realtime, lagElement) { + var lag = realtime.getLag(); + var lagSec = lag.lag/1000; + var lagMsg = Messages.lag + ' '; + if (lag.waiting && lagSec > 1) { + lagMsg += "?? " + Math.floor(lagSec); + } else { + lagMsg += lagSec; + } + lagElement.textContent = lagMsg; + }; + + // this is a little hack, it should go in it's own file. + // FIXME ok, so let's put it in its own file then + // TODO there should also be a 'clear recent pads' button + var rememberPad = function () { + // FIXME, this is overly complicated, use array methods + var recentPadsStr = localStorage['CryptPad_RECENTPADS']; + var recentPads = []; + if (recentPadsStr) { recentPads = JSON.parse(recentPadsStr); } + // TODO use window.location.hash or something like that + if (window.location.href.indexOf('#') === -1) { return; } + var now = new Date(); + var out = []; + for (var i = recentPads.length; i >= 0; i--) { + if (recentPads[i] && + // TODO precompute this time value, maybe make it configurable? + // FIXME precompute the date too, why getTime every time? + now.getTime() - recentPads[i][1] < (1000*60*60*24*30) && + recentPads[i][0] !== window.location.href) + { + out.push(recentPads[i]); + } + } + out.push([window.location.href, now.getTime()]); + localStorage['CryptPad_RECENTPADS'] = JSON.stringify(out); + }; + + var create = function ($container, myUserName, realtime) { + var toolbar = createRealtimeToolbar($container); + createEscape(toolbar.find('.rtwysiwyg-toolbar-leftside')); + var userListElement = createUserList(toolbar.find('.rtwysiwyg-toolbar-leftside')); + var spinner = createSpinner(toolbar.find('.rtwysiwyg-toolbar-rightside')); + var lagElement = createLagElement(toolbar.find('.rtwysiwyg-toolbar-rightside')); + + rememberPad(); + + var connected = false; + + realtime.onUserListChange(function (userList) { + if (userList.indexOf(myUserName) !== -1) { connected = true; } + if (!connected) { return; } + updateUserList(myUserName, userListElement, userList); + }); + + var ks = function () { + if (connected) { kickSpinner(spinner, false); } + }; + + realtime.onPatch(ks); + // Try to filter out non-patch messages, doesn't have to be perfect this is just the spinner + realtime.onMessage(function (msg) { if (msg.indexOf(':[2,') > -1) { ks(); } }); + + setInterval(function () { + if (!connected) { return; } + checkLag(realtime, lagElement); + }, 3000); + + return { + failed: function () { + connected = false; + userListElement.textContent = ''; + lagElement.textContent = ''; + }, + reconnecting: function () { + connected = false; + userListElement.textContent = Messages.reconnecting; + lagElement.textContent = ''; + }, + connected: function () { + connected = true; + } + }; + }; + + return { create: create }; +}); \ No newline at end of file From 9330b00f02ec04adf163d2c051afbf48d3a5a2fa Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 11 Apr 2016 14:54:49 +0200 Subject: [PATCH 44/65] Hide the spreadsheet button until it is fixed --- customize.dist/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/customize.dist/index.html b/customize.dist/index.html index cdf6e9934..3dd81b486 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -220,7 +220,7 @@ From 713c90242b2e74be51b0f7d41961647ac35b478c Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 11 Apr 2016 15:00:22 +0200 Subject: [PATCH 45/65] Hide the usernames when they have not been changed --- www/common/toolbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 738d20c57..6b6323684 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -132,7 +132,7 @@ define([ userList.forEach(function(user) { if(user !== myUserName) { var data = (userData) ? (userData[user] || null) : null; - var userName = (data) ? data.name : user; + var userName = (data) ? data.name : null; if(userName) { if(i === 0) list = ' : '; list += userName + ', '; From c2e0dded3ad176d1addd831dd8723c71e2fa9fed Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 11 Apr 2016 15:47:40 +0200 Subject: [PATCH 46/65] Fix an issue with the cursor sometimes disappearing when a patch was received Update CodeMirror to the latest version (5.13.2) --- www/code/codemirror-5.13.2/.gitattributes | 8 + www/code/codemirror-5.13.2/.gitignore | 8 + www/code/codemirror-5.13.2/.npmignore | 10 + www/code/codemirror-5.13.2/.travis.yml | 4 + www/code/codemirror-5.13.2/AUTHORS | 558 ++ www/code/codemirror-5.13.2/CHANGELOG.md | 718 +++ www/code/codemirror-5.13.2/CONTRIBUTING.md | 88 + www/code/codemirror-5.13.2/LICENSE | 19 + www/code/codemirror-5.13.2/README.md | 28 + .../addon/comment/comment.js | 196 + .../addon/comment/continuecomment.js | 85 + .../codemirror-5.13.2/addon/dialog/dialog.css | 32 + .../codemirror-5.13.2/addon/dialog/dialog.js | 157 + .../addon/display/autorefresh.js | 47 + .../addon/display/fullscreen.css | 6 + .../addon/display/fullscreen.js | 41 + .../codemirror-5.13.2/addon/display/panel.js | 112 + .../addon/display/placeholder.js | 62 + .../codemirror-5.13.2/addon/display/rulers.js | 63 + .../addon/edit/closebrackets.js | 195 + .../codemirror-5.13.2/addon/edit/closetag.js | 169 + .../addon/edit/continuelist.js | 51 + .../addon/edit/matchbrackets.js | 120 + .../codemirror-5.13.2/addon/edit/matchtags.js | 66 + .../addon/edit/trailingspace.js | 27 + .../addon/fold/brace-fold.js | 105 + .../addon/fold/comment-fold.js | 59 + .../codemirror-5.13.2/addon/fold/foldcode.js | 149 + .../addon/fold/foldgutter.css | 20 + .../addon/fold/foldgutter.js | 146 + .../addon/fold/indent-fold.js | 44 + .../addon/fold/markdown-fold.js | 49 + .../codemirror-5.13.2/addon/fold/xml-fold.js | 182 + .../addon/hint/anyword-hint.js | 41 + .../codemirror-5.13.2/addon/hint/css-hint.js | 60 + .../codemirror-5.13.2/addon/hint/html-hint.js | 348 ++ .../addon/hint/javascript-hint.js | 146 + .../addon/hint/show-hint.css | 38 + .../codemirror-5.13.2/addon/hint/show-hint.js | 447 ++ .../codemirror-5.13.2/addon/hint/sql-hint.js | 281 + .../codemirror-5.13.2/addon/hint/xml-hint.js | 110 + .../addon/lint/coffeescript-lint.js | 41 + .../codemirror-5.13.2/addon/lint/css-lint.js | 35 + .../codemirror-5.13.2/addon/lint/html-lint.js | 46 + .../addon/lint/javascript-lint.js | 136 + .../codemirror-5.13.2/addon/lint/json-lint.js | 31 + .../codemirror-5.13.2/addon/lint/lint.css | 73 + www/code/codemirror-5.13.2/addon/lint/lint.js | 238 + .../codemirror-5.13.2/addon/lint/yaml-lint.js | 28 + .../codemirror-5.13.2/addon/merge/merge.css | 113 + .../codemirror-5.13.2/addon/merge/merge.js | 773 +++ .../codemirror-5.13.2/addon/mode/loadmode.js | 64 + .../codemirror-5.13.2/addon/mode/multiplex.js | 123 + .../addon/mode/multiplex_test.js | 33 + .../codemirror-5.13.2/addon/mode/overlay.js | 85 + .../codemirror-5.13.2/addon/mode/simple.js | 213 + .../addon/runmode/colorize.js | 40 + .../addon/runmode/runmode-standalone.js | 157 + .../addon/runmode/runmode.js | 72 + .../addon/runmode/runmode.node.js | 179 + .../addon/scroll/annotatescrollbar.js | 118 + .../addon/scroll/scrollpastend.js | 46 + .../addon/scroll/simplescrollbars.css | 66 + .../addon/scroll/simplescrollbars.js | 149 + .../addon/search/jump-to-line.js | 49 + .../addon/search/match-highlighter.js | 149 + .../addon/search/matchesonscrollbar.css | 8 + .../addon/search/matchesonscrollbar.js | 97 + .../codemirror-5.13.2/addon/search/search.js | 228 + .../addon/search/searchcursor.js | 189 + .../addon/selection/active-line.js | 74 + .../addon/selection/mark-selection.js | 118 + .../addon/selection/selection-pointer.js | 98 + .../codemirror-5.13.2/addon/tern/tern.css | 87 + www/code/codemirror-5.13.2/addon/tern/tern.js | 701 +++ .../codemirror-5.13.2/addon/tern/worker.js | 44 + .../codemirror-5.13.2/addon/wrap/hardwrap.js | 142 + www/code/codemirror-5.13.2/bin/authors.sh | 6 + www/code/codemirror-5.13.2/bin/compress | 92 + www/code/codemirror-5.13.2/bin/lint | 3 + www/code/codemirror-5.13.2/bin/release | 45 + .../codemirror-5.13.2/bin/source-highlight | 51 + www/code/codemirror-5.13.2/bower.json | 17 + .../codemirror-5.13.2/demo/activeline.html | 78 + .../codemirror-5.13.2/demo/anywordhint.html | 79 + www/code/codemirror-5.13.2/demo/bidi.html | 74 + www/code/codemirror-5.13.2/demo/btree.html | 83 + www/code/codemirror-5.13.2/demo/buffers.html | 109 + .../codemirror-5.13.2/demo/changemode.html | 58 + .../codemirror-5.13.2/demo/closebrackets.html | 52 + www/code/codemirror-5.13.2/demo/closetag.html | 41 + www/code/codemirror-5.13.2/demo/complete.html | 79 + www/code/codemirror-5.13.2/demo/emacs.html | 75 + www/code/codemirror-5.13.2/demo/folding.html | 95 + .../codemirror-5.13.2/demo/fullscreen.html | 83 + www/code/codemirror-5.13.2/demo/hardwrap.html | 75 + .../codemirror-5.13.2/demo/html5complete.html | 56 + .../codemirror-5.13.2/demo/indentwrap.html | 59 + www/code/codemirror-5.13.2/demo/lint.html | 171 + www/code/codemirror-5.13.2/demo/loadmode.html | 72 + www/code/codemirror-5.13.2/demo/marker.html | 52 + .../codemirror-5.13.2/demo/markselection.html | 52 + .../demo/matchhighlighter.html | 103 + .../codemirror-5.13.2/demo/matchtags.html | 48 + www/code/codemirror-5.13.2/demo/merge.html | 122 + .../codemirror-5.13.2/demo/multiplex.html | 75 + www/code/codemirror-5.13.2/demo/mustache.html | 69 + www/code/codemirror-5.13.2/demo/panel.html | 136 + .../codemirror-5.13.2/demo/placeholder.html | 45 + www/code/codemirror-5.13.2/demo/preview.html | 87 + .../codemirror-5.13.2/demo/requirejs.html | 70 + www/code/codemirror-5.13.2/demo/resize.html | 51 + www/code/codemirror-5.13.2/demo/rulers.html | 49 + www/code/codemirror-5.13.2/demo/runmode.html | 62 + www/code/codemirror-5.13.2/demo/search.html | 99 + .../codemirror-5.13.2/demo/simplemode.html | 186 + .../demo/simplescrollbars.html | 82 + .../demo/spanaffectswrapping_shim.html | 85 + www/code/codemirror-5.13.2/demo/sublime.html | 77 + www/code/codemirror-5.13.2/demo/tern.html | 133 + www/code/codemirror-5.13.2/demo/theme.html | 162 + .../codemirror-5.13.2/demo/trailingspace.html | 48 + .../demo/variableheight.html | 67 + www/code/codemirror-5.13.2/demo/vim.html | 112 + .../codemirror-5.13.2/demo/visibletabs.html | 62 + www/code/codemirror-5.13.2/demo/widget.html | 85 + .../codemirror-5.13.2/demo/xmlcomplete.html | 119 + .../codemirror-5.13.2/doc/activebookmark.js | 57 + www/code/codemirror-5.13.2/doc/compress.html | 346 ++ www/code/codemirror-5.13.2/doc/docs.css | 271 + www/code/codemirror-5.13.2/doc/internals.html | 504 ++ www/code/codemirror-5.13.2/doc/logo.png | Bin 0 -> 9310 bytes www/code/codemirror-5.13.2/doc/logo.svg | 181 + www/code/codemirror-5.13.2/doc/manual.html | 3382 +++++++++++ www/code/codemirror-5.13.2/doc/realworld.html | 179 + www/code/codemirror-5.13.2/doc/releases.html | 1238 ++++ www/code/codemirror-5.13.2/doc/reporting.html | 61 + .../codemirror-5.13.2/doc/upgrade_v2.2.html | 96 + .../codemirror-5.13.2/doc/upgrade_v3.html | 230 + .../codemirror-5.13.2/doc/upgrade_v4.html | 144 + www/code/codemirror-5.13.2/doc/yinyang.png | Bin 0 -> 4633 bytes www/code/codemirror-5.13.2/index.html | 199 + www/code/codemirror-5.13.2/keymap/emacs.js | 412 ++ www/code/codemirror-5.13.2/keymap/sublime.js | 569 ++ www/code/codemirror-5.13.2/keymap/vim.js | 5066 +++++++++++++++++ .../lib}/codemirror.css | 6 +- .../{ => codemirror-5.13.2/lib}/codemirror.js | 370 +- .../{ => codemirror-5.13.2}/mode/apl/apl.js | 0 .../mode/apl/index.html | 0 .../mode/asciiarmor/asciiarmor.js | 0 .../mode/asciiarmor/index.html | 0 .../mode/asn.1/asn.1.js | 0 .../mode/asn.1/index.html | 3 +- .../mode/asterisk/asterisk.js | 0 .../mode/asterisk/index.html | 0 .../mode/brainfuck/brainfuck.js | 0 .../mode/brainfuck/index.html | 0 .../mode/clike/clike.js | 302 +- .../mode/clike/index.html | 112 +- .../mode/clike/scala.html | 0 .../mode/clike/test.js | 9 + .../mode/clojure/clojure.js | 12 +- .../mode/clojure/index.html | 3 + .../mode/cmake/cmake.js | 0 .../mode/cmake/index.html | 0 .../mode/cobol/cobol.js | 0 .../mode/cobol/index.html | 0 .../mode/coffeescript/coffeescript.js | 11 - .../mode/coffeescript/index.html | 0 .../mode/commonlisp/commonlisp.js | 0 .../mode/commonlisp/index.html | 0 .../codemirror-5.13.2/mode/crystal/crystal.js | 391 ++ .../codemirror-5.13.2/mode/crystal/index.html | 119 + .../{ => codemirror-5.13.2}/mode/css/css.js | 38 +- .../{ => codemirror-5.13.2}/mode/css/gss.html | 0 .../mode/css/gss_test.js | 0 .../mode/css/index.html | 0 .../mode/css/less.html | 0 .../mode/css/less_test.js | 4 +- .../mode/css/scss.html | 0 .../mode/css/scss_test.js | 2 +- .../{ => codemirror-5.13.2}/mode/css/test.js | 10 +- .../mode/cypher/cypher.js | 6 +- .../mode/cypher/index.html | 0 www/code/{ => codemirror-5.13.2}/mode/d/d.js | 0 .../{ => codemirror-5.13.2}/mode/d/index.html | 0 www/code/codemirror-5.13.2/mode/dart/dart.js | 157 + .../mode/dart/index.html | 0 .../{ => codemirror-5.13.2}/mode/diff/diff.js | 0 .../mode/diff/index.html | 0 .../mode/django/django.js | 32 +- .../mode/django/index.html | 0 .../mode/dockerfile/dockerfile.js | 0 .../mode/dockerfile/index.html | 0 .../{ => codemirror-5.13.2}/mode/dtd/dtd.js | 0 .../mode/dtd/index.html | 0 .../mode/dylan/dylan.js | 85 +- .../mode/dylan/index.html | 0 www/code/codemirror-5.13.2/mode/dylan/test.js | 88 + .../{ => codemirror-5.13.2}/mode/ebnf/ebnf.js | 0 .../mode/ebnf/index.html | 0 .../{ => codemirror-5.13.2}/mode/ecl/ecl.js | 0 .../mode/ecl/index.html | 0 .../mode/eiffel/eiffel.js | 0 .../mode/eiffel/index.html | 0 .../{ => codemirror-5.13.2}/mode/elm/elm.js | 0 .../mode/elm/index.html | 0 .../mode/erlang/erlang.js | 0 .../mode/erlang/index.html | 0 .../mode/factor/factor.js | 0 .../mode/factor/index.html | 0 www/code/codemirror-5.13.2/mode/fcl/fcl.js | 173 + .../codemirror-5.13.2/mode/fcl/index.html | 108 + .../mode/forth/forth.js | 0 .../mode/forth/index.html | 0 .../mode/fortran/fortran.js | 0 .../mode/fortran/index.html | 0 .../{ => codemirror-5.13.2}/mode/gas/gas.js | 0 .../mode/gas/index.html | 0 .../{ => codemirror-5.13.2}/mode/gfm/gfm.js | 0 .../mode/gfm/index.html | 0 .../{ => codemirror-5.13.2}/mode/gfm/test.js | 0 .../mode/gherkin/gherkin.js | 0 .../mode/gherkin/index.html | 0 .../{ => codemirror-5.13.2}/mode/go/go.js | 0 .../mode/go/index.html | 0 .../mode/groovy/groovy.js | 0 .../mode/groovy/index.html | 0 .../{ => codemirror-5.13.2}/mode/haml/haml.js | 6 +- .../mode/haml/index.html | 0 .../{ => codemirror-5.13.2}/mode/haml/test.js | 0 .../mode/handlebars/handlebars.js | 19 +- .../mode/handlebars/index.html | 18 +- .../mode/haskell-literate/haskell-literate.js | 43 + .../mode/haskell-literate/index.html | 282 + .../mode/haskell/haskell.js | 0 .../mode/haskell/index.html | 0 .../{ => codemirror-5.13.2}/mode/haxe/haxe.js | 17 +- .../mode/haxe/index.html | 0 .../mode/htmlembedded/htmlembedded.js | 0 .../mode/htmlembedded/index.html | 0 .../mode/htmlmixed/htmlmixed.js | 42 +- .../mode/htmlmixed/index.html | 0 .../{ => codemirror-5.13.2}/mode/http/http.js | 0 .../mode/http/index.html | 0 .../{ => codemirror-5.13.2}/mode/idl/idl.js | 0 .../mode/idl/index.html | 0 .../{ => codemirror-5.13.2}/mode/index.html | 11 +- .../mode/jade/index.html | 0 .../{ => codemirror-5.13.2}/mode/jade/jade.js | 6 +- .../mode/javascript/index.html | 0 .../mode/javascript/javascript.js | 76 +- .../mode/javascript/json-ld.html | 0 .../mode/javascript/test.js | 48 +- .../mode/javascript/typescript.html | 0 .../mode/jinja2/index.html | 0 .../mode/jinja2/jinja2.js | 0 .../codemirror-5.13.2/mode/jsx/index.html | 89 + www/code/codemirror-5.13.2/mode/jsx/jsx.js | 147 + www/code/codemirror-5.13.2/mode/jsx/test.js | 69 + .../mode/julia/index.html | 0 .../codemirror-5.13.2/mode/julia/julia.js | 392 ++ .../mode/livescript/index.html | 0 .../mode/livescript/livescript.js | 0 .../mode/lua/index.html | 0 .../{ => codemirror-5.13.2}/mode/lua/lua.js | 0 .../mode/markdown/index.html | 0 .../mode/markdown/markdown.js | 207 +- .../mode/markdown/test.js | 120 + .../mode/mathematica/index.html | 0 .../mode/mathematica/mathematica.js | 0 www/code/{ => codemirror-5.13.2}/mode/meta.js | 21 +- .../mode/mirc/index.html | 0 .../{ => codemirror-5.13.2}/mode/mirc/mirc.js | 0 .../mode/mllike/index.html | 0 .../mode/mllike/mllike.js | 0 .../mode/modelica/index.html | 0 .../mode/modelica/modelica.js | 0 .../codemirror-5.13.2/mode/mscgen/index.html | 151 + .../mode/mscgen/mscgen.js | 4 +- .../mode/mscgen/mscgen_test.js | 0 .../mode/mscgen/msgenny_test.js | 0 .../mode/mscgen/xu_test.js | 0 .../mode/mumps/index.html | 4 +- .../mode/mumps/mumps.js | 0 .../mode/nginx/index.html | 4 +- .../mode/nginx/nginx.js | 2 +- .../codemirror-5.13.2/mode/nsis/index.html | 80 + www/code/codemirror-5.13.2/mode/nsis/nsis.js | 95 + .../mode/ntriples/index.html | 0 .../mode/ntriples/ntriples.js | 0 .../mode/octave/index.html | 0 .../mode/octave/octave.js | 0 .../mode/oz/index.html | 0 .../{ => codemirror-5.13.2}/mode/oz/oz.js | 0 .../mode/pascal/index.html | 0 .../mode/pascal/pascal.js | 0 .../mode/pegjs/index.html | 0 .../mode/pegjs/pegjs.js | 0 .../mode/perl/index.html | 0 .../{ => codemirror-5.13.2}/mode/perl/perl.js | 0 .../mode/php/index.html | 0 .../{ => codemirror-5.13.2}/mode/php/php.js | 0 .../{ => codemirror-5.13.2}/mode/php/test.js | 0 .../mode/pig/index.html | 4 +- .../{ => codemirror-5.13.2}/mode/pig/pig.js | 0 .../mode/properties/index.html | 0 .../mode/properties/properties.js | 0 .../mode/protobuf/index.html | 64 + .../mode/protobuf/protobuf.js | 68 + .../mode/puppet/index.html | 0 .../mode/puppet/puppet.js | 0 .../mode/python/index.html | 0 .../mode/python/python.js | 108 +- .../{ => codemirror-5.13.2}/mode/q/index.html | 0 www/code/{ => codemirror-5.13.2}/mode/q/q.js | 0 .../{ => codemirror-5.13.2}/mode/r/index.html | 0 www/code/{ => codemirror-5.13.2}/mode/r/r.js | 2 + .../mode/rpm/changes/index.html | 0 .../mode/rpm/index.html | 0 .../{ => codemirror-5.13.2}/mode/rpm/rpm.js | 26 +- .../mode/rst/index.html | 0 .../{ => codemirror-5.13.2}/mode/rst/rst.js | 0 .../mode/ruby/index.html | 0 .../{ => codemirror-5.13.2}/mode/ruby/ruby.js | 2 +- .../{ => codemirror-5.13.2}/mode/ruby/test.js | 0 .../mode/rust/index.html | 0 .../{ => codemirror-5.13.2}/mode/rust/rust.js | 19 +- .../{ => codemirror-5.13.2}/mode/rust/test.js | 1 - .../mode/sass/index.html | 0 .../{ => codemirror-5.13.2}/mode/sass/sass.js | 0 .../mode/scheme/index.html | 0 .../mode/scheme/scheme.js | 0 .../mode/shell/index.html | 0 .../mode/shell/shell.js | 0 .../mode/shell/test.js | 0 .../mode/sieve/index.html | 0 .../mode/sieve/sieve.js | 0 .../mode/slim/index.html | 0 .../{ => codemirror-5.13.2}/mode/slim/slim.js | 0 .../{ => codemirror-5.13.2}/mode/slim/test.js | 0 .../mode/smalltalk/index.html | 0 .../mode/smalltalk/smalltalk.js | 0 .../mode/smarty/index.html | 0 .../mode/smarty/smarty.js | 0 .../mode/solr/index.html | 0 .../{ => codemirror-5.13.2}/mode/solr/solr.js | 0 .../mode/soy/index.html | 0 .../{ => codemirror-5.13.2}/mode/soy/soy.js | 0 .../mode/sparql/index.html | 0 .../mode/sparql/sparql.js | 10 +- .../mode/spreadsheet/index.html | 0 .../mode/spreadsheet/spreadsheet.js | 5 +- .../mode/sql/index.html | 1 + .../{ => codemirror-5.13.2}/mode/sql/sql.js | 16 +- .../mode/stex/index.html | 0 .../{ => codemirror-5.13.2}/mode/stex/stex.js | 0 .../{ => codemirror-5.13.2}/mode/stex/test.js | 0 .../mode/stylus/index.html | 0 .../mode/stylus/stylus.js | 0 .../mode/swift/index.html | 20 +- .../codemirror-5.13.2/mode/swift/swift.js | 202 + .../mode/tcl/index.html | 0 .../{ => codemirror-5.13.2}/mode/tcl/tcl.js | 32 +- .../mode/textile/index.html | 0 .../mode/textile/test.js | 0 .../mode/textile/textile.js | 0 .../mode/tiddlywiki/index.html | 0 .../mode/tiddlywiki/tiddlywiki.css | 0 .../mode/tiddlywiki/tiddlywiki.js | 0 .../mode/tiki/index.html | 0 .../mode/tiki/tiki.css | 2 +- .../{ => codemirror-5.13.2}/mode/tiki/tiki.js | 0 .../mode/toml/index.html | 0 .../{ => codemirror-5.13.2}/mode/toml/toml.js | 0 .../mode/tornado/index.html | 0 .../mode/tornado/tornado.js | 0 .../mode/troff/index.html | 0 .../mode/troff/troff.js | 4 +- .../mode/ttcn-cfg/index.html | 0 .../mode/ttcn-cfg/ttcn-cfg.js | 0 .../mode/ttcn/index.html | 0 .../{ => codemirror-5.13.2}/mode/ttcn/ttcn.js | 0 .../mode/turtle/index.html | 0 .../mode/turtle/turtle.js | 0 .../mode/twig/index.html | 0 .../{ => codemirror-5.13.2}/mode/twig/twig.js | 15 +- .../mode/vb/index.html | 0 .../{ => codemirror-5.13.2}/mode/vb/vb.js | 0 .../mode/vbscript/index.html | 0 .../mode/vbscript/vbscript.js | 0 .../mode/velocity/index.html | 2 + .../mode/velocity/velocity.js | 2 +- .../mode/verilog/index.html | 0 .../mode/verilog/test.js | 0 .../mode/verilog/verilog.js | 0 .../mode/vhdl/index.html | 0 .../{ => codemirror-5.13.2}/mode/vhdl/vhdl.js | 0 .../mode/vue/index.html | 0 .../{ => codemirror-5.13.2}/mode/vue/vue.js | 0 .../mode/xml/index.html | 6 +- .../{ => codemirror-5.13.2}/mode/xml/test.js | 0 .../{ => codemirror-5.13.2}/mode/xml/xml.js | 149 +- .../mode/xquery/index.html | 0 .../mode/xquery/test.js | 0 .../mode/xquery/xquery.js | 0 .../mode/yaml-frontmatter/index.html | 121 + .../mode/yaml-frontmatter/yaml-frontmatter.js | 68 + .../mode/yaml/index.html | 0 .../{ => codemirror-5.13.2}/mode/yaml/yaml.js | 0 .../mode/z80/index.html | 0 .../{ => codemirror-5.13.2}/mode/z80/z80.js | 0 www/code/codemirror-5.13.2/package.json | 28 + .../codemirror-5.13.2/test/comment_test.js | 100 + www/code/codemirror-5.13.2/test/doc_test.js | 371 ++ www/code/codemirror-5.13.2/test/driver.js | 138 + www/code/codemirror-5.13.2/test/emacs_test.js | 147 + www/code/codemirror-5.13.2/test/index.html | 253 + www/code/codemirror-5.13.2/test/lint.js | 11 + www/code/codemirror-5.13.2/test/mode_test.css | 23 + www/code/codemirror-5.13.2/test/mode_test.js | 192 + www/code/codemirror-5.13.2/test/multi_test.js | 285 + .../codemirror-5.13.2/test/phantom_driver.js | 31 + www/code/codemirror-5.13.2/test/run.js | 31 + .../codemirror-5.13.2/test/scroll_test.js | 115 + .../codemirror-5.13.2/test/search_test.js | 62 + .../codemirror-5.13.2/test/sql-hint-test.js | 189 + .../codemirror-5.13.2/test/sublime_test.js | 303 + www/code/codemirror-5.13.2/test/test.js | 2151 +++++++ www/code/codemirror-5.13.2/test/vim_test.js | 4003 +++++++++++++ www/code/codemirror-5.13.2/theme/3024-day.css | 41 + .../codemirror-5.13.2/theme/3024-night.css | 39 + www/code/codemirror-5.13.2/theme/abcdef.css | 32 + .../theme/ambiance-mobile.css | 5 + www/code/codemirror-5.13.2/theme/ambiance.css | 74 + .../codemirror-5.13.2/theme/base16-dark.css | 38 + .../codemirror-5.13.2/theme/base16-light.css | 38 + www/code/codemirror-5.13.2/theme/bespin.css | 34 + .../codemirror-5.13.2/theme/blackboard.css | 32 + www/code/codemirror-5.13.2/theme/cobalt.css | 25 + .../codemirror-5.13.2/theme/colorforth.css | 33 + www/code/codemirror-5.13.2/theme/dracula.css | 41 + www/code/codemirror-5.13.2/theme/eclipse.css | 23 + www/code/codemirror-5.13.2/theme/elegant.css | 13 + .../codemirror-5.13.2/theme/erlang-dark.css | 34 + .../codemirror-5.13.2/theme/hopscotch.css | 34 + www/code/codemirror-5.13.2/theme/icecoder.css | 43 + www/code/codemirror-5.13.2/theme/isotope.css | 34 + .../codemirror-5.13.2/theme/lesser-dark.css | 47 + .../codemirror-5.13.2/theme/liquibyte.css | 95 + www/code/codemirror-5.13.2/theme/material.css | 53 + www/code/codemirror-5.13.2/theme/mbo.css | 37 + www/code/codemirror-5.13.2/theme/mdn-like.css | 46 + www/code/codemirror-5.13.2/theme/midnight.css | 45 + www/code/codemirror-5.13.2/theme/monokai.css | 36 + www/code/codemirror-5.13.2/theme/neat.css | 12 + www/code/codemirror-5.13.2/theme/neo.css | 43 + www/code/codemirror-5.13.2/theme/night.css | 27 + .../codemirror-5.13.2/theme/paraiso-dark.css | 38 + .../codemirror-5.13.2/theme/paraiso-light.css | 38 + .../theme/pastel-on-dark.css | 53 + .../codemirror-5.13.2/theme/railscasts.css | 34 + www/code/codemirror-5.13.2/theme/rubyblue.css | 25 + www/code/codemirror-5.13.2/theme/seti.css | 44 + .../codemirror-5.13.2/theme/solarized.css | 163 + .../codemirror-5.13.2/theme/the-matrix.css | 30 + .../theme/tomorrow-night-bright.css | 35 + .../theme/tomorrow-night-eighties.css | 38 + www/code/codemirror-5.13.2/theme/ttcn.css | 64 + www/code/codemirror-5.13.2/theme/twilight.css | 32 + .../codemirror-5.13.2/theme/vibrant-ink.css | 34 + www/code/codemirror-5.13.2/theme/xq-dark.css | 53 + www/code/codemirror-5.13.2/theme/xq-light.css | 43 + www/code/codemirror-5.13.2/theme/yeti.css | 44 + www/code/codemirror-5.13.2/theme/zenburn.css | 37 + www/code/inner.html | 38 +- www/code/mode/dart/dart.js | 50 - www/code/mode/julia/julia.js | 299 - www/code/mode/kotlin/index.html | 89 - www/code/mode/kotlin/kotlin.js | 284 - www/code/mode/mscgen/index.html | 65 - www/code/mode/mscgen/index_msgenny.html | 44 - www/code/mode/mscgen/index_xu.html | 70 - www/code/mode/swift/swift.js | 203 - www/code/rt_codemirror.js | 4 +- www/code/toolbar.js | 19 +- 486 files changed, 41341 insertions(+), 1823 deletions(-) create mode 100644 www/code/codemirror-5.13.2/.gitattributes create mode 100644 www/code/codemirror-5.13.2/.gitignore create mode 100644 www/code/codemirror-5.13.2/.npmignore create mode 100644 www/code/codemirror-5.13.2/.travis.yml create mode 100644 www/code/codemirror-5.13.2/AUTHORS create mode 100644 www/code/codemirror-5.13.2/CHANGELOG.md create mode 100644 www/code/codemirror-5.13.2/CONTRIBUTING.md create mode 100644 www/code/codemirror-5.13.2/LICENSE create mode 100644 www/code/codemirror-5.13.2/README.md create mode 100644 www/code/codemirror-5.13.2/addon/comment/comment.js create mode 100644 www/code/codemirror-5.13.2/addon/comment/continuecomment.js create mode 100644 www/code/codemirror-5.13.2/addon/dialog/dialog.css create mode 100644 www/code/codemirror-5.13.2/addon/dialog/dialog.js create mode 100644 www/code/codemirror-5.13.2/addon/display/autorefresh.js create mode 100644 www/code/codemirror-5.13.2/addon/display/fullscreen.css create mode 100644 www/code/codemirror-5.13.2/addon/display/fullscreen.js create mode 100644 www/code/codemirror-5.13.2/addon/display/panel.js create mode 100644 www/code/codemirror-5.13.2/addon/display/placeholder.js create mode 100644 www/code/codemirror-5.13.2/addon/display/rulers.js create mode 100644 www/code/codemirror-5.13.2/addon/edit/closebrackets.js create mode 100644 www/code/codemirror-5.13.2/addon/edit/closetag.js create mode 100644 www/code/codemirror-5.13.2/addon/edit/continuelist.js create mode 100644 www/code/codemirror-5.13.2/addon/edit/matchbrackets.js create mode 100644 www/code/codemirror-5.13.2/addon/edit/matchtags.js create mode 100644 www/code/codemirror-5.13.2/addon/edit/trailingspace.js create mode 100644 www/code/codemirror-5.13.2/addon/fold/brace-fold.js create mode 100644 www/code/codemirror-5.13.2/addon/fold/comment-fold.js create mode 100644 www/code/codemirror-5.13.2/addon/fold/foldcode.js create mode 100644 www/code/codemirror-5.13.2/addon/fold/foldgutter.css create mode 100644 www/code/codemirror-5.13.2/addon/fold/foldgutter.js create mode 100644 www/code/codemirror-5.13.2/addon/fold/indent-fold.js create mode 100644 www/code/codemirror-5.13.2/addon/fold/markdown-fold.js create mode 100644 www/code/codemirror-5.13.2/addon/fold/xml-fold.js create mode 100644 www/code/codemirror-5.13.2/addon/hint/anyword-hint.js create mode 100644 www/code/codemirror-5.13.2/addon/hint/css-hint.js create mode 100644 www/code/codemirror-5.13.2/addon/hint/html-hint.js create mode 100644 www/code/codemirror-5.13.2/addon/hint/javascript-hint.js create mode 100644 www/code/codemirror-5.13.2/addon/hint/show-hint.css create mode 100644 www/code/codemirror-5.13.2/addon/hint/show-hint.js create mode 100644 www/code/codemirror-5.13.2/addon/hint/sql-hint.js create mode 100644 www/code/codemirror-5.13.2/addon/hint/xml-hint.js create mode 100644 www/code/codemirror-5.13.2/addon/lint/coffeescript-lint.js create mode 100644 www/code/codemirror-5.13.2/addon/lint/css-lint.js create mode 100644 www/code/codemirror-5.13.2/addon/lint/html-lint.js create mode 100644 www/code/codemirror-5.13.2/addon/lint/javascript-lint.js create mode 100644 www/code/codemirror-5.13.2/addon/lint/json-lint.js create mode 100644 www/code/codemirror-5.13.2/addon/lint/lint.css create mode 100644 www/code/codemirror-5.13.2/addon/lint/lint.js create mode 100644 www/code/codemirror-5.13.2/addon/lint/yaml-lint.js create mode 100644 www/code/codemirror-5.13.2/addon/merge/merge.css create mode 100644 www/code/codemirror-5.13.2/addon/merge/merge.js create mode 100644 www/code/codemirror-5.13.2/addon/mode/loadmode.js create mode 100644 www/code/codemirror-5.13.2/addon/mode/multiplex.js create mode 100644 www/code/codemirror-5.13.2/addon/mode/multiplex_test.js create mode 100644 www/code/codemirror-5.13.2/addon/mode/overlay.js create mode 100644 www/code/codemirror-5.13.2/addon/mode/simple.js create mode 100644 www/code/codemirror-5.13.2/addon/runmode/colorize.js create mode 100644 www/code/codemirror-5.13.2/addon/runmode/runmode-standalone.js create mode 100644 www/code/codemirror-5.13.2/addon/runmode/runmode.js create mode 100644 www/code/codemirror-5.13.2/addon/runmode/runmode.node.js create mode 100644 www/code/codemirror-5.13.2/addon/scroll/annotatescrollbar.js create mode 100644 www/code/codemirror-5.13.2/addon/scroll/scrollpastend.js create mode 100644 www/code/codemirror-5.13.2/addon/scroll/simplescrollbars.css create mode 100644 www/code/codemirror-5.13.2/addon/scroll/simplescrollbars.js create mode 100644 www/code/codemirror-5.13.2/addon/search/jump-to-line.js create mode 100644 www/code/codemirror-5.13.2/addon/search/match-highlighter.js create mode 100644 www/code/codemirror-5.13.2/addon/search/matchesonscrollbar.css create mode 100644 www/code/codemirror-5.13.2/addon/search/matchesonscrollbar.js create mode 100644 www/code/codemirror-5.13.2/addon/search/search.js create mode 100644 www/code/codemirror-5.13.2/addon/search/searchcursor.js create mode 100644 www/code/codemirror-5.13.2/addon/selection/active-line.js create mode 100644 www/code/codemirror-5.13.2/addon/selection/mark-selection.js create mode 100644 www/code/codemirror-5.13.2/addon/selection/selection-pointer.js create mode 100644 www/code/codemirror-5.13.2/addon/tern/tern.css create mode 100644 www/code/codemirror-5.13.2/addon/tern/tern.js create mode 100644 www/code/codemirror-5.13.2/addon/tern/worker.js create mode 100644 www/code/codemirror-5.13.2/addon/wrap/hardwrap.js create mode 100644 www/code/codemirror-5.13.2/bin/authors.sh create mode 100644 www/code/codemirror-5.13.2/bin/compress create mode 100644 www/code/codemirror-5.13.2/bin/lint create mode 100644 www/code/codemirror-5.13.2/bin/release create mode 100644 www/code/codemirror-5.13.2/bin/source-highlight create mode 100644 www/code/codemirror-5.13.2/bower.json create mode 100644 www/code/codemirror-5.13.2/demo/activeline.html create mode 100644 www/code/codemirror-5.13.2/demo/anywordhint.html create mode 100644 www/code/codemirror-5.13.2/demo/bidi.html create mode 100644 www/code/codemirror-5.13.2/demo/btree.html create mode 100644 www/code/codemirror-5.13.2/demo/buffers.html create mode 100644 www/code/codemirror-5.13.2/demo/changemode.html create mode 100644 www/code/codemirror-5.13.2/demo/closebrackets.html create mode 100644 www/code/codemirror-5.13.2/demo/closetag.html create mode 100644 www/code/codemirror-5.13.2/demo/complete.html create mode 100644 www/code/codemirror-5.13.2/demo/emacs.html create mode 100644 www/code/codemirror-5.13.2/demo/folding.html create mode 100644 www/code/codemirror-5.13.2/demo/fullscreen.html create mode 100644 www/code/codemirror-5.13.2/demo/hardwrap.html create mode 100644 www/code/codemirror-5.13.2/demo/html5complete.html create mode 100644 www/code/codemirror-5.13.2/demo/indentwrap.html create mode 100644 www/code/codemirror-5.13.2/demo/lint.html create mode 100644 www/code/codemirror-5.13.2/demo/loadmode.html create mode 100644 www/code/codemirror-5.13.2/demo/marker.html create mode 100644 www/code/codemirror-5.13.2/demo/markselection.html create mode 100644 www/code/codemirror-5.13.2/demo/matchhighlighter.html create mode 100644 www/code/codemirror-5.13.2/demo/matchtags.html create mode 100644 www/code/codemirror-5.13.2/demo/merge.html create mode 100644 www/code/codemirror-5.13.2/demo/multiplex.html create mode 100644 www/code/codemirror-5.13.2/demo/mustache.html create mode 100644 www/code/codemirror-5.13.2/demo/panel.html create mode 100644 www/code/codemirror-5.13.2/demo/placeholder.html create mode 100644 www/code/codemirror-5.13.2/demo/preview.html create mode 100644 www/code/codemirror-5.13.2/demo/requirejs.html create mode 100644 www/code/codemirror-5.13.2/demo/resize.html create mode 100644 www/code/codemirror-5.13.2/demo/rulers.html create mode 100644 www/code/codemirror-5.13.2/demo/runmode.html create mode 100644 www/code/codemirror-5.13.2/demo/search.html create mode 100644 www/code/codemirror-5.13.2/demo/simplemode.html create mode 100644 www/code/codemirror-5.13.2/demo/simplescrollbars.html create mode 100644 www/code/codemirror-5.13.2/demo/spanaffectswrapping_shim.html create mode 100644 www/code/codemirror-5.13.2/demo/sublime.html create mode 100644 www/code/codemirror-5.13.2/demo/tern.html create mode 100644 www/code/codemirror-5.13.2/demo/theme.html create mode 100644 www/code/codemirror-5.13.2/demo/trailingspace.html create mode 100644 www/code/codemirror-5.13.2/demo/variableheight.html create mode 100644 www/code/codemirror-5.13.2/demo/vim.html create mode 100644 www/code/codemirror-5.13.2/demo/visibletabs.html create mode 100644 www/code/codemirror-5.13.2/demo/widget.html create mode 100644 www/code/codemirror-5.13.2/demo/xmlcomplete.html create mode 100644 www/code/codemirror-5.13.2/doc/activebookmark.js create mode 100644 www/code/codemirror-5.13.2/doc/compress.html create mode 100644 www/code/codemirror-5.13.2/doc/docs.css create mode 100644 www/code/codemirror-5.13.2/doc/internals.html create mode 100644 www/code/codemirror-5.13.2/doc/logo.png create mode 100644 www/code/codemirror-5.13.2/doc/logo.svg create mode 100644 www/code/codemirror-5.13.2/doc/manual.html create mode 100644 www/code/codemirror-5.13.2/doc/realworld.html create mode 100644 www/code/codemirror-5.13.2/doc/releases.html create mode 100644 www/code/codemirror-5.13.2/doc/reporting.html create mode 100644 www/code/codemirror-5.13.2/doc/upgrade_v2.2.html create mode 100644 www/code/codemirror-5.13.2/doc/upgrade_v3.html create mode 100644 www/code/codemirror-5.13.2/doc/upgrade_v4.html create mode 100644 www/code/codemirror-5.13.2/doc/yinyang.png create mode 100644 www/code/codemirror-5.13.2/index.html create mode 100644 www/code/codemirror-5.13.2/keymap/emacs.js create mode 100644 www/code/codemirror-5.13.2/keymap/sublime.js create mode 100644 www/code/codemirror-5.13.2/keymap/vim.js rename www/code/{ => codemirror-5.13.2/lib}/codemirror.css (97%) rename www/code/{ => codemirror-5.13.2/lib}/codemirror.js (97%) rename www/code/{ => codemirror-5.13.2}/mode/apl/apl.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/apl/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/asciiarmor/asciiarmor.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/asciiarmor/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/asn.1/asn.1.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/asn.1/index.html (98%) rename www/code/{ => codemirror-5.13.2}/mode/asterisk/asterisk.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/asterisk/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/brainfuck/brainfuck.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/brainfuck/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/clike/clike.js (70%) rename www/code/{ => codemirror-5.13.2}/mode/clike/index.html (65%) rename www/code/{ => codemirror-5.13.2}/mode/clike/scala.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/clike/test.js (83%) rename www/code/{ => codemirror-5.13.2}/mode/clojure/clojure.js (96%) rename www/code/{ => codemirror-5.13.2}/mode/clojure/index.html (97%) rename www/code/{ => codemirror-5.13.2}/mode/cmake/cmake.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/cmake/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/cobol/cobol.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/cobol/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/coffeescript/coffeescript.js (97%) rename www/code/{ => codemirror-5.13.2}/mode/coffeescript/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/commonlisp/commonlisp.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/commonlisp/index.html (100%) create mode 100644 www/code/codemirror-5.13.2/mode/crystal/crystal.js create mode 100644 www/code/codemirror-5.13.2/mode/crystal/index.html rename www/code/{ => codemirror-5.13.2}/mode/css/css.js (96%) rename www/code/{ => codemirror-5.13.2}/mode/css/gss.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/css/gss_test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/css/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/css/less.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/css/less_test.js (91%) rename www/code/{ => codemirror-5.13.2}/mode/css/scss.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/css/scss_test.js (97%) rename www/code/{ => codemirror-5.13.2}/mode/css/test.js (96%) rename www/code/{ => codemirror-5.13.2}/mode/cypher/cypher.js (86%) rename www/code/{ => codemirror-5.13.2}/mode/cypher/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/d/d.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/d/index.html (100%) create mode 100644 www/code/codemirror-5.13.2/mode/dart/dart.js rename www/code/{ => codemirror-5.13.2}/mode/dart/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/diff/diff.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/diff/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/django/django.js (90%) rename www/code/{ => codemirror-5.13.2}/mode/django/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/dockerfile/dockerfile.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/dockerfile/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/dtd/dtd.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/dtd/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/dylan/dylan.js (80%) rename www/code/{ => codemirror-5.13.2}/mode/dylan/index.html (100%) create mode 100644 www/code/codemirror-5.13.2/mode/dylan/test.js rename www/code/{ => codemirror-5.13.2}/mode/ebnf/ebnf.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/ebnf/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/ecl/ecl.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/ecl/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/eiffel/eiffel.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/eiffel/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/elm/elm.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/elm/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/erlang/erlang.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/erlang/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/factor/factor.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/factor/index.html (100%) create mode 100644 www/code/codemirror-5.13.2/mode/fcl/fcl.js create mode 100644 www/code/codemirror-5.13.2/mode/fcl/index.html rename www/code/{ => codemirror-5.13.2}/mode/forth/forth.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/forth/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/fortran/fortran.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/fortran/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/gas/gas.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/gas/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/gfm/gfm.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/gfm/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/gfm/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/gherkin/gherkin.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/gherkin/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/go/go.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/go/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/groovy/groovy.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/groovy/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/haml/haml.js (97%) rename www/code/{ => codemirror-5.13.2}/mode/haml/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/haml/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/handlebars/handlebars.js (67%) rename www/code/{ => codemirror-5.13.2}/mode/handlebars/index.html (84%) create mode 100644 www/code/codemirror-5.13.2/mode/haskell-literate/haskell-literate.js create mode 100644 www/code/codemirror-5.13.2/mode/haskell-literate/index.html rename www/code/{ => codemirror-5.13.2}/mode/haskell/haskell.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/haskell/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/haxe/haxe.js (97%) rename www/code/{ => codemirror-5.13.2}/mode/haxe/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/htmlembedded/htmlembedded.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/htmlembedded/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/htmlmixed/htmlmixed.js (76%) rename www/code/{ => codemirror-5.13.2}/mode/htmlmixed/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/http/http.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/http/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/idl/idl.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/idl/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/index.html (92%) rename www/code/{ => codemirror-5.13.2}/mode/jade/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/jade/jade.js (99%) rename www/code/{ => codemirror-5.13.2}/mode/javascript/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/javascript/javascript.js (91%) rename www/code/{ => codemirror-5.13.2}/mode/javascript/json-ld.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/javascript/test.js (76%) rename www/code/{ => codemirror-5.13.2}/mode/javascript/typescript.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/jinja2/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/jinja2/jinja2.js (100%) create mode 100644 www/code/codemirror-5.13.2/mode/jsx/index.html create mode 100644 www/code/codemirror-5.13.2/mode/jsx/jsx.js create mode 100644 www/code/codemirror-5.13.2/mode/jsx/test.js rename www/code/{ => codemirror-5.13.2}/mode/julia/index.html (100%) create mode 100644 www/code/codemirror-5.13.2/mode/julia/julia.js rename www/code/{ => codemirror-5.13.2}/mode/livescript/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/livescript/livescript.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/lua/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/lua/lua.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/markdown/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/markdown/markdown.js (83%) rename www/code/{ => codemirror-5.13.2}/mode/markdown/test.js (83%) rename www/code/{ => codemirror-5.13.2}/mode/mathematica/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/mathematica/mathematica.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/meta.js (92%) rename www/code/{ => codemirror-5.13.2}/mode/mirc/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/mirc/mirc.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/mllike/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/mllike/mllike.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/modelica/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/modelica/modelica.js (100%) create mode 100644 www/code/codemirror-5.13.2/mode/mscgen/index.html rename www/code/{ => codemirror-5.13.2}/mode/mscgen/mscgen.js (98%) rename www/code/{ => codemirror-5.13.2}/mode/mscgen/mscgen_test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/mscgen/msgenny_test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/mscgen/xu_test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/mumps/index.html (98%) rename www/code/{ => codemirror-5.13.2}/mode/mumps/mumps.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/nginx/index.html (99%) rename www/code/{ => codemirror-5.13.2}/mode/nginx/nginx.js (99%) create mode 100644 www/code/codemirror-5.13.2/mode/nsis/index.html create mode 100644 www/code/codemirror-5.13.2/mode/nsis/nsis.js rename www/code/{ => codemirror-5.13.2}/mode/ntriples/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/ntriples/ntriples.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/octave/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/octave/octave.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/oz/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/oz/oz.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/pascal/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/pascal/pascal.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/pegjs/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/pegjs/pegjs.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/perl/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/perl/perl.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/php/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/php/php.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/php/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/pig/index.html (98%) rename www/code/{ => codemirror-5.13.2}/mode/pig/pig.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/properties/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/properties/properties.js (100%) create mode 100644 www/code/codemirror-5.13.2/mode/protobuf/index.html create mode 100644 www/code/codemirror-5.13.2/mode/protobuf/protobuf.js rename www/code/{ => codemirror-5.13.2}/mode/puppet/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/puppet/puppet.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/python/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/python/python.js (78%) rename www/code/{ => codemirror-5.13.2}/mode/q/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/q/q.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/r/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/r/r.js (99%) rename www/code/{ => codemirror-5.13.2}/mode/rpm/changes/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/rpm/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/rpm/rpm.js (80%) rename www/code/{ => codemirror-5.13.2}/mode/rst/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/rst/rst.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/ruby/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/ruby/ruby.js (99%) rename www/code/{ => codemirror-5.13.2}/mode/ruby/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/rust/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/rust/rust.js (82%) rename www/code/{ => codemirror-5.13.2}/mode/rust/test.js (96%) rename www/code/{ => codemirror-5.13.2}/mode/sass/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/sass/sass.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/scheme/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/scheme/scheme.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/shell/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/shell/shell.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/shell/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/sieve/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/sieve/sieve.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/slim/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/slim/slim.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/slim/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/smalltalk/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/smalltalk/smalltalk.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/smarty/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/smarty/smarty.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/solr/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/solr/solr.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/soy/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/soy/soy.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/sparql/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/sparql/sparql.js (96%) rename www/code/{ => codemirror-5.13.2}/mode/spreadsheet/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/spreadsheet/spreadsheet.js (97%) rename www/code/{ => codemirror-5.13.2}/mode/sql/index.html (97%) rename www/code/{ => codemirror-5.13.2}/mode/sql/sql.js (78%) rename www/code/{ => codemirror-5.13.2}/mode/stex/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/stex/stex.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/stex/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/stylus/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/stylus/stylus.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/swift/index.html (99%) create mode 100644 www/code/codemirror-5.13.2/mode/swift/swift.js rename www/code/{ => codemirror-5.13.2}/mode/tcl/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/tcl/tcl.js (88%) rename www/code/{ => codemirror-5.13.2}/mode/textile/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/textile/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/textile/textile.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/tiddlywiki/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/tiddlywiki/tiddlywiki.css (100%) rename www/code/{ => codemirror-5.13.2}/mode/tiddlywiki/tiddlywiki.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/tiki/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/tiki/tiki.css (91%) rename www/code/{ => codemirror-5.13.2}/mode/tiki/tiki.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/toml/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/toml/toml.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/tornado/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/tornado/tornado.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/troff/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/troff/troff.js (93%) rename www/code/{ => codemirror-5.13.2}/mode/ttcn-cfg/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/ttcn-cfg/ttcn-cfg.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/ttcn/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/ttcn/ttcn.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/turtle/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/turtle/turtle.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/twig/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/twig/twig.js (87%) rename www/code/{ => codemirror-5.13.2}/mode/vb/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/vb/vb.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/vbscript/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/vbscript/vbscript.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/velocity/index.html (98%) rename www/code/{ => codemirror-5.13.2}/mode/velocity/velocity.js (99%) rename www/code/{ => codemirror-5.13.2}/mode/verilog/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/verilog/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/verilog/verilog.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/vhdl/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/vhdl/vhdl.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/vue/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/vue/vue.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/xml/index.html (86%) rename www/code/{ => codemirror-5.13.2}/mode/xml/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/xml/xml.js (70%) rename www/code/{ => codemirror-5.13.2}/mode/xquery/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/xquery/test.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/xquery/xquery.js (100%) create mode 100644 www/code/codemirror-5.13.2/mode/yaml-frontmatter/index.html create mode 100644 www/code/codemirror-5.13.2/mode/yaml-frontmatter/yaml-frontmatter.js rename www/code/{ => codemirror-5.13.2}/mode/yaml/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/yaml/yaml.js (100%) rename www/code/{ => codemirror-5.13.2}/mode/z80/index.html (100%) rename www/code/{ => codemirror-5.13.2}/mode/z80/z80.js (100%) create mode 100644 www/code/codemirror-5.13.2/package.json create mode 100644 www/code/codemirror-5.13.2/test/comment_test.js create mode 100644 www/code/codemirror-5.13.2/test/doc_test.js create mode 100644 www/code/codemirror-5.13.2/test/driver.js create mode 100644 www/code/codemirror-5.13.2/test/emacs_test.js create mode 100644 www/code/codemirror-5.13.2/test/index.html create mode 100644 www/code/codemirror-5.13.2/test/lint.js create mode 100644 www/code/codemirror-5.13.2/test/mode_test.css create mode 100644 www/code/codemirror-5.13.2/test/mode_test.js create mode 100644 www/code/codemirror-5.13.2/test/multi_test.js create mode 100644 www/code/codemirror-5.13.2/test/phantom_driver.js create mode 100644 www/code/codemirror-5.13.2/test/run.js create mode 100644 www/code/codemirror-5.13.2/test/scroll_test.js create mode 100644 www/code/codemirror-5.13.2/test/search_test.js create mode 100644 www/code/codemirror-5.13.2/test/sql-hint-test.js create mode 100644 www/code/codemirror-5.13.2/test/sublime_test.js create mode 100644 www/code/codemirror-5.13.2/test/test.js create mode 100644 www/code/codemirror-5.13.2/test/vim_test.js create mode 100644 www/code/codemirror-5.13.2/theme/3024-day.css create mode 100644 www/code/codemirror-5.13.2/theme/3024-night.css create mode 100644 www/code/codemirror-5.13.2/theme/abcdef.css create mode 100644 www/code/codemirror-5.13.2/theme/ambiance-mobile.css create mode 100644 www/code/codemirror-5.13.2/theme/ambiance.css create mode 100644 www/code/codemirror-5.13.2/theme/base16-dark.css create mode 100644 www/code/codemirror-5.13.2/theme/base16-light.css create mode 100644 www/code/codemirror-5.13.2/theme/bespin.css create mode 100644 www/code/codemirror-5.13.2/theme/blackboard.css create mode 100644 www/code/codemirror-5.13.2/theme/cobalt.css create mode 100644 www/code/codemirror-5.13.2/theme/colorforth.css create mode 100644 www/code/codemirror-5.13.2/theme/dracula.css create mode 100644 www/code/codemirror-5.13.2/theme/eclipse.css create mode 100644 www/code/codemirror-5.13.2/theme/elegant.css create mode 100644 www/code/codemirror-5.13.2/theme/erlang-dark.css create mode 100644 www/code/codemirror-5.13.2/theme/hopscotch.css create mode 100644 www/code/codemirror-5.13.2/theme/icecoder.css create mode 100644 www/code/codemirror-5.13.2/theme/isotope.css create mode 100644 www/code/codemirror-5.13.2/theme/lesser-dark.css create mode 100644 www/code/codemirror-5.13.2/theme/liquibyte.css create mode 100644 www/code/codemirror-5.13.2/theme/material.css create mode 100644 www/code/codemirror-5.13.2/theme/mbo.css create mode 100644 www/code/codemirror-5.13.2/theme/mdn-like.css create mode 100644 www/code/codemirror-5.13.2/theme/midnight.css create mode 100644 www/code/codemirror-5.13.2/theme/monokai.css create mode 100644 www/code/codemirror-5.13.2/theme/neat.css create mode 100644 www/code/codemirror-5.13.2/theme/neo.css create mode 100644 www/code/codemirror-5.13.2/theme/night.css create mode 100644 www/code/codemirror-5.13.2/theme/paraiso-dark.css create mode 100644 www/code/codemirror-5.13.2/theme/paraiso-light.css create mode 100644 www/code/codemirror-5.13.2/theme/pastel-on-dark.css create mode 100644 www/code/codemirror-5.13.2/theme/railscasts.css create mode 100644 www/code/codemirror-5.13.2/theme/rubyblue.css create mode 100644 www/code/codemirror-5.13.2/theme/seti.css create mode 100644 www/code/codemirror-5.13.2/theme/solarized.css create mode 100644 www/code/codemirror-5.13.2/theme/the-matrix.css create mode 100644 www/code/codemirror-5.13.2/theme/tomorrow-night-bright.css create mode 100644 www/code/codemirror-5.13.2/theme/tomorrow-night-eighties.css create mode 100644 www/code/codemirror-5.13.2/theme/ttcn.css create mode 100644 www/code/codemirror-5.13.2/theme/twilight.css create mode 100644 www/code/codemirror-5.13.2/theme/vibrant-ink.css create mode 100644 www/code/codemirror-5.13.2/theme/xq-dark.css create mode 100644 www/code/codemirror-5.13.2/theme/xq-light.css create mode 100644 www/code/codemirror-5.13.2/theme/yeti.css create mode 100644 www/code/codemirror-5.13.2/theme/zenburn.css delete mode 100644 www/code/mode/dart/dart.js delete mode 100644 www/code/mode/julia/julia.js delete mode 100644 www/code/mode/kotlin/index.html delete mode 100644 www/code/mode/kotlin/kotlin.js delete mode 100644 www/code/mode/mscgen/index.html delete mode 100644 www/code/mode/mscgen/index_msgenny.html delete mode 100644 www/code/mode/mscgen/index_xu.html delete mode 100644 www/code/mode/swift/swift.js diff --git a/www/code/codemirror-5.13.2/.gitattributes b/www/code/codemirror-5.13.2/.gitattributes new file mode 100644 index 000000000..f8bdd60f4 --- /dev/null +++ b/www/code/codemirror-5.13.2/.gitattributes @@ -0,0 +1,8 @@ +*.txt text +*.js text +*.html text +*.md text +*.json text +*.yml text +*.css text +*.svg text diff --git a/www/code/codemirror-5.13.2/.gitignore b/www/code/codemirror-5.13.2/.gitignore new file mode 100644 index 000000000..f91c241f2 --- /dev/null +++ b/www/code/codemirror-5.13.2/.gitignore @@ -0,0 +1,8 @@ +/node_modules +/npm-debug.log +/test*.html +.tern-* +*~ +*.swp +.idea +*.iml diff --git a/www/code/codemirror-5.13.2/.npmignore b/www/code/codemirror-5.13.2/.npmignore new file mode 100644 index 000000000..5ed053f89 --- /dev/null +++ b/www/code/codemirror-5.13.2/.npmignore @@ -0,0 +1,10 @@ +/node_modules +/demo +/doc +/test +/test*.html +/index.html +/mode/*/*test.js +/mode/*/*.html +/mode/index.html +.* diff --git a/www/code/codemirror-5.13.2/.travis.yml b/www/code/codemirror-5.13.2/.travis.yml new file mode 100644 index 000000000..52b8b8159 --- /dev/null +++ b/www/code/codemirror-5.13.2/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - stable +sudo: false diff --git a/www/code/codemirror-5.13.2/AUTHORS b/www/code/codemirror-5.13.2/AUTHORS new file mode 100644 index 000000000..03b1ac5b0 --- /dev/null +++ b/www/code/codemirror-5.13.2/AUTHORS @@ -0,0 +1,558 @@ +List of CodeMirror contributors. Updated before every release. + +4r2r +Aaron Brooks +Abdelouahab +Abe Fettig +Adam Ahmed +Adam King +adanlobato +Adán Lobato +Adrian Aichner +aeroson +Ahmad Amireh +Ahmad M. Zawawi +ahoward +Akeksandr Motsjonov +Alberto González Palomo +Alberto Pose +Albert Xing +Alexander Pavlov +Alexander Schepanovski +Alexander Shvets +Alexander Solovyov +Alexandre Bique +alexey-k +Alex Piggott +Aliaksei Chapyzhenka +Allen Sarkisyan +Amin Shali +Amin Ullah Khan +amshali@google.com +Amsul +amuntean +Amy +Ananya Sen +anaran +AndersMad +Anders Nawroth +Anderson Mesquita +Anders Wåglund +Andrea G +Andreas Reischuck +Andres Taylor +Andre von Houck +Andrey Fedorov +Andrey Klyuchnikov +Andrey Lushnikov +Andy Joslin +Andy Kimball +Andy Li +Angelo +angelozerr +angelo.zerr@gmail.com +Ankit +Ankit Ahuja +Ansel Santosa +Anthony Dugois +anthonygego +Anthony Gégo +Anthony Grimes +Anton Kovalyov +AQNOUCH Mohammed +areos +Arnab Bose +as3boyan +AtomicPages LLC +Atul Bhouraskar +Aurelian Oancea +Barret Rennie +Basarat Ali Syed +Bastian Müller +belhaj +Bem Jones-Bey +benbro +Beni Cherniavsky-Paskin +Benjamin DeCoste +Ben Keen +Ben Mosher +Bernhard Sirlinger +Bert Chang +Billy Moon +binny +B Krishna Chaitanya +Blaine G +blukat29 +boomyjee +borawjm +Brad Metcalf +Brandon Frohs +Brandon Wamboldt +Brett Zamir +Brian Grinstead +Brian Sletten +Bruce Mitchener +Caitlin Potter +Calin Barbat +Chad Jolly +Chandra Sekhar Pydi +Charles Skelton +Cheah Chu Yeow +Chris Coyier +Chris Ford +Chris Granger +Chris Houseknecht +Chris Lohfink +Chris Morgan +Christian Oyarzun +Christian Petrov +Christopher Brown +Christopher Mitchell +Christopher Pfohl +Chunliang Lyu +ciaranj +CodeAnimal +coderaiser +Cole R Lawrence +ComFreek +Curtis Gagliardi +dagsta +daines +Dale Jung +Dan Bentley +Dan Heberden +Daniel, Dao Quang Minh +Daniele Di Sarli +Daniel Faust +Daniel Huigens +Daniel Kesler +Daniel KJ +Daniel Neel +Daniel Parnell +Danny Yoo +darealshinji +Darius Roberts +Dave Brondsema +Dave Myers +David Barnett +David Mignot +David Pathakjee +David Vázquez +David Whittington +deebugger +Deep Thought +Devin Abbott +Devon Carew +dignifiedquire +Dimage Sapelkin +Dmitry Kiselyov +domagoj412 +Dominator008 +Domizio Demichelis +Doug Wikle +Drew Bratcher +Drew Hintz +Drew Khoury +Drini Cami +Dror BG +duralog +eborden +edsharp +ekhaled +Elisée +Enam Mijbah Noor +Eric Allam +Erik Welander +eustas +Fabien O'Carroll +Fabio Zendhi Nagao +Faiza Alsaied +Fauntleroy +fbuchinger +feizhang365 +Felipe Lalanne +Felix Raab +Filip Noetzel +flack +ForbesLindesay +Forbes Lindesay +Ford_Lawnmower +Forrest Oliphant +Frank Wiegand +Gabriel Gheorghian +Gabriel Horner +Gabriel Nahmias +galambalazs +Gautam Mehta +Gavin Douglas +gekkoe +geowarin +Gerard Braad +Gergely Hegykozi +Giovanni Calò +Glebov Boris +Glenn Jorde +Glenn Ruehle +Golevka +Google Inc. +Gordon Smith +Grant Skinner +greengiant +Gregory Koberger +Guillaume Massé +Guillaume Massé +guraga +Gustavo Rodrigues +Hakan Tunc +Hans Engel +Hardest +Hasan Karahan +Hector Oswaldo Caballero +Herculano Campos +Hiroyuki Makino +hitsthings +Hocdoc +Hugues Malphettes +Ian Beck +Ian Dickinson +Ian Wehrman +Ian Wetherbee +Ice White +ICHIKAWA, Yuji +idleberg +ilvalle +Ingo Richter +Irakli Gozalishvili +Ivan Kurnosov +Ivoah +Jacob Lee +Jakob Miland +Jakub Vrana +Jakub Vrána +James Campos +James Thorne +Jamie Hill +Jan Jongboom +jankeromnes +Jan Keromnes +Jan Odvarko +Jan Schär +Jan T. Sott +Jared Forsyth +Jason +Jason Barnabe +Jason Grout +Jason Johnston +Jason San Jose +Jason Siefken +Jaydeep Solanki +Jean Boussier +Jeff Blaisdell +jeffkenton +Jeff Pickhardt +jem (graphite) +Jeremy Parmenter +Jim +JobJob +jochenberger +Jochen Berger +Johan Ask +John Connor +John Engler +John Lees-Miller +John Snelson +John Van Der Loo +Jon Ander Peñalba +Jonas Döbertin +Jonathan Malmaud +jongalloway +Jon Malmaud +Jon Sangster +Joost-Wim Boekesteijn +Joseph Pecoraro +Josh Cohen +Joshua Newman +Josh Watzman +jots +jsoojeon +ju1ius +Juan Benavides Romero +Jucovschi Constantin +Juho Vuori +Julien Rebetez +Justin Andresen +Justin Hileman +jwallers@gmail.com +kaniga +karevn +Kayur Patel +Ken Newman +ken restivo +Ken Rockot +Kevin Earls +Kevin Sawicki +Kevin Ushey +Klaus Silveira +Koh Zi Han, Cliff +komakino +Konstantin Lopuhin +koops +ks-ifware +kubelsmieci +KwanEsq +Lanfei +Lanny +Laszlo Vidacs +leaf corcoran +Leonid Khachaturov +Leon Sorokin +Leonya Khachaturov +Liam Newman +Libo Cannici +LloydMilligan +LM +lochel +Lorenzo Stoakes +Luciano Longo +Luke Stagner +lynschinzer +M1cha +Madhura Jayaratne +Maksim Lin +Maksym Taran +Malay Majithia +Manuel Rego Casasnovas +Marat Dreizin +Marcel Gerber +Marco Aurélio +Marco Munizaga +Marcus Bointon +Marek Rudnicki +Marijn Haverbeke +Mário Gonçalves +Mario Pietsch +Mark Anderson +Mark Lentczner +Marko Bonaci +Markus Bordihn +Martin Balek +Martín Gaitán +Martin Hasoň +Martin Hunt +Martin Laine +Martin Zagora +Mason Malone +Mateusz Paprocki +Mathias Bynens +mats cronqvist +Matt Gaide +Matthew Bauer +Matthew Beale +Matthew Rathbone +Matthias Bussonnier +Matthias BUSSONNIER +Matt McDonald +Matt Pass +Matt Sacks +mauricio +Maximilian Hils +Maxim Kraev +Max Kirsch +Max Schaefer +Max Xiantu +mbarkhau +McBrainy +melpon +Metatheos +Micah Dubinko +Michael +Michael Goderbauer +Michael Grey +Michael Kaminsky +Michael Lehenbauer +Michael Zhou +Michal Dorner +Mighty Guava +Miguel Castillo +mihailik +Mike +Mike Brevoort +Mike Diaz +Mike Ivanov +Mike Kadin +MinRK +Miraculix87 +misfo +mkaminsky11 +mloginov +Moritz Schwörer +mps +ms +mtaran-google +Narciso Jaramillo +Nathan Williams +ndr +nerbert +nextrevision +ngn +nguillaumin +Ng Zhi An +Nicholas Bollweg +Nicholas Bollweg (Nick) +Nick Kreeger +Nick Small +Nicolò Ribaudo +Niels van Groningen +nightwing +Nikita Beloglazov +Nikita Vasilyev +Nikolay Kostov +nilp0inter +Nisarg Jhaveri +nlwillia +noragrossman +Norman Rzepka +Oreoluwa Onatemowo +pablo +pabloferz +Page +Panupong Pasupat +paris +Paris +Patil Arpith +Patrick Stoica +Patrick Strawderman +Paul Garvin +Paul Ivanov +Pavel +Pavel Feldman +Pavel Strashkin +Paweł Bartkiewicz +peteguhl +peter +Peter Flynn +peterkroon +Peter Kroon +Philipp A +Philip Stadermann +Pierre Gerold +Piët Delport +prasanthj +Prasanth J +Prayag Verma +Radek Piórkowski +Rahul +Rahul Anand +ramwin1 +Randall Mason +Randy Burden +Randy Edmunds +Rasmus Erik Voel Jensen +ray ratchup +Ray Ratchup +Richard Denton +Richard van der Meer +Richard Z.H. Wang +Robert Crossfield +Roberto Abdelkader Martínez Pérez +robertop23 +Robert Plummer +Rrandom +Ruslan Osmanov +Ryan Prior +sabaca +Samuel Ainsworth +sandeepshetty +Sander AKA Redsandro +santec +Sascha Peilicke +satamas +satchmorun +sathyamoorthi +S. Chris Colbert +SCLINIC\jdecker +Scott Aikin +Scott Goodhew +Sebastian Zaha +Sergey Goder +Se-Won Kim +shaund +shaun gilchrist +Shawn A +Shea Bunge +sheopory +Shiv Deepak +Shmuel Englard +Shubham Jain +silverwind +sinkuu +snasa +soliton4 +sonson +spastorelli +srajanpaliwal +Stanislav Oaserele +Stas Kobzar +Stefan Borsje +Steffen Beyer +Steffen Bruchmann +Stephen Lavelle +Steve Champagne +Steve O'Hara +stoskov +Stu Kennedy +Sungho Kim +sverweij +Taha Jahangir +Tako Schotanus +Takuji Shimokawa +Tarmil +TDaglis +tel +tfjgeorge +Thaddee Tyl +thanasis +TheHowl +think +Thomas Dvornik +Thomas Schmid +Tim Alby +Tim Baumann +Timothy Farrell +Timothy Hatcher +TobiasBg +Tomas-A +Tomas Varaneckas +Tom Erik Støwer +Tom MacWright +Tony Jian +Travis Heppe +Triangle717 +Tristan Tarrant +TSUYUSATO Kitsune +twifkak +Vestimir Markov +vf +Victor Bocharsky +Vincent Woo +Volker Mische +wenli +Wes Cossick +Wesley Wiser +Will Binns-Smith +Will Dean +William Jamieson +William Stein +Willy +Wojtek Ptak +Wu Cheng-Han +Xavier Mendez +Yassin N. Hassan +YNH Webdev +Yunchi Luo +Yuvi Panda +Zac Anger +Zachary Dremann +Zhang Hao +zziuni +魏鹏刚 diff --git a/www/code/codemirror-5.13.2/CHANGELOG.md b/www/code/codemirror-5.13.2/CHANGELOG.md new file mode 100644 index 000000000..b0a6ab57f --- /dev/null +++ b/www/code/codemirror-5.13.2/CHANGELOG.md @@ -0,0 +1,718 @@ +## 5.13.2 (2016-03-23) + +### Bugfixes + +Solves a problem where the gutter would sometimes not extend all the way to the end of the document. + +## 5.13.0 (2016-03-21) + +### New features + +New DOM event forwarded: [`"dragleave"`](http://codemirror.net/doc/manual.html#event_dom). + +[protobuf mode](http://codemirror.net/mode/protobuf/index.html): Newly added. + +### Bugfixes + +Fix problem where [`findMarks`](http://codemirror.net/doc/manual.html#findMarks) sometimes failed to find multi-line marks. + +Fix crash that showed up when atomic ranges and bidi text were combined. + +[show-hint addon](http://codemirror.net/demo/complete.html): Completion widgets no longer close when the line indented or dedented. + +[merge addon](http://codemirror.net/demo/merge.html): Fix bug when merging chunks at the end of the file. + +[placeholder addon](http://codemirror.net/doc/manual.html#addon_placeholder): No longer gets confused by [`swapDoc`](http://codemirror.net/doc/manual.html#swapDoc). + +[simplescrollbars addon](http://codemirror.net/doc/manual.html#addon_simplescrollbars): Fix invalid state when deleting at end of document. + +[clike mode](http://codemirror.net/mode/clike/index.html): No longer gets confused when a comment starts after an operator. + +[markdown mode](http://codemirror.net/mode/markdown/index.html): Now supports CommonMark-style flexible list indentation. + +[dylan mode](http://codemirror.net/mode/dylan/index.html): Several improvements and fixes. + +## 5.12.0 (2016-02-19) + +### New features + +[Vim bindings](http://codemirror.net/demo/vim.html): Ctrl-Q is now an alias for Ctrl-V. + +[Vim bindings](http://codemirror.net/demo/vim.html): The Vim API now exposes an `unmap` method to unmap bindings. + +[active-line addon](http://codemirror.net/demo/activeline.html): This addon can now style the active line's gutter. + +[FCL mode](http://codemirror.net/mode/fcl/): Newly added. + +[SQL mode](http://codemirror.net/mode/sql/): Now has a Postgresql dialect. + +### Bugfixes + +Fix [issue](https://github.com/codemirror/CodeMirror/issues/3781) where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly. + +Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a [problem](https://github.com/codemirror/CodeMirror/issues/3238) when the editor is inside a transformed parent container. + +Solve a [problem](https://github.com/codemirror/CodeMirror/issues/3821) where the horizontal scrollbar could hide text in Firefox. + +Fix a [bug](https://github.com/codemirror/CodeMirror/issues/3834) that caused phantom scroll space under the text in some situations. + +[Sublime Text bindings](http://codemirror.net/demo/sublime.html): Bind delete-line to Shift-Ctrl-K on OS X. + +[Markdown mode](http://codemirror.net/mode/markdown/): Fix [issue](https://github.com/codemirror/CodeMirror/issues/3787) where the mode would keep state related to fenced code blocks in an unsafe way, leading to occasional corrupted parses. + +[Markdown mode](http://codemirror.net/mode/markdown/): Ignore backslashes in code fragments. + +[Markdown mode](http://codemirror.net/mode/markdown/): Use whichever mode is registered as `text/html` to parse HTML. + +[Clike mode](http://codemirror.net/mode/clike/): Improve indentation of Scala `=>` functions. + +[Python mode](http://codemirror.net/mode/python/): Improve indentation of bracketed code. + +[HTMLMixed mode](http://codemirror.net/mode/htmlmixed/): Support multi-line opening tags for sub-languages (` + + + + + +
+

Active Line Demo

+
+ + + +

Styling the current cursor line.

+ +
diff --git a/www/code/codemirror-5.13.2/demo/anywordhint.html b/www/code/codemirror-5.13.2/demo/anywordhint.html new file mode 100644 index 000000000..0a7caece2 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/anywordhint.html @@ -0,0 +1,79 @@ + + +CodeMirror: Any Word Completion Demo + + + + + + + + + + + +
+

Any Word Completion Demo

+
+ +

Press ctrl-space to activate autocompletion. The +completion uses +the anyword-hint.js +module, which simply looks at nearby words in the buffer and completes +to those.

+ + +
diff --git a/www/code/codemirror-5.13.2/demo/bidi.html b/www/code/codemirror-5.13.2/demo/bidi.html new file mode 100644 index 000000000..6dd73bec3 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/bidi.html @@ -0,0 +1,74 @@ + + +CodeMirror: Bi-directional Text Demo + + + + + + + + + +
+

Bi-directional Text Demo

+
+ + + +

Demonstration of bi-directional text support. See + the related + blog post for more background.

+ +

Note: There is + a known + bug with cursor motion and mouse clicks in bi-directional lines + that are line wrapped.

+ +
diff --git a/www/code/codemirror-5.13.2/demo/btree.html b/www/code/codemirror-5.13.2/demo/btree.html new file mode 100644 index 000000000..ba07bc74f --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/btree.html @@ -0,0 +1,83 @@ + + +CodeMirror: B-Tree visualization + + + + + + + + +
+

B-Tree visualization

+
+
+ + + +

+ +
diff --git a/www/code/codemirror-5.13.2/demo/buffers.html b/www/code/codemirror-5.13.2/demo/buffers.html new file mode 100644 index 000000000..16ffc7dfe --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/buffers.html @@ -0,0 +1,109 @@ + + +CodeMirror: Multiple Buffer & Split View Demo + + + + + + + + + + +
+

Multiple Buffer & Split View Demo

+ + +
+
+ Select buffer: +     +
+
+
+ Select buffer: +     +
+ + + +

Demonstration of + using linked documents + to provide a split view on a document, and + using swapDoc + to use a single editor to display multiple documents.

+ +
diff --git a/www/code/codemirror-5.13.2/demo/changemode.html b/www/code/codemirror-5.13.2/demo/changemode.html new file mode 100644 index 000000000..9405932ab --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/changemode.html @@ -0,0 +1,58 @@ + + +CodeMirror: Mode-Changing Demo + + + + + + + + + + +
+

Mode-Changing Demo

+
+ +

On changes to the content of the above editor, a (crude) script +tries to auto-detect the language used, and switches the editor to +either JavaScript or Scheme mode based on that.

+ + +
diff --git a/www/code/codemirror-5.13.2/demo/closebrackets.html b/www/code/codemirror-5.13.2/demo/closebrackets.html new file mode 100644 index 000000000..d702f5269 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/closebrackets.html @@ -0,0 +1,52 @@ + + +CodeMirror: Closebrackets Demo + + + + + + + + + + +
+

Closebrackets Demo

+
+ + +
diff --git a/www/code/codemirror-5.13.2/demo/closetag.html b/www/code/codemirror-5.13.2/demo/closetag.html new file mode 100644 index 000000000..79959d2c4 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/closetag.html @@ -0,0 +1,41 @@ + + +CodeMirror: Close-Tag Demo + + + + + + + + + + + + + + +
+

Close-Tag Demo

+
+ + +
diff --git a/www/code/codemirror-5.13.2/demo/complete.html b/www/code/codemirror-5.13.2/demo/complete.html new file mode 100644 index 000000000..cdf49dbeb --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/complete.html @@ -0,0 +1,79 @@ + + +CodeMirror: Autocomplete Demo + + + + + + + + + + + + +
+

Autocomplete Demo

+
+ +

Press ctrl-space to activate autocompletion. Built +on top of the show-hint +and javascript-hint +addons.

+ + +
diff --git a/www/code/codemirror-5.13.2/demo/emacs.html b/www/code/codemirror-5.13.2/demo/emacs.html new file mode 100644 index 000000000..c626b8d40 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/emacs.html @@ -0,0 +1,75 @@ + + +CodeMirror: Emacs bindings demo + + + + + + + + + + + + + + + + +
+

Emacs bindings demo

+
+ +

The emacs keybindings are enabled by +including keymap/emacs.js and setting +the keyMap option to "emacs". Because +CodeMirror's internal API is quite different from Emacs, they are only +a loose approximation of actual emacs bindings, though.

+ +

Also note that a lot of browsers disallow certain keys from being +captured. For example, Chrome blocks both Ctrl-W and Ctrl-N, with the +result that idiomatic use of Emacs keys will constantly close your tab +or open a new window.

+ + + +
diff --git a/www/code/codemirror-5.13.2/demo/folding.html b/www/code/codemirror-5.13.2/demo/folding.html new file mode 100644 index 000000000..81cbf9894 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/folding.html @@ -0,0 +1,95 @@ + + + + CodeMirror: Code Folding Demo + + + + + + + + + + + + + + + + + + + + + +
+

Code Folding Demo

+
+
JavaScript:
+
+
HTML:
+
+
Markdown:
+
+
+ +
+ diff --git a/www/code/codemirror-5.13.2/demo/fullscreen.html b/www/code/codemirror-5.13.2/demo/fullscreen.html new file mode 100644 index 000000000..1fbdc488e --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/fullscreen.html @@ -0,0 +1,83 @@ + + +CodeMirror: Full Screen Editing + + + + + + + + + + + + +
+

Full Screen Editing

+
+ + +

Demonstration of + the fullscreen + addon. Press F11 when cursor is in the editor to + toggle full screen editing. Esc can also be used + to exit full screen editing.

+
diff --git a/www/code/codemirror-5.13.2/demo/hardwrap.html b/www/code/codemirror-5.13.2/demo/hardwrap.html new file mode 100644 index 000000000..84ba0cc0c --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/hardwrap.html @@ -0,0 +1,75 @@ + + +CodeMirror: Hard-wrapping Demo + + + + + + + + + + +
+

Hard-wrapping Demo

+
+ +

Demonstration of +the hardwrap addon. +The above editor has its change event hooked up to +the wrapParagraphsInRange method, so that the paragraphs +are reflown as you are typing.

+ + + +
diff --git a/www/code/codemirror-5.13.2/demo/html5complete.html b/www/code/codemirror-5.13.2/demo/html5complete.html new file mode 100644 index 000000000..411baae3e --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/html5complete.html @@ -0,0 +1,56 @@ + + + + CodeMirror: HTML completion demo + + + + + + + + + + + + + + + + + + + +
+

HTML completion demo

+ +

Shows the XML completer + parameterized with information about the tags in HTML. + Press ctrl-space to activate completion.

+ +
+ + +
+ diff --git a/www/code/codemirror-5.13.2/demo/indentwrap.html b/www/code/codemirror-5.13.2/demo/indentwrap.html new file mode 100644 index 000000000..3d3d0af6a --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/indentwrap.html @@ -0,0 +1,59 @@ + + +CodeMirror: Indented wrapped line demo + + + + + + + + + +
+

Indented wrapped line demo

+
+ +

This page uses a hack on top of the "renderLine" + event to make wrapped text line up with the base indentation of + the line.

+ + + +
diff --git a/www/code/codemirror-5.13.2/demo/lint.html b/www/code/codemirror-5.13.2/demo/lint.html new file mode 100644 index 000000000..96009b4e1 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/lint.html @@ -0,0 +1,171 @@ + + +CodeMirror: Linter Demo + + + + + + + + + + + + + + + + + + +
+

Linter Demo

+ + +

+ +

+ +

+ + +
diff --git a/www/code/codemirror-5.13.2/demo/loadmode.html b/www/code/codemirror-5.13.2/demo/loadmode.html new file mode 100644 index 000000000..809cd9022 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/loadmode.html @@ -0,0 +1,72 @@ + + +CodeMirror: Lazy Mode Loading Demo + + + + + + + + + + +
+

Lazy Mode Loading Demo

+

Current mode: text/plain

+
+

Filename, mime, or mode name:

+ + +
diff --git a/www/code/codemirror-5.13.2/demo/marker.html b/www/code/codemirror-5.13.2/demo/marker.html new file mode 100644 index 000000000..3a8b85000 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/marker.html @@ -0,0 +1,52 @@ + + +CodeMirror: Breakpoint Demo + + + + + + + + + +
+

Breakpoint Demo

+
+ +

Click the line-number gutter to add or remove 'breakpoints'.

+ + + +
diff --git a/www/code/codemirror-5.13.2/demo/markselection.html b/www/code/codemirror-5.13.2/demo/markselection.html new file mode 100644 index 000000000..d4c8a7a0d --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/markselection.html @@ -0,0 +1,52 @@ + + +CodeMirror: Selection Marking Demo + + + + + + + + + + +
+

Selection Marking Demo

+
+ + + +

Simple addon to easily mark (and style) selected text. Docs.

+ +
diff --git a/www/code/codemirror-5.13.2/demo/matchhighlighter.html b/www/code/codemirror-5.13.2/demo/matchhighlighter.html new file mode 100644 index 000000000..893d72134 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/matchhighlighter.html @@ -0,0 +1,103 @@ + + +CodeMirror: Match Highlighter Demo + + + + + + + + + + + + +
+

Match Highlighter Demo

+
+ + + +

Search and highlight occurences of the selected text.

+ +
diff --git a/www/code/codemirror-5.13.2/demo/matchtags.html b/www/code/codemirror-5.13.2/demo/matchtags.html new file mode 100644 index 000000000..175639a39 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/matchtags.html @@ -0,0 +1,48 @@ + + +CodeMirror: Tag Matcher Demo + + + + + + + + + + + +
+

Tag Matcher Demo

+ + +
+ + + +

Put the cursor on or inside a pair of tags to highlight them. + Press Ctrl-J to jump to the tag that matches the one under the + cursor.

+
diff --git a/www/code/codemirror-5.13.2/demo/merge.html b/www/code/codemirror-5.13.2/demo/merge.html new file mode 100644 index 000000000..20783b866 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/merge.html @@ -0,0 +1,122 @@ + + +CodeMirror: merge view demo + + + + + + + + + + + + + + + +
+

merge view demo

+ + +
+ +

The merge +addon provides an interface for displaying and merging diffs, +either two-way +or three-way. +The left (or center) pane is editable, and the differences with the +other pane(s) are optionally shown live as you edit +it. In the two-way configuration, there are also options to pad changed +sections to align them, and to collapse unchanged +stretches of text.

+ +

This addon depends on +the google-diff-match-patch +library to compute the diffs.

+ + +
diff --git a/www/code/codemirror-5.13.2/demo/multiplex.html b/www/code/codemirror-5.13.2/demo/multiplex.html new file mode 100644 index 000000000..ca8c80aed --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/multiplex.html @@ -0,0 +1,75 @@ + + +CodeMirror: Multiplexing Parser Demo + + + + + + + + + + +
+

Multiplexing Parser Demo

+
+ + + +

Demonstration of a multiplexing mode, which, at certain + boundary strings, switches to one or more inner modes. The out + (HTML) mode does not get fed the content of the << + >> blocks. See + the manual and + the source for more + information.

+ +

+ Parsing/Highlighting Tests: + normal, + verbose. +

+ +
diff --git a/www/code/codemirror-5.13.2/demo/mustache.html b/www/code/codemirror-5.13.2/demo/mustache.html new file mode 100644 index 000000000..ae4e6a891 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/mustache.html @@ -0,0 +1,69 @@ + + +CodeMirror: Overlay Parser Demo + + + + + + + + + + +
+

Overlay Parser Demo

+
+ + + +

Demonstration of a mode that parses HTML, highlighting + the Mustache templating + directives inside of it by using the code + in overlay.js. View + source to see the 15 lines of code needed to accomplish this.

+ +
diff --git a/www/code/codemirror-5.13.2/demo/panel.html b/www/code/codemirror-5.13.2/demo/panel.html new file mode 100644 index 000000000..b3b0b7ca6 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/panel.html @@ -0,0 +1,136 @@ + + +CodeMirror: Panel Demo + + + + + + + + + + + + + +
+ +

Panel Demo

+ +
+ +
+ +

+ The panel + addon allows you to display panels above or below an editor. +
+ Click the links below to add panels at the given position: +

+ +
+

+ top + after-top + before-bottom + bottom +

+

+ You can also replace an existing panel: +

+
+ + +
+ + + +
+ +
diff --git a/www/code/codemirror-5.13.2/demo/placeholder.html b/www/code/codemirror-5.13.2/demo/placeholder.html new file mode 100644 index 000000000..432331a48 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/placeholder.html @@ -0,0 +1,45 @@ + + +CodeMirror: Placeholder demo + + + + + + + + + +
+

Placeholder demo

+
+ +

The placeholder + plug-in adds an option placeholder that can be set to + make text appear in the editor when it is empty and not focused. + If the source textarea has a placeholder attribute, + it will automatically be inherited.

+ + + +
diff --git a/www/code/codemirror-5.13.2/demo/preview.html b/www/code/codemirror-5.13.2/demo/preview.html new file mode 100644 index 000000000..19e1530b8 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/preview.html @@ -0,0 +1,87 @@ + + +CodeMirror: HTML5 preview + + + + + + + + + + + + +
+

HTML5 preview

+ + + + +
diff --git a/www/code/codemirror-5.13.2/demo/requirejs.html b/www/code/codemirror-5.13.2/demo/requirejs.html new file mode 100644 index 000000000..438ef9fbf --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/requirejs.html @@ -0,0 +1,70 @@ + + + + CodeMirror: HTML completion demo + + + + + + + + + + + + +
+

RequireJS module loading demo

+ +

This demo does the same thing as + the HTML5 completion demo, but + loads its dependencies + with Require.js, rather than + explicitly. Press ctrl-space to activate + completion.

+ +
+ + + + +
+ diff --git a/www/code/codemirror-5.13.2/demo/resize.html b/www/code/codemirror-5.13.2/demo/resize.html new file mode 100644 index 000000000..1c1ef390a --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/resize.html @@ -0,0 +1,51 @@ + + +CodeMirror: Autoresize Demo + + + + + + + + + +
+

Autoresize Demo

+
+ +

By setting an editor's height style +to auto and giving +the viewportMargin +a value of Infinity, CodeMirror can be made to +automatically resize to fit its content.

+ + + +
diff --git a/www/code/codemirror-5.13.2/demo/rulers.html b/www/code/codemirror-5.13.2/demo/rulers.html new file mode 100644 index 000000000..2ac411158 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/rulers.html @@ -0,0 +1,49 @@ + + +CodeMirror: Ruler Demo + + + + + + + + + +
+

Ruler Demo

+ + + +

Demonstration of +the rulers addon, which +displays vertical lines at given column offsets.

+ +
diff --git a/www/code/codemirror-5.13.2/demo/runmode.html b/www/code/codemirror-5.13.2/demo/runmode.html new file mode 100644 index 000000000..ab8938d8d --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/runmode.html @@ -0,0 +1,62 @@ + + +CodeMirror: Mode Runner Demo + + + + + + + + + +
+

Mode Runner Demo

+ + +
+ +

+
+    
+
+    

Running a CodeMirror mode outside of the editor. + The CodeMirror.runMode function, defined + in addon/runmode/runmode.js takes the following arguments:

+ +
+
text (string)
+
The document to run through the highlighter.
+
mode (mode spec)
+
The mode to use (must be loaded as normal).
+
output (function or DOM node)
+
If this is a function, it will be called for each token with + two arguments, the token's text and the token's style class (may + be null for unstyled tokens). If it is a DOM node, + the tokens will be converted to span elements as in + an editor, and inserted into the node + (through innerHTML).
+
+ +
diff --git a/www/code/codemirror-5.13.2/demo/search.html b/www/code/codemirror-5.13.2/demo/search.html new file mode 100644 index 000000000..a5a889719 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/search.html @@ -0,0 +1,99 @@ + + +CodeMirror: Search/Replace Demo + + + + + + + + + + + + + + + + + +
+

Search/Replace Demo

+
+ + + +

Demonstration of primitive search/replace functionality. The + keybindings (which can be configured with custom keymaps) are:

+
+
Ctrl-F / Cmd-F
Start searching
+
Ctrl-G / Cmd-G
Find next
+
Shift-Ctrl-G / Shift-Cmd-G
Find previous
+
Shift-Ctrl-F / Cmd-Option-F
Replace
+
Shift-Ctrl-R / Shift-Cmd-Option-F
Replace all
+
Alt-F
Persistent search (dialog doesn't autoclose, + enter to find next, Shift-Enter to find previous)
+
Alt-G
Jump to line
+
+

Searching is enabled by + including addon/search/search.js + and addon/search/searchcursor.js. + Jump to line - including addon/search/jumpToLine.js.

+

For good-looking input dialogs, you also want to include + addon/dialog/dialog.js + and addon/dialog/dialog.css.

+
diff --git a/www/code/codemirror-5.13.2/demo/simplemode.html b/www/code/codemirror-5.13.2/demo/simplemode.html new file mode 100644 index 000000000..49778ef1e --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/simplemode.html @@ -0,0 +1,186 @@ + + +CodeMirror: Simple Mode Demo + + + + + + + + + + + +
+

Simple Mode Demo

+ +

The mode/simple +addon allows CodeMirror modes to be specified using a relatively simple +declarative format. This format is not as powerful as writing code +directly against the mode +interface, but is a lot easier to get started with, and +sufficiently expressive for many simple language modes.

+ +

This interface is still in flux. It is unlikely to be scrapped or +overhauled completely, so do start writing code against it, but +details might change as it stabilizes, and you might have to tweak +your code when upgrading.

+ +

Simple modes (loosely based on +the Common +JavaScript Syntax Highlighting Specification, which never took +off), are state machines, where each state has a number of rules that +match tokens. A rule describes a type of token that may occur in the +current state, and possibly a transition to another state caused by +that token.

+ +

The CodeMirror.defineSimpleMode(name, states) method +takes a mode name and an object that describes the mode's states. The +editor below shows an example of such a mode (and is itself +highlighted by the mode shown in it).

+ +
+ +

Each state is an array of rules. A rule may have the following properties:

+ +
+
regex: string | RegExp
+
The regular expression that matches the token. May be a string + or a regex object. When a regex, the ignoreCase flag + will be taken into account when matching the token. This regex + should only capture groups when the token property is + an array.
+
token: string | null
+
An optional token style. Multiple styles can be specified by + separating them with dots or spaces. When the regex for + this rule captures groups, it must capture all of the + string (since JS provides no way to find out where a group matched), + and this property must hold an array of token styles that has one + style for each matched group.
+
sol: boolean
+
When true, this token will only match at the start of the line. + (The ^ regexp marker doesn't work as you'd expect in + this context because of limitations in JavaScript's RegExp + API.)
+
next: string
+
When a next property is present, the mode will + transfer to the state named by the property when the token is + encountered.
+
push: string
+
Like next, but instead replacing the current state + by the new state, the current state is kept on a stack, and can be + returned to with the pop directive.
+
pop: bool
+
When true, and there is another state on the state stack, will + cause the mode to pop that state off the stack and transition to + it.
+
mode: {spec, end, persistent}
+
Can be used to embed another mode inside a mode. When present, + must hold an object with a spec property that describes + the embedded mode, and an optional end end property + that specifies the regexp that will end the extent of the mode. When + a persistent property is set (and true), the nested + mode's state will be preserved between occurrences of the mode.
+
indent: bool
+
When true, this token changes the indentation to be one unit + more than the current line's indentation.
+
dedent: bool
+
When true, this token will pop one scope off the indentation + stack.
+
dedentIfLineStart: bool
+
If a token has its dedent property set, it will, by + default, cause lines where it appears at the start to be dedented. + Set this property to false to prevent that behavior.
+
+ +

The meta property of the states object is special, and +will not be interpreted as a state. Instead, properties set on it will +be set on the mode, which is useful for properties +like lineComment, +which sets the comment style for a mode. The simple mode addon also +recognizes a few such properties:

+ +
+
dontIndentStates: array<string>
+
An array of states in which the mode's auto-indentation should + not take effect. Usually used for multi-line comment and string + states.
+
+ + + + + +
diff --git a/www/code/codemirror-5.13.2/demo/simplescrollbars.html b/www/code/codemirror-5.13.2/demo/simplescrollbars.html new file mode 100644 index 000000000..9d4093264 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/simplescrollbars.html @@ -0,0 +1,82 @@ + + +CodeMirror: Simple Scrollbar Demo + + + + + + + + + + + + +
+

Simple Scrollbar Demo

+
+ +

The simplescrollbars addon defines two +styles of non-native scrollbars: "simple" and "overlay" (click to try), which can be passed to +the scrollbarStyle option. These implement +the scrollbar using DOM elements, allowing more control over +its appearance.

+ + +
diff --git a/www/code/codemirror-5.13.2/demo/spanaffectswrapping_shim.html b/www/code/codemirror-5.13.2/demo/spanaffectswrapping_shim.html new file mode 100644 index 000000000..879d99b60 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/spanaffectswrapping_shim.html @@ -0,0 +1,85 @@ + + +CodeMirror: Automatically derive odd wrapping behavior for your browser + + + + + +
+

Automatically derive odd wrapping behavior for your browser

+ + +

This is a hack to automatically derive + a spanAffectsWrapping regexp for a browser. See the + comments above that variable + in lib/codemirror.js + for some more details.

+ +
+

+
+    
+  
diff --git a/www/code/codemirror-5.13.2/demo/sublime.html b/www/code/codemirror-5.13.2/demo/sublime.html new file mode 100644 index 000000000..233dd83a4 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/sublime.html @@ -0,0 +1,77 @@ + + +CodeMirror: Sublime Text bindings demo + + + + + + + + + + + + + + + + + + + + + + +
+

Sublime Text bindings demo

+ +

The sublime keymap defines many Sublime Text-specific +bindings for CodeMirror. See the code below for an overview.

+ +

Enable the keymap by +loading keymap/sublime.js +and setting +the keyMap +option to "sublime".

+ +

(A lot of the search functionality is still missing.) + + + +

diff --git a/www/code/codemirror-5.13.2/demo/tern.html b/www/code/codemirror-5.13.2/demo/tern.html new file mode 100644 index 000000000..b7b65608e --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/tern.html @@ -0,0 +1,133 @@ + + +CodeMirror: Tern Demo + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Tern Demo

+
+ +

Demonstrates integration of Tern +and CodeMirror. The following keys are bound:

+ +
+
Ctrl-Space
Autocomplete
+
Ctrl-O
Find docs for the expression at the cursor
+
Ctrl-I
Find type at cursor
+
Alt-.
Jump to definition (Alt-, to jump back)
+
Ctrl-Q
Rename variable
+
Ctrl-.
Select all occurrences of a variable
+
+ +

Documentation is sparse for now. See the top of +the script for a rough API +overview.

+ + + +
diff --git a/www/code/codemirror-5.13.2/demo/theme.html b/www/code/codemirror-5.13.2/demo/theme.html new file mode 100644 index 000000000..300e2625e --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/theme.html @@ -0,0 +1,162 @@ + + +CodeMirror: Theme Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Theme Demo

+
+ +

Select a theme: +

+ + +
diff --git a/www/code/codemirror-5.13.2/demo/trailingspace.html b/www/code/codemirror-5.13.2/demo/trailingspace.html new file mode 100644 index 000000000..1992ba3ff --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/trailingspace.html @@ -0,0 +1,48 @@ + + +CodeMirror: Trailing Whitespace Demo + + + + + + + + + +
+

Trailing Whitespace Demo

+
+ + + +

Uses +the trailingspace +addon to highlight trailing whitespace.

+ +
diff --git a/www/code/codemirror-5.13.2/demo/variableheight.html b/www/code/codemirror-5.13.2/demo/variableheight.html new file mode 100644 index 000000000..d49942864 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/variableheight.html @@ -0,0 +1,67 @@ + + +CodeMirror: Variable Height Demo + + + + + + + + + + +
+

Variable Height Demo

+
+ +
diff --git a/www/code/codemirror-5.13.2/demo/vim.html b/www/code/codemirror-5.13.2/demo/vim.html new file mode 100644 index 000000000..f27b8b8e2 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/vim.html @@ -0,0 +1,112 @@ + + +CodeMirror: Vim bindings demo + + + + + + + + + + + + + + + + +
+

Vim bindings demo

+ +

Note: The CodeMirror vim bindings do not have an +active maintainer. That means that if you report bugs in it, they are +likely to go unanswered. It also means that if you want to help, you +are very welcome to look +at the +open issues and see which ones you can solve.

+ +
+
Key buffer:
+ +

The vim keybindings are enabled by including keymap/vim.js and setting the +keyMap option to vim.

+ +

Features

+ +
    +
  • All common motions and operators, including text objects
  • +
  • Operator motion orthogonality
  • +
  • Visual mode - characterwise, linewise, blockwise
  • +
  • Full macro support (q, @)
  • +
  • Incremental highlighted search (/, ?, #, *, g#, g*)
  • +
  • Search/replace with confirm (:substitute, :%s)
  • +
  • Search history
  • +
  • Jump lists (Ctrl-o, Ctrl-i)
  • +
  • Key/command mapping with API (:map, :nmap, :vmap)
  • +
  • Sort (:sort)
  • +
  • Marks (`, ')
  • +
  • :global
  • +
  • Insert mode behaves identical to base CodeMirror
  • +
  • Cross-buffer yank/paste
  • +
+ +

For the full list of key mappings and Ex commands, refer to the +defaultKeymap and defaultExCommandMap at the +top of keymap/vim.js. + +

Note that while the vim mode tries to emulate the most useful +features of vim as faithfully as possible, it does not strive to +become a complete vim implementation

+ + + +
diff --git a/www/code/codemirror-5.13.2/demo/visibletabs.html b/www/code/codemirror-5.13.2/demo/visibletabs.html new file mode 100644 index 000000000..2eec337ed --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/visibletabs.html @@ -0,0 +1,62 @@ + + +CodeMirror: Visible tabs demo + + + + + + + + + +
+

Visible tabs demo

+
+ +

Tabs inside the editor are spans with the +class cm-tab, and can be styled.

+ + + +
diff --git a/www/code/codemirror-5.13.2/demo/widget.html b/www/code/codemirror-5.13.2/demo/widget.html new file mode 100644 index 000000000..da39a9297 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/widget.html @@ -0,0 +1,85 @@ + + +CodeMirror: Inline Widget Demo + + + + + + + + + + +
+

Inline Widget Demo

+ + +
+ +

This demo runs JSHint over the code +in the editor (which is the script used on this page), and +inserts line widgets to +display the warnings that JSHint comes up with.

+
diff --git a/www/code/codemirror-5.13.2/demo/xmlcomplete.html b/www/code/codemirror-5.13.2/demo/xmlcomplete.html new file mode 100644 index 000000000..043f0c429 --- /dev/null +++ b/www/code/codemirror-5.13.2/demo/xmlcomplete.html @@ -0,0 +1,119 @@ + + +CodeMirror: XML Autocomplete Demo + + + + + + + + + + + + +
+

XML Autocomplete Demo

+
+ +

Press ctrl-space, or type a '<' character to + activate autocompletion. This demo defines a simple schema that + guides completion. The schema can be customized—see + the manual.

+ +

Development of the xml-hint addon was kindly + sponsored + by www.xperiment.mobi.

+ + +
diff --git a/www/code/codemirror-5.13.2/doc/activebookmark.js b/www/code/codemirror-5.13.2/doc/activebookmark.js new file mode 100644 index 000000000..407282d02 --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/activebookmark.js @@ -0,0 +1,57 @@ +// Kludge in HTML5 tag recognition in IE8 +document.createElement("section"); +document.createElement("article"); + +(function() { + if (!window.addEventListener) return; + var pending = false, prevVal = null; + + function updateSoon() { + if (!pending) { + pending = true; + setTimeout(update, 250); + } + } + + function update() { + pending = false; + var marks = document.getElementById("nav").getElementsByTagName("a"), found; + for (var i = 0; i < marks.length; ++i) { + var mark = marks[i], m; + if (mark.getAttribute("data-default")) { + if (found == null) found = i; + } else if (m = mark.href.match(/#(.*)/)) { + var ref = document.getElementById(m[1]); + if (ref && ref.getBoundingClientRect().top < 50) + found = i; + } + } + if (found != null && found != prevVal) { + prevVal = found; + var lis = document.getElementById("nav").getElementsByTagName("li"); + for (var i = 0; i < lis.length; ++i) lis[i].className = ""; + for (var i = 0; i < marks.length; ++i) { + if (found == i) { + marks[i].className = "active"; + for (var n = marks[i]; n; n = n.parentNode) + if (n.nodeName == "LI") n.className = "active"; + } else { + marks[i].className = ""; + } + } + } + } + + window.addEventListener("scroll", updateSoon); + window.addEventListener("load", updateSoon); + window.addEventListener("hashchange", function() { + setTimeout(function() { + var hash = document.location.hash, found = null, m; + var marks = document.getElementById("nav").getElementsByTagName("a"); + for (var i = 0; i < marks.length; i++) + if ((m = marks[i].href.match(/(#.*)/)) && m[1] == hash) { found = i; break; } + if (found != null) for (var i = 0; i < marks.length; i++) + marks[i].className = i == found ? "active" : ""; + }, 300); + }); +})(); diff --git a/www/code/codemirror-5.13.2/doc/compress.html b/www/code/codemirror-5.13.2/doc/compress.html new file mode 100644 index 000000000..47d689429 --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/compress.html @@ -0,0 +1,346 @@ + + +CodeMirror: Compression Helper + + + + + + + + +
+ +

Script compression helper

+ +

To optimize loading CodeMirror, especially when including a + bunch of different modes, it is recommended that you combine and + minify (and preferably also gzip) the scripts. This page makes + those first two steps very easy. Simply select the version and + scripts you need in the form below, and + click Compress to download the minified script + file.

+ +
+ +

Version:

+ + + +

+ with UglifyJS +

+ +

Custom code to add to the compressed file:

+
+ + + +
diff --git a/www/code/codemirror-5.13.2/doc/docs.css b/www/code/codemirror-5.13.2/doc/docs.css new file mode 100644 index 000000000..881d2aa45 --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/docs.css @@ -0,0 +1,271 @@ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(//themes.googleusercontent.com/static/fonts/sourcesanspro/v5/ODelI1aHBYDBqgeIAH2zlBM0YzuT7MdOe03otPbuUS0.woff) format('woff'); +} + +body, html { margin: 0; padding: 0; height: 100%; } +section, article { display: block; padding: 0; } + +body { + background: #f8f8f8; + font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; + line-height: 1.5; +} + +p { margin-top: 0; } + +h2, h3, h1 { + font-weight: normal; + margin-bottom: .7em; +} +h1 { font-size: 140%; } +h2 { font-size: 120%; } +h3 { font-size: 110%; } +article > h2:first-child, section:first-child > h2 { margin-top: 0; } + +#nav h1 { + margin-right: 12px; + margin-top: 0; + margin-bottom: 2px; + color: #d30707; + letter-spacing: .5px; +} + +a, a:visited, a:link, .quasilink { + color: #A21313; + text-decoration: none; +} + +em { + padding-right: 2px; +} + +.quasilink { + cursor: pointer; +} + +article { + max-width: 700px; + margin: 0 0 0 160px; + border-left: 2px solid #E30808; + border-right: 1px solid #ddd; + padding: 30px 50px 100px 50px; + background: white; + z-index: 2; + position: relative; + min-height: 100%; + box-sizing: border-box; + -moz-box-sizing: border-box; +} + +#nav { + position: fixed; + padding-top: 30px; + max-height: 100%; + box-sizing: -moz-border-box; + box-sizing: border-box; + overflow-y: auto; + left: 0; right: none; + width: 160px; + text-align: right; + z-index: 1; +} + +@media screen and (min-width: 1000px) { + article { + margin: 0 auto; + } + #nav { + right: 50%; + width: auto; + border-right: 349px solid transparent; + } +} + +#nav ul { + display: block; + margin: 0; padding: 0; + margin-bottom: 32px; +} + +#nav li { + display: block; + margin-bottom: 4px; +} + +#nav li ul { + font-size: 80%; + margin-bottom: 0; + display: none; +} + +#nav li.active ul { + display: block; +} + +#nav li li a { + padding-right: 20px; + display: inline-block; +} + +#nav ul a { + color: black; + padding: 0 7px 1px 11px; +} + +#nav ul a.active, #nav ul a:hover { + border-bottom: 1px solid #E30808; + margin-bottom: -1px; + color: #E30808; +} + +#logo { + border: 0; + margin-right: 12px; + margin-bottom: 25px; +} + +section { + border-top: 1px solid #E30808; + margin: 1.5em 0; +} + +section.first { + border: none; + margin-top: 0; +} + +#demo { + position: relative; +} + +#demolist { + position: absolute; + right: 5px; + top: 5px; + z-index: 25; +} + +.yinyang { + position: absolute; + top: -10px; + left: 0; right: 0; + margin: auto; + display: block; + height: 120px; +} + +.actions { + margin: 1em 0 0; + min-height: 100px; + position: relative; +} + +.actionspicture { + pointer-events: none; + position: absolute; + height: 100px; + top: 0; left: 0; right: 0; +} + +.actionlink { + pointer-events: auto; + font-family: arial; + font-size: 80%; + font-weight: bold; + position: absolute; + top: 0; bottom: 0; + line-height: 1; + height: 1em; + margin: auto; +} + +.actionlink.download { + color: white; + right: 50%; + margin-right: 13px; + text-shadow: -1px 1px 3px #b00, -1px -1px 3px #b00, 1px 0px 3px #b00; +} + +.actionlink.fund { + color: #b00; + left: 50%; + margin-left: 15px; +} + +.actionlink:hover { + text-decoration: underline; +} + +.actionlink a { + color: inherit; +} + +.actionsleft { + float: left; +} + +.actionsright { + float: right; + text-align: right; +} + +@media screen and (max-width: 800px) { + .actions { + padding-top: 120px; + } + .actionsleft, .actionsright { + float: none; + text-align: left; + margin-bottom: 1em; + } +} + +th { + text-decoration: underline; + font-weight: normal; + text-align: left; +} + +#features ul { + list-style: none; + margin: 0 0 1em; + padding: 0 0 0 1.2em; +} + +#features li:before { + content: "-"; + width: 1em; + display: inline-block; + padding: 0; + margin: 0; + margin-left: -1em; +} + +.rel { + margin-bottom: 0; +} +.rel-note { + margin-top: 0; + color: #555; +} + +pre { + padding-left: 15px; + border-left: 2px solid #ddd; +} + +code { + padding: 0 2px; +} + +strong { + text-decoration: underline; + font-weight: normal; +} + +.field { + border: 1px solid #A21313; +} diff --git a/www/code/codemirror-5.13.2/doc/internals.html b/www/code/codemirror-5.13.2/doc/internals.html new file mode 100644 index 000000000..079466f63 --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/internals.html @@ -0,0 +1,504 @@ + + +CodeMirror: Internals + + + + + + + +
+ +

(Re-) Implementing A Syntax-Highlighting Editor in JavaScript

+ +

+ Topic: JavaScript, code editor implementation
+ Author: Marijn Haverbeke
+ Date: March 2nd 2011 (updated November 13th 2011) +

+ +

Caution: this text was written briefly after +version 2 was initially written. It no longer (even including the +update at the bottom) fully represents the current implementation. I'm +leaving it here as a historic document. For more up-to-date +information, look at the entries +tagged cm-internals +on my blog.

+ +

This is a followup to +my Brutal Odyssey to the +Dark Side of the DOM Tree story. That one describes the +mind-bending process of implementing (what would become) CodeMirror 1. +This one describes the internals of CodeMirror 2, a complete rewrite +and rethink of the old code base. I wanted to give this piece another +Hunter Thompson copycat subtitle, but somehow that would be out of +place—the process this time around was one of straightforward +engineering, requiring no serious mind-bending whatsoever.

+ +

So, what is wrong with CodeMirror 1? I'd estimate, by mailing list +activity and general search-engine presence, that it has been +integrated into about a thousand systems by now. The most prominent +one, since a few weeks, +being Google +code's project hosting. It works, and it's being used widely.

+ +

Still, I did not start replacing it because I was bored. CodeMirror +1 was heavily reliant on designMode +or contentEditable (depending on the browser). Neither of +these are well specified (HTML5 tries +to specify +their basics), and, more importantly, they tend to be one of the more +obscure and buggy areas of browser functionality—CodeMirror, by using +this functionality in a non-typical way, was constantly running up +against browser bugs. WebKit wouldn't show an empty line at the end of +the document, and in some releases would suddenly get unbearably slow. +Firefox would show the cursor in the wrong place. Internet Explorer +would insist on linkifying everything that looked like a URL or email +address, a behaviour that can't be turned off. Some bugs I managed to +work around (which was often a frustrating, painful process), others, +such as the Firefox cursor placement, I gave up on, and had to tell +user after user that they were known problems, but not something I +could help.

+ +

Also, there is the fact that designMode (which seemed +to be less buggy than contentEditable in Webkit and +Firefox, and was thus used by CodeMirror 1 in those browsers) requires +a frame. Frames are another tricky area. It takes some effort to +prevent getting tripped up by domain restrictions, they don't +initialize synchronously, behave strangely in response to the back +button, and, on several browsers, can't be moved around the DOM +without having them re-initialize. They did provide a very nice way to +namespace the library, though—CodeMirror 1 could freely pollute the +namespace inside the frame.

+ +

Finally, working with an editable document means working with +selection in arbitrary DOM structures. Internet Explorer (8 and +before) has an utterly different (and awkward) selection API than all +of the other browsers, and even among the different implementations of +document.selection, details about how exactly a selection +is represented vary quite a bit. Add to that the fact that Opera's +selection support tended to be very buggy until recently, and you can +imagine why CodeMirror 1 contains 700 lines of selection-handling +code.

+ +

And that brings us to the main issue with the CodeMirror 1 +code base: The proportion of browser-bug-workarounds to real +application code was getting dangerously high. By building on top of a +few dodgy features, I put the system in a vulnerable position—any +incompatibility and bugginess in these features, I had to paper over +with my own code. Not only did I have to do some serious stunt-work to +get it to work on older browsers (as detailed in the +previous story), things +also kept breaking in newly released versions, requiring me to come up +with new scary hacks in order to keep up. This was starting +to lose its appeal.

+ +
+

General Approach

+ +

What CodeMirror 2 does is try to sidestep most of the hairy hacks +that came up in version 1. I owe a lot to the +ACE editor for inspiration on how to +approach this.

+ +

I absolutely did not want to be completely reliant on key events to +generate my input. Every JavaScript programmer knows that key event +information is horrible and incomplete. Some people (most awesomely +Mihai Bazon with Ymacs) have been able +to build more or less functioning editors by directly reading key +events, but it takes a lot of work (the kind of never-ending, fragile +work I described earlier), and will never be able to properly support +things like multi-keystoke international character +input. [see below for caveat]

+ +

So what I do is focus a hidden textarea, and let the browser +believe that the user is typing into that. What we show to the user is +a DOM structure we built to represent his document. If this is updated +quickly enough, and shows some kind of believable cursor, it feels +like a real text-input control.

+ +

Another big win is that this DOM representation does not have to +span the whole document. Some CodeMirror 1 users insisted that they +needed to put a 30 thousand line XML document into CodeMirror. Putting +all that into the DOM takes a while, especially since, for some +reason, an editable DOM tree is slower than a normal one on most +browsers. If we have full control over what we show, we must only +ensure that the visible part of the document has been added, and can +do the rest only when needed. (Fortunately, the onscroll +event works almost the same on all browsers, and lends itself well to +displaying things only as they are scrolled into view.)

+
+
+

Input

+ +

ACE uses its hidden textarea only as a text input shim, and does +all cursor movement and things like text deletion itself by directly +handling key events. CodeMirror's way is to let the browser do its +thing as much as possible, and not, for example, define its own set of +key bindings. One way to do this would have been to have the whole +document inside the hidden textarea, and after each key event update +the display DOM to reflect what's in that textarea.

+ +

That'd be simple, but it is not realistic. For even medium-sized +document the editor would be constantly munging huge strings, and get +terribly slow. What CodeMirror 2 does is put the current selection, +along with an extra line on the top and on the bottom, into the +textarea.

+ +

This means that the arrow keys (and their ctrl-variations), home, +end, etcetera, do not have to be handled specially. We just read the +cursor position in the textarea, and update our cursor to match it. +Also, copy and paste work pretty much for free, and people get their +native key bindings, without any special work on my part. For example, +I have emacs key bindings configured for Chrome and Firefox. There is +no way for a script to detect this. [no longer the case]

+ +

Of course, since only a small part of the document sits in the +textarea, keys like page up and ctrl-end won't do the right thing. +CodeMirror is catching those events and handling them itself.

+
+
+

Selection

+ +

Getting and setting the selection range of a textarea in modern +browsers is trivial—you just use the selectionStart +and selectionEnd properties. On IE you have to do some +insane stuff with temporary ranges and compensating for the fact that +moving the selection by a 'character' will treat \r\n as a single +character, but even there it is possible to build functions that +reliably set and get the selection range.

+ +

But consider this typical case: When I'm somewhere in my document, +press shift, and press the up arrow, something gets selected. Then, if +I, still holding shift, press the up arrow again, the top of my +selection is adjusted. The selection remembers where its head +and its anchor are, and moves the head when we shift-move. +This is a generally accepted property of selections, and done right by +every editing component built in the past twenty years.

+ +

But not something that the browser selection APIs expose.

+ +

Great. So when someone creates an 'upside-down' selection, the next +time CodeMirror has to update the textarea, it'll re-create the +selection as an 'upside-up' selection, with the anchor at the top, and +the next cursor motion will behave in an unexpected way—our second +up-arrow press in the example above will not do anything, since it is +interpreted in exactly the same way as the first.

+ +

No problem. We'll just, ehm, detect that the selection is +upside-down (you can tell by the way it was created), and then, when +an upside-down selection is present, and a cursor-moving key is +pressed in combination with shift, we quickly collapse the selection +in the textarea to its start, allow the key to take effect, and then +combine its new head with its old anchor to get the real +selection.

+ +

In short, scary hacks could not be avoided entirely in CodeMirror +2.

+ +

And, the observant reader might ask, how do you even know that a +key combo is a cursor-moving combo, if you claim you support any +native key bindings? Well, we don't, but we can learn. The editor +keeps a set known cursor-movement combos (initialized to the +predictable defaults), and updates this set when it observes that +pressing a certain key had (only) the effect of moving the cursor. +This, of course, doesn't work if the first time the key is used was +for extending an inverted selection, but it works most of the +time.

+
+
+

Intelligent Updating

+ +

One thing that always comes up when you have a complicated internal +state that's reflected in some user-visible external representation +(in this case, the displayed code and the textarea's content) is +keeping the two in sync. The naive way is to just update the display +every time you change your state, but this is not only error prone +(you'll forget), it also easily leads to duplicate work on big, +composite operations. Then you start passing around flags indicating +whether the display should be updated in an attempt to be efficient +again and, well, at that point you might as well give up completely.

+ +

I did go down that road, but then switched to a much simpler model: +simply keep track of all the things that have been changed during an +action, and then, only at the end, use this information to update the +user-visible display.

+ +

CodeMirror uses a concept of operations, which start by +calling a specific set-up function that clears the state and end by +calling another function that reads this state and does the required +updating. Most event handlers, and all the user-visible methods that +change state are wrapped like this. There's a method +called operation that accepts a function, and returns +another function that wraps the given function as an operation.

+ +

It's trivial to extend this (as CodeMirror does) to detect nesting, +and, when an operation is started inside an operation, simply +increment the nesting count, and only do the updating when this count +reaches zero again.

+ +

If we have a set of changed ranges and know the currently shown +range, we can (with some awkward code to deal with the fact that +changes can add and remove lines, so we're dealing with a changing +coordinate system) construct a map of the ranges that were left +intact. We can then compare this map with the part of the document +that's currently visible (based on scroll offset and editor height) to +determine whether something needs to be updated.

+ +

CodeMirror uses two update algorithms—a full refresh, where it just +discards the whole part of the DOM that contains the edited text and +rebuilds it, and a patch algorithm, where it uses the information +about changed and intact ranges to update only the out-of-date parts +of the DOM. When more than 30 percent (which is the current heuristic, +might change) of the lines need to be updated, the full refresh is +chosen (since it's faster to do than painstakingly finding and +updating all the changed lines), in the other case it does the +patching (so that, if you scroll a line or select another character, +the whole screen doesn't have to be +re-rendered). [the full-refresh +algorithm was dropped, it wasn't really faster than the patching +one]

+ +

All updating uses innerHTML rather than direct DOM +manipulation, since that still seems to be by far the fastest way to +build documents. There's a per-line function that combines the +highlighting, marking, and +selection info for that line into a snippet of HTML. The patch updater +uses this to reset individual lines, the refresh updater builds an +HTML chunk for the whole visible document at once, and then uses a +single innerHTML update to do the refresh.

+
+
+

Parsers can be Simple

+ +

When I wrote CodeMirror 1, I +thought interruptable +parsers were a hugely scary and complicated thing, and I used a +bunch of heavyweight abstractions to keep this supposed complexity +under control: parsers +were iterators +that consumed input from another iterator, and used funny +closure-resetting tricks to copy and resume themselves.

+ +

This made for a rather nice system, in that parsers formed strictly +separate modules, and could be composed in predictable ways. +Unfortunately, it was quite slow (stacking three or four iterators on +top of each other), and extremely intimidating to people not used to a +functional programming style.

+ +

With a few small changes, however, we can keep all those +advantages, but simplify the API and make the whole thing less +indirect and inefficient. CodeMirror +2's mode API uses explicit state +objects, and makes the parser/tokenizer a function that simply takes a +state and a character stream abstraction, advances the stream one +token, and returns the way the token should be styled. This state may +be copied, optionally in a mode-defined way, in order to be able to +continue a parse at a given point. Even someone who's never touched a +lambda in his life can understand this approach. Additionally, far +fewer objects are allocated in the course of parsing now.

+ +

The biggest speedup comes from the fact that the parsing no longer +has to touch the DOM though. In CodeMirror 1, on an older browser, you +could see the parser work its way through the document, +managing some twenty lines in each 50-millisecond time slice it got. It +was reading its input from the DOM, and updating the DOM as it went +along, which any experienced JavaScript programmer will immediately +spot as a recipe for slowness. In CodeMirror 2, the parser usually +finishes the whole document in a single 100-millisecond time slice—it +manages some 1500 lines during that time on Chrome. All it has to do +is munge strings, so there is no real reason for it to be slow +anymore.

+
+
+

What Gives?

+ +

Given all this, what can you expect from CodeMirror 2?

+ +
    + +
  • Small. the base library is +some 45k when minified +now, 17k when gzipped. It's smaller than +its own logo.
  • + +
  • Lightweight. CodeMirror 2 initializes very +quickly, and does almost no work when it is not focused. This means +you can treat it almost like a textarea, have multiple instances on a +page without trouble.
  • + +
  • Huge document support. Since highlighting is +really fast, and no DOM structure is being built for non-visible +content, you don't have to worry about locking up your browser when a +user enters a megabyte-sized document.
  • + +
  • Extended API. Some things kept coming up in the +mailing list, such as marking pieces of text or lines, which were +extremely hard to do with CodeMirror 1. The new version has proper +support for these built in.
  • + +
  • Tab support. Tabs inside editable documents were, +for some reason, a no-go. At least six different people announced they +were going to add tab support to CodeMirror 1, none survived (I mean, +none delivered a working version). CodeMirror 2 no longer removes tabs +from your document.
  • + +
  • Sane styling. iframe nodes aren't +really known for respecting document flow. Now that an editor instance +is a plain div element, it is much easier to size it to +fit the surrounding elements. You don't even have to make it scroll if +you do not want to.
  • + +
+ +

On the downside, a CodeMirror 2 instance is not a native +editable component. Though it does its best to emulate such a +component as much as possible, there is functionality that browsers +just do not allow us to hook into. Doing select-all from the context +menu, for example, is not currently detected by CodeMirror.

+ +

[Updates from November 13th 2011] Recently, I've made +some changes to the codebase that cause some of the text above to no +longer be current. I've left the text intact, but added markers at the +passages that are now inaccurate. The new situation is described +below.

+
+
+

Content Representation

+ +

The original implementation of CodeMirror 2 represented the +document as a flat array of line objects. This worked well—splicing +arrays will require the part of the array after the splice to be +moved, but this is basically just a simple memmove of a +bunch of pointers, so it is cheap even for huge documents.

+ +

However, I recently added line wrapping and code folding (line +collapsing, basically). Once lines start taking up a non-constant +amount of vertical space, looking up a line by vertical position +(which is needed when someone clicks the document, and to determine +the visible part of the document during scrolling) can only be done +with a linear scan through the whole array, summing up line heights as +you go. Seeing how I've been going out of my way to make big documents +fast, this is not acceptable.

+ +

The new representation is based on a B-tree. The leaves of the tree +contain arrays of line objects, with a fixed minimum and maximum size, +and the non-leaf nodes simply hold arrays of child nodes. Each node +stores both the amount of lines that live below them and the vertical +space taken up by these lines. This allows the tree to be indexed both +by line number and by vertical position, and all access has +logarithmic complexity in relation to the document size.

+ +

I gave line objects and tree nodes parent pointers, to the node +above them. When a line has to update its height, it can simply walk +these pointers to the top of the tree, adding or subtracting the +difference in height from each node it encounters. The parent pointers +also make it cheaper (in complexity terms, the difference is probably +tiny in normal-sized documents) to find the current line number when +given a line object. In the old approach, the whole document array had +to be searched. Now, we can just walk up the tree and count the sizes +of the nodes coming before us at each level.

+ +

I chose B-trees, not regular binary trees, mostly because they +allow for very fast bulk insertions and deletions. When there is a big +change to a document, it typically involves adding, deleting, or +replacing a chunk of subsequent lines. In a regular balanced tree, all +these inserts or deletes would have to be done separately, which could +be really expensive. In a B-tree, to insert a chunk, you just walk +down the tree once to find where it should go, insert them all in one +shot, and then break up the node if needed. This breaking up might +involve breaking up nodes further up, but only requires a single pass +back up the tree. For deletion, I'm somewhat lax in keeping things +balanced—I just collapse nodes into a leaf when their child count goes +below a given number. This means that there are some weird editing +patterns that may result in a seriously unbalanced tree, but even such +an unbalanced tree will perform well, unless you spend a day making +strangely repeating edits to a really big document.

+
+
+

Keymaps

+ +

Above, I claimed that directly catching key +events for things like cursor movement is impractical because it +requires some browser-specific kludges. I then proceeded to explain +some awful hacks that were needed to make it +possible for the selection changes to be detected through the +textarea. In fact, the second hack is about as bad as the first.

+ +

On top of that, in the presence of user-configurable tab sizes and +collapsed and wrapped lines, lining up cursor movement in the textarea +with what's visible on the screen becomes a nightmare. Thus, I've +decided to move to a model where the textarea's selection is no longer +depended on.

+ +

So I moved to a model where all cursor movement is handled by my +own code. This adds support for a goal column, proper interaction of +cursor movement with collapsed lines, and makes it possible for +vertical movement to move through wrapped lines properly, instead of +just treating them like non-wrapped lines.

+ +

The key event handlers now translate the key event into a string, +something like Ctrl-Home or Shift-Cmd-R, and +use that string to look up an action to perform. To make keybinding +customizable, this lookup goes through +a table, using a scheme that +allows such tables to be chained together (for example, the default +Mac bindings fall through to a table named 'emacsy', which defines +basic Emacs-style bindings like Ctrl-F, and which is also +used by the custom Emacs bindings).

+ +

A new +option extraKeys +allows ad-hoc keybindings to be defined in a much nicer way than what +was possible with the +old onKeyEvent +callback. You simply provide an object mapping key identifiers to +functions, instead of painstakingly looking at raw key events.

+ +

Built-in commands map to strings, rather than functions, for +example "goLineUp" is the default action bound to the up +arrow key. This allows new keymaps to refer to them without +duplicating any code. New commands can be defined by assigning to +the CodeMirror.commands object, which maps such commands +to functions.

+ +

The hidden textarea now only holds the current selection, with no +extra characters around it. This has a nice advantage: polling for +input becomes much, much faster. If there's a big selection, this text +does not have to be read from the textarea every time—when we poll, +just noticing that something is still selected is enough to tell us +that no new text was typed.

+ +

The reason that cheap polling is important is that many browsers do +not fire useful events on IME (input method engine) input, which is +the thing where people inputting a language like Japanese or Chinese +use multiple keystrokes to create a character or sequence of +characters. Most modern browsers fire input when the +composing is finished, but many don't fire anything when the character +is updated during composition. So we poll, whenever the +editor is focused, to provide immediate updates of the display.

+ +
+
diff --git a/www/code/codemirror-5.13.2/doc/logo.png b/www/code/codemirror-5.13.2/doc/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9aabda1d709f565bb115fa083cc16b6d52db64cf GIT binary patch literal 9310 zcmWk!Wl$VV6g=D|1b25!aEIUyhr7GGTL{5|1$PMU!Gb&FaCaxTyW6*4U2W~w{MehF z_g=s5o{3aZl14)&LC@FTctEwWVDw?~{3&X8ah!PRD9*tQT8757ES=R=O zZIdr2ugtx4l6@a%-%CqPVF44eaQ%2fx0Jpla6f)NKFVjM^Z_h)hKuzjB4sSI@ekEg z@U#NrAp>;|c$0x9hg?WZTdZG(0VD4>#GX6z>|wuiM$CS;G$FUC%`Yu#T3S`2xx9Q% z&L>hk-l%{+T9H<*SA=}sbo%%o@Lho^HR{1J(LU%?j7M6P@vF0Z0pn zRg3c$bb5L^PunUDMRKYs2Bi84p?^h2s-wqmdrz@zp~k4I#jPdj=S+$6^$1(n#c@vV zQV@AYWo0F0iuwG?$(^c;iwoWx)a709!-19&TMi)>)^tO&r)EX+#@$=$u6%0kebYPc zJhdwF-;A+6Nv~cvrd5Mk9vYf~d&?L>K|$|Ad8<9vw25-A3Z(NDgczyq)xxNFX3irt z5&W3H!9k+!9`(QJE||;POE|miR+~Sm6)TtkQ-cj}VANhC(QrgTL%V&q;;Zro-FDY} zLEOj{mjZVGgd>u4m z9zF7e&^@tyDqDe64B@zwmmb{Zn8=9D0EkzmV(u^0Er>Me3Q1W@zsY6$umA0>sVZm? zf(GAr5tO2~04nkJ@wvUbz0JP5-v~N(8$pml|2M|7;cjEJKc2naVlq*V$ErUVu%xCc zfs#1n{*MfE1l}<8>Gn8IRz?OtB`Ikq$e{Y8Acr;iHY83OaqB8@B8#tYMp?M1xTHiN zI7wa+cO)-Jl$p=D%D=t+`FM2=o|c+=mO*=?NKfnSwkBw*RJ8!PBhEop&nIA&$mb;v z`(VuSI$`*;(y|?=VesS!rUgt{Jdt*)`0hZ?G z7usFYr+DK=vmmf4*I#j$aOU?KF(7dFFyg^IqOV_XZ;56L^zPYvdwU(1ez>LJ;p|HN z4VWobRPeX^YQ|=%QMp8X`Hn78u&x4ka1N?5=y}S^Blo>WDpAfA7QE<&X|fPi*P!91 zP1%g!{BoS*|03)2@?Zo#sF~owf;%$sj_3DF)yW$ZALRySHTxJ?p2Ot=fq#BIgX)5|XO;LcR_79G0BHnqmaV{@z-l*0V&*FitQ+$n$GKK~(Sm8!dKL(9qB_GBTi@yf$25rWcs(1P9>X)ru?O`26nlrz=f0 zm@U<-rU~O=l$e1aj>uh@$@ff@7IHbdSS0=;!NwcMhud z53gSK=5Ft3eehCzt=I&`DimK+)jCpbx;}m$z19HbQtn`d)!hQVW?ChL; zbyKo%9MIsH(Q>--r@-OVua@MI!wIa-jBE9Yw#B6Hdl@SfkOB%&hU2fK`1pfgB~cEi z>K7acZUFF41c0jUSTU1(EXAar0TlOu9ixo!E{YZ*$|c}u>+__hBK&*hRIDa8Af}Q; zWDit2MeZN4SZfyUHj}GWt#8A|6Cu@MLU~bqjhe+}#m%DI(CommzwUQ#@%IP8XxgXH zkyN(SV_iLNCzH9TWd7jqj-)r0;Kso}!idX{4~*c?b&}V(1|!o(&NG`&2I&`ADZUIJ|pBJ69gpMjxo{`=IqPIR49;HZJ)o;WIDAx4o8- z1j5nrhNlg>pzELyfsea#kffV~rD%Ax^X=p*GhV+t=9NEE zwGMXJptrZT*>W|aY^89`irs&YC|M#w!f}@{7kC*bUPqjh!Uwy0bsC~_hGjab`^uy} ztltpI_08Dvm(#}fwt2E&myYl$E+P(Q!cUqD3UxVZSM03`xg;(xj3F4~&-sfCMd~A{ zKqcWr3+}cf1UzhoH@&vk(EDzNAZT}ZqK%}mY#-Nj(O&AtR+xan;967_CN{F;l|~|8 zbv4eSqnd;^x`NQ7i^pBJQWXuEPbA(AiyNG_JNvs^k2SQ48Kr?Gew5?MKFwqKY(F(DdQVId#=h;KNtK-Z_T*+|&{hTo_Dmm~TmWj5{v?&Y`#Q-Eb zTz&kT>byP|t@F$QR+V?o4xQ)xp3Ohpq{U`hSM0{j5y}#&wUnJ=k1^XyQfGGV`7zm0 zE56Y;?0&vKmsggnf@JGdGPs5f1=AO!H`mBk5|vl*z}R z*lpQn=>%|W7C2x6OuNW6mFZ4#aq$jO;SZRY@s{oHf#++f@5Odiy7Y#IuOBOL;sdb5rDr#wo^`On0`NwfaORizU;5S`T|a`{+m2KhV!OF2ne zn)5u+*B7c3=iXT%Iyyc+&gM;Z1*+9%ehzt68mX~f`BMd1@9^Uoo+;_u?+Wkg3K|X| zjHx*~QxY;V!iLQ-hH3LW{Wn{t=CjfuRg$i^ESx3gzuNLkZD0E0=BB+uR50L5z2_I@ z0CYH%y42$a@fVdzp$8SB%r$@50Z3+R&bl?>tn~Idgp!Uc6LT~!GE$bg@oDPt9dgA| z)Zx3K0a~`_w(`&$?;ANGV>IlkAOtPLHeJ=_4)Imh6kAU=Ael_$2x%+q9s8$9adapc z@ZR;|^71uWa}qB9aq`j%@do6JI#;g5gO~kkLO@fV9hIo_EwQ|kO+AI6P%VPG$x%=- z@5C!jI64WxmL(<%YY`eDXQg`aZFOF5&|6FJU=Oa3e0$`#p1wY+f@)TXyJT_UFBF@h zm&fbunEXn@wzIX*%ASx7e`1A#zcI}#*00OAaA}0x8_-MYP#IkIi|jhGU7Z~{*Y~X5 z3Bz%wvHe+$Y!UGLy#h-+zuEcix%UM~Ky`&lIGtqNPV4$8-t%@U8wSgDQ_X&PC>YzX z3~E{1JN?fOkI56WeWg6J@KEE%i6B9%?8D(U|25<-TDdbvw=dRGJ=x+Pzw*;ZBPnm%Q1e{g)fvam7XWvnY_ z`y0C)y*qsiC%B2!CtHWE!`L|+|AvSX;yDQyy+nYrR3nwpYQ zJZH@bm2-AR0s{?ADlac$ zkx01)laBkr=%@l^j&DXHo$?;5U6S;#=JU>H{Ll9EfOHzrZG8vU#$Rw({7!P;`l4{N zG@f%v$3a!h-uc>@$S^BZL8p1fpBpb(buJ}6eJJIeClz0mfySxp)sr;X(9p1*+mZQx z!-#)MRuy}>As`~X-m2E~m7g@|Z z=1NnAGf&Ig$rmUc%eOqeCP(*^Mb*c>Os6!yZIt|SaB{C9#pri-&IJgJk7N6HT&Wah z9k2~ZFz|&pG>2etaRG99GsCkF10?k5H=YgX&BM13?U#Rv!fbO-JOx}*c;EOh@qo|r z_Ve35Jynb$;s zfuT&*mrEAvtLK5Y$;_@8VTK)M0G9rk15iwC*`mH_Y zTr=PfTv5ukYR1Rc+8hNU>Mh4i*R!&&r!1IK@N6nQwpM*`S>B?G6*I9&NzcFo9czwO z+kaz5b9qwTtS0c5r3$9l*|Ip zP^-oekWvM_v054$w@z9d+*6_vZd-3KQ%$?{!;VcH9!Kh$%pCUVqFY}MjCIzeeTz#*Hqqwvp81OK z>w5Tar|?+{)Wdf5+Gqw>w8+~-A3=L)=`d4m%3^!p;98rP6--6QoNvdQA%%n&g@t^# zv?^zShDMby`x6Kd;J*Q$ddy?Eu)?a{5OH(kUWM&y!3RQx>x5e!j9k36&nKs)rb*V7 z)*tK&p$qH(8g{oIsgfK-Ay%0yB$zKClk&(7fSgmtC+!^t&zphF<=^r zfXnT$+)%DLg|4o_l^Z!y6#b|*7LCUyC#i}xW~t`$c_3g1#GXpZgxxO^OImgjKMJ^H zmAZ{lZ2Mzgux)N&UT>c(K)z^Lzdzrdn(h5y<8?n!>49^J`K9qU>96{ik|{+vzF)>Z z#*Hte5ISXIWgtS3I$dl)FX)PvKo8v8g{Z+1L5QP@lsBZA%r7c6nV2*yjW2)IH2dD# z<~!6?rt7ymcVe1tTyZeBd7bWLI(1R)b@zOIg1CA1pjg%)K7-GTzPwRW^8`QiMpIqC z#Vxl==J5M0*HfRZ45$P-az3C>LU-ZX`_S*@jr5ER4i3VcFE`rpkes|L)bIQnjfWEVseduN~aJG0f7S$^cFb@LBvA6KZ`~dpI%eGT+_{$sb$t%P3(UDByT#lH|WYvjN*cf z0b`D{*oIX|&b=(W1r zq&JVJ8U?0r+~GQsY#yJAzl%+t2IhB1iR0GGTTlUx9YO?z zAu95t;&6Z;IzQoVSbIa-C^2`YjU#(fc^`)GL!Jug_2mVhllLc(c$sI7RMN53%*;Q0 zuJ>yF_F1QA8YuB;<+S$H+w=aFE1GqWv-6dM6UO1yLjF9Q1{2P1}rE3j*P*0 zIVlSY36*KpkV=K-?D_U7D!(UUVI52t+2xpS%_x^lHTbBJp5JSeUQ@v)0*N}L4*mHE zEvFkP&(fq7i+qxxpM3KNhjh`Db;Og2UM;f0zL5Jv{lsod+@U)^CH#%m&R_y8ta|m2 zYQXBXI=osepS; zW2_+-c5}5$jVv_v1(TM&0^HglgzAQI>w21-jw`3oc#pYtZj@9xB8BCd@u z@Wngwe$o@ezzdf@fR+wc%iNrL0^KVGv%iv?f?{Ib60{b=6IJ>LDz`T#1kV!caeBDQ zuq6u5&^upyC6mKy+de=Tj*`R?) z(kA$|$GF>?bDh)pXFZf_@hSgBP3IFM(Az5wTaX!w1_wuV0ZCD^@G~(H(%;{|8Y@1( z@61ojEV_}wyvM;4Y+aZ3ZZ}I5NCUCswZf24b4epXE?H@-NM zHNT2_@l_*jG#{IUTs~6Q58po2Ea+KWNEtwD{`cLk+VPw^ZgDr9mb2nU0$gU_F$oAF zHTCYOp>Y!<2M2pf0s9NuNtsQvN>3c1!O{9?uBk>E8aep`jd?G0K{BiN&;glP)!{V|1@J_|hJ&i%FZv@t`iu~WCNpYixA@}peMe)$3kJHL zLK-iHvItWVHJ4D#f}vlPOg;AgtJ_9rK&4(=acTBlb{%{z3n-6`y(^8@E*pv&OCoU~ z3wG6*i0|h#`a#7K$5OdY zijMc=q<&g*+<5S+cd<776F!~5CbT=Dqu>?e&lnD! z(G94TE2LprS-{rGMuubO<4ad0;@%55pRTgYx4Dp$|Gzsg{VW=H-K1l*wUSKc0QmqR z$su4@Ia~KBAR8{35!pSMqUt?t(!pb_<Ww zfah;*u-#flq}%R!tb6?#V;`Au<>vt&jL&cYeWmW|TKE5+1eMf}L96O5?Bka@Z1-5M z%sz|(k|&l8L%Z6S@*Yw{bWkRUDO$Cr1DIJ5n>$5UdQq^dlQq%_+?AjMu!Z*UwBlq9 zl*bV7*ek-|+bL)XL9tZwL?~mqanj?w{~O85VyBx`4(h{7=f;Q!d%MLoE?}Y2*Vore zj>FUsVp|cYPD=ceO{DE~)NR5bvBc#M_PsrtVQj#0N$Ep}VdOIkOC=%guEwUbg?=xJ zZ)iu?wW>?Yr3as}M7D&hIz>=JdKt;YuXlUl2B2-aoD+WVMgJya)d}{%IdJ>O&VUZp zu38{fsnhglG_)S*%8!pX6InT`{$|4Vfq$CgNAqLIN-f;nf@I|=qZc!=M4532FAV{$ zN`nVFfH{HE#TnR#sv`E6TM~13zvJVBdXa-Id|maKrVR{MU}&-Px0hmM8!!@SKU+Ch zrB!C_Abonc+*ZgDMGJVV#GJUM$TtIRpd=dS_-4-2BiKwnDn#b5>v&tpX z+AB6&%40nwtEH|!`;R#sFZg_0@pIMA0D>}r1)bRUL!VA1!$C@>_lm3!W%A?-AB&Xm)~uFywT%$n#=8W zSc4QS2PcF5l_!JKZn>6P^~$N%R1aX07$IAdoU(Afnaq1wHX(A^zt&25yg|nd@i)^x zHj23&m@DH#MXhJimqE_F+fQ3gRl1EpTUt>JyWbs29RXB@d2vaxk3JdbvPG)ov0p>@ zk`;cE=mDNSO0@<4=CjTy4Z4oz(F-DN^pc!X`E6C$*dUqLsZ`46@R`5;c8?&~gLSG( z?T#bBLq|kGsSkL%r0a#s!`b1i%B;>GGR;tzg{nk(0iy=v1;-Xa{Z`R*o54ryh)BRaz z5J?M`5b%%LpmMR>W#POLL;o^>KrkEsp9SF0-QH7?$QrD>3h1@?Lt{WyhUvn> z&K{4J$-V9@)oe9ED%EvUmjvdw%!^3)a`!oNO!Dao@JqYcXOdE5 zP~Xs{v8`s31Z?}sq-tuke#*lT#l*Mx?Y7jopM}}thh+oM+9gUPdjWj!@E|D9)8#OZ zV9-*$YhO1(Og11yY8g!l`2PNZbCCNgDjdcb#DR~x&4}1;T(JH-F-s*&5(*#gzyQkF zChYYmC>>VN;NeR5;m7^$ZP7aijQ3U;Ues(tjz~~vo)HYPX+ZQIiu#**o zL!i(O2=(@h-sqdNnYP8gb-?Gx`(yebn_n#fr(qrCD+?ypH)5%_B1HTNeoDv46gar` z<-rTyM`(v@2M>-;KvJP50NSBeP3ojN^&u(7R~!!^O_ISI2^slcx)_YlQCSHxm+$cC zXrbSN@KF#`XZ_8wjuK;H8}<)^djB7KXf{L8ZE{pRp0B#Lpcoq* z)Ltp}I5x)&b@}|y^%qsCX#>M}LqX{=kKOzUY4)T?Lht&pdF>CDJ~wY(Y?2A+HK$%R zeOO_bJ)oH*qsh9;i!$et<0~rJc4Zdx)?nd_0vk>F`S~q*o=!`3MLHo+rTAgpdXm)g z6a}kmYdn(D(kqOcIYiI~B)OzC&F5XjFWsg9IQcOBy-oRgz{&o$WqhRTkk?NFCCr^lDm1lJD@v#Qli~ZnEZ$Xji^p29BteJ3I(;JX2>ye%XjYfT!8qfs zA%}?qr2)g~1wkjqECv7o literal 0 HcmV?d00001 diff --git a/www/code/codemirror-5.13.2/doc/logo.svg b/www/code/codemirror-5.13.2/doc/logo.svg new file mode 100644 index 000000000..0aa6323f9 --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/logo.svg @@ -0,0 +1,181 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/www/code/codemirror-5.13.2/doc/manual.html b/www/code/codemirror-5.13.2/doc/manual.html new file mode 100644 index 000000000..9642a11ad --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/manual.html @@ -0,0 +1,3382 @@ + + +CodeMirror: User Manual + + + + + + + + + + + + + + + + +
+ +
+

+ User manual and reference guide + version 5.13.2 +

+ +

CodeMirror is a code-editor component that can be embedded in + Web pages. The core library provides only the editor + component, no accompanying buttons, auto-completion, or other IDE + functionality. It does provide a rich API on top of which such + functionality can be straightforwardly implemented. See + the addons included in the distribution, + and the list + of externally hosted addons, for reusable + implementations of extra features.

+ +

CodeMirror works with language-specific modes. Modes are + JavaScript programs that help color (and optionally indent) text + written in a given language. The distribution comes with a number + of modes (see the mode/ + directory), and it isn't hard to write new + ones for other languages.

+
+ +
+

Basic Usage

+ +

The easiest way to use CodeMirror is to simply load the script + and style sheet found under lib/ in the distribution, + plus a mode script from one of the mode/ directories. + (See the compression helper for an + easy way to combine scripts.) For example:

+ +
<script src="lib/codemirror.js"></script>
+<link rel="stylesheet" href="lib/codemirror.css">
+<script src="mode/javascript/javascript.js"></script>
+ +

(Alternatively, use a module loader. More + about that later.)

+ +

Having done this, an editor instance can be created like + this:

+ +
var myCodeMirror = CodeMirror(document.body);
+ +

The editor will be appended to the document body, will start + empty, and will use the mode that we loaded. To have more control + over the new editor, a configuration object can be passed + to CodeMirror as a second + argument:

+ +
var myCodeMirror = CodeMirror(document.body, {
+  value: "function myScript(){return 100;}\n",
+  mode:  "javascript"
+});
+ +

This will initialize the editor with a piece of code already in + it, and explicitly tell it to use the JavaScript mode (which is + useful when multiple modes are loaded). + See below for a full discussion of the + configuration options that CodeMirror accepts.

+ +

In cases where you don't want to append the editor to an + element, and need more control over the way it is inserted, the + first argument to the CodeMirror function can also + be a function that, when given a DOM element, inserts it into the + document somewhere. This could be used to, for example, replace a + textarea with a real editor:

+ +
var myCodeMirror = CodeMirror(function(elt) {
+  myTextArea.parentNode.replaceChild(elt, myTextArea);
+}, {value: myTextArea.value});
+ +

However, for this use case, which is a common way to use + CodeMirror, the library provides a much more powerful + shortcut:

+ +
var myCodeMirror = CodeMirror.fromTextArea(myTextArea);
+ +

This will, among other things, ensure that the textarea's value + is updated with the editor's contents when the form (if it is part + of a form) is submitted. See the API + reference for a full description of this method.

+ +

Module loaders

+ +

The files in the CodeMirror distribution contain shims for + loading them (and their dependencies) in AMD or CommonJS + environments. If the variables exports + and module exist and have type object, CommonJS-style + require will be used. If not, but there is a + function define with an amd property + present, AMD-style (RequireJS) will be used.

+ +

It is possible to + use Browserify or similar + tools to statically build modules using CodeMirror. Alternatively, + use RequireJS to dynamically + load dependencies at runtime. Both of these approaches have the + advantage that they don't use the global namespace and can, thus, + do things like load multiple versions of CodeMirror alongside each + other.

+ +

Here's a simple example of using RequireJS to load CodeMirror:

+ +
require([
+  "cm/lib/codemirror", "cm/mode/htmlmixed/htmlmixed"
+], function(CodeMirror) {
+  CodeMirror.fromTextArea(document.getElementById("code"), {
+    lineNumbers: true,
+    mode: "htmlmixed"
+  });
+});
+ +

It will automatically load the modes that the mixed HTML mode + depends on (XML, JavaScript, and CSS). Do not use + RequireJS' paths option to configure the path to + CodeMirror, since it will break loading submodules through + relative paths. Use + the packages + configuration option instead, as in:

+ +
require.config({
+  packages: [{
+    name: "codemirror",
+    location: "../path/to/codemirror",
+    main: "lib/codemirror"
+  }]
+});
+ +
+ +
+

Configuration

+ +

Both the CodeMirror + function and its fromTextArea method take as second + (optional) argument an object containing configuration options. + Any option not supplied like this will be taken + from CodeMirror.defaults, an + object containing the default options. You can update this object + to change the defaults on your page.

+ +

Options are not checked in any way, so setting bogus option + values is bound to lead to odd errors.

+ +

These are the supported options:

+ +
+
value: string|CodeMirror.Doc
+
The starting value of the editor. Can be a string, or + a document object.
+ +
mode: string|object
+
The mode to use. When not given, this will default to the + first mode that was loaded. It may be a string, which either + simply names the mode or is + a MIME type + associated with the mode. Alternatively, it may be an object + containing configuration options for the mode, with + a name property that names the mode (for + example {name: "javascript", json: true}). The demo + pages for each mode contain information about what configuration + parameters the mode supports. You can ask CodeMirror which modes + and MIME types have been defined by inspecting + the CodeMirror.modes + and CodeMirror.mimeModes objects. The first maps + mode names to their constructors, and the second maps MIME types + to mode specs.
+ +
lineSeparator: string|null
+
Explicitly set the line separator for the editor. By default + (value null), the document will be split on CRLFs + as well as lone CRs and LFs, and a single LF will be used as + line separator in all output (such + as getValue). When a + specific string is given, lines will only be split on that + string, and output will, by default, use that same + separator.
+ +
theme: string
+
The theme to style the editor with. You must make sure the + CSS file defining the corresponding .cm-s-[name] + styles is loaded (see + the theme directory in the + distribution). The default is "default", for which + colors are included in codemirror.css. It is + possible to use multiple theming classes at once—for + example "foo bar" will assign both + the cm-s-foo and the cm-s-bar classes + to the editor.
+ +
indentUnit: integer
+
How many spaces a block (whatever that means in the edited + language) should be indented. The default is 2.
+ +
smartIndent: boolean
+
Whether to use the context-sensitive indentation that the + mode provides (or just indent the same as the line before). + Defaults to true.
+ +
tabSize: integer
+
The width of a tab character. Defaults to 4.
+ +
indentWithTabs: boolean
+
Whether, when indenting, the first N*tabSize + spaces should be replaced by N tabs. Default is false.
+ +
electricChars: boolean
+
Configures whether the editor should re-indent the current + line when a character is typed that might change its proper + indentation (only works if the mode supports indentation). + Default is true.
+ +
specialChars: RegExp
+
A regular expression used to determine which characters + should be replaced by a + special placeholder. + Mostly useful for non-printing special characters. The default + is /[\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/.
+
specialCharPlaceholder: function(char) → Element
+
A function that, given a special character identified by + the specialChars + option, produces a DOM node that is used to represent the + character. By default, a red dot () + is shown, with a title tooltip to indicate the character code.
+ +
rtlMoveVisually: boolean
+
Determines whether horizontal cursor movement through + right-to-left (Arabic, Hebrew) text is visual (pressing the left + arrow moves the cursor left) or logical (pressing the left arrow + moves to the next lower index in the string, which is visually + right in right-to-left text). The default is false + on Windows, and true on other platforms.
+ +
keyMap: string
+
Configures the key map to use. The default + is "default", which is the only key map defined + in codemirror.js itself. Extra key maps are found in + the key map directory. See + the section on key maps for more + information.
+ +
extraKeys: object
+
Can be used to specify extra key bindings for the editor, + alongside the ones defined + by keyMap. Should be + either null, or a valid key map value.
+ +
lineWrapping: boolean
+
Whether CodeMirror should scroll or wrap for long lines. + Defaults to false (scroll).
+ +
lineNumbers: boolean
+
Whether to show line numbers to the left of the editor.
+ +
firstLineNumber: integer
+
At which number to start counting lines. Default is 1.
+ +
lineNumberFormatter: function(line: integer) → string
+
A function used to format line numbers. The function is + passed the line number, and should return a string that will be + shown in the gutter.
+ +
gutters: array<string>
+
Can be used to add extra gutters (beyond or instead of the + line number gutter). Should be an array of CSS class names, each + of which defines a width (and optionally a + background), and which will be used to draw the background of + the gutters. May include + the CodeMirror-linenumbers class, in order to + explicitly set the position of the line number gutter (it will + default to be to the right of all other gutters). These class + names are the keys passed + to setGutterMarker.
+ +
fixedGutter: boolean
+
Determines whether the gutter scrolls along with the content + horizontally (false) or whether it stays fixed during horizontal + scrolling (true, the default).
+ +
scrollbarStyle: string
+
Chooses a scrollbar implementation. The default + is "native", showing native scrollbars. The core + library also provides the "null" style, which + completely hides the + scrollbars. Addons can + implement additional scrollbar models.
+ +
coverGutterNextToScrollbar: boolean
+
When fixedGutter + is on, and there is a horizontal scrollbar, by default the + gutter will be visible to the left of this scrollbar. If this + option is set to true, it will be covered by an element with + class CodeMirror-gutter-filler.
+ +
inputStyle: string
+
Selects the way CodeMirror handles input and focus. The core + library defines the "textarea" + and "contenteditable" input models. On mobile + browsers, the default is "contenteditable". On + desktop browsers, the default is "textarea". + Support for IME and screen readers is better in + the "contenteditable" model. The intention is to + make it the default on modern desktop browsers in the + future.
+ +
readOnly: boolean|string
+
This disables editing of the editor content by the user. If + the special value "nocursor" is given (instead of + simply true), focusing of the editor is also + disallowed.
+ +
showCursorWhenSelecting: boolean
+
Whether the cursor should be drawn when a selection is + active. Defaults to false.
+ +
lineWiseCopyCut: boolean
+
When enabled, which is the default, doing copy or cut when + there is no selection will copy or cut the whole lines that have + cursors on them.
+ +
undoDepth: integer
+
The maximum number of undo levels that the editor stores. + Note that this includes selection change events. Defaults to + 200.
+ +
historyEventDelay: integer
+
The period of inactivity (in milliseconds) that will cause a + new history event to be started when typing or deleting. + Defaults to 1250.
+ +
tabindex: integer
+
The tab + index to assign to the editor. If not given, no tab index + will be assigned.
+ +
autofocus: boolean
+
Can be used to make CodeMirror focus itself on + initialization. Defaults to off. + When fromTextArea is + used, and no explicit value is given for this option, it will be + set to true when either the source textarea is focused, or it + has an autofocus attribute and no other element is + focused.
+
+ +

Below this a few more specialized, low-level options are + listed. These are only useful in very specific situations, you + might want to skip them the first time you read this manual.

+ +
+
dragDrop: boolean
+
Controls whether drag-and-drop is enabled. On by default.
+ +
allowDropFileTypes: array<string>
+
When set (default is null) only files whose + type is in the array can be dropped into the editor. The strings + should be MIME types, and will be checked against + the type + of the File object as reported by the browser.
+ +
cursorBlinkRate: number
+
Half-period in milliseconds used for cursor blinking. The default blink + rate is 530ms. By setting this to zero, blinking can be disabled. A + negative value hides the cursor entirely.
+ +
cursorScrollMargin: number
+
How much extra space to always keep above and below the + cursor when approaching the top or bottom of the visible view in + a scrollable document. Default is 0.
+ +
cursorHeight: number
+
Determines the height of the cursor. Default is 1, meaning + it spans the whole height of the line. For some fonts (and by + some tastes) a smaller height (for example 0.85), + which causes the cursor to not reach all the way to the bottom + of the line, looks better
+ +
resetSelectionOnContextMenu: boolean
+
Controls whether, when the context menu is opened with a + click outside of the current selection, the cursor is moved to + the point of the click. Defaults to true.
+ +
workTime, workDelay: number
+
Highlighting is done by a pseudo background-thread that will + work for workTime milliseconds, and then use + timeout to sleep for workDelay milliseconds. The + defaults are 200 and 300, you can change these options to make + the highlighting more or less aggressive.
+ +
pollInterval: number
+
Indicates how quickly CodeMirror should poll its input + textarea for changes (when focused). Most input is captured by + events, but some things, like IME input on some browsers, don't + generate events that allow CodeMirror to properly detect it. + Thus, it polls. Default is 100 milliseconds.
+ +
flattenSpans: boolean
+
By default, CodeMirror will combine adjacent tokens into a + single span if they have the same class. This will result in a + simpler DOM tree, and thus perform better. With some kinds of + styling (such as rounded corners), this will change the way the + document looks. You can set this option to false to disable this + behavior.
+ +
addModeClass: boolean
+
When enabled (off by default), an extra CSS class will be + added to each token, indicating the + (inner) mode that produced it, prefixed + with "cm-m-". For example, tokens from the XML mode + will get the cm-m-xml class.
+ +
maxHighlightLength: number
+
When highlighting long lines, in order to stay responsive, + the editor will give up and simply style the rest of the line as + plain text when it reaches a certain position. The default is + 10 000. You can set this to Infinity to turn off + this behavior.
+ +
viewportMargin: integer
+
Specifies the amount of lines that are rendered above and + below the part of the document that's currently scrolled into + view. This affects the amount of updates needed when scrolling, + and the amount of work that such an update does. You should + usually leave it at its default, 10. Can be set + to Infinity to make sure the whole document is + always rendered, and thus the browser's text search works on it. + This will have bad effects on performance of big + documents.
+
+
+ +
+

Events

+ +

Various CodeMirror-related objects emit events, which allow + client code to react to various situations. Handlers for such + events can be registered with the on + and off methods on the objects + that the event fires on. To fire your own events, + use CodeMirror.signal(target, name, args...), + where target is a non-DOM-node object.

+ +

An editor instance fires the following events. + The instance argument always refers to the editor + itself.

+ +
+
"change" (instance: CodeMirror, changeObj: object)
+
Fires every time the content of the editor is changed. + The changeObj is a {from, to, text, removed, + origin} object containing information about the changes + that occurred as second argument. from + and to are the positions (in the pre-change + coordinate system) where the change started and ended (for + example, it might be {ch:0, line:18} if the + position is at the beginning of line #19). text is + an array of strings representing the text that replaced the + changed range (split by line). removed is the text + that used to be between from and to, + which is overwritten by this change. This event is + fired before the end of + an operation, before the DOM updates + happen.
+ +
"changes" (instance: CodeMirror, changes: array<object>)
+
Like the "change" + event, but batched per operation, + passing an array containing all the changes that happened in the + operation. This event is fired after the operation finished, and + display changes it makes will trigger a new operation.
+ +
"beforeChange" (instance: CodeMirror, changeObj: object)
+
This event is fired before a change is applied, and its + handler may choose to modify or cancel the change. + The changeObj object + has from, to, and text + properties, as with + the "change" event. It + also has a cancel() method, which can be called to + cancel the change, and, if the change isn't + coming from an undo or redo event, an update(from, to, + text) method, which may be used to modify the change. + Undo or redo changes can't be modified, because they hold some + metainformation for restoring old marked ranges that is only + valid for that specific change. All three arguments + to update are optional, and can be left off to + leave the existing value for that field + intact. Note: you may not do anything from + a "beforeChange" handler that would cause changes + to the document or its visualization. Doing so will, since this + handler is called directly from the bowels of the CodeMirror + implementation, probably cause the editor to become + corrupted.
+ +
"cursorActivity" (instance: CodeMirror)
+
Will be fired when the cursor or selection moves, or any + change is made to the editor content.
+ +
"keyHandled" (instance: CodeMirror, name: string, event: Event)
+
Fired after a key is handled through a + key map. name is the name of the handled key (for + example "Ctrl-X" or "'q'"), + and event is the DOM keydown + or keypress event.
+ +
"inputRead" (instance: CodeMirror, changeObj: object)
+
Fired whenever new input is read from the hidden textarea + (typed or pasted by the user).
+ +
"electricInput" (instance: CodeMirror, line: integer)
+
Fired if text input matched the + mode's electric patterns, + and this caused the line's indentation to change.
+ +
"beforeSelectionChange" (instance: CodeMirror, obj: {ranges, origin, update})
+
This event is fired before the selection is moved. Its + handler may inspect the set of selection ranges, present as an + array of {anchor, head} objects in + the ranges property of the obj + argument, and optionally change them by calling + the update method on this object, passing an array + of ranges in the same format. The object also contains + an origin property holding the origin string passed + to the selection-changing method, if any. Handlers for this + event have the same restriction + as "beforeChange" + handlers — they should not do anything to directly update the + state of the editor.
+ +
"viewportChange" (instance: CodeMirror, from: number, to: number)
+
Fires whenever the view port of + the editor changes (due to scrolling, editing, or any other + factor). The from and to arguments + give the new start and end of the viewport.
+ +
"swapDoc" (instance: CodeMirror, oldDoc: Doc)
+
This is signalled when the editor's document is replaced + using the swapDoc + method.
+ +
"gutterClick" (instance: CodeMirror, line: integer, gutter: string, clickEvent: Event)
+
Fires when the editor gutter (the line-number area) is + clicked. Will pass the editor instance as first argument, the + (zero-based) number of the line that was clicked as second + argument, the CSS class of the gutter that was clicked as third + argument, and the raw mousedown event object as + fourth argument.
+ +
"gutterContextMenu" (instance: CodeMirror, line: integer, gutter: string, contextMenu: Event: Event)
+
Fires when the editor gutter (the line-number area) + receives a contextmenu event. Will pass the editor + instance as first argument, the (zero-based) number of the line + that was clicked as second argument, the CSS class of the + gutter that was clicked as third argument, and the raw + contextmenu mouse event object as fourth argument. + You can preventDefault the event, to signal that + CodeMirror should do no further handling.
+ +
"focus" (instance: CodeMirror)
+
Fires whenever the editor is focused.
+ +
"blur" (instance: CodeMirror)
+
Fires whenever the editor is unfocused.
+ +
"scroll" (instance: CodeMirror)
+
Fires when the editor is scrolled.
+ +
"scrollCursorIntoView" (instance: CodeMirror, event: Event)
+
Fires when the editor tries to scroll its cursor into view. + Can be hooked into to take care of additional scrollable + containers around the editor. When the event object has + its preventDefault method called, CodeMirror will + not itself try to scroll the window.
+ +
"update" (instance: CodeMirror)
+
Will be fired whenever CodeMirror updates its DOM display.
+ +
"renderLine" (instance: CodeMirror, line: LineHandle, element: Element)
+
Fired whenever a line is (re-)rendered to the DOM. Fired + right after the DOM element is built, before it is + added to the document. The handler may mess with the style of + the resulting element, or add event handlers, but + should not try to change the state of the editor.
+ +
"mousedown", + "dblclick", "touchstart", "contextmenu", + "keydown", "keypress", + "keyup", "cut", "copy", "paste", + "dragstart", "dragenter", + "dragover", "dragleave", + "drop" + (instance: CodeMirror, event: Event)
+
Fired when CodeMirror is handling a DOM event of this type. + You can preventDefault the event, or give it a + truthy codemirrorIgnore property, to signal that + CodeMirror should do no further handling.
+
+ +

Document objects (instances + of CodeMirror.Doc) emit the + following events:

+ +
+
"change" (doc: CodeMirror.Doc, changeObj: object)
+
Fired whenever a change occurs to the + document. changeObj has a similar type as the + object passed to the + editor's "change" + event.
+ +
"beforeChange" (doc: CodeMirror.Doc, change: object)
+
See the description of the + same event on editor instances.
+ +
"cursorActivity" (doc: CodeMirror.Doc)
+
Fired whenever the cursor or selection in this document + changes.
+ +
"beforeSelectionChange" (doc: CodeMirror.Doc, selection: {head, anchor})
+
Equivalent to + the event by the same + name as fired on editor instances.
+
+ +

Line handles (as returned by, for + example, getLineHandle) + support these events:

+ +
+
"delete" ()
+
Will be fired when the line object is deleted. A line object + is associated with the start of the line. Mostly useful + when you need to find out when your gutter + markers on a given line are removed.
+
"change" (line: LineHandle, changeObj: object)
+
Fires when the line's text content is changed in any way + (but the line is not deleted outright). The change + object is similar to the one passed + to change event on the editor + object.
+
+ +

Marked range handles (CodeMirror.TextMarker), as returned + by markText + and setBookmark, emit the + following events:

+ +
+
"beforeCursorEnter" ()
+
Fired when the cursor enters the marked range. From this + event handler, the editor state may be inspected + but not modified, with the exception that the range on + which the event fires may be cleared.
+
"clear" (from: {line, ch}, to: {line, ch})
+
Fired when the range is cleared, either through cursor + movement in combination + with clearOnEnter + or through a call to its clear() method. Will only + be fired once per handle. Note that deleting the range through + text editing does not fire this event, because an undo action + might bring the range back into existence. from + and to give the part of the document that the range + spanned when it was cleared.
+
"hide" ()
+
Fired when the last part of the marker is removed from the + document by editing operations.
+
"unhide" ()
+
Fired when, after the marker was removed by editing, a undo + operation brought the marker back.
+
+ +

Line widgets (CodeMirror.LineWidget), returned + by addLineWidget, fire + these events:

+ +
+
"redraw" ()
+
Fired whenever the editor re-adds the widget to the DOM. + This will happen once right after the widget is added (if it is + scrolled into view), and then again whenever it is scrolled out + of view and back in again, or when changes to the editor options + or the line the widget is on require the widget to be + redrawn.
+
+
+ +
+

Key Maps

+ +

Key maps are ways to associate keys with functionality. A key map + is an object mapping strings that identify the keys to functions + that implement their functionality.

+ +

The CodeMirror distributions comes + with Emacs, Vim, + and Sublime Text-style keymaps.

+ +

Keys are identified either by name or by character. + The CodeMirror.keyNames object defines names for + common keys and associates them with their key codes. Examples of + names defined here are Enter, F5, + and Q. These can be prefixed + with Shift-, Cmd-, Ctrl-, + and Alt- to specify a modifier. So for + example, Shift-Ctrl-Space would be a valid key + identifier.

+ +

Common example: map the Tab key to insert spaces instead of a tab + character.

+ +
+editor.setOption("extraKeys", {
+  Tab: function(cm) {
+    var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
+    cm.replaceSelection(spaces);
+  }
+});
+ +

Alternatively, a character can be specified directly by + surrounding it in single quotes, for example '$' + or 'q'. Due to limitations in the way browsers fire + key events, these may not be prefixed with modifiers.

+ +

Multi-stroke key bindings can be specified + by separating the key names by spaces in the property name, for + example Ctrl-X Ctrl-V. When a map contains + multi-stoke bindings or keys with modifiers that are not specified + in the default order (Shift-Cmd-Ctrl-Alt), you must + call CodeMirror.normalizeKeyMap on it before it can + be used. This function takes a keymap and modifies it to normalize + modifier order and properly recognize multi-stroke bindings. It + will return the keymap itself.

+ +

The CodeMirror.keyMap object associates key maps + with names. User code and key map definitions can assign extra + properties to this object. Anywhere where a key map is expected, a + string can be given, which will be looked up in this object. It + also contains the "default" key map holding the + default bindings.

+ +

The values of properties in key maps can be either functions of + a single argument (the CodeMirror instance), strings, or + false. Strings refer + to commands, which are described below. If + the property is set to false, CodeMirror leaves + handling of the key up to the browser. A key handler function may + return CodeMirror.Pass to indicate that it has + decided not to handle the key, and other handlers (or the default + behavior) should be given a turn.

+ +

Keys mapped to command names that start with the + characters "go" or to functions that have a + truthy motion property (which should be used for + cursor-movement actions) will be fired even when an + extra Shift modifier is present (i.e. "Up": + "goLineUp" matches both up and shift-up). This is used to + easily implement shift-selection.

+ +

Key maps can defer to each other by defining + a fallthrough property. This indicates that when a + key is not found in the map itself, one or more other maps should + be searched. It can hold either a single key map or an array of + key maps.

+ +

When a key map needs to set something up when it becomes + active, or tear something down when deactivated, it can + contain attach and/or detach properties, + which should hold functions that take the editor instance and the + next or previous keymap. Note that this only works for the + top-level keymap, not for fallthrough + maps or maps added + with extraKeys + or addKeyMap.

+
+ +
+

Commands

+ +

Commands are parameter-less actions that can be performed on an + editor. Their main use is for key bindings. Commands are defined by + adding properties to the CodeMirror.commands object. + A number of common commands are defined by the library itself, + most of them used by the default key bindings. The value of a + command property must be a function of one argument (an editor + instance).

+ +

Some of the commands below are referenced in the default + key map, but not defined by the core library. These are intended to + be defined by user code or addons.

+ +

Commands can also be run with + the execCommand + method.

+ +
+
selectAllCtrl-A (PC), Cmd-A (Mac)
+
Select the whole content of the editor.
+ +
singleSelectionEsc
+
When multiple selections are present, this deselects all but + the primary selection.
+ +
killLineCtrl-K (Mac)
+
Emacs-style line killing. Deletes the part of the line after + the cursor. If that consists only of whitespace, the newline at + the end of the line is also deleted.
+ +
deleteLineCtrl-D (PC), Cmd-D (Mac)
+
Deletes the whole line under the cursor, including newline at the end.
+ +
delLineLeft
+
Delete the part of the line before the cursor.
+ +
delWrappedLineLeftCmd-Backspace (Mac)
+
Delete the part of the line from the left side of the visual line the cursor is on to the cursor.
+ +
delWrappedLineRightCmd-Delete (Mac)
+
Delete the part of the line from the cursor to the right side of the visual line the cursor is on.
+ +
undoCtrl-Z (PC), Cmd-Z (Mac)
+
Undo the last change.
+ +
redoCtrl-Y (PC), Shift-Cmd-Z (Mac), Cmd-Y (Mac)
+
Redo the last undone change.
+ +
undoSelectionCtrl-U (PC), Cmd-U (Mac)
+
Undo the last change to the selection, or if there are no + selection-only changes at the top of the history, undo the last + change.
+ +
redoSelectionAlt-U (PC), Shift-Cmd-U (Mac)
+
Redo the last change to the selection, or the last text change if + no selection changes remain.
+ +
goDocStartCtrl-Home (PC), Cmd-Up (Mac), Cmd-Home (Mac)
+
Move the cursor to the start of the document.
+ +
goDocEndCtrl-End (PC), Cmd-End (Mac), Cmd-Down (Mac)
+
Move the cursor to the end of the document.
+ +
goLineStartAlt-Left (PC), Ctrl-A (Mac)
+
Move the cursor to the start of the line.
+ +
goLineStartSmartHome
+
Move to the start of the text on the line, or if we are + already there, to the actual start of the line (including + whitespace).
+ +
goLineEndAlt-Right (PC), Ctrl-E (Mac)
+
Move the cursor to the end of the line.
+ +
goLineRightCmd-Right (Mac)
+
Move the cursor to the right side of the visual line it is on.
+ +
goLineLeftCmd-Left (Mac)
+
Move the cursor to the left side of the visual line it is on. If + this line is wrapped, that may not be the start of the line.
+ +
goLineLeftSmart
+
Move the cursor to the left side of the visual line it is + on. If that takes it to the start of the line, behave + like goLineStartSmart.
+ +
goLineUpUp, Ctrl-P (Mac)
+
Move the cursor up one line.
+ +
goLineDownDown, Ctrl-N (Mac)
+
Move down one line.
+ +
goPageUpPageUp, Shift-Ctrl-V (Mac)
+
Move the cursor up one screen, and scroll up by the same distance.
+ +
goPageDownPageDown, Ctrl-V (Mac)
+
Move the cursor down one screen, and scroll down by the same distance.
+ +
goCharLeftLeft, Ctrl-B (Mac)
+
Move the cursor one character left, going to the previous line + when hitting the start of line.
+ +
goCharRightRight, Ctrl-F (Mac)
+
Move the cursor one character right, going to the next line + when hitting the end of line.
+ +
goColumnLeft
+
Move the cursor one character left, but don't cross line boundaries.
+ +
goColumnRight
+
Move the cursor one character right, don't cross line boundaries.
+ +
goWordLeftAlt-B (Mac)
+
Move the cursor to the start of the previous word.
+ +
goWordRightAlt-F (Mac)
+
Move the cursor to the end of the next word.
+ +
goGroupLeftCtrl-Left (PC), Alt-Left (Mac)
+
Move to the left of the group before the cursor. A group is + a stretch of word characters, a stretch of punctuation + characters, a newline, or a stretch of more than one + whitespace character.
+ +
goGroupRightCtrl-Right (PC), Alt-Right (Mac)
+
Move to the right of the group after the cursor + (see above).
+ +
delCharBeforeShift-Backspace, Ctrl-H (Mac)
+
Delete the character before the cursor.
+ +
delCharAfterDelete, Ctrl-D (Mac)
+
Delete the character after the cursor.
+ +
delWordBeforeAlt-Backspace (Mac)
+
Delete up to the start of the word before the cursor.
+ +
delWordAfterAlt-D (Mac)
+
Delete up to the end of the word after the cursor.
+ +
delGroupBeforeCtrl-Backspace (PC), Alt-Backspace (Mac)
+
Delete to the left of the group before the cursor.
+ +
delGroupAfterCtrl-Delete (PC), Ctrl-Alt-Backspace (Mac), Alt-Delete (Mac)
+
Delete to the start of the group after the cursor.
+ +
indentAutoShift-Tab
+
Auto-indent the current line or selection.
+ +
indentMoreCtrl-] (PC), Cmd-] (Mac)
+
Indent the current line or selection by one indent unit.
+ +
indentLessCtrl-[ (PC), Cmd-[ (Mac)
+
Dedent the current line or selection by one indent unit.
+ +
insertTab
+
Insert a tab character at the cursor.
+ +
insertSoftTab
+
Insert the amount of spaces that match the width a tab at + the cursor position would have.
+ +
defaultTabTab
+
If something is selected, indent it by + one indent unit. If nothing is + selected, insert a tab character.
+ +
transposeCharsCtrl-T (Mac)
+
Swap the characters before and after the cursor.
+ +
newlineAndIndentEnter
+
Insert a newline and auto-indent the new line.
+ +
toggleOverwriteInsert
+
Flip the overwrite flag.
+ +
saveCtrl-S (PC), Cmd-S (Mac)
+
Not defined by the core library, only referred to in + key maps. Intended to provide an easy way for user code to define + a save command.
+ +
findCtrl-F (PC), Cmd-F (Mac)
+
findNextCtrl-G (PC), Cmd-G (Mac)
+
findPrevShift-Ctrl-G (PC), Shift-Cmd-G (Mac)
+
replaceShift-Ctrl-F (PC), Cmd-Alt-F (Mac)
+
replaceAllShift-Ctrl-R (PC), Shift-Cmd-Alt-F (Mac)
+
Not defined by the core library, but defined in + the search addon (or custom client + addons).
+ +
+ +
+ +
+

Customized Styling

+ +

Up to a certain extent, CodeMirror's look can be changed by + modifying style sheet files. The style sheets supplied by modes + simply provide the colors for that mode, and can be adapted in a + very straightforward way. To style the editor itself, it is + possible to alter or override the styles defined + in codemirror.css.

+ +

Some care must be taken there, since a lot of the rules in this + file are necessary to have CodeMirror function properly. Adjusting + colors should be safe, of course, and with some care a lot of + other things can be changed as well. The CSS classes defined in + this file serve the following roles:

+ +
+
CodeMirror
+
The outer element of the editor. This should be used for the + editor width, height, borders and positioning. Can also be used + to set styles that should hold for everything inside the editor + (such as font and font size), or to set a background. Setting + this class' height style to auto will + make the editor resize to fit its + content (it is recommended to also set + the viewportMargin + option to Infinity when doing this.
+ +
CodeMirror-focused
+
Whenever the editor is focused, the top element gets this + class. This is used to hide the cursor and give the selection a + different color when the editor is not focused.
+ +
CodeMirror-gutters
+
This is the backdrop for all gutters. Use it to set the + default gutter background color, and optionally add a border on + the right of the gutters.
+ +
CodeMirror-linenumbers
+
Use this for giving a background or width to the line number + gutter.
+ +
CodeMirror-linenumber
+
Used to style the actual individual line numbers. These + won't be children of the CodeMirror-linenumbers + (plural) element, but rather will be absolutely positioned to + overlay it. Use this to set alignment and text properties for + the line numbers.
+ +
CodeMirror-lines
+
The visible lines. This is where you specify vertical + padding for the editor content.
+ +
CodeMirror-cursor
+
The cursor is a block element that is absolutely positioned. + You can make it look whichever way you want.
+ +
CodeMirror-selected
+
The selection is represented by span elements + with this class.
+ +
CodeMirror-matchingbracket, + CodeMirror-nonmatchingbracket
+
These are used to style matched (or unmatched) brackets.
+
+ +

If your page's style sheets do funky things to + all div or pre elements (you probably + shouldn't do that), you'll have to define rules to cancel these + effects out again for elements under the CodeMirror + class.

+ +

Themes are also simply CSS files, which define colors for + various syntactic elements. See the files in + the theme directory.

+
+ +
+

Programming API

+ +

A lot of CodeMirror features are only available through its + API. Thus, you need to write code (or + use addons) if you want to expose them to + your users.

+ +

Whenever points in the document are represented, the API uses + objects with line and ch properties. + Both are zero-based. CodeMirror makes sure to 'clip' any positions + passed by client code so that they fit inside the document, so you + shouldn't worry too much about sanitizing your coordinates. If you + give ch a value of null, or don't + specify it, it will be replaced with the length of the specified + line.

+ +

Methods prefixed with doc. can, unless otherwise + specified, be called both on CodeMirror (editor) + instances and CodeMirror.Doc instances. Methods + prefixed with cm. are only available + on CodeMirror instances.

+ +

Constructor

+ +

Constructing an editor instance is done with + the CodeMirror(place: Element|fn(Element), + ?option: object) constructor. If the place + argument is a DOM element, the editor will be appended to it. If + it is a function, it will be called, and is expected to place the + editor into the document. options may be an element + mapping option names to values. The options + that it doesn't explicitly specify (or all options, if it is not + passed) will be taken + from CodeMirror.defaults.

+ +

Note that the options object passed to the constructor will be + mutated when the instance's options + are changed, so you shouldn't share such + objects between instances.

+ +

See CodeMirror.fromTextArea + for another way to construct an editor instance.

+ +

Content manipulation methods

+ +
+
doc.getValue(?separator: string) → string
+
Get the current editor content. You can pass it an optional + argument to specify the string to be used to separate lines + (defaults to "\n").
+
doc.setValue(content: string)
+
Set the editor content.
+ +
doc.getRange(from: {line, ch}, to: {line, ch}, ?separator: string) → string
+
Get the text between the given points in the editor, which + should be {line, ch} objects. An optional third + argument can be given to indicate the line separator string to + use (defaults to "\n").
+
doc.replaceRange(replacement: string, from: {line, ch}, to: {line, ch}, ?origin: string)
+
Replace the part of the document between from + and to with the given string. from + and to must be {line, ch} + objects. to can be left off to simply insert the + string at position from. When origin + is given, it will be passed on + to "change" events, and + its first letter will be used to determine whether this change + can be merged with previous history events, in the way described + for selection origins.
+ +
doc.getLine(n: integer) → string
+
Get the content of line n.
+ +
doc.lineCount() → integer
+
Get the number of lines in the editor.
+
doc.firstLine() → integer
+
Get the first line of the editor. This will + usually be zero but for linked sub-views, + or documents instantiated with a non-zero + first line, it might return other values.
+
doc.lastLine() → integer
+
Get the last line of the editor. This will + usually be doc.lineCount() - 1, + but for linked sub-views, + it might return other values.
+ +
doc.getLineHandle(num: integer) → LineHandle
+
Fetches the line handle for the given line number.
+
doc.getLineNumber(handle: LineHandle) → integer
+
Given a line handle, returns the current position of that + line (or null when it is no longer in the + document).
+
doc.eachLine(f: (line: LineHandle))
+
doc.eachLine(start: integer, end: integer, f: (line: LineHandle))
+
Iterate over the whole document, or if start + and end line numbers are given, the range + from start up to (not including) end, + and call f for each line, passing the line handle. + This is a faster way to visit a range of line handlers than + calling getLineHandle + for each of them. Note that line handles have + a text property containing the line's content (as a + string).
+ +
doc.markClean()
+
Set the editor content as 'clean', a flag that it will + retain until it is edited, and which will be set again when such + an edit is undone again. Useful to track whether the content + needs to be saved. This function is deprecated in favor + of changeGeneration, + which allows multiple subsystems to track different notions of + cleanness without interfering.
+
doc.changeGeneration(?closeEvent: boolean) → integer
+
Returns a number that can later be passed + to isClean to test whether + any edits were made (and not undone) in the meantime. + If closeEvent is true, the current history event + will be ‘closed’, meaning it can't be combined with further + changes (rapid typing or deleting events are typically + combined).
+
doc.isClean(?generation: integer) → boolean
+
Returns whether the document is currently clean — not + modified since initialization or the last call + to markClean if no + argument is passed, or since the matching call + to changeGeneration + if a generation value is given.
+
+ +

Cursor and selection methods

+ +
+
doc.getSelection(?lineSep: string) → string
+
Get the currently selected code. Optionally pass a line + separator to put between the lines in the output. When multiple + selections are present, they are concatenated with instances + of lineSep in between.
+
doc.getSelections(?lineSep: string) → string
+
Returns an array containing a string for each selection, + representing the content of the selections.
+ +
doc.replaceSelection(replacement: string, ?select: string)
+
Replace the selection(s) with the given string. By default, + the new selection ends up after the inserted text. The + optional select argument can be used to change + this—passing "around" will cause the new text to be + selected, passing "start" will collapse the + selection to the start of the inserted text.
+
doc.replaceSelections(replacements: array<string>, ?select: string)
+
The length of the given array should be the same as the + number of active selections. Replaces the content of the + selections with the strings in the array. + The select argument works the same as + in replaceSelection.
+ +
doc.getCursor(?start: string) → {line, ch}
+
Retrieve one end of the primary + selection. start is an optional string indicating + which end of the selection to return. It may + be "from", "to", "head" + (the side of the selection that moves when you press + shift+arrow), or "anchor" (the fixed side of the + selection). Omitting the argument is the same as + passing "head". A {line, ch} object + will be returned.
+
doc.listSelections() → array<{anchor, head}>
+
Retrieves a list of all current selections. These will + always be sorted, and never overlap (overlapping selections are + merged). Each object in the array contains anchor + and head properties referring to {line, + ch} objects.
+ +
doc.somethingSelected() → boolean
+
Return true if any text is selected.
+
doc.setCursor(pos: {line, ch}|number, ?ch: number, ?options: object)
+
Set the cursor position. You can either pass a + single {line, ch} object, or the line and the + character as two separate parameters. Will replace all + selections with a single, empty selection at the given position. + The supported options are the same as for setSelection.
+ +
doc.setSelection(anchor: {line, ch}, ?head: {line, ch}, ?options: object)
+
Set a single selection range. anchor + and head should be {line, ch} + objects. head defaults to anchor when + not given. These options are supported: +
+
scroll: boolean
+
Determines whether the selection head should be scrolled + into view. Defaults to true.
+
origin: string
+
Determines whether the selection history event may be + merged with the previous one. When an origin starts with the + character +, and the last recorded selection had + the same origin and was similar (close + in time, both + collapsed or both non-collapsed), the new one will replace the + old one. When it starts with *, it will always + replace the previous event (if that had the same origin). + Built-in motion uses the "+move" origin. User input uses the "+input" origin.
+
bias: number
+
Determine the direction into which the selection endpoints + should be adjusted when they fall inside + an atomic range. Can be either -1 + (backward) or 1 (forward). When not given, the bias will be + based on the relative position of the old selection—the editor + will try to move further away from that, to prevent getting + stuck.
+
+ +
doc.setSelections(ranges: array<{anchor, head}>, ?primary: integer, ?options: object)
+
Sets a new set of selections. There must be at least one + selection in the given array. When primary is a + number, it determines which selection is the primary one. When + it is not given, the primary index is taken from the previous + selection, or set to the last range if the previous selection + had less ranges than the new one. Supports the same options + as setSelection.
+
doc.addSelection(anchor: {line, ch}, ?head: {line, ch})
+
Adds a new selection to the existing set of selections, and + makes it the primary selection.
+ +
doc.extendSelection(from: {line, ch}, ?to: {line, ch}, ?options: object)
+
Similar + to setSelection, but + will, if shift is held or + the extending flag is set, move the + head of the selection while leaving the anchor at its current + place. to is optional, and can be passed to ensure + a region (for example a word or paragraph) will end up selected + (in addition to whatever lies between that region and the + current anchor). When multiple selections are present, all but + the primary selection will be dropped by this method. + Supports the same options as setSelection.
+
doc.extendSelections(heads: array<{line, ch}>, ?options: object)
+
An equivalent + of extendSelection + that acts on all selections at once.
+
doc.extendSelectionsBy(f: function(range: {anchor, head}) → {line, ch}), ?options: object)
+
Applies the given function to all existing selections, and + calls extendSelections + on the result.
+
doc.setExtending(value: boolean)
+
Sets or clears the 'extending' flag, which acts similar to + the shift key, in that it will cause cursor movement and calls + to extendSelection + to leave the selection anchor in place.
+
doc.getExtending() → boolean
+
Get the value of the 'extending' flag.
+ +
cm.hasFocus() → boolean
+
Tells you whether the editor currently has focus.
+ +
cm.findPosH(start: {line, ch}, amount: integer, unit: string, visually: boolean) → {line, ch, ?hitSide: boolean}
+
Used to find the target position for horizontal cursor + motion. start is a {line, ch} + object, amount an integer (may be negative), + and unit one of the + string "char", "column", + or "word". Will return a position that is produced + by moving amount times the distance specified + by unit. When visually is true, motion + in right-to-left text will be visual rather than logical. When + the motion was clipped by hitting the end or start of the + document, the returned value will have a hitSide + property set to true.
+
cm.findPosV(start: {line, ch}, amount: integer, unit: string) → {line, ch, ?hitSide: boolean}
+
Similar to findPosH, + but used for vertical motion. unit may + be "line" or "page". The other + arguments and the returned value have the same interpretation as + they have in findPosH.
+ +
cm.findWordAt(pos: {line, ch}) → {anchor: {line, ch}, head: {line, ch}}
+
Returns the start and end of the 'word' (the stretch of + letters, whitespace, or punctuation) at the given position.
+
+ +

Configuration methods

+ +
+
cm.setOption(option: string, value: any)
+
Change the configuration of the editor. option + should the name of an option, + and value should be a valid value for that + option.
+
cm.getOption(option: string) → any
+
Retrieves the current value of the given option for this + editor instance.
+ +
cm.addKeyMap(map: object, bottom: boolean)
+
Attach an additional key map to the + editor. This is mostly useful for addons that need to register + some key handlers without trampling on + the extraKeys + option. Maps added in this way have a higher precedence than + the extraKeys + and keyMap options, + and between them, the maps added earlier have a lower precedence + than those added later, unless the bottom argument + was passed, in which case they end up below other key maps added + with this method.
+
cm.removeKeyMap(map: object)
+
Disable a keymap added + with addKeyMap. Either + pass in the key map object itself, or a string, which will be + compared against the name property of the active + key maps.
+ +
cm.addOverlay(mode: string|object, ?options: object)
+
Enable a highlighting overlay. This is a stateless mini-mode + that can be used to add extra highlighting. For example, + the search addon uses it to + highlight the term that's currently being + searched. mode can be a mode + spec or a mode object (an object with + a token method). + The options parameter is optional. If given, it + should be an object. Currently, only the opaque + option is recognized. This defaults to off, but can be given to + allow the overlay styling, when not null, to + override the styling of the base mode entirely, instead of the + two being applied together.
+
cm.removeOverlay(mode: string|object)
+
Pass this the exact value passed for the mode + parameter to addOverlay, + or a string that corresponds to the name property of + that value, to remove an overlay again.
+ +
cm.on(type: string, func: (...args))
+
Register an event handler for the given event type (a + string) on the editor instance. There is also + a CodeMirror.on(object, type, func) version + that allows registering of events on any object.
+
cm.off(type: string, func: (...args))
+
Remove an event handler on the editor instance. An + equivalent CodeMirror.off(object, type, + func) also exists.
+
+ +

Document management methods

+ +

Each editor is associated with an instance + of CodeMirror.Doc, its document. A document + represents the editor content, plus a selection, an undo history, + and a mode. A document can only be + associated with a single editor at a time. You can create new + documents by calling the CodeMirror.Doc(text, mode, + firstLineNumber) constructor. The last two arguments are + optional and can be used to set a mode for the document and make + it start at a line number other than 0, respectively.

+ +
+
cm.getDoc() → Doc
+
Retrieve the currently active document from an editor.
+
doc.getEditor() → CodeMirror
+
Retrieve the editor associated with a document. May + return null.
+ +
cm.swapDoc(doc: CodeMirror.Doc) → Doc
+
Attach a new document to the editor. Returns the old + document, which is now no longer associated with an editor.
+ +
doc.copy(copyHistory: boolean) → Doc
+
Create an identical copy of the given doc. + When copyHistory is true, the history will also be + copied. Can not be called directly on an editor.
+ +
doc.linkedDoc(options: object) → Doc
+
Create a new document that's linked to the target document. + Linked documents will stay in sync (changes to one are also + applied to the other) until unlinked. + These are the options that are supported: +
+
sharedHist: boolean
+
When turned on, the linked copy will share an undo + history with the original. Thus, something done in one of + the two can be undone in the other, and vice versa.
+
from: integer
+
to: integer
+
Can be given to make the new document a subview of the + original. Subviews only show a given range of lines. Note + that line coordinates inside the subview will be consistent + with those of the parent, so that for example a subview + starting at line 10 will refer to its first line as line 10, + not 0.
+
mode: string|object
+
By default, the new document inherits the mode of the + parent. This option can be set to + a mode spec to give it a + different mode.
+
+
doc.unlinkDoc(doc: CodeMirror.Doc)
+
Break the link between two documents. After calling this, + changes will no longer propagate between the documents, and, if + they had a shared history, the history will become + separate.
+
doc.iterLinkedDocs(function: (doc: CodeMirror.Doc, sharedHist: boolean))
+
Will call the given function for all documents linked to the + target document. It will be passed two arguments, the linked document + and a boolean indicating whether that document shares history + with the target.
+
+ +

History-related methods

+ +
+
doc.undo()
+
Undo one edit (if any undo events are stored).
+
doc.redo()
+
Redo one undone edit.
+ +
doc.undoSelection()
+
Undo one edit or selection change.
+
doc.redoSelection()
+
Redo one undone edit or selection change.
+ +
doc.historySize() → {undo: integer, redo: integer}
+
Returns an object with {undo, redo} properties, + both of which hold integers, indicating the amount of stored + undo and redo operations.
+
doc.clearHistory()
+
Clears the editor's undo history.
+
doc.getHistory() → object
+
Get a (JSON-serializable) representation of the undo history.
+
doc.setHistory(history: object)
+
Replace the editor's undo history with the one provided, + which must be a value as returned + by getHistory. Note that + this will have entirely undefined results if the editor content + isn't also the same as it was when getHistory was + called.
+
+ +

Text-marking methods

+ +
+
doc.markText(from: {line, ch}, to: {line, ch}, ?options: object) → TextMarker
+
Can be used to mark a range of text with a specific CSS + class name. from and to should + be {line, ch} objects. The options + parameter is optional. When given, it should be an object that + may contain the following configuration options: +
+
className: string
+
Assigns a CSS class to the marked stretch of text.
+
inclusiveLeft: boolean
+
Determines whether + text inserted on the left of the marker will end up inside + or outside of it.
+
inclusiveRight: boolean
+
Like inclusiveLeft, + but for the right side.
+
atomic: boolean
+
Atomic ranges act as a single unit when cursor movement is + concerned—i.e. it is impossible to place the cursor inside of + them. In atomic ranges, inclusiveLeft + and inclusiveRight have a different meaning—they + will prevent the cursor from being placed respectively + directly before and directly after the range.
+
collapsed: boolean
+
Collapsed ranges do not show up in the display. Setting a + range to be collapsed will automatically make it atomic.
+
clearOnEnter: boolean
+
When enabled, will cause the mark to clear itself whenever + the cursor enters its range. This is mostly useful for + text-replacement widgets that need to 'snap open' when the + user tries to edit them. The + "clear" event + fired on the range handle can be used to be notified when this + happens.
+
clearWhenEmpty: boolean
+
Determines whether the mark is automatically cleared when + it becomes empty. Default is true.
+
replacedWith: Element
+
Use a given node to display this range. Implies both + collapsed and atomic. The given DOM node must be an + inline element (as opposed to a block element).
+
handleMouseEvents: boolean
+
When replacedWith is given, this determines + whether the editor will capture mouse and drag events + occurring in this widget. Default is false—the events will be + left alone for the default browser handler, or specific + handlers on the widget, to capture.
+
readOnly: boolean
+
A read-only span can, as long as it is not cleared, not be + modified except by + calling setValue to reset + the whole document. Note: adding a read-only span + currently clears the undo history of the editor, because + existing undo events being partially nullified by read-only + spans would corrupt the history (in the current + implementation).
+
addToHistory: boolean
+
When set to true (default is false), adding this marker + will create an event in the undo history that can be + individually undone (clearing the marker).
+
startStyle: string
Can be used to specify + an extra CSS class to be applied to the leftmost span that + is part of the marker.
+
endStyle: string
Equivalent + to startStyle, but for the rightmost span.
+
css: string
+
A string of CSS to be applied to the covered text. For example "color: #fe3".
+
title: + string
When given, will give the nodes created + for this span a HTML title attribute with the + given value.
+
shared: boolean
When the + target document is linked to other + documents, you can set shared to true to make the + marker appear in all documents. By default, a marker appears + only in its target document.
+
+ The method will return an object that represents the marker + (with constructor CodeMirror.TextMarker), which + exposes three methods: + clear(), to remove the mark, + find(), which returns + a {from, to} object (both holding document + positions), indicating the current position of the marked range, + or undefined if the marker is no longer in the + document, and finally changed(), + which you can call if you've done something that might change + the size of the marker (for example changing the content of + a replacedWith + node), and want to cheaply update the display.
+ +
doc.setBookmark(pos: {line, ch}, ?options: object) → TextMarker
+
Inserts a bookmark, a handle that follows the text around it + as it is being edited, at the given position. A bookmark has two + methods find() and clear(). The first + returns the current position of the bookmark, if it is still in + the document, and the second explicitly removes the bookmark. + The options argument is optional. If given, the following + properties are recognized: +
+
widget: Element
Can be used to display a DOM + node at the current location of the bookmark (analogous to + the replacedWith + option to markText).
+
insertLeft: boolean
By default, text typed + when the cursor is on top of the bookmark will end up to the + right of the bookmark. Set this option to true to make it go + to the left instead.
+
shared: boolean
See + the corresponding option + to markText.
+
handleMouseEvents: boolean
+
As with markText, + this determines whether mouse events on the widget inserted + for this bookmark are handled by CodeMirror. The default is + false.
+
+ +
doc.findMarks(from: {line, ch}, to: {line, ch}) → array<TextMarker>
+
Returns an array of all the bookmarks and marked ranges + found between the given positions.
+
doc.findMarksAt(pos: {line, ch}) → array<TextMarker>
+
Returns an array of all the bookmarks and marked ranges + present at the given position.
+
doc.getAllMarks() → array<TextMarker>
+
Returns an array containing all marked ranges in the document.
+
+ +

Widget, gutter, and decoration methods

+ +
+
cm.setGutterMarker(line: integer|LineHandle, gutterID: string, value: Element) → LineHandle
+
Sets the gutter marker for the given gutter (identified by + its CSS class, see + the gutters option) + to the given value. Value can be either null, to + clear the marker, or a DOM element, to set it. The DOM element + will be shown in the specified gutter next to the specified + line.
+ +
cm.clearGutter(gutterID: string)
+
Remove all gutter markers in + the gutter with the given ID.
+ +
doc.addLineClass(line: integer|LineHandle, where: string, class: string) → LineHandle
+
Set a CSS class name for the given line. line + can be a number or a line handle. where determines + to which element this class should be applied, can can be one + of "text" (the text element, which lies in front of + the selection), "background" (a background element + that will be behind the selection), "gutter" (the + line's gutter space), or "wrap" (the wrapper node + that wraps all of the line's elements, including gutter + elements). class should be the name of the class to + apply.
+ +
doc.removeLineClass(line: integer|LineHandle, where: string, class: string) → LineHandle
+
Remove a CSS class from a line. line can be a + line handle or number. where should be one + of "text", "background", + or "wrap" + (see addLineClass). class + can be left off to remove all classes for the specified node, or + be a string to remove only a specific class.
+ +
cm.lineInfo(line: integer|LineHandle) → object
+
Returns the line number, text content, and marker status of + the given line, which can be either a number or a line handle. + The returned object has the structure {line, handle, text, + gutterMarkers, textClass, bgClass, wrapClass, widgets}, + where gutterMarkers is an object mapping gutter IDs + to marker elements, and widgets is an array + of line widgets attached to this + line, and the various class properties refer to classes added + with addLineClass.
+ +
cm.addWidget(pos: {line, ch}, node: Element, scrollIntoView: boolean)
+
Puts node, which should be an absolutely + positioned DOM node, into the editor, positioned right below the + given {line, ch} position. + When scrollIntoView is true, the editor will ensure + that the entire node is visible (if possible). To remove the + widget again, simply use DOM methods (move it somewhere else, or + call removeChild on its parent).
+ +
doc.addLineWidget(line: integer|LineHandle, node: Element, ?options: object) → LineWidget
+
Adds a line widget, an element shown below a line, spanning + the whole of the editor's width, and moving the lines below it + downwards. line should be either an integer or a + line handle, and node should be a DOM node, which + will be displayed below the given line. options, + when given, should be an object that configures the behavior of + the widget. The following options are supported (all default to + false): +
+
coverGutter: boolean
+
Whether the widget should cover the gutter.
+
noHScroll: boolean
+
Whether the widget should stay fixed in the face of + horizontal scrolling.
+
above: boolean
+
Causes the widget to be placed above instead of below + the text of the line.
+
handleMouseEvents: boolean
+
Determines whether the editor will capture mouse and + drag events occurring in this widget. Default is false—the + events will be left alone for the default browser handler, + or specific handlers on the widget, to capture.
+
insertAt: integer
+
By default, the widget is added below other widgets for + the line. This option can be used to place it at a different + position (zero for the top, N to put it after the Nth other + widget). Note that this only has effect once, when the + widget is created. +
+ Note that the widget node will become a descendant of nodes with + CodeMirror-specific CSS classes, and those classes might in some + cases affect it. This method returns an object that represents + the widget placement. It'll have a line property + pointing at the line handle that it is associated with, and the following methods: +
+
clear()
Removes the widget.
+
changed()
Call + this if you made some change to the widget's DOM node that + might affect its height. It'll force CodeMirror to update + the height of the line that contains the widget.
+
+
+
+ +

Sizing, scrolling and positioning methods

+ +
+
cm.setSize(width: number|string, height: number|string)
+
Programmatically set the size of the editor (overriding the + applicable CSS + rules). width and height + can be either numbers (interpreted as pixels) or CSS units + ("100%", for example). You can + pass null for either of them to indicate that that + dimension should not be changed.
+ +
cm.scrollTo(x: number, y: number)
+
Scroll the editor to a given (pixel) position. Both + arguments may be left as null + or undefined to have no effect.
+
cm.getScrollInfo() → {left, top, width, height, clientWidth, clientHeight}
+
Get an {left, top, width, height, clientWidth, + clientHeight} object that represents the current scroll + position, the size of the scrollable area, and the size of the + visible area (minus scrollbars).
+
cm.scrollIntoView(what: {line, ch}|{left, top, right, bottom}|{from, to}|null, ?margin: number)
+
Scrolls the given position into view. what may + be null to scroll the cursor into view, + a {line, ch} position to scroll a character into + view, a {left, top, right, bottom} pixel range (in + editor-local coordinates), or a range {from, to} + containing either two character positions or two pixel squares. + The margin parameter is optional. When given, it + indicates the amount of vertical pixels around the given area + that should be made visible as well.
+ +
cm.cursorCoords(where: boolean|{line, ch}, mode: string) → {left, top, bottom}
+
Returns an {left, top, bottom} object + containing the coordinates of the cursor position. + If mode is "local", they will be + relative to the top-left corner of the editable document. If it + is "page" or not given, they are relative to the + top-left corner of the page. If mode + is "window", the coordinates are relative to the + top-left corner of the currently visible (scrolled) + window. where can be a boolean indicating whether + you want the start (true) or the end + (false) of the selection, or, if a {line, + ch} object is given, it specifies the precise position at + which you want to measure.
+
cm.charCoords(pos: {line, ch}, ?mode: string) → {left, right, top, bottom}
+
Returns the position and dimensions of an arbitrary + character. pos should be a {line, ch} + object. This differs from cursorCoords in that + it'll give the size of the whole character, rather than just the + position that the cursor would have when it would sit at that + position.
+
cm.coordsChar(object: {left, top}, ?mode: string) → {line, ch}
+
Given an {left, top} object, returns + the {line, ch} position that corresponds to it. The + optional mode parameter determines relative to what + the coordinates are interpreted. It may + be "window", "page" (the default), + or "local".
+
cm.lineAtHeight(height: number, ?mode: string) → number
+
Computes the line at the given pixel + height. mode can be one of the same strings + that coordsChar + accepts.
+
cm.heightAtLine(line: integer|LineHandle, ?mode: string) → number
+
Computes the height of the top of a line, in the coordinate + system specified by mode + (see coordsChar), which + defaults to "page". When a line below the bottom of + the document is specified, the returned value is the bottom of + the last line in the document.
+
cm.defaultTextHeight() → number
+
Returns the line height of the default font for the editor.
+
cm.defaultCharWidth() → number
+
Returns the pixel width of an 'x' in the default font for + the editor. (Note that for non-monospace fonts, this is mostly + useless, and even for monospace fonts, non-ascii characters + might have a different width).
+ +
cm.getViewport() → {from: number, to: number}
+
Returns a {from, to} object indicating the + start (inclusive) and end (exclusive) of the currently rendered + part of the document. In big documents, when most content is + scrolled out of view, CodeMirror will only render the visible + part, and a margin around it. See also + the viewportChange + event.
+ +
cm.refresh()
+
If your code does something to change the size of the editor + element (window resizes are already listened for), or unhides + it, you should probably follow up by calling this method to + ensure CodeMirror is still looking as intended. See also + the autorefresh addon.
+
+ +

Mode, state, and token-related methods

+ +

When writing language-aware functionality, it can often be + useful to hook into the knowledge that the CodeMirror language + mode has. See the section on modes for a + more detailed description of how these work.

+ +
+
doc.getMode() → object
+
Gets the (outer) mode object for the editor. Note that this + is distinct from getOption("mode"), which gives you + the mode specification, rather than the resolved, instantiated + mode object.
+ +
cm.getModeAt(pos: {line, ch}) → object
+
Gets the inner mode at a given position. This will return + the same as getMode for + simple modes, but will return an inner mode for nesting modes + (such as htmlmixed).
+ +
cm.getTokenAt(pos: {line, ch}, ?precise: boolean) → object
+
Retrieves information about the token the current mode found + before the given position (a {line, ch} object). The + returned object has the following properties: +
+
start
The character (on the given line) at which the token starts.
+
end
The character at which the token ends.
+
string
The token's string.
+
type
The token type the mode assigned + to the token, such as "keyword" + or "comment" (may also be null).
+
state
The mode's state at the end of this token.
+
+ If precise is true, the token will be guaranteed to be accurate based on recent edits. If false or + not specified, the token will use cached state information, which will be faster but might not be accurate if + edits were recently made and highlighting has not yet completed. +
+ +
cm.getLineTokens(line: integer, ?precise: boolean) → array<{start, end, string, type, state}>
+
This is similar + to getTokenAt, but + collects all tokens for a given line into an array. It is much + cheaper than repeatedly calling getTokenAt, which + re-parses the part of the line before the token for every call.
+ +
cm.getTokenTypeAt(pos: {line, ch}) → string
+
This is a (much) cheaper version + of getTokenAt useful for + when you just need the type of the token at a given position, + and no other information. Will return null for + unstyled tokens, and a string, potentially containing multiple + space-separated style names, otherwise.
+ +
cm.getHelpers(pos: {line, ch}, type: string) → array<helper>
+
Fetch the set of applicable helper values for the given + position. Helpers provide a way to look up functionality + appropriate for a mode. The type argument provides + the helper namespace (see + registerHelper), in + which the values will be looked up. When the mode itself has a + property that corresponds to the type, that + directly determines the keys that are used to look up the helper + values (it may be either a single string, or an array of + strings). Failing that, the mode's helperType + property and finally the mode's name are used.
+
For example, the JavaScript mode has a + property fold containing "brace". When + the brace-fold addon is loaded, that defines a + helper named brace in the fold + namespace. This is then used by + the foldcode addon to + figure out that it can use that folding function to fold + JavaScript code.
+
When any 'global' + helpers are defined for the given namespace, their predicates + are called on the current mode and editor, and all those that + declare they are applicable will also be added to the array that + is returned.
+ +
cm.getHelper(pos: {line, ch}, type: string) → helper
+
Returns the first applicable helper value. + See getHelpers.
+ +
cm.getStateAfter(?line: integer, ?precise: boolean) → object
+
Returns the mode's parser state, if any, at the end of the + given line number. If no line number is given, the state at the + end of the document is returned. This can be useful for storing + parsing errors in the state, or getting other kinds of + contextual information for a line. precise is defined + as in getTokenAt().
+
+ +

Miscellaneous methods

+ +
+
cm.operation(func: () → any) → any
+
CodeMirror internally buffers changes and only updates its + DOM structure after it has finished performing some operation. + If you need to perform a lot of operations on a CodeMirror + instance, you can call this method with a function argument. It + will call the function, buffering up all changes, and only doing + the expensive update after the function returns. This can be a + lot faster. The return value from this method will be the return + value of your function.
+ +
cm.indentLine(line: integer, ?dir: string|integer)
+
Adjust the indentation of the given line. The second + argument (which defaults to "smart") may be one of: +
+
"prev"
+
Base indentation on the indentation of the previous line.
+
"smart"
+
Use the mode's smart indentation if available, behave + like "prev" otherwise.
+
"add"
+
Increase the indentation of the line by + one indent unit.
+
"subtract"
+
Reduce the indentation of the line.
+
<integer>
+
Add (positive number) or reduce (negative number) the + indentation by the given amount of spaces.
+
+ +
cm.toggleOverwrite(?value: boolean)
+
Switches between overwrite and normal insert mode (when not + given an argument), or sets the overwrite mode to a specific + state (when given an argument).
+ +
cm.isReadOnly() → boolean
+
Tells you whether the editor's content can be edited by the + user.
+ +
doc.lineSeparator()
+
Returns the preferred line separator string for this + document, as per the option + by the same name. When that option is null, the + string "\n" is returned.
+ +
cm.execCommand(name: string)
+
Runs the command with the given name on the editor.
+ +
doc.posFromIndex(index: integer) → {line, ch}
+
Calculates and returns a {line, ch} object for a + zero-based index who's value is relative to the start of the + editor's text. If the index is out of range of the text then + the returned object is clipped to start or end of the text + respectively.
+
doc.indexFromPos(object: {line, ch}) → integer
+
The reverse of posFromIndex.
+ +
cm.focus()
+
Give the editor focus.
+ +
cm.getInputField() → Element
+
Returns the input field for the editor. Will be a textarea + or an editable div, depending on the value of + the inputStyle + option.
+
cm.getWrapperElement() → Element
+
Returns the DOM node that represents the editor, and + controls its size. Remove this from your tree to delete an + editor instance.
+
cm.getScrollerElement() → Element
+
Returns the DOM node that is responsible for the scrolling + of the editor.
+
cm.getGutterElement() → Element
+
Fetches the DOM node that contains the editor gutters.
+
+ +

Static properties

+

The CodeMirror object itself provides + several useful properties.

+ +
+
CodeMirror.version: string
+
It contains a string that indicates the version of the + library. This is a triple of + integers "major.minor.patch", + where patch is zero for releases, and something + else (usually one) for dev snapshots.
+ +
CodeMirror.fromTextArea(textArea: TextAreaElement, ?config: object)
+
+ The method provides another way to initialize an editor. It + takes a textarea DOM node as first argument and an optional + configuration object as second. It will replace the textarea + with a CodeMirror instance, and wire up the form of that + textarea (if any) to make sure the editor contents are put + into the textarea when the form is submitted. The text in the + textarea will provide the content for the editor. A CodeMirror + instance created this way has three additional methods: +
+
cm.save()
+
Copy the content of the editor into the textarea.
+ +
cm.toTextArea()
+
Remove the editor, and restore the original textarea (with + the editor's current content).
+ +
cm.getTextArea() → TextAreaElement
+
Returns the textarea that the instance was based on.
+
+
+ +
CodeMirror.defaults: object
+
An object containing default values for + all options. You can assign to its + properties to modify defaults (though this won't affect editors + that have already been created).
+ +
CodeMirror.defineExtension(name: string, value: any)
+
If you want to define extra methods in terms of the + CodeMirror API, it is possible to + use defineExtension. This will cause the given + value (usually a method) to be added to all CodeMirror instances + created from then on.
+ +
CodeMirror.defineDocExtension(name: string, value: any)
+
Like defineExtension, + but the method will be added to the interface + for Doc objects instead.
+ +
CodeMirror.defineOption(name: string, + default: any, updateFunc: function)
+
Similarly, defineOption can be used to define new options for + CodeMirror. The updateFunc will be called with the + editor instance and the new value when an editor is initialized, + and whenever the option is modified + through setOption.
+ +
CodeMirror.defineInitHook(func: function)
+
If your extension just needs to run some + code whenever a CodeMirror instance is initialized, + use CodeMirror.defineInitHook. Give it a function as + its only argument, and from then on, that function will be called + (with the instance as argument) whenever a new CodeMirror instance + is initialized.
+ +
CodeMirror.registerHelper(type: string, name: string, value: helper)
+
Registers a helper value with the given name in + the given namespace (type). This is used to define + functionality that may be looked up by mode. Will create (if it + doesn't already exist) a property on the CodeMirror + object for the given type, pointing to an object + that maps names to values. I.e. after + doing CodeMirror.registerHelper("hint", "foo", + myFoo), the value CodeMirror.hint.foo will + point to myFoo.
+ +
CodeMirror.registerGlobalHelper(type: string, name: string, predicate: fn(mode, CodeMirror), value: helper)
+
Acts + like registerHelper, + but also registers this helper as 'global', meaning that it will + be included by getHelpers + whenever the given predicate returns true when + called with the local mode and editor.
+ +
CodeMirror.Pos(line: integer, ?ch: integer)
+
A constructor for the {line, ch} objects that + are used to represent positions in editor documents.
+ +
CodeMirror.changeEnd(change: object) → {line, ch}
+
Utility function that computes an end position from a change + (an object with from, to, + and text properties, as passed to + various event handlers). The + returned position will be the end of the changed + range, after the change is applied.
+
+
+ +
+

Addons

+ +

The addon directory in the distribution contains a + number of reusable components that implement extra editor + functionality (on top of extension functions + like defineOption, defineExtension, + and registerHelper). In + brief, they are:

+ +
+
dialog/dialog.js
+
Provides a very simple way to query users for text input. + Adds the openDialog(template, callback, options) → + closeFunction method to CodeMirror instances, + which can be called with an HTML fragment or a detached DOM + node that provides the prompt (should include an input + or button tag), and a callback function that is called + when the user presses enter. It returns a function closeFunction + which, if called, will close the dialog immediately. + openDialog takes the following options: +
+
closeOnEnter:
+
If true, the dialog will be closed when the user presses + enter in the input. Defaults to true.
+
onKeyDown:
+
An event handler of the signature (event, value, closeFunction) + that will be called whenever keydown fires in the + dialog's input. If your callback returns true, + the dialog will not do any further processing of the event.
+
onKeyUp:
+
Same as onKeyDown but for the + keyup event.
+
onInput:
+
Same as onKeyDown but for the + input event.
+
onClose:
+
A callback of the signature (dialogInstance) + that will be called after the dialog has been closed and + removed from the DOM. No return value.
+
+ +

Also adds an openNotification(template, options) → + closeFunction function that simply shows an HTML + fragment as a notification at the top of the editor. It takes a + single option: duration, the amount of time after + which the notification will be automatically closed. If + duration is zero, the dialog will not be closed automatically.

+ +

Depends on addon/dialog/dialog.css.

+ +
search/searchcursor.js
+
Adds the getSearchCursor(query, start, caseFold) → + cursor method to CodeMirror instances, which can be used + to implement search/replace functionality. query + can be a regular expression or a string (only strings will match + across lines—if they contain newlines). start + provides the starting position of the search. It can be + a {line, ch} object, or can be left off to default + to the start of the document. caseFold is only + relevant when matching a string. It will cause the search to be + case-insensitive. A search cursor has the following methods: +
+
findNext() → boolean
+
findPrevious() → boolean
+
Search forward or backward from the current position. + The return value indicates whether a match was found. If + matching a regular expression, the return value will be the + array returned by the match method, in case you + want to extract matched groups.
+
from() → {line, ch}
+
to() → {line, ch}
+
These are only valid when the last call + to findNext or findPrevious did + not return false. They will return {line, ch} + objects pointing at the start and end of the match.
+
replace(text: string, ?origin: string)
+
Replaces the currently found match with the given text + and adjusts the cursor position to reflect the + replacement.
+
+ + +
Implements the search commands. CodeMirror has keys bound to + these by default, but will not do anything with them unless an + implementation is provided. Depends + on searchcursor.js, and will make use + of openDialog when + available to make prompting for search queries less ugly.
+ +
search/jump-to-line.js
+
Implements a jumpToLine command and binding Alt-G to it. + Accepts linenumber, +/-linenumber, line:char, + scroll% and :linenumber formats. + This will make use of openDialog + when available to make prompting for line number neater.
+ +
search/matchesonscrollbar.js
+
Adds a showMatchesOnScrollbar method to editor + instances, which should be given a query (string or regular + expression), optionally a case-fold flag (only applicable for + strings), and optionally a class name (defaults + to CodeMirror-search-match) as arguments. When + called, matches of the given query will be displayed on the + editor's vertical scrollbar. The method returns an object with + a clear method that can be called to remove the + matches. Depends on + the annotatescrollbar + addon, and + the matchesonscrollbar.css + file provides a default (transparent yellowish) definition of + the CSS class applied to the matches. Note that the matches are + only perfectly aligned if your scrollbar does not have buttons + at the top and bottom. You can use + the simplescrollbar + addon to make sure of this. If this addon is loaded, + the search addon will + automatically use it.
+ +
edit/matchbrackets.js
+
Defines an option matchBrackets which, when set + to true, causes matching brackets to be highlighted whenever the + cursor is next to them. It also adds a + method matchBrackets that forces this to happen + once, and a method findMatchingBracket that can be + used to run the bracket-finding algorithm that this uses + internally.
+ +
edit/closebrackets.js
+
Defines an option autoCloseBrackets that will + auto-close brackets and quotes when typed. By default, it'll + auto-close ()[]{}''"", but you can pass it a string + similar to that (containing pairs of matching characters), or an + object with pairs and + optionally explode properties to customize + it. explode should be a similar string that gives + the pairs of characters that, when enter is pressed between + them, should have the second character also moved to its own + line. Demo here.
+ +
edit/matchtags.js
+
Defines an option matchTags that, when enabled, + will cause the tags around the cursor to be highlighted (using + the CodeMirror-matchingtag class). Also + defines + a command toMatchingTag, + which you can bind a key to in order to jump to the tag matching + the one under the cursor. Depends on + the addon/fold/xml-fold.js + addon. Demo here.
+ +
edit/trailingspace.js
+
Adds an option showTrailingSpace which, when + enabled, adds the CSS class cm-trailingspace to + stretches of whitespace at the end of lines. + The demo has a nice + squiggly underline style for this class.
+ +
edit/closetag.js
+
Defines an autoCloseTags option that will + auto-close XML tags when '>' or '/' + is typed, and + a closeTag command that + closes the nearest open tag. Depends on + the fold/xml-fold.js addon. See + the demo.
+ +
edit/continuelist.js
+
Markdown specific. Defines + a "newlineAndIndentContinueMarkdownList" command + that can be bound to enter to automatically + insert the leading characters for continuing a list. See + the Markdown mode + demo.
+ +
comment/comment.js
+
Addon for commenting and uncommenting code. Adds four + methods to CodeMirror instances: +
+
toggleComment(from: {line, ch}, to: {line, ch}, ?options: object)
+
Tries to uncomment the current selection, and if that + fails, line-comments it.
+
lineComment(from: {line, ch}, to: {line, ch}, ?options: object)
+
Set the lines in the given range to be line comments. Will + fall back to blockComment when no line comment + style is defined for the mode.
+
blockComment(from: {line, ch}, to: {line, ch}, ?options: object)
+
Wrap the code in the given range in a block comment. Will + fall back to lineComment when no block comment + style is defined for the mode.
+
uncomment(from: {line, ch}, to: {line, ch}, ?options: object) → boolean
+
Try to uncomment the given range. + Returns true if a comment range was found and + removed, false otherwise.
+
+ The options object accepted by these methods may + have the following properties: +
+
blockCommentStart, blockCommentEnd, blockCommentLead, lineComment: string
+
Override the comment string + properties of the mode with custom comment strings.
+
padding: string
+
A string that will be inserted after opening and leading + markers, and before closing comment markers. Defaults to a + single space.
+
commentBlankLines: boolean
+
Whether, when adding line comments, to also comment lines + that contain only whitespace.
+
indent: boolean
+
When adding line comments and this is turned on, it will + align the comment block to the current indentation of the + first line of the block.
+
fullLines: boolean
+
When block commenting, this controls whether the whole + lines are indented, or only the precise range that is given. + Defaults to true.
+
+ The addon also defines + a toggleComment command, + which is a shorthand command for calling + toggleComment with no options.
+ +
fold/foldcode.js
+
Helps with code folding. Adds a foldCode method + to editor instances, which will try to do a code fold starting + at the given line, or unfold the fold that is already present. + The method takes as first argument the position that should be + folded (may be a line number or + a Pos), and as second optional + argument either a range-finder function, or an options object, + supporting the following properties: +
+
rangeFinder: fn(CodeMirror, Pos)
+
The function that is used to find + foldable ranges. If this is not directly passed, it will + default to CodeMirror.fold.auto, which + uses getHelpers with + a "fold" type to find folding functions + appropriate for the local mode. There are files in + the addon/fold/ + directory providing CodeMirror.fold.brace, which + finds blocks in brace languages (JavaScript, C, Java, + etc), CodeMirror.fold.indent, for languages where + indentation determines block structure (Python, Haskell), + and CodeMirror.fold.xml, for XML-style languages, + and CodeMirror.fold.comment, for folding comment + blocks.
+
widget: string|Element
+
The widget to show for folded ranges. Can be either a + string, in which case it'll become a span with + class CodeMirror-foldmarker, or a DOM node.
+
scanUp: boolean
+
When true (default is false), the addon will try to find + foldable ranges on the lines above the current one if there + isn't an eligible one on the given line.
+
minFoldSize: integer
+
The minimum amount of lines that a fold should span to be + accepted. Defaults to 0, which also allows single-line + folds.
+
+ See the demo for an + example.
+ +
fold/foldgutter.js
+
Provides an option foldGutter, which can be + used to create a gutter with markers indicating the blocks that + can be folded. Create a gutter using + the gutters option, + giving it the class CodeMirror-foldgutter or + something else if you configure the addon to use a different + class, and this addon will show markers next to folded and + foldable blocks, and handle clicks in this gutter. Note that + CSS styles should be applied to make the gutter, and the fold + markers within it, visible. A default set of CSS styles are + available in: + + addon/fold/foldgutter.css + . + The option + can be either set to true, or an object containing + the following optional option fields: +
+
gutter: string
+
The CSS class of the gutter. Defaults + to "CodeMirror-foldgutter". You will have to + style this yourself to give it a width (and possibly a + background). See the default gutter style rules above.
+
indicatorOpen: string | Element
+
A CSS class or DOM element to be used as the marker for + open, foldable blocks. Defaults + to "CodeMirror-foldgutter-open".
+
indicatorFolded: string | Element
+
A CSS class or DOM element to be used as the marker for + folded blocks. Defaults to "CodeMirror-foldgutter-folded".
+
rangeFinder: fn(CodeMirror, Pos)
+
The range-finder function to use when determining whether + something can be folded. When not + given, CodeMirror.fold.auto + will be used as default.
+
+ The foldOptions editor option can be set to an + object to provide an editor-wide default configuration. + Demo here.
+ +
runmode/runmode.js
+
Can be used to run a CodeMirror mode over text without + actually opening an editor instance. + See the demo for an example. + There are alternate versions of the file available for + running stand-alone + (without including all of CodeMirror) and + for running under + node.js (see bin/source-highlight for an example of using the latter).
+ +
runmode/colorize.js
+
Provides a convenient way to syntax-highlight code snippets + in a webpage. Depends on + the runmode addon (or + its standalone variant). Provides + a CodeMirror.colorize function that can be called + with an array (or other array-ish collection) of DOM nodes that + represent the code snippets. By default, it'll get + all pre tags. Will read the data-lang + attribute of these nodes to figure out their language, and + syntax-color their content using the relevant CodeMirror mode + (you'll have to load the scripts for the relevant modes + yourself). A second argument may be provided to give a default + mode, used when no language attribute is found for a node. Used + in this manual to highlight example code.
+ +
mode/overlay.js
+
Mode combinator that can be used to extend a mode with an + 'overlay' — a secondary mode is run over the stream, along with + the base mode, and can color specific pieces of text without + interfering with the base mode. + Defines CodeMirror.overlayMode, which is used to + create such a mode. See this + demo for a detailed example.
+ +
mode/multiplex.js
+
Mode combinator that can be used to easily 'multiplex' + between several modes. + Defines CodeMirror.multiplexingMode which, when + given as first argument a mode object, and as other arguments + any number of {open, close, mode [, delimStyle, innerStyle, parseDelimiters]} + objects, will return a mode object that starts parsing using the + mode passed as first argument, but will switch to another mode + as soon as it encounters a string that occurs in one of + the open fields of the passed objects. When in a + sub-mode, it will go back to the top mode again when + the close string is encountered. + Pass "\n" for open or close + if you want to switch on a blank line. +
  • When delimStyle is specified, it will be the token + style returned for the delimiter tokens (as well as + [delimStyle]-open on the opening token and + [delimStyle]-close on the closing token).
  • +
  • When innerStyle is specified, it will be the token + style added for each inner mode token.
  • +
  • When parseDelimiters is true, the content of + the delimiters will also be passed to the inner mode. + (And delimStyle is ignored.)
The outer + mode will not see the content between the delimiters. + See this demo for an + example.
+ +
hint/show-hint.js
+
Provides a framework for showing autocompletion hints. + Defines editor.showHint, which takes an optional + options object, and pops up a widget that allows the user to + select a completion. Finding hints is done with a hinting + functions (the hint option), which is a function + that take an editor instance and options object, and return + a {list, from, to} object, where list + is an array of strings or objects (the completions), + and from and to give the start and end + of the token that is being completed as {line, ch} + objects. An optional selectedHint property (an + integer) can be added to the completion object to control the + initially selected hint.
+
If no hinting function is given, the addon will + use CodeMirror.hint.auto, which + calls getHelpers with + the "hint" type to find applicable hinting + functions, and tries them one by one. If that fails, it looks + for a "hintWords" helper to fetch a list of + completable words for the mode, and + uses CodeMirror.hint.fromList to complete from + those.
+
When completions aren't simple strings, they should be + objects with the following properties: +
+
text: string
+
The completion text. This is the only required + property.
+
displayText: string
+
The text that should be displayed in the menu.
+
className: string
+
A CSS class name to apply to the completion's line in the + menu.
+
render: fn(Element, self, data)
+
A method used to create the DOM structure for showing the + completion by appending it to its first argument.
+
hint: fn(CodeMirror, self, data)
+
A method used to actually apply the completion, instead of + the default behavior.
+
from: {line, ch}
+
Optional from position that will be used by pick() instead + of the global one passed with the full list of completions.
+
to: {line, ch}
+
Optional to position that will be used by pick() instead + of the global one passed with the full list of completions.
+
+ The plugin understands the following options (the options object + will also be passed along to the hinting function, which may + understand additional options): +
+
hint: function
+
A hinting function, as specified above. It is possible to + set the async property on a hinting function to + true, in which case it will be called with + arguments (cm, callback, ?options), and the + completion interface will only be popped up when the hinting + function calls the callback, passing it the object holding the + completions. By default, hinting only works when there is no + selection. You can give a hinting function + a supportsSelection property with a truthy value + to indicate that it supports selections.
+
completeSingle: boolean
+
Determines whether, when only a single completion is + available, it is completed without showing the dialog. + Defaults to true.
+
alignWithWord: boolean
+
Whether the pop-up should be horizontally aligned with the + start of the word (true, default), or with the cursor (false).
+
closeOnUnfocus: boolean
+
When enabled (which is the default), the pop-up will close + when the editor is unfocused.
+
customKeys: keymap
+
Allows you to provide a custom key map of keys to be active + when the pop-up is active. The handlers will be called with an + extra argument, a handle to the completion menu, which + has moveFocus(n), setFocus(n), pick(), + and close() methods (see the source for details), + that can be used to change the focused element, pick the + current element or close the menu. Additionally menuSize() + can give you access to the size of the current dropdown menu, + length give you the number of available completions, and + data give you full access to the completion returned by the + hinting function.
+
extraKeys: keymap
+
Like customKeys above, but the bindings will + be added to the set of default bindings, instead of replacing + them.
+
+ The following events will be fired on the completions object + during completion: +
+
"shown" ()
+
Fired when the pop-up is shown.
+
"select" (completion, Element)
+
Fired when a completion is selected. Passed the completion + value (string or object) and the DOM node that represents it + in the menu.
+
"pick" (completion)
+
Fired when a completion is picked. Passed the completion value + (string or object).
+
"close" ()
+
Fired when the completion is finished.
+
+ This addon depends on styles + from addon/hint/show-hint.css. Check + out the demo for an + example.
+ +
hint/javascript-hint.js
+
Defines a simple hinting function for JavaScript + (CodeMirror.hint.javascript) and CoffeeScript + (CodeMirror.hint.coffeescript) code. This will + simply use the JavaScript environment that the editor runs in as + a source of information about objects and their properties.
+ +
hint/xml-hint.js
+
Defines CodeMirror.hint.xml, which produces + hints for XML tagnames, attribute names, and attribute values, + guided by a schemaInfo option (a property of the + second argument passed to the hinting function, or the third + argument passed to CodeMirror.showHint).
The + schema info should be an object mapping tag names to information + about these tags, with optionally a "!top" property + containing a list of the names of valid top-level tags. The + values of the properties should be objects with optional + properties children (an array of valid child + element names, omit to simply allow all tags to appear) + and attrs (an object mapping attribute names + to null for free-form attributes, and an array of + valid values for restricted + attributes). Demo + here.
+ +
hint/html-hint.js
+
Provides schema info to + the xml-hint addon for HTML + documents. Defines a schema + object CodeMirror.htmlSchema that you can pass to + as a schemaInfo option, and + a CodeMirror.hint.html hinting function that + automatically calls CodeMirror.hint.xml with this + schema data. See + the demo.
+ +
hint/css-hint.js
+
A hinting function for CSS, SCSS, or LESS code. + Defines CodeMirror.hint.css.
+ +
hint/anyword-hint.js
+
A very simple hinting function + (CodeMirror.hint.anyword) that simply looks for + words in the nearby code and completes to those. Takes two + optional options, word, a regular expression that + matches words (sequences of one or more character), + and range, which defines how many lines the addon + should scan when completing (defaults to 500).
+ +
hint/sql-hint.js
+
A simple SQL hinter. Defines CodeMirror.hint.sql. + Takes two optional options, tables, a object with + table names as keys and array of respective column names as values, + and defaultTable, a string corresponding to a + table name in tables for autocompletion.
+ +
search/match-highlighter.js
+
Adds a highlightSelectionMatches option that + can be enabled to highlight all instances of a currently + selected word. Can be set either to true or to an object + containing the following options: minChars, for the + minimum amount of selected characters that triggers a highlight + (default 2), style, for the style to be used to + highlight the matches (default "matchhighlight", + which will correspond to CSS + class cm-matchhighlight), + and showToken which can be set to true + or to a regexp matching the characters that make up a word. When + enabled, it causes the current word to be highlighted when + nothing is selected (defaults to off). + Demo here.
+ +
lint/lint.js
+
Defines an interface component for showing linting warnings, + with pluggable warning sources + (see html-lint.js, + json-lint.js, + javascript-lint.js, + coffeescript-lint.js, + and css-lint.js + in the same directory). Defines a lint option that + can be set to an annotation source (for + example CodeMirror.lint.javascript), to an options + object (in which case the getAnnotations field is + used as annotation source), or simply to true. When + no annotation source is + specified, getHelper with + type "lint" is used to find an annotation function. + An annotation source function should, when given a document + string, an options object, and an editor instance, return an + array of {message, severity, from, to} objects + representing problems. When the function has + an async property with a truthy value, it will be + called with an additional second argument, which is a callback + to pass the array to. By default, the linter will run + (debounced) whenever the document is changed. You can pass + a lintOnChange: false option to disable that. + Depends on addon/lint/lint.css. A demo can be + found here.
+ +
selection/mark-selection.js
+
Causes the selected text to be marked with the CSS class + CodeMirror-selectedtext when the styleSelectedText option + is enabled. Useful to change the colour of the selection (in addition to the background), + like in this demo.
+ +
selection/active-line.js
+
Defines a styleActiveLine option that, when enabled, + gives the wrapper of the active line the class CodeMirror-activeline, + adds a background with the class CodeMirror-activeline-background, + and adds the class CodeMirror-activeline-gutter to the + line's gutter space is enabled. See the + demo.
+ +
selection/selection-pointer.js
+
Defines a selectionPointer option which you can + use to control the mouse cursor appearance when hovering over + the selection. It can be set to a string, + like "pointer", or to true, in which case + the "default" (arrow) cursor will be used. You can + see a demo here.
+ +
mode/loadmode.js
+
Defines a CodeMirror.requireMode(modename, + callback) function that will try to load a given mode and + call the callback when it succeeded. You'll have to + set CodeMirror.modeURL to a string that mode paths + can be constructed from, for + example "mode/%N/%N.js"—the %N's will + be replaced with the mode name. Also + defines CodeMirror.autoLoadMode(instance, mode), + which will ensure the given mode is loaded and cause the given + editor instance to refresh its mode when the loading + succeeded. See the demo.
+ +
mode/meta.js
+
Provides meta-information about all the modes in the + distribution in a single file. + Defines CodeMirror.modeInfo, an array of objects + with {name, mime, mode} properties, + where name is the human-readable + name, mime the MIME type, and mode the + name of the mode file that defines this MIME. There are optional + properties mimes, which holds an array of MIME + types for modes with multiple MIMEs associated, + and ext, which holds an array of file extensions + associated with this mode. Four convenience + functions, CodeMirror.findModeByMIME, + CodeMirror.findModeByExtension, + CodeMirror.findModeByFileName + and CodeMirror.findModeByName are provided, which + return such an object given a MIME, extension, file name or mode name + string. Note that, for historical reasons, this file resides in the + top-level mode directory, not + under addon. Demo.
+ +
comment/continuecomment.js
+
Adds a continueComments option, which sets whether the + editor will make the next line continue a comment when you press Enter + inside a comment block. Can be set to a boolean to enable/disable this + functionality. Set to a string, it will continue comments using a custom + shortcut. Set to an object, it will use the key property for + a custom shortcut and the boolean continueLineComment + property to determine whether single-line comments should be continued + (defaulting to true).
+ +
display/placeholder.js
+
Adds a placeholder option that can be used to + make content appear in the editor when it is empty and not + focused. It can hold either a string or a DOM node. Also gives + the editor a CodeMirror-empty CSS class whenever it + doesn't contain any text. + See the demo.
+ +
display/fullscreen.js
+
Defines an option fullScreen that, when set + to true, will make the editor full-screen (as in, + taking up the whole browser window). Depends + on fullscreen.css. Demo + here.
+ +
display/autorefresh.js
+
This addon can be useful when initializing an editor in a + hidden DOM node, in cases where it is difficult to + call refresh when the editor + becomes visible. It defines an option autoRefresh + which you can set to true to ensure that, if the editor wasn't + visible on initialization, it will be refreshed the first time + it becomes visible. This is done by polling every 250 + milliseconds (you can pass a value like {delay: + 500} as the option value to configure this). Note that + this addon will only refresh the editor once when it + first becomes visible, and won't take care of further restyling + and resizing.
+ +
scroll/simplescrollbars.js
+
Defines two additional scrollbar + models, "simple" and "overlay" + (see demo) that can + be selected with + the scrollbarStyle + option. Depends + on simplescrollbars.css, + which can be further overridden to style your own + scrollbars.
+ +
scroll/annotatescrollbar.js
+
Provides functionality for showing markers on the scrollbar + to call out certain parts of the document. Adds a + method annotateScrollbar to editor instances that + can be called, with a CSS class name as argument, to create a + set of annotations. The method returns an object + whose update method can be called with an array + of {from: Pos, to: Pos} objects marking the ranges + to be highlighted. To detach the annotations, call the + object's clear method.
+ +
display/rulers.js
+
Adds a rulers option, which can be used to show + one or more vertical rulers in the editor. The option, if + defined, should be given an array of {column [, className, + color, lineStyle, width]} objects or numbers (which + indicate a column). The ruler will be displayed at the column + indicated by the number or the column property. + The className property can be used to assign a + custom style to a ruler. Demo + here.
+ +
display/panel.js
+
Defines an addPanel method for CodeMirror + instances, which places a DOM node above or below an editor, and + shrinks the editor to make room for the node. The method takes + as first argument as DOM node, and as second an optional options + object. The Panel object returned by this method + has a clear method that is used to remove the + panel, and a changed method that can be used to + notify the addon when the size of the panel's DOM node has + changed.
+ The method accepts the following options: +
+
position : string
+
Controls the position of the newly added panel. The + following values are recognized: +
+
top (default)
+
Adds the panel at the very top.
+
after-top
+
Adds the panel at the bottom of the top panels.
+
bottom
+
Adds the panel at the very bottom.
+
before-bottom
+
Adds the panel at the top of the bottom panels.
+
+
+
before : Panel
+
The new panel will be added before the given panel.
+
after : Panel
+
The new panel will be added after the given panel.
+
replace : Panel
+
The new panel will replace the given panel.
+
+ When using the after, before or replace options, + if the panel doesn't exists or has been removed, + the value of the position option will be used as a fallback. +
+ A demo of the addon is available here. +
+ +
wrap/hardwrap.js
+
Addon to perform hard line wrapping/breaking for paragraphs + of text. Adds these methods to editor instances: +
+
wrapParagraph(?pos: {line, ch}, ?options: object)
+
Wraps the paragraph at the given position. + If pos is not given, it defaults to the cursor + position.
+
wrapRange(from: {line, ch}, to: {line, ch}, ?options: object)
+
Wraps the given range as one big paragraph.
+
wrapParagraphsInRange(from: {line, ch}, to: {line, ch}, ?options: object)
+
Wraps the paragraphs in (and overlapping with) the + given range individually.
+
+ The following options are recognized: +
+
paragraphStart, paragraphEnd: RegExp
+
Blank lines are always considered paragraph boundaries. + These options can be used to specify a pattern that causes + lines to be considered the start or end of a paragraph.
+
column: number
+
The column to wrap at. Defaults to 80.
+
wrapOn: RegExp
+
A regular expression that matches only those + two-character strings that allow wrapping. By default, the + addon wraps on whitespace and after dash characters.
+
killTrailingSpace: boolean
+
Whether trailing space caused by wrapping should be + preserved, or deleted. Defaults to true.
+
+ A demo of the addon is available here. +
+ +
merge/merge.js
+
Implements an interface for merging changes, using either a + 2-way or a 3-way view. The CodeMirror.MergeView + constructor takes arguments similar to + the CodeMirror + constructor, first a node to append the interface to, and then + an options object. Options are passed through to the editors + inside the view. These extra options are recognized: +
+
origLeft and origRight: string
+
If given these provide original versions of the + document, which will be shown to the left and right of the + editor in non-editable CodeMirror instances. The merge + interface will highlight changes between the editable + document and the original(s). To create a 2-way (as opposed + to 3-way) merge view, provide only one of them.
+
revertButtons: boolean
+
Determines whether buttons that allow the user to revert + changes are shown. Defaults to true.
+
connect: string
+
Sets the style used to connect changed chunks of code. + By default, connectors are drawn. When this is set + to "align", the smaller chunk is padded to + align with the bigger chunk instead.
+
collapseIdentical: boolean|number
+
When true (default is false), stretches of unchanged + text will be collapsed. When a number is given, this + indicates the amount of lines to leave visible around such + stretches (which defaults to 2).
+
allowEditingOriginals: boolean
+
Determines whether the original editor allows editing. + Defaults to false.
+
showDifferences: boolean
+
When true (the default), changed pieces of text are + highlighted.
+
+ The addon also defines commands "goNextDiff" + and "goPrevDiff" to quickly jump to the next + changed chunk. Demo + here.
+ +
tern/tern.js
+
Provides integration with + the Tern JavaScript analysis + engine, for completion, definition finding, and minor + refactoring help. See the demo + for a very simple integration. For more involved scenarios, see + the comments at the top of + the addon and the + implementation of the + (multi-file) demonstration + on the Tern website.
+
+
+ +
+

Writing CodeMirror Modes

+ +

Modes typically consist of a single JavaScript file. This file + defines, in the simplest case, a lexer (tokenizer) for your + language—a function that takes a character stream as input, + advances it past a token, and returns a style for that token. More + advanced modes can also handle indentation for the language.

+ +

This section describes the low-level mode interface. Many modes + are written directly against this, since it offers a lot of + control, but for a quick mode definition, you might want to use + the simple mode addon.

+ +

The mode script should + call CodeMirror.defineMode to + register itself with CodeMirror. This function takes two + arguments. The first should be the name of the mode, for which you + should use a lowercase string, preferably one that is also the + name of the files that define the mode (i.e. "xml" is + defined in xml.js). The second argument should be a + function that, given a CodeMirror configuration object (the thing + passed to the CodeMirror function) and an optional + mode configuration object (as in + the mode option), returns + a mode object.

+ +

Typically, you should use this second argument + to defineMode as your module scope function (modes + should not leak anything into the global scope!), i.e. write your + whole mode inside this function.

+ +

The main responsibility of a mode script is parsing + the content of the editor. Depending on the language and the + amount of functionality desired, this can be done in really easy + or extremely complicated ways. Some parsers can be stateless, + meaning that they look at one element (token) of the code + at a time, with no memory of what came before. Most, however, will + need to remember something. This is done by using a state + object, which is an object that is always passed when + reading a token, and which can be mutated by the tokenizer.

+ +

Modes that use a state must define + a startState method on their mode + object. This is a function of no arguments that produces a state + object to be used at the start of a document.

+ +

The most important part of a mode object is + its token(stream, state) method. All + modes must define this method. It should read one token from the + stream it is given as an argument, optionally update its state, + and return a style string, or null for tokens that do + not have to be styled. For your styles, you are encouraged to use + the 'standard' names defined in the themes (without + the cm- prefix). If that fails, it is also possible + to come up with your own and write your own CSS theme file.

+ +

A typical token string would + be "variable" or "comment". Multiple + styles can be returned (separated by spaces), for + example "string error" for a thing that looks like a + string but is invalid somehow (say, missing its closing quote). + When a style is prefixed by "line-" + or "line-background-", the style will be applied to + the whole line, analogous to what + the addLineClass method + does—styling the "text" in the simple case, and + the "background" element + when "line-background-" is prefixed.

+ +

The stream object that's passed + to token encapsulates a line of code (tokens may + never span lines) and our current position in that line. It has + the following API:

+ +
+
eol() → boolean
+
Returns true only if the stream is at the end of the + line.
+
sol() → boolean
+
Returns true only if the stream is at the start of the + line.
+ +
peek() → string
+
Returns the next character in the stream without advancing + it. Will return a null at the end of the + line.
+
next() → string
+
Returns the next character in the stream and advances it. + Also returns null when no more characters are + available.
+ +
eat(match: string|regexp|function(char: string) → boolean) → string
+
match can be a character, a regular expression, + or a function that takes a character and returns a boolean. If + the next character in the stream 'matches' the given argument, + it is consumed and returned. Otherwise, undefined + is returned.
+
eatWhile(match: string|regexp|function(char: string) → boolean) → boolean
+
Repeatedly calls eat with the given argument, + until it fails. Returns true if any characters were eaten.
+
eatSpace() → boolean
+
Shortcut for eatWhile when matching + white-space.
+
skipToEnd()
+
Moves the position to the end of the line.
+
skipTo(ch: string) → boolean
+
Skips to the next occurrence of the given character, if + found on the current line (doesn't advance the stream if the + character does not occur on the line). Returns true if the + character was found.
+
match(pattern: string, ?consume: boolean, ?caseFold: boolean) → boolean
+
match(pattern: regexp, ?consume: boolean) → array<string>
+
Act like a + multi-character eat—if consume is true + or not given—or a look-ahead that doesn't update the stream + position—if it is false. pattern can be either a + string or a regular expression starting with ^. + When it is a string, caseFold can be set to true to + make the match case-insensitive. When successfully matching a + regular expression, the returned value will be the array + returned by match, in case you need to extract + matched groups.
+ +
backUp(n: integer)
+
Backs up the stream n characters. Backing it up + further than the start of the current token will cause things to + break, so be careful.
+
column() → integer
+
Returns the column (taking into account tabs) at which the + current token starts.
+
indentation() → integer
+
Tells you how far the current line has been indented, in + spaces. Corrects for tab characters.
+ +
current() → string
+
Get the string between the start of the current token and + the current stream position.
+
+ +

By default, blank lines are simply skipped when + tokenizing a document. For languages that have significant blank + lines, you can define + a blankLine(state) method on your + mode that will get called whenever a blank line is passed over, so + that it can update the parser state.

+ +

Because state object are mutated, and CodeMirror + needs to keep valid versions of a state around so that it can + restart a parse at any line, copies must be made of state objects. + The default algorithm used is that a new state object is created, + which gets all the properties of the old object. Any properties + which hold arrays get a copy of these arrays (since arrays tend to + be used as mutable stacks). When this is not correct, for example + because a mode mutates non-array properties of its state object, a + mode object should define + a copyState method, which is given a + state and should return a safe copy of that state.

+ +

If you want your mode to provide smart indentation + (through the indentLine + method and the indentAuto + and newlineAndIndent commands, to which keys can be + bound), you must define + an indent(state, textAfter) method + on your mode object.

+ +

The indentation method should inspect the given state object, + and optionally the textAfter string, which contains + the text on the line that is being indented, and return an + integer, the amount of spaces to indent. It should usually take + the indentUnit + option into account. An indentation method may + return CodeMirror.Pass to indicate that it + could not come up with a precise indentation.

+ +

To work well with + the commenting addon, a mode may + define lineComment (string that + starts a line + comment), blockCommentStart, blockCommentEnd + (strings that start and end block comments), + and blockCommentLead (a string to put at the start of + continued lines in a block comment). All of these are + optional.

+ +

Finally, a mode may define either + an electricChars or an electricInput + property, which are used to automatically reindent the line when + certain patterns are typed and + the electricChars + option is enabled. electricChars may be a string, and + will trigger a reindent whenever one of the characters in that + string are typed. Often, it is more appropriate to + use electricInput, which should hold a regular + expression, and will trigger indentation when the part of the + line before the cursor matches the expression. It should + usually end with a $ character, so that it only + matches when the indentation-changing pattern was just typed, not when something was + typed after the pattern.

+ +

So, to summarize, a mode must provide + a token method, and it may + provide startState, copyState, + and indent methods. For an example of a trivial mode, + see the diff mode, for a more + involved example, see the C-like + mode.

+ +

Sometimes, it is useful for modes to nest—to have one + mode delegate work to another mode. An example of this kind of + mode is the mixed-mode HTML + mode. To implement such nesting, it is usually necessary to + create mode objects and copy states yourself. To create a mode + object, there are CodeMirror.getMode(options, + parserConfig), where the first argument is a configuration + object as passed to the mode constructor function, and the second + argument is a mode specification as in + the mode option. To copy a + state object, call CodeMirror.copyState(mode, state), + where mode is the mode that created the given + state.

+ +

In a nested mode, it is recommended to add an + extra method, innerMode which, given + a state object, returns a {state, mode} object with + the inner mode and its state for the current position. These are + used by utility scripts such as the tag + closer to get context information. Use + the CodeMirror.innerMode helper function to, starting + from a mode and a state, recursively walk down to the innermost + mode and state.

+ +

To make indentation work properly in a nested parser, it is + advisable to give the startState method of modes that + are intended to be nested an optional argument that provides the + base indentation for the block of code. The JavaScript and CSS + parser do this, for example, to allow JavaScript and CSS code + inside the mixed-mode HTML mode to be properly indented.

+ +

It is possible, and encouraged, to associate + your mode, or a certain configuration of your mode, with + a MIME type. For + example, the JavaScript mode associates itself + with text/javascript, and its JSON variant + with application/json. To do this, + call CodeMirror.defineMIME(mime, + modeSpec), where modeSpec can be a string or + object specifying a mode, as in + the mode option.

+ +

If a mode specification wants to add some properties to the + resulting mode object, typically for use + with getHelpers, it may + contain a modeProps property, which holds an object. + This object's properties will be copied to the actual mode + object.

+ +

Sometimes, it is useful to add or override mode + object properties from external code. + The CodeMirror.extendMode function + can be used to add properties to mode objects produced for a + specific mode. Its first argument is the name of the mode, its + second an object that specifies the properties that should be + added. This is mostly useful to add utilities that can later be + looked up through getMode.

+
+ +
+

VIM Mode API

+ +

CodeMirror has a robust VIM mode that attempts to faithfully + emulate VIM's most useful features. It can be enabled by + including keymap/vim.js + and setting the keyMap option to + "vim".

+ +

Configuration

+ +

VIM mode accepts configuration options for customizing + behavior at run time. These methods can be called at any time + and will affect all existing CodeMirror instances unless + specified otherwise. The methods are exposed on the + CodeMirror.Vim object.

+ +
+
setOption(name: string, value: any, ?cm: CodeMirror, ?cfg: object)
+
Sets the value of a VIM option. name should + be the name of an option. If cfg.scope is not set + and cm is provided, then sets the global and + instance values of the option. Otherwise, sets either the + global or instance value of the option depending on whether + cfg.scope is global or + local.
+
getOption(name: string, ?cm: CodeMirror: ?cfg: object)
+
Gets the current value of a VIM option. If + cfg.scope is not set and cm is + provided, then gets the instance value of the option, falling + back to the global value if not set. If cfg.scope is provided, then gets the global or + local value without checking the other.
+ +
map(lhs: string, rhs: string, ?context: string)
+
Maps a key sequence to another key sequence. Implements + VIM's :map command. To map ; to : in VIM would be + :map ; :. That would translate to + CodeMirror.Vim.map(';', ':');. + The context can be normal, + visual, or insert, which correspond + to :nmap, :vmap, and + :imap + respectively.
+ +
mapCommand(keys: string, type: string, name: string, ?args: object, ?extra: object)
+
Maps a key sequence to a motion, + operator, or action type command. + The args object is passed through to the command when it is + invoked by the provided key sequence. + extras.context can be normal, + visual, or insert, to map the key + sequence only in the corresponding mode. + extras.isEdit is applicable only to actions, + determining whether it is recorded for replay for the + . single-repeat command. +
+ +

Extending VIM

+ +

CodeMirror's VIM mode implements a large subset of VIM's core + editing functionality. But since there's always more to be + desired, there is a set of APIs for extending VIM's + functionality. As with the configuration API, the methods are + exposed on CodeMirror.Vim and may + be called at any time.

+ +
+
defineOption(name: string, default: any, type: string, ?aliases: array<string>, ?callback: function (?value: any, ?cm: CodeMirror) → ?any)
+
Defines a VIM style option and makes it available to the + :set command. Type can be boolean or + string, used for validation and by + :set to determine which syntax to accept. If a + callback is passed in, VIM does not store the value of the + option itself, but instead uses the callback as a setter/getter. If the + first argument to the callback is undefined, then the + callback should return the value of the option. Otherwise, it should set + instead. Since VIM options have global and instance values, whether a + CodeMirror instance is passed in denotes whether the global + or local value should be used. Consequently, it's possible for the + callback to be called twice for a single setOption or + getOption call. Note that right now, VIM does not support + defining buffer-local options that do not have global values. If an + option should not have a global value, either always ignore the + cm parameter in the callback, or always pass in a + cfg.scope to setOption and + getOption.
+ +
defineMotion(name: string, fn: function(cm: CodeMirror, head: {line, ch}, ?motionArgs: object}) → {line, ch})
+
Defines a motion command for VIM. The motion should return + the desired result position of the cursor. head + is the current position of the cursor. It can differ from + cm.getCursor('head') if VIM is in visual mode. + motionArgs is the object passed into + mapCommand().
+ +
defineOperator(name: string, fn: function(cm: CodeMirror, ?operatorArgs: object, ranges: array<{anchor, head}>) → ?{line, ch})
+
Defines an operator command, similar to + defineMotion. ranges is the range + of text the operator should operate on. If the cursor should + be set to a certain position after the operation finishes, it + can return a cursor object.
+ +
defineAction(name: string, fn: function(cm: CodeMirror, ?actionArgs: object))
+
Defines an action command, similar to + defineMotion. Action commands + can have arbitrary behavior, making them more flexible than + motions and operators, at the loss of orthogonality.
+ +
defineEx(name: string, ?prefix: string, fn: function(cm: CodeMirror, ?params: object))
+
Defines an Ex command, and maps it to :name. + If a prefix is provided, it, and any prefixed substring of the + name beginning with the prefix can + be used to invoke the command. If the prefix is + falsy, then name is used as the prefix. + params.argString contains the part of the prompted + string after the command name. params.args is + params.argString split by whitespace. If the + command was prefixed with a + line range, + params.line and params.lineEnd will + be set. +
+ +
+ +
+ + diff --git a/www/code/codemirror-5.13.2/doc/realworld.html b/www/code/codemirror-5.13.2/doc/realworld.html new file mode 100644 index 000000000..03a67c7d2 --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/realworld.html @@ -0,0 +1,179 @@ + + +CodeMirror: Real-world Uses + + + + + +
+ +

CodeMirror real-world uses

+ +

Create a pull + request if you'd like your project to be added to this list.

+ + + +
+ diff --git a/www/code/codemirror-5.13.2/doc/releases.html b/www/code/codemirror-5.13.2/doc/releases.html new file mode 100644 index 000000000..99306b91c --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/releases.html @@ -0,0 +1,1238 @@ + + +CodeMirror: Release History + + + + + + +
+ +

Release notes and version history

+ +
+ +

Version 5.x

+ +

21-03-2016: Version 5.13.2:

+ +
    +
  • Solves a problem where the gutter would sometimes not extend all the way to the end of the document.
  • +
+ +

21-03-2016: Version 5.13:

+ + + +

19-02-2016: Version 5.12:

+ +
    +
  • Vim bindings: Ctrl-Q is now an alias for Ctrl-V.
  • +
  • Vim bindings: The Vim API now exposes an unmap method to unmap bindings.
  • +
  • active-line addon: This addon can now style the active line's gutter.
  • +
  • FCL mode: Newly added.
  • +
  • SQL mode: Now has a Postgresql dialect.
  • +
  • Fix issue where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly.
  • +
  • Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a problem when the editor is inside a transformed parent container.
  • +
  • Solve a problem where the horizontal scrollbar could hide text in Firefox.
  • +
  • Fix a bug that caused phantom scroll space under the text in some situations.
  • +
  • Sublime Text bindings: Bind delete-line to Shift-Ctrl-K on OS X.
  • +
  • Markdown mode: Fix issue where the mode would keep state related to fenced code blocks in an unsafe way, leading to occasional corrupted parses.
  • +
  • Markdown mode: Ignore backslashes in code fragments.
  • +
  • Markdown mode: Use whichever mode is registered as text/html to parse HTML.
  • +
  • Clike mode: Improve indentation of Scala => functions.
  • +
  • Python mode: Improve indentation of bracketed code.
  • +
  • HTMLMixed mode: Support multi-line opening tags for sub-languages (<script>, <style>, etc).
  • +
  • Spreadsheet mode: Fix bug where the mode did not advance the stream when finding a backslash.
  • +
  • XML mode: The mode now takes a matchClosing option to configure whether mismatched closing tags should be highlighted as errors.
  • +
+ +

20-01-2016: Version 5.11:

+ +
    +
  • New modes: JSX, literate Haskell
  • +
  • The editor now forwards more DOM events: cut, copy, paste, and touchstart. It will also forward mousedown for drag events
  • +
  • Fixes a bug where bookmarks next to collapsed spans were not rendered
  • +
  • The Swift mode now supports auto-indentation
  • +
  • Frontmatters in the YAML frontmatter mode are now optional as intended
  • +
  • Full list of patches
  • +
+ +

21-12-2015: Version 5.10:

+ + + +

23-11-2015: Version 5.9:

+ +
    +
  • Improve the way overlay (OS X-style) scrollbars are handled
  • +
  • Make annotatescrollbar and scrollpastend addons work properly together
  • +
  • Make show-hint addon select options on single click by default, move selection to hovered item
  • +
  • Properly fold comments that include block-comment-start markers
  • +
  • Many small language mode fixes
  • +
  • Full list of patches
  • +
+ +

20-10-2015: Version 5.8:

+ + + +

20-09-2015: Version 5.7:

+ + + +

20-08-2015: Version 5.6:

+ +
    +
  • Fix bug where you could paste into a readOnly editor
  • +
  • Show a cursor at the drop location when dragging over the editor
  • +
  • The Rust mode was rewritten to handle modern Rust
  • +
  • The editor and theme CSS was cleaned up. Some selectors are now less specific than before
  • +
  • New theme: abcdef
  • +
  • Lines longer than maxHighlightLength are now less likely to mess up indentation
  • +
  • New addons: autorefresh for refreshing an editor the first time it becomes visible, and html-lint for using HTMLHint
  • +
  • The search addon now recognizes \r and \n in pattern and replacement input
  • +
  • Full list of patches
  • +
+ +

20-07-2015: Version 5.5:

+ + + +

25-06-2015: Version 5.4:

+ + + +

20-05-2015: Version 5.3:

+ + + +

20-04-2015: Version 5.2:

+ + + +

23-03-2015: Version 5.1:

+ + + +

20-02-2015: Version 5.0:

+ +
    +
  • Experimental mobile support (tested on iOS, Android Chrome, stock Android browser)
  • +
  • New option inputStyle to switch between hidden textarea and contenteditable input.
  • +
  • The getInputField + method is no longer guaranteed to return a textarea.
  • +
  • Full list of patches.
  • +
+ +
+ +
+ +

Version 4.x

+ +

20-02-2015: Version 4.13:

+ + + +

22-01-2015: Version 4.12:

+ + + +

9-01-2015: Version 4.11:

+ +

Unfortunately, 4.10 did not take care of the + Firefox scrolling issue entirely. This release adds two more patches + to address that.

+ +

29-12-2014: Version 4.10:

+ +

Emergency single-patch update to 4.9. Fixes + Firefox-specific problem where the cursor could end up behind the + horizontal scrollbar.

+ +

23-12-2014: Version 4.9:

+ + + +

22-11-2014: Version 4.8:

+ + + +

20-10-2014: Version 4.7:

+ +
    +
  • Incompatible: + The lint addon now passes the + editor's value as first argument to asynchronous lint functions, + for consistency. The editor is still passed, as fourth + argument.
  • +
  • Improved handling of unicode identifiers in modes for + languages that support them.
  • +
  • More mode + improvements: CoffeeScript + (indentation), Verilog + (indentation), Scala + (indentation, triple-quoted strings), + and PHP (interpolated + variables in heredoc strings).
  • +
  • New modes: Textile and Tornado templates.
  • +
  • Experimental new way to define modes.
  • +
  • Improvements to the Vim + bindings: Arbitrary insert mode key mappings are now possible, + and text objects are supported in visual mode.
  • +
  • The mode meta-information file + now includes information about file extensions, + and helper + functions findModeByMIME + and findModeByExtension.
  • +
  • New logo!
  • +
  • Full list of patches.
  • +
+ +

19-09-2014: Version 4.6:

+ + + +

21-08-2014: Version 4.5:

+ +
    +
  • Fix several serious bugs with horizontal scrolling
  • +
  • New mode: Slim
  • +
  • New command: goLineLeftSmart
  • +
  • More fixes and extensions for the Vim visual block mode
  • +
  • Full list of patches.
  • +
+ +

21-07-2014: Version 4.4:

+ +
    +
  • Note: Some events might now fire in slightly + different order ("change" is still guaranteed to fire + before "cursorActivity")
  • +
  • Nested operations in multiple editors are now synced (complete + at same time, reducing DOM reflows)
  • +
  • Visual block mode for vim (<C-v>) is nearly complete
  • +
  • New mode: Kotlin
  • +
  • Better multi-selection paste for text copied from multiple CodeMirror selections
  • +
  • Full list of patches.
  • +
+ +

23-06-2014: Version 4.3:

+ +
    +
  • Several vim bindings + improvements: search and exCommand history, global flag + for :substitute, :global command. +
  • Allow hiding the cursor by + setting cursorBlinkRate + to a negative value.
  • +
  • Make gutter markers themeable, use this in foldgutter.
  • +
  • Full list of patches.
  • +
+ +

19-05-2014: Version 4.2:

+ +
    +
  • Fix problem where some modes were broken by the fact that empty tokens were forbidden.
  • +
  • Several fixes to context menu handling.
  • +
  • On undo, scroll change, not cursor, into view.
  • +
  • Rewritten Jade mode.
  • +
  • Various improvements to Shell (support for more syntax) and Python (better indentation) modes.
  • +
  • New mode: Cypher.
  • +
  • New theme: Neo.
  • +
  • Support direct styling options (color, line style, width) in the rulers addon.
  • +
  • Recognize per-editor configuration for the show-hint and foldcode addons.
  • +
  • More intelligent scanning for existing close tags in closetag addon.
  • +
  • In the Vim bindings: Fix bracket matching, support case conversion in visual mode, visual paste, append action.
  • +
  • Full list of patches.
  • +
+ +

22-04-2014: Version 4.1:

+ +
    +
  • Slightly incompatible: + The "cursorActivity" + event now fires after all other events for the operation (and only + for handlers that were actually registered at the time the + activity happened).
  • +
  • New command: insertSoftTab.
  • +
  • New mode: Django.
  • +
  • Improved modes: Verilog (rewritten), Jinja2, Haxe, PHP (string interpolation highlighted), JavaScript (indentation of trailing else, template strings), LiveScript (multi-line strings).
  • +
  • Many small issues from the 3.x→4.x transition were found and fixed.
  • +
  • Full list of patches.
  • +
+ +

20-03-2014: Version 4.0:

+ +

This is a new major version of CodeMirror. There + are a few incompatible changes in the API. Upgrade + with care, and read the upgrading + guide.

+ + + +
+ +
+ +

Version 3.x

+ +

22-04-2014: Version 3.24:

+ +

Merges the improvements from 4.1 that could + easily be applied to the 3.x code. Also improves the way the editor + size is updated when line widgets change.

+ +

20-03-2014: Version 3.23:

+ +
    +
  • In the XML mode, + add brackets style to angle brackets, fix + case-sensitivity of tags for HTML.
  • +
  • New mode: Dylan.
  • +
  • Many improvements to the Vim bindings.
  • +
+ +

21-02-2014: Version 3.22:

+ + + +

16-01-2014: Version 3.21:

+ +
    +
  • Auto-indenting a block will no longer add trailing whitespace to blank lines.
  • +
  • Marking text has a new option clearWhenEmpty to control auto-removal.
  • +
  • Several bugfixes in the handling of bidirectional text.
  • +
  • The XML and CSS modes were largely rewritten. LESS support was added to the CSS mode.
  • +
  • The OCaml mode was moved to an mllike mode, F# support added.
  • +
  • Make it possible to fetch multiple applicable helper values with getHelpers, and to register helpers matched on predicates with registerGlobalHelper.
  • +
  • New theme pastel-on-dark.
  • +
  • Better ECMAScript 6 support in JavaScript mode.
  • +
  • Full list of patches.
  • +
+ +

21-11-2013: Version 3.20:

+ + + +

21-10-2013: Version 3.19:

+ + + +

23-09-2013: Version 3.18:

+ +

Emergency release to fix a problem in 3.17 + where .setOption("lineNumbers", false) would raise an + error.

+ +

23-09-2013: Version 3.17:

+ + + +

21-08-2013: Version 3.16:

+ + + +

29-07-2013: Version 3.15:

+ + + +

20-06-2013: Version 3.14:

+ + + +

20-05-2013: Version 3.13:

+ + + +

19-04-2013: Version 3.12:

+ + + +

20-03-2013: Version 3.11:

+ + + +

21-02-2013: Version 3.1:

+ + + + +

25-01-2013: Version 3.02:

+ +

Single-bugfix release. Fixes a problem that + prevents CodeMirror instances from being garbage-collected after + they become unused.

+ +

21-01-2013: Version 3.01:

+ + + +

10-12-2012: Version 3.0:

+ +

New major version. Only + partially backwards-compatible. See + the upgrading guide for more + information. Changes since release candidate 2:

+ +
    +
  • Rewritten VIM mode.
  • +
  • Fix a few minor scrolling and sizing issues.
  • +
  • Work around Safari segfault when dragging.
  • +
  • Full list of patches.
  • +
+ +

20-11-2012: Version 3.0, release candidate 2:

+ +
    +
  • New mode: HTTP.
  • +
  • Improved handling of selection anchor position.
  • +
  • Improve IE performance on longer lines.
  • +
  • Reduce gutter glitches during horiz. scrolling.
  • +
  • Add addKeyMap and removeKeyMap methods.
  • +
  • Rewrite formatting and closetag add-ons.
  • +
  • Full list of patches.
  • +
+ +

20-11-2012: Version 3.0, release candidate 1:

+ + + +

22-10-2012: Version 3.0, beta 2:

+ +
    +
  • Fix page-based coordinate computation.
  • +
  • Fix firing of gutterClick event.
  • +
  • Add cursorHeight option.
  • +
  • Fix bi-directional text regression.
  • +
  • Add viewportMargin option.
  • +
  • Directly handle mousewheel events (again, hopefully better).
  • +
  • Make vertical cursor movement more robust (through widgets, big line gaps).
  • +
  • Add flattenSpans option.
  • +
  • Many optimizations. Poor responsiveness should be fixed.
  • +
  • Initialization in hidden state works again.
  • +
  • Full list of patches.
  • +
+ +

19-09-2012: Version 3.0, beta 1:

+ +
    +
  • Bi-directional text support.
  • +
  • More powerful gutter model.
  • +
  • Support for arbitrary text/widget height.
  • +
  • In-line widgets.
  • +
  • Generalized event handling.
  • +
+ +
+ +
+ +

Version 2.x

+ +

21-01-2013: Version 2.38:

+ +

Integrate some bugfixes, enhancements to the vim keymap, and new + modes + (D, Sass, APL) + from the v3 branch.

+ +

20-12-2012: Version 2.37:

+ +
    +
  • New mode: SQL (will replace plsql and mysql modes).
  • +
  • Further work on the new VIM mode.
  • +
  • Fix Cmd/Ctrl keys on recent Operas on OS X.
  • +
  • Full list of patches.
  • +
+ +

20-11-2012: Version 2.36:

+ + + +

22-10-2012: Version 2.35:

+ +
    +
  • New (sub) mode: TypeScript.
  • +
  • Don't overwrite (insert key) when pasting.
  • +
  • Fix several bugs in markText/undo interaction.
  • +
  • Better indentation of JavaScript code without semicolons.
  • +
  • Add defineInitHook function.
  • +
  • Full list of patches.
  • +
+ +

19-09-2012: Version 2.34:

+ +
    +
  • New mode: Common Lisp.
  • +
  • Fix right-click select-all on most browsers.
  • +
  • Change the way highlighting happens:
      Saves memory and CPU cycles.
      compareStates is no longer needed.
      onHighlightComplete no longer works.
  • +
  • Integrate mode (Markdown, XQuery, CSS, sTex) tests in central testsuite.
  • +
  • Add a CodeMirror.version property.
  • +
  • More robust handling of nested modes in formatting and closetag plug-ins.
  • +
  • Un/redo now preserves marked text and bookmarks.
  • +
  • Full list of patches.
  • +
+ +

23-08-2012: Version 2.33:

+ +
    +
  • New mode: Sieve.
  • +
  • New getViewPort and onViewportChange API.
  • +
  • Configurable cursor blink rate.
  • +
  • Make binding a key to false disabling handling (again).
  • +
  • Show non-printing characters as red dots.
  • +
  • More tweaks to the scrolling model.
  • +
  • Expanded testsuite. Basic linter added.
  • +
  • Remove most uses of innerHTML. Remove CodeMirror.htmlEscape.
  • +
  • Full list of patches.
  • +
+ +

23-07-2012: Version 2.32:

+ +

Emergency fix for a bug where an editor with + line wrapping on IE will break when there is no + scrollbar.

+ +

20-07-2012: Version 2.31:

+ + + +

22-06-2012: Version 2.3:

+ +
    +
  • New scrollbar implementation. Should flicker less. Changes DOM structure of the editor.
  • +
  • New theme: vibrant-ink.
  • +
  • Many extensions to the VIM keymap (including text objects).
  • +
  • Add mode-multiplexing utility script.
  • +
  • Fix bug where right-click paste works in read-only mode.
  • +
  • Add a getScrollInfo method.
  • +
  • Lots of other fixes.
  • +
+ +

23-05-2012: Version 2.25:

+ +
    +
  • New mode: Erlang.
  • +
  • Remove xmlpure mode (use xml.js).
  • +
  • Fix line-wrapping in Opera.
  • +
  • Fix X Windows middle-click paste in Chrome.
  • +
  • Fix bug that broke pasting of huge documents.
  • +
  • Fix backspace and tab key repeat in Opera.
  • +
+ +

23-04-2012: Version 2.24:

+ +
    +
  • Drop support for Internet Explorer 6.
  • +
  • New + modes: Shell, Tiki + wiki, Pig Latin.
  • +
  • New themes: Ambiance, Blackboard.
  • +
  • More control over drag/drop + with dragDrop + and onDragEvent + options.
  • +
  • Make HTML mode a bit less pedantic.
  • +
  • Add compoundChange API method.
  • +
  • Several fixes in undo history and line hiding.
  • +
  • Remove (broken) support for catchall in key maps, + add nofallthrough boolean field instead.
  • +
+ +

26-03-2012: Version 2.23:

+ +
    +
  • Change default binding for tab [more] + +
  • +
  • New modes: XQuery and VBScript.
  • +
  • Two new themes: lesser-dark and xq-dark.
  • +
  • Differentiate between background and text styles in setLineClass.
  • +
  • Fix drag-and-drop in IE9+.
  • +
  • Extend charCoords + and cursorCoords with a mode argument.
  • +
  • Add autofocus option.
  • +
  • Add findMarksAt method.
  • +
+ +

27-02-2012: Version 2.22:

+ + + +

27-01-2012: Version 2.21:

+ +
    +
  • Added LESS, MySQL, + Go, and Verilog modes.
  • +
  • Add smartIndent + option.
  • +
  • Support a cursor in readOnly-mode.
  • +
  • Support assigning multiple styles to a token.
  • +
  • Use a new approach to drawing the selection.
  • +
  • Add scrollTo method.
  • +
  • Allow undo/redo events to span non-adjacent lines.
  • +
  • Lots and lots of bugfixes.
  • +
+ +

20-12-2011: Version 2.2:

+ + + +

21-11-2011: Version 2.18:

+

Fixes TextMarker.clear, which is broken in 2.17.

+ +

21-11-2011: Version 2.17:

+
    +
  • Add support for line + wrapping and code + folding.
  • +
  • Add Github-style Markdown mode.
  • +
  • Add Monokai + and Rubyblue themes.
  • +
  • Add setBookmark method.
  • +
  • Move some of the demo code into reusable components + under lib/util.
  • +
  • Make screen-coord-finding code faster and more reliable.
  • +
  • Fix drag-and-drop in Firefox.
  • +
  • Improve support for IME.
  • +
  • Speed up content rendering.
  • +
  • Fix browser's built-in search in Webkit.
  • +
  • Make double- and triple-click work in IE.
  • +
  • Various fixes to modes.
  • +
+ +

27-10-2011: Version 2.16:

+
    +
  • Add Perl, Rust, TiddlyWiki, and Groovy modes.
  • +
  • Dragging text inside the editor now moves, rather than copies.
  • +
  • Add a coordsFromIndex method.
  • +
  • API change: setValue now no longer clears history. Use clearHistory for that.
  • +
  • API change: markText now + returns an object with clear and find + methods. Marked text is now more robust when edited.
  • +
  • Fix editing code with tabs in Internet Explorer.
  • +
+ +

26-09-2011: Version 2.15:

+

Fix bug that snuck into 2.14: Clicking the + character that currently has the cursor didn't re-focus the + editor.

+ +

26-09-2011: Version 2.14:

+ + + +

23-08-2011: Version 2.13:

+ + +

25-07-2011: Version 2.12:

+
    +
  • Add a SPARQL mode.
  • +
  • Fix bug with cursor jumping around in an unfocused editor in IE.
  • +
  • Allow key and mouse events to bubble out of the editor. Ignore widget clicks.
  • +
  • Solve cursor flakiness after undo/redo.
  • +
  • Fix block-reindent ignoring the last few lines.
  • +
  • Fix parsing of multi-line attrs in XML mode.
  • +
  • Use innerHTML for HTML-escaping.
  • +
  • Some fixes to indentation in C-like mode.
  • +
  • Shrink horiz scrollbars when long lines removed.
  • +
  • Fix width feedback loop bug that caused the width of an inner DIV to shrink.
  • +
+ +

04-07-2011: Version 2.11:

+
    +
  • Add a Scheme mode.
  • +
  • Add a replace method to search cursors, for cursor-preserving replacements.
  • +
  • Make the C-like mode mode more customizable.
  • +
  • Update XML mode to spot mismatched tags.
  • +
  • Add getStateAfter API and compareState mode API methods for finer-grained mode magic.
  • +
  • Add a getScrollerElement API method to manipulate the scrolling DIV.
  • +
  • Fix drag-and-drop for Firefox.
  • +
  • Add a C# configuration for the C-like mode.
  • +
  • Add full-screen editing and mode-changing demos.
  • +
+ +

07-06-2011: Version 2.1:

+

Add + a theme system + (demo). Note that this is not + backwards-compatible—you'll have to update your styles and + modes!

+ +

07-06-2011: Version 2.02:

+
    +
  • Add a Lua mode.
  • +
  • Fix reverse-searching for a regexp.
  • +
  • Empty lines can no longer break highlighting.
  • +
  • Rework scrolling model (the outer wrapper no longer does the scrolling).
  • +
  • Solve horizontal jittering on long lines.
  • +
  • Add runmode.js.
  • +
  • Immediately re-highlight text when typing.
  • +
  • Fix problem with 'sticking' horizontal scrollbar.
  • +
+ +

26-05-2011: Version 2.01:

+
    +
  • Add a Smalltalk mode.
  • +
  • Add a reStructuredText mode.
  • +
  • Add a Python mode.
  • +
  • Add a PL/SQL mode.
  • +
  • coordsChar now works
  • +
  • Fix a problem where onCursorActivity interfered with onChange.
  • +
  • Fix a number of scrolling and mouse-click-position glitches.
  • +
  • Pass information about the changed lines to onChange.
  • +
  • Support cmd-up/down on OS X.
  • +
  • Add triple-click line selection.
  • +
  • Don't handle shift when changing the selection through the API.
  • +
  • Support "nocursor" mode for readOnly option.
  • +
  • Add an onHighlightComplete option.
  • +
  • Fix the context menu for Firefox.
  • +
+ +

28-03-2011: Version 2.0:

+

CodeMirror 2 is a complete rewrite that's + faster, smaller, simpler to use, and less dependent on browser + quirks. See this + and this + for more information.

+ +

22-02-2011: Version 2.0 beta 2:

+

Somewhat more mature API, lots of bugs shaken out.

+ +

17-02-2011: Version 0.94:

+
    +
  • tabMode: "spaces" was modified slightly (now indents when something is selected).
  • +
  • Fixes a bug that would cause the selection code to break on some IE versions.
  • +
  • Disabling spell-check on WebKit browsers now works.
  • +
+ +

08-02-2011: Version 2.0 beta 1:

+

CodeMirror 2 is a complete rewrite of + CodeMirror, no longer depending on an editable frame.

+ +

19-01-2011: Version 0.93:

+
    +
  • Added a Regular Expression parser.
  • +
  • Fixes to the PHP parser.
  • +
  • Support for regular expression in search/replace.
  • +
  • Add save method to instances created with fromTextArea.
  • +
  • Add support for MS T-SQL in the SQL parser.
  • +
  • Support use of CSS classes for highlighting brackets.
  • +
  • Fix yet another hang with line-numbering in hidden editors.
  • +
+
+ +
+ +

Version 0.x

+ +

28-03-2011: Version 1.0:

+
    +
  • Fix error when debug history overflows.
  • +
  • Refine handling of C# verbatim strings.
  • +
  • Fix some issues with JavaScript indentation.
  • +
+ +

17-12-2010: Version 0.92:

+
    +
  • Make CodeMirror work in XHTML documents.
  • +
  • Fix bug in handling of backslashes in Python strings.
  • +
  • The styleNumbers option is now officially + supported and documented.
  • +
  • onLineNumberClick option added.
  • +
  • More consistent names onLoad and + onCursorActivity callbacks. Old names still work, but + are deprecated.
  • +
  • Add a Freemarker mode.
  • +
+ +

11-11-2010: Version 0.91:

+
    +
  • Adds support for Java.
  • +
  • Small additions to the PHP and SQL parsers.
  • +
  • Work around various Webkit issues.
  • +
  • Fix toTextArea to update the code in the textarea.
  • +
  • Add a noScriptCaching option (hack to ease development).
  • +
  • Make sub-modes of HTML mixed mode configurable.
  • +
+ +

02-10-2010: Version 0.9:

+
    +
  • Add support for searching backwards.
  • +
  • There are now parsers for Scheme, XQuery, and OmetaJS.
  • +
  • Makes height: "dynamic" more robust.
  • +
  • Fixes bug where paste did not work on OS X.
  • +
  • Add a enterMode and electricChars options to make indentation even more customizable.
  • +
  • Add firstLineNumber option.
  • +
  • Fix bad handling of @media rules by the CSS parser.
  • +
  • Take a new, more robust approach to working around the invisible-last-line bug in WebKit.
  • +
+ +

22-07-2010: Version 0.8:

+
    +
  • Add a cursorCoords method to find the screen + coordinates of the cursor.
  • +
  • A number of fixes and support for more syntax in the PHP parser.
  • +
  • Fix indentation problem with JSON-mode JS parser in Webkit.
  • +
  • Add a minification UI.
  • +
  • Support a height: dynamic mode, where the editor's + height will adjust to the size of its content.
  • +
  • Better support for IME input mode.
  • +
  • Fix JavaScript parser getting confused when seeing a no-argument + function call.
  • +
  • Have CSS parser see the difference between selectors and other + identifiers.
  • +
  • Fix scrolling bug when pasting in a horizontally-scrolled + editor.
  • +
  • Support toTextArea method in instances created with + fromTextArea.
  • +
  • Work around new Opera cursor bug that causes the cursor to jump + when pressing backspace at the end of a line.
  • +
+ +

27-04-2010: Version + 0.67:

+

More consistent page-up/page-down behaviour + across browsers. Fix some issues with hidden editors looping forever + when line-numbers were enabled. Make PHP parser parse + "\\" correctly. Have jumpToLine work on + line handles, and add cursorLine function to fetch the + line handle where the cursor currently is. Add new + setStylesheet function to switch style-sheets in a + running editor.

+ +

01-03-2010: Version + 0.66:

+

Adds removeLine method to API. + Introduces the PLSQL parser. + Marks XML errors by adding (rather than replacing) a CSS class, so + that they can be disabled by modifying their style. Fixes several + selection bugs, and a number of small glitches.

+ +

12-11-2009: Version + 0.65:

+

Add support for having both line-wrapping and + line-numbers turned on, make paren-highlighting style customisable + (markParen and unmarkParen config + options), work around a selection bug that Opera + reintroduced in version 10.

+ +

23-10-2009: Version + 0.64:

+

Solves some issues introduced by the + paste-handling changes from the previous release. Adds + setSpellcheck, setTextWrapping, + setIndentUnit, setUndoDepth, + setTabMode, and setLineNumbers to + customise a running editor. Introduces an SQL parser. Fixes a few small + problems in the Python + parser. And, as usual, add workarounds for various newly discovered + browser incompatibilities.

+ +

31-08-2009: Version 0.63:

+

Overhaul of paste-handling (less fragile), fixes for several + serious IE8 issues (cursor jumping, end-of-document bugs) and a number + of small problems.

+ +

30-05-2009: Version 0.62:

+

Introduces Python + and Lua parsers. Add + setParser (on-the-fly mode changing) and + clearHistory methods. Make parsing passes time-based + instead of lines-based (see the passTime option).

+ +
+
diff --git a/www/code/codemirror-5.13.2/doc/reporting.html b/www/code/codemirror-5.13.2/doc/reporting.html new file mode 100644 index 000000000..3c582d961 --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/reporting.html @@ -0,0 +1,61 @@ + + +CodeMirror: Reporting Bugs + + + + + +
+ +

Reporting bugs effectively

+ +
+ +

So you found a problem in CodeMirror. By all means, report it! Bug +reports from users are the main drive behind improvements to +CodeMirror. But first, please read over these points:

+ +
    +
  1. CodeMirror is maintained by volunteers. They don't owe you + anything, so be polite. Reports with an indignant or belligerent + tone tend to be moved to the bottom of the pile.
  2. + +
  3. Include information about the browser in which the + problem occurred. Even if you tested several browsers, and + the problem occurred in all of them, mention this fact in the bug + report. Also include browser version numbers and the operating + system that you're on.
  4. + +
  5. Mention which release of CodeMirror you're using. Preferably, + try also with the current development snapshot, to ensure the + problem has not already been fixed.
  6. + +
  7. Mention very precisely what went wrong. "X is broken" is not a + good bug report. What did you expect to happen? What happened + instead? Describe the exact steps a maintainer has to take to make + the problem occur. We can not fix something that we can not + observe.
  8. + +
  9. If the problem can not be reproduced in any of the demos + included in the CodeMirror distribution, please provide an HTML + document that demonstrates the problem. The best way to do this is + to go to jsbin.com, enter + it there, press save, and include the resulting link in your bug + report.
  10. +
+ +
+ +
diff --git a/www/code/codemirror-5.13.2/doc/upgrade_v2.2.html b/www/code/codemirror-5.13.2/doc/upgrade_v2.2.html new file mode 100644 index 000000000..3948ce6e5 --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/upgrade_v2.2.html @@ -0,0 +1,96 @@ + + +CodeMirror: Version 2.2 upgrade guide + + + + + +
+ +

Upgrading to v2.2

+ +

There are a few things in the 2.2 release that require some care +when upgrading.

+ +

No more default.css

+ +

The default theme is now included +in codemirror.css, so +you do not have to included it separately anymore. (It was tiny, so +even if you're not using it, the extra data overhead is negligible.) + +

Different key customization

+ +

CodeMirror has moved to a system +where keymaps are used to +bind behavior to keys. This means custom +bindings are now possible.

+ +

Three options that influenced key +behavior, tabMode, enterMode, +and smartHome, are no longer supported. Instead, you can +provide custom bindings to influence the way these keys act. This is +done through the +new extraKeys +option, which can hold an object mapping key names to functionality. A +simple example would be:

+ +
  extraKeys: {
+    "Ctrl-S": function(instance) { saveText(instance.getValue()); },
+    "Ctrl-/": "undo"
+  }
+ +

Keys can be mapped either to functions, which will be given the +editor instance as argument, or to strings, which are mapped through +functions through the CodeMirror.commands table, which +contains all the built-in editing commands, and can be inspected and +extended by external code.

+ +

By default, the Home key is bound to +the "goLineStartSmart" command, which moves the cursor to +the first non-whitespace character on the line. You can set do this to +make it always go to the very start instead:

+ +
  extraKeys: {"Home": "goLineStart"}
+ +

Similarly, Enter is bound +to "newlineAndIndent" by default. You can bind it to +something else to get different behavior. To disable special handling +completely and only get a newline character inserted, you can bind it +to false:

+ +
  extraKeys: {"Enter": false}
+ +

The same works for Tab. If you don't want CodeMirror +to handle it, bind it to false. The default behaviour is +to indent the current line more ("indentMore" command), +and indent it less when shift is held ("indentLess"). +There are also "indentAuto" (smart indent) +and "insertTab" commands provided for alternate +behaviors. Or you can write your own handler function to do something +different altogether.

+ +

Tabs

+ +

Handling of tabs changed completely. The display width of tabs can +now be set with the tabSize option, and tabs can +be styled by setting CSS rules +for the cm-tab class.

+ +

The default width for tabs is now 4, as opposed to the 8 that is +hard-wired into browsers. If you are relying on 8-space tabs, make +sure you explicitly set tabSize: 8 in your options.

+ +
diff --git a/www/code/codemirror-5.13.2/doc/upgrade_v3.html b/www/code/codemirror-5.13.2/doc/upgrade_v3.html new file mode 100644 index 000000000..5f94067aa --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/upgrade_v3.html @@ -0,0 +1,230 @@ + + +CodeMirror: Version 3 upgrade guide + + + + + + + + + + + + + + +
+ +

Upgrading to version 3

+ +

Version 3 does not depart too much from 2.x API, and sites that use +CodeMirror in a very simple way might be able to upgrade without +trouble. But it does introduce a number of incompatibilities. Please +at least skim this text before upgrading.

+ +

Note that version 3 drops full support for Internet +Explorer 7. The editor will mostly work on that browser, but +it'll be significantly glitchy.

+ +
+

DOM structure

+ +

This one is the most likely to cause problems. The internal +structure of the editor has changed quite a lot, mostly to implement a +new scrolling model.

+ +

Editor height is now set on the outer wrapper element (CSS +class CodeMirror), not on the scroller element +(CodeMirror-scroll).

+ +

Other nodes were moved, dropped, and added. If you have any code +that makes assumptions about the internal DOM structure of the editor, +you'll have to re-test it and probably update it to work with v3.

+ +

See the styling section of the +manual for more information.

+
+
+

Gutter model

+ +

In CodeMirror 2.x, there was a single gutter, and line markers +created with setMarker would have to somehow coexist with +the line numbers (if present). Version 3 allows you to specify an +array of gutters, by class +name, +use setGutterMarker +to add or remove markers in individual gutters, and clear whole +gutters +with clearGutter. +Gutter markers are now specified as DOM nodes, rather than HTML +snippets.

+ +

The gutters no longer horizontally scrolls along with the content. +The fixedGutter option was removed (since it is now the +only behavior).

+ +
+<style>
+  /* Define a gutter style */
+  .note-gutter { width: 3em; background: cyan; }
+</style>
+<script>
+  // Create an instance with two gutters -- line numbers and notes
+  var cm = new CodeMirror(document.body, {
+    gutters: ["note-gutter", "CodeMirror-linenumbers"],
+    lineNumbers: true
+  });
+  // Add a note to line 0
+  cm.setGutterMarker(0, "note-gutter", document.createTextNode("hi"));
+</script>
+
+
+
+

Event handling

+ +

Most of the onXYZ options have been removed. The same +effect is now obtained by calling +the on method with a string +identifying the event type. Multiple handlers can now be registered +(and individually unregistered) for an event, and objects such as line +handlers now also expose events. See the +full list here.

+ +

(The onKeyEvent and onDragEvent options, +which act more as hooks than as event handlers, are still there in +their old form.)

+ +
+cm.on("change", function(cm, change) {
+  console.log("something changed! (" + change.origin + ")");
+});
+
+
+
+

markText method arguments

+ +

The markText method +(which has gained some interesting new features, such as creating +atomic and read-only spans, or replacing spans with widgets) no longer +takes the CSS class name as a separate argument, but makes it an +optional field in the options object instead.

+ +
+// Style first ten lines, and forbid the cursor from entering them
+cm.markText({line: 0, ch: 0}, {line: 10, ch: 0}, {
+  className: "magic-text",
+  inclusiveLeft: true,
+  atomic: true
+});
+
+
+
+

Line folding

+ +

The interface for hiding lines has been +removed. markText can +now be used to do the same in a more flexible and powerful way.

+ +

The folding script has been +updated to use the new interface, and should now be more robust.

+ +
+// Fold a range, replacing it with the text "??"
+var range = cm.markText({line: 4, ch: 2}, {line: 8, ch: 1}, {
+  replacedWith: document.createTextNode("??"),
+  // Auto-unfold when cursor moves into the range
+  clearOnEnter: true
+});
+// Get notified when auto-unfolding
+CodeMirror.on(range, "clear", function() {
+  console.log("boom");
+});
+
+
+
+

Line CSS classes

+ +

The setLineClass method has been replaced +by addLineClass +and removeLineClass, +which allow more modular control over the classes attached to a line.

+ +
+var marked = cm.addLineClass(10, "background", "highlighted-line");
+setTimeout(function() {
+  cm.removeLineClass(marked, "background", "highlighted-line");
+});
+
+
+
+

Position properties

+ +

All methods that take or return objects that represent screen +positions now use {left, top, bottom, right} properties +(not always all of them) instead of the {x, y, yBot} used +by some methods in v2.x.

+ +

Affected methods +are cursorCoords, charCoords, coordsChar, +and getScrollInfo.

+
+
+

Bracket matching no longer in core

+ +

The matchBrackets +option is no longer defined in the core editor. +Load addon/edit/matchbrackets.js to enable it.

+
+
+

Mode management

+ +

The CodeMirror.listModes +and CodeMirror.listMIMEs functions, used for listing +defined modes, are gone. You are now encouraged to simply +inspect CodeMirror.modes (mapping mode names to mode +constructors) and CodeMirror.mimeModes (mapping MIME +strings to mode specs).

+
+
+

New features

+ +

Some more reasons to upgrade to version 3.

+ +
    +
  • Bi-directional text support. CodeMirror will now mostly do the + right thing when editing Arabic or Hebrew text.
  • +
  • Arbitrary line heights. Using fonts with different heights + inside the editor (whether off by one pixel or fifty) is now + supported and handled gracefully.
  • +
  • In-line widgets. See the demo + and the docs.
  • +
  • Defining custom options + with CodeMirror.defineOption.
  • +
+
+
+ + diff --git a/www/code/codemirror-5.13.2/doc/upgrade_v4.html b/www/code/codemirror-5.13.2/doc/upgrade_v4.html new file mode 100644 index 000000000..09df00ca1 --- /dev/null +++ b/www/code/codemirror-5.13.2/doc/upgrade_v4.html @@ -0,0 +1,144 @@ + + +CodeMirror: Version 4 upgrade guide + + + + + + +
+ +

Upgrading to version 4

+ +

CodeMirror 4's interface is very close version 3, but it +does fix a few awkward details in a backwards-incompatible ways. At +least skim the text below before upgrading.

+ +

Multiple selections

+ +

The main new feature in version 4 is multiple selections. The +single-selection variants of methods are still there, but now +typically act only on the primary selection (usually the last +one added).

+ +

The exception to this +is getSelection, +which will now return the content of all selections +(separated by newlines, or whatever lineSep parameter you passed +it).

+ +
+ +

The beforeSelectionChange event

+ +

This event still exists, but the object it is passed has +a completely new +interface, because such changes now concern multiple +selections.

+ +
+ +

replaceSelection's collapsing behavior

+ +

By +default, replaceSelection +would leave the newly inserted text selected. This is only rarely what +you want, and also (slightly) more expensive in the new model, so the +default was changed to "end", meaning the old behavior +must be explicitly specified by passing a second argument +of "around".

+ +
+ +

change event data

+ +

Rather than forcing client code to follow next +pointers from one change object to the next, the library will now +simply fire +multiple "change" +events. Existing code will probably continue to work unmodified.

+ +
+ +

showIfHidden option to line widgets

+ +

This option, which conceptually caused line widgets to be visible +even if their line was hidden, was never really well-defined, and was +buggy from the start. It would be a rather expensive feature, both in +code complexity and run-time performance, to implement properly. It +has been dropped entirely in 4.0.

+ +
+ +

Module loaders

+ +

All modules in the CodeMirror distribution are now wrapped in a +shim function to make them compatible with both AMD +(requirejs) and CommonJS (as used +by node +and browserify) module loaders. +When neither of these is present, they fall back to simply using the +global CodeMirror variable.

+ +

If you have a module loader present in your environment, CodeMirror +will attempt to use it, and you might need to change the way you load +CodeMirror modules.

+ +
+ +

Mutating shared data structures

+ +

Data structures produced by the library should not be mutated +unless explicitly allowed, in general. This is slightly more strict in +4.0 than it was in earlier versions, which copied the position objects +returned by getCursor +for nebulous, historic reasons. In 4.0, mutating these +objects will corrupt your editor's selection.

+ +
+ +

Deprecated interfaces dropped

+ +

A few properties and methods that have been deprecated for a while +are now gone. Most notably, the onKeyEvent +and onDragEvent options (use the +corresponding events instead).

+ +

Two silly methods, which were mostly there to stay close to the 0.x +API, setLine and removeLine are now gone. +Use the more +flexible replaceRange +method instead.

+ +

The long names for folding and completing functions +(CodeMirror.braceRangeFinder, CodeMirror.javascriptHint, +etc) are also gone +(use CodeMirror.fold.brace, CodeMirror.hint.javascript).

+ +

The className property in the return value +of getTokenAt, which +has been superseded by the type property, is also no +longer present.

+ +
+
diff --git a/www/code/codemirror-5.13.2/doc/yinyang.png b/www/code/codemirror-5.13.2/doc/yinyang.png new file mode 100644 index 0000000000000000000000000000000000000000..2eafd3f1ca137898ede8c27d0e64d99919bdc1ab GIT binary patch literal 4633 zcmV+!66WoRP)02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{01<&nL_t(|+U=cPY#Y~=$Nxhy zV#HV?X-TGS*&^w9;jhFRrrmgx#ahFz1vCy?kuL#aq!!t?6h%Z81?uLZB43geHDU@V z+Mp;vst;|SN~%ad0C~-k)9hlmSaX=zPPDc)WGqXTMTJAjrb?Oe-3N*cW$Tk1&I~CX zfUqS?^p$>dKJU5bQiKrHlFYFz0f02eVNnnYpU(;c7Gg1IX}a*adb1fCv$^nbBg0Ur zF`=pzaE9keIvT}1$05D+nvTcOcJd?uKugo8)9VXguh#2PZ#E;%afQz<#A3+AVug?I zT)vD}lL=ayhL)z$$S`Q==;%@#3>UWnOCae;gd_$BF~_o)9vj1Ms}+q511(KMV>Ux; zwTkiJV2))A4^1)jMuvgDyBivdh1v!zk|aYRB7{O14+gQ*Y=+)$hrwdXeFI?DcrCgJCeg6hO_fEbc`jSeTt%_4{{s zbQA)kyswm6vdJW70s&0ER} z6HG1_T6%gU_0>fWEG9f2EX3n5`~7IL*{CfAIK%T~!s|u+4}ZXscizEbB7s{MF084g zrAn*G1f$!XkA5Ww9dkONu~@LDzh4&6k^`Rd`N*|%=U`}RLW|3V*asg}1nf#${cK+! z_If;$KhO!k9}^xA8f`Z0zi>g)yjn8A8J;J%91cv~xPeFMv}|sn)nv+>P04-ql+TAT zpAXLk0x&onRLuaM@Oa4RyYJ%B!h&M5hXXx5*ni;y)CPkrW?S!YU~hka#kE;g;ivZ0 zXJ7w18U5MMkW~;|02mt_#Hh`NY%(e7|J!7<;V8!egy6c>I-HI~NL6ZZIuarO{qoDX zd@PeuY^7&+M+Yo^ziiR=mF{jlH!y$}my4o>KKu}ib91OBkrQ$@nZzi=Kx?(a5(tP2S+xx}CkwN);9V}s zeKG*34F()yS%45+qiK>&CQm5}a5kAdHFo9rYb4vK1)f4+1{@;td%_?{ksqC_#*WdpwU{PU!i;O71D zOR<>bIdUZ+mt~^I&YY<^xOsnk+wGQh4VJ+{WOyFD(@A6je5b#keDUFjH3zrw@c!mE z$nde&R&@_y_y=Y_@)EB{Bh{Z6+vY7CCaj&SqS!^x- zw^T|I7UlEKPBbzM+Pq#$bim1AkbH9XEVB3SOZEdb=yZ5K6xvXa)l48zG0<}7#d*o_ zJpQYr1209R8}8uTkcrMFlczp;;|)pS?xAT|S$4w>bj?mDjz%KbQ*<;%ESY0jMQE_b zV!;uw7crMhTpxVf?H-0&Eh*eX;V>FD39C-8$H8C_4LV&-0tNtMUmpk|n7VLbL#B6K z6FnOalc?P;N#9gnUgkJ7baa&b{qr1$2*Y4rB;TndKfIL5;C~qgFLKu`t~O{XIf#Q@e?tvKLzZzxY!t*5<{pMDB5KQGzh#p-(0 z+wG#RE32@Iozd+^JQzfV=Sj(cW8e9Xq#clZXc{{@JH-Lq-7TswUZ_wSR;s8Ch7AQ= zs{&5={UjxEDNvrWAflPhC=75u;L7kkX2W5!X~2{3y(ej4wJoK)v`Mdba~eDw z4wJq29p5M~Ak%X*LdBO{{1-F^JH;{CAJYDJw+hh#8FRt22; z^Pgos+F7t7PABH0(bCL(G>T8UyG31(lFjq7yx8r2!o@8EJpJomD`v5g&dyCko*5j> zeV@o119Hs#D0Q9Vs{zjNJW1TSqnO3cr&9P&nnuj!!a_X0c6yacVbbf(X?_KEeZ;P7 zS4uP%3wD~#%Nq0wo4jI0uVTxsKmfM_0X*w;LT|T2t=FT0rZLZP$O;04PzV#jU`4sWwPJc~tgwx!0&+c9>FqM%ljhmski93mq_oazEmZMT0FdUmVM?RfCig&p{^;m7)gm#=5UqC z$O;0~3$YloTey3&818Dvc&MfG(ChuXB29E=3v?c?CWn9W6CC9@)amuP`wb{+#aA)N z)&4zyM}pEsy%oW>wzuOzFoO?;9r8GIx-;cxLuwHC+U!5<^C=##lzFlW05m&x z)CAn;d_J@|omI8wN(EkbE?-8I%|=m#5c~&4k!pv#k6~~q9Inc>iyd@jcpjq+gX8fy zl?$?JU_aRG_gC%O#WvM*MKOp90G>HjdUHP0OHGMlm2(KVli+|C$3wqiW%TG8xCWq*}QCq^s<(n_9_Zk#Gae6CVTX zHzAW8hg%{JJuOFsQ0R$L?J~fI*Iuide7I4hvBP<}X}4pt#P+4zEW4?D6e<;L2}46q zJow82I~8Gp0|B4wsFT`RP9~mj@J#Ga8qR%C4h)M6}~~@cA?pD7|!+-%`-}&>{3X^xLeaif# z*9+qsx0SC7xXEUt_LO-+RW(Sn$UR(AZhp*$!^rSFT3jw_(|`fM^uY&;OqtRmo~NqQ z>tPnDg14`;Q&JNhbGg$|{C?D%Oso3dysUdU zWrpX;$48DRZcv(-BlZ^s0iz6q$uj13?&;{jQ4uZ6TDwWvWD?h{R+#<%bpfw?K3HS1 zP{-bV7ixv2O=%H_r`GB9IK;BpWipjkcxh^aMZ)}d!s9_>XXmxc7@4~9ahZ))1K=AMW~CG)+r;V{Phe(d-8x;FCl8$RK02?U^0kT0%8-M4|JaUvSU zvm4yR-`ClRLo5ra&RA!YNyJ<(bPNs_dal+JYBM_)cl!Iu&GYAxRlvm^0MIE2P>X!q zcX0t^!sEdl%jQ~{mSmU7ghqye(e197)~@D%zgTJyZXX!f@MZzE*$%$XPMjbU9H)rh zt&TtdEfV!cEiN6$ltNYloD2qW+wI0tj_cZ}_Lc-Vn@pbiTU*=kq5`JlRkANjPI(+~ z{z~_+vFsE3y`uTxLZ!xFxcK77A1lHiPsd_Nh_uzGRJhq>5;q+V9P#-!4Ywq~`2xt% z_uhk60X@kRRXUY@qe0cE&4%Wlo;AynqMGPp8GHS8a{G^els)K@(icmqy}Z*25CY5K zV96aARJn(p#$JD&O#St*vi4JJbUOS?JYKPgS}M4}SIu_3(}{E>f@9HWS?x7tPr5ts zyWdg9Z+=r&bDc@05UaA2{VQtQV{i|#Y*)GODat6r_Fw#>tEsI`R><*C2vb!$%)8h$ z=gso5Xq4JaI#f}>YJ=h8p^rY29rBI-eo<@GQkv#$GKo=!Dck2(1aKbmv8z{QHP@L` z3cS;aY=!PItQ6dZSPY{!8|qCaQQ?XTxY#GRzx5W>GEMHe%W)WG7{x=*!xcgy{0~jT z*xM^QTv4Lv)gC>{9>`W?!E1jK+DQ6NwaBpA$f45q(qq7r-M@Fbh!mYRl7fUi2B%i$T26kw* zvS{-@hJiT{ki}#ZLXeI`F#dxdfDi&pAh6;ogsRivVlg-z)W438ckMiVdedDKV#qkl z;%{a%CM3L*Fq=x@_PKMo-Peb|GYs_I-8eilLP-TzQVlMa*>ITLId=|suU^F?8S2;? zoeu3T7mRLqMKn5_O5vW*htGfdQ&1FzwwGUq=~usklwf5ATnu{rhd;#KYu9A!+BDhi z&@a8psby!E4xUZoy(U2wOUr#E5Vfa0L^qQBBw3 + +CodeMirror + + + + + + + + + + + + + + + + + +
+ +
+

CodeMirror is a versatile text editor + implemented in JavaScript for the browser. It is specialized for + editing code, and comes with a number of language modes and addons + that implement more advanced editing functionality.

+ +

A rich programming API and a + CSS theming system are + available for customizing CodeMirror to fit your application, and + extending it with new functionality.

+
+ +
+

This is CodeMirror

+
+
+ + +
+
+ + + +
+
+ Get the current version: 5.13.
+ You can see the code or
+ read the release notes.
+ There is a minification helper. +
+
+ Software needs maintenance,
+ maintainers need to subsist.
+ Current funding status =
+ You can help per month or + once. +
+ + +
+
+
+ +
+ +
+

Features

+ +
+ +
+

Community

+ +

CodeMirror is an open-source project shared under + an MIT license. It is the editor used in the + dev tools for + both Firefox + and Chrome, Light + Table, Adobe + Brackets, Bitbucket, + and many other projects.

+ +

Development and bug tracking happens + on github + (alternate git + repository). + Please read these + pointers before submitting a bug. Use pull requests to submit + patches. All contributions must be released under the same MIT + license that CodeMirror uses.

+ +

Discussion around the project is done on + a discussion forum. + There is also + the codemirror-announce + list, which is only used for major announcements (such as new + versions). If needed, you can + contact the maintainer + directly. We aim to be an inclusive, welcoming community. To make + that explicit, we have + a code of + conduct that applies to communication around the project.

+ +

A list of CodeMirror-related software that is not part of the + main distribution is maintained + on our + wiki. Feel free to add your project.

+
+ +
+

Browser support

+

The desktop versions of the following browsers, + in standards mode (HTML5 <!doctype html> + recommended) are supported:

+ + + + + + +
Firefoxversion 4 and up
Chromeany version
Safariversion 5.2 and up
Internet Explorerversion 8 and up
Operaversion 9 and up
+

Support for modern mobile browsers is experimental. Recent + versions of the iOS browser and Chrome on Android should work + pretty well.

+
+ +
diff --git a/www/code/codemirror-5.13.2/keymap/emacs.js b/www/code/codemirror-5.13.2/keymap/emacs.js new file mode 100644 index 000000000..3eec1e576 --- /dev/null +++ b/www/code/codemirror-5.13.2/keymap/emacs.js @@ -0,0 +1,412 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(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"; + + var Pos = CodeMirror.Pos; + function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } + + // Kill 'ring' + + var killRing = []; + function addToRing(str) { + killRing.push(str); + if (killRing.length > 50) killRing.shift(); + } + function growRingTop(str) { + if (!killRing.length) return addToRing(str); + killRing[killRing.length - 1] += str; + } + function getFromRing(n) { return killRing[killRing.length - (n ? Math.min(n, 1) : 1)] || ""; } + function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); } + + var lastKill = null; + + function kill(cm, from, to, mayGrow, text) { + if (text == null) text = cm.getRange(from, to); + + if (mayGrow && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen)) + growRingTop(text); + else + addToRing(text); + cm.replaceRange("", from, to, "+delete"); + + if (mayGrow) lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()}; + else lastKill = null; + } + + // Boundaries of various units + + function byChar(cm, pos, dir) { + return cm.findPosH(pos, dir, "char", true); + } + + function byWord(cm, pos, dir) { + return cm.findPosH(pos, dir, "word", true); + } + + function byLine(cm, pos, dir) { + return cm.findPosV(pos, dir, "line", cm.doc.sel.goalColumn); + } + + function byPage(cm, pos, dir) { + return cm.findPosV(pos, dir, "page", cm.doc.sel.goalColumn); + } + + function byParagraph(cm, pos, dir) { + var no = pos.line, line = cm.getLine(no); + var sawText = /\S/.test(dir < 0 ? line.slice(0, pos.ch) : line.slice(pos.ch)); + var fst = cm.firstLine(), lst = cm.lastLine(); + for (;;) { + no += dir; + if (no < fst || no > lst) + return cm.clipPos(Pos(no - dir, dir < 0 ? 0 : null)); + line = cm.getLine(no); + var hasText = /\S/.test(line); + if (hasText) sawText = true; + else if (sawText) return Pos(no, 0); + } + } + + function bySentence(cm, pos, dir) { + var line = pos.line, ch = pos.ch; + var text = cm.getLine(pos.line), sawWord = false; + for (;;) { + var next = text.charAt(ch + (dir < 0 ? -1 : 0)); + if (!next) { // End/beginning of line reached + if (line == (dir < 0 ? cm.firstLine() : cm.lastLine())) return Pos(line, ch); + text = cm.getLine(line + dir); + if (!/\S/.test(text)) return Pos(line, ch); + line += dir; + ch = dir < 0 ? text.length : 0; + continue; + } + if (sawWord && /[!?.]/.test(next)) return Pos(line, ch + (dir > 0 ? 1 : 0)); + if (!sawWord) sawWord = /\w/.test(next); + ch += dir; + } + } + + function byExpr(cm, pos, dir) { + var wrap; + if (cm.findMatchingBracket && (wrap = cm.findMatchingBracket(pos, true)) + && wrap.match && (wrap.forward ? 1 : -1) == dir) + return dir > 0 ? Pos(wrap.to.line, wrap.to.ch + 1) : wrap.to; + + for (var first = true;; first = false) { + var token = cm.getTokenAt(pos); + var after = Pos(pos.line, dir < 0 ? token.start : token.end); + if (first && dir > 0 && token.end == pos.ch || !/\w/.test(token.string)) { + var newPos = cm.findPosH(after, dir, "char"); + if (posEq(after, newPos)) return pos; + else pos = newPos; + } else { + return after; + } + } + } + + // Prefixes (only crudely supported) + + function getPrefix(cm, precise) { + var digits = cm.state.emacsPrefix; + if (!digits) return precise ? null : 1; + clearPrefix(cm); + return digits == "-" ? -1 : Number(digits); + } + + function repeated(cmd) { + var f = typeof cmd == "string" ? function(cm) { cm.execCommand(cmd); } : cmd; + return function(cm) { + var prefix = getPrefix(cm); + f(cm); + for (var i = 1; i < prefix; ++i) f(cm); + }; + } + + function findEnd(cm, pos, by, dir) { + var prefix = getPrefix(cm); + if (prefix < 0) { dir = -dir; prefix = -prefix; } + for (var i = 0; i < prefix; ++i) { + var newPos = by(cm, pos, dir); + if (posEq(newPos, pos)) break; + pos = newPos; + } + return pos; + } + + function move(by, dir) { + var f = function(cm) { + cm.extendSelection(findEnd(cm, cm.getCursor(), by, dir)); + }; + f.motion = true; + return f; + } + + function killTo(cm, by, dir) { + var selections = cm.listSelections(), cursor; + var i = selections.length; + while (i--) { + cursor = selections[i].head; + kill(cm, cursor, findEnd(cm, cursor, by, dir), true); + } + } + + function killRegion(cm) { + if (cm.somethingSelected()) { + var selections = cm.listSelections(), selection; + var i = selections.length; + while (i--) { + selection = selections[i]; + kill(cm, selection.anchor, selection.head); + } + return true; + } + } + + function addPrefix(cm, digit) { + if (cm.state.emacsPrefix) { + if (digit != "-") cm.state.emacsPrefix += digit; + return; + } + // Not active yet + cm.state.emacsPrefix = digit; + cm.on("keyHandled", maybeClearPrefix); + cm.on("inputRead", maybeDuplicateInput); + } + + var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true}; + + function maybeClearPrefix(cm, arg) { + if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg)) + clearPrefix(cm); + } + + function clearPrefix(cm) { + cm.state.emacsPrefix = null; + cm.off("keyHandled", maybeClearPrefix); + cm.off("inputRead", maybeDuplicateInput); + } + + function maybeDuplicateInput(cm, event) { + var dup = getPrefix(cm); + if (dup > 1 && event.origin == "+input") { + var one = event.text.join("\n"), txt = ""; + for (var i = 1; i < dup; ++i) txt += one; + cm.replaceSelection(txt); + } + } + + function addPrefixMap(cm) { + cm.state.emacsPrefixMap = true; + cm.addKeyMap(prefixMap); + cm.on("keyHandled", maybeRemovePrefixMap); + cm.on("inputRead", maybeRemovePrefixMap); + } + + function maybeRemovePrefixMap(cm, arg) { + if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return; + cm.removeKeyMap(prefixMap); + cm.state.emacsPrefixMap = false; + cm.off("keyHandled", maybeRemovePrefixMap); + cm.off("inputRead", maybeRemovePrefixMap); + } + + // Utilities + + function setMark(cm) { + cm.setCursor(cm.getCursor()); + cm.setExtending(!cm.getExtending()); + cm.on("change", function() { cm.setExtending(false); }); + } + + function clearMark(cm) { + cm.setExtending(false); + cm.setCursor(cm.getCursor()); + } + + function getInput(cm, msg, f) { + if (cm.openDialog) + cm.openDialog(msg + ": ", f, {bottom: true}); + else + f(prompt(msg, "")); + } + + function operateOnWord(cm, op) { + var start = cm.getCursor(), end = cm.findPosH(start, 1, "word"); + cm.replaceRange(op(cm.getRange(start, end)), start, end); + cm.setCursor(end); + } + + function toEnclosingExpr(cm) { + var pos = cm.getCursor(), line = pos.line, ch = pos.ch; + var stack = []; + while (line >= cm.firstLine()) { + var text = cm.getLine(line); + for (var i = ch == null ? text.length : ch; i > 0;) { + var ch = text.charAt(--i); + if (ch == ")") + stack.push("("); + else if (ch == "]") + stack.push("["); + else if (ch == "}") + stack.push("{"); + else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch)) + return cm.extendSelection(Pos(line, i)); + } + --line; ch = null; + } + } + + function quit(cm) { + cm.execCommand("clearSearch"); + clearMark(cm); + } + + // Actual keymap + + var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({ + "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));}, + "Ctrl-K": repeated(function(cm) { + var start = cm.getCursor(), end = cm.clipPos(Pos(start.line)); + var text = cm.getRange(start, end); + if (!/\S/.test(text)) { + text += "\n"; + end = Pos(start.line + 1, 0); + } + kill(cm, start, end, true, text); + }), + "Alt-W": function(cm) { + addToRing(cm.getSelection()); + clearMark(cm); + }, + "Ctrl-Y": function(cm) { + var start = cm.getCursor(); + cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste"); + cm.setSelection(start, cm.getCursor()); + }, + "Alt-Y": function(cm) {cm.replaceSelection(popFromRing(), "around", "paste");}, + + "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark, + + "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1), + "Right": move(byChar, 1), "Left": move(byChar, -1), + "Ctrl-D": function(cm) { killTo(cm, byChar, 1); }, + "Delete": function(cm) { killRegion(cm) || killTo(cm, byChar, 1); }, + "Ctrl-H": function(cm) { killTo(cm, byChar, -1); }, + "Backspace": function(cm) { killRegion(cm) || killTo(cm, byChar, -1); }, + + "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1), + "Alt-D": function(cm) { killTo(cm, byWord, 1); }, + "Alt-Backspace": function(cm) { killTo(cm, byWord, -1); }, + + "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1), + "Down": move(byLine, 1), "Up": move(byLine, -1), + "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "End": "goLineEnd", "Home": "goLineStart", + + "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1), + "PageUp": move(byPage, -1), "PageDown": move(byPage, 1), + + "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1), + + "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1), + "Alt-K": function(cm) { killTo(cm, bySentence, 1); }, + + "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1); }, + "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1); }, + "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1), + + "Shift-Ctrl-Alt-2": function(cm) { + var cursor = cm.getCursor(); + cm.setSelection(findEnd(cm, cursor, byExpr, 1), cursor); + }, + "Ctrl-Alt-T": function(cm) { + var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1); + var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1); + cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) + + cm.getRange(leftStart, leftEnd), leftStart, rightEnd); + }, + "Ctrl-Alt-U": repeated(toEnclosingExpr), + + "Alt-Space": function(cm) { + var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line); + while (from && /\s/.test(text.charAt(from - 1))) --from; + while (to < text.length && /\s/.test(text.charAt(to))) ++to; + cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to)); + }, + "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }), + "Ctrl-T": repeated(function(cm) { + cm.execCommand("transposeChars"); + }), + + "Alt-C": repeated(function(cm) { + operateOnWord(cm, function(w) { + var letter = w.search(/\w/); + if (letter == -1) return w; + return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase(); + }); + }), + "Alt-U": repeated(function(cm) { + operateOnWord(cm, function(w) { return w.toUpperCase(); }); + }), + "Alt-L": repeated(function(cm) { + operateOnWord(cm, function(w) { return w.toLowerCase(); }); + }), + + "Alt-;": "toggleComment", + + "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"), + "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"), + "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", + "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace", + "Alt-/": "autocomplete", + "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto", + + "Alt-G G": function(cm) { + var prefix = getPrefix(cm, true); + if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1); + + getInput(cm, "Goto line", function(str) { + var num; + if (str && !isNaN(num = Number(str)) && num == (num|0) && num > 0) + cm.setCursor(num - 1); + }); + }, + + "Ctrl-X Tab": function(cm) { + cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit")); + }, + "Ctrl-X Ctrl-X": function(cm) { + cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor")); + }, + "Ctrl-X Ctrl-S": "save", + "Ctrl-X Ctrl-W": "save", + "Ctrl-X S": "saveAll", + "Ctrl-X F": "open", + "Ctrl-X U": repeated("undo"), + "Ctrl-X K": "close", + "Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); }, + "Ctrl-X H": "selectAll", + + "Ctrl-Q Tab": repeated("insertTab"), + "Ctrl-U": addPrefixMap + }); + + var prefixMap = {"Ctrl-G": clearPrefix}; + function regPrefix(d) { + prefixMap[d] = function(cm) { addPrefix(cm, d); }; + keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); }; + prefixPreservingKeys["Ctrl-" + d] = true; + } + for (var i = 0; i < 10; ++i) regPrefix(String(i)); + regPrefix("-"); +}); diff --git a/www/code/codemirror-5.13.2/keymap/sublime.js b/www/code/codemirror-5.13.2/keymap/sublime.js new file mode 100644 index 000000000..49db3b871 --- /dev/null +++ b/www/code/codemirror-5.13.2/keymap/sublime.js @@ -0,0 +1,569 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// A rough approximation of Sublime Text's keybindings +// Depends on addon/search/searchcursor.js and optionally addon/dialog/dialogs.js + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/edit/matchbrackets")); + else if (typeof define == "function" && define.amd) // AMD + define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/edit/matchbrackets"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var map = CodeMirror.keyMap.sublime = {fallthrough: "default"}; + var cmds = CodeMirror.commands; + var Pos = CodeMirror.Pos; + var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault; + var ctrl = mac ? "Cmd-" : "Ctrl-"; + + // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that. + function findPosSubword(doc, start, dir) { + if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1)); + var line = doc.getLine(start.line); + if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0)); + var state = "start", type; + for (var pos = start.ch, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) { + var next = line.charAt(dir < 0 ? pos - 1 : pos); + var cat = next != "_" && CodeMirror.isWordChar(next) ? "w" : "o"; + if (cat == "w" && next.toUpperCase() == next) cat = "W"; + if (state == "start") { + if (cat != "o") { state = "in"; type = cat; } + } else if (state == "in") { + if (type != cat) { + if (type == "w" && cat == "W" && dir < 0) pos--; + if (type == "W" && cat == "w" && dir > 0) { type = "w"; continue; } + break; + } + } + } + return Pos(start.line, pos); + } + + function moveSubword(cm, dir) { + cm.extendSelectionsBy(function(range) { + if (cm.display.shift || cm.doc.extend || range.empty()) + return findPosSubword(cm.doc, range.head, dir); + else + return dir < 0 ? range.from() : range.to(); + }); + } + + cmds[map["Alt-Left"] = "goSubwordLeft"] = function(cm) { moveSubword(cm, -1); }; + cmds[map["Alt-Right"] = "goSubwordRight"] = function(cm) { moveSubword(cm, 1); }; + + var scrollLineCombo = mac ? "Ctrl-Alt-" : "Ctrl-"; + + cmds[map[scrollLineCombo + "Up"] = "scrollLineUp"] = function(cm) { + var info = cm.getScrollInfo(); + if (!cm.somethingSelected()) { + var visibleBottomLine = cm.lineAtHeight(info.top + info.clientHeight, "local"); + if (cm.getCursor().line >= visibleBottomLine) + cm.execCommand("goLineUp"); + } + cm.scrollTo(null, info.top - cm.defaultTextHeight()); + }; + cmds[map[scrollLineCombo + "Down"] = "scrollLineDown"] = function(cm) { + var info = cm.getScrollInfo(); + if (!cm.somethingSelected()) { + var visibleTopLine = cm.lineAtHeight(info.top, "local")+1; + if (cm.getCursor().line <= visibleTopLine) + cm.execCommand("goLineDown"); + } + cm.scrollTo(null, info.top + cm.defaultTextHeight()); + }; + + cmds[map["Shift-" + ctrl + "L"] = "splitSelectionByLine"] = function(cm) { + var ranges = cm.listSelections(), lineRanges = []; + for (var i = 0; i < ranges.length; i++) { + var from = ranges[i].from(), to = ranges[i].to(); + for (var line = from.line; line <= to.line; ++line) + if (!(to.line > from.line && line == to.line && to.ch == 0)) + lineRanges.push({anchor: line == from.line ? from : Pos(line, 0), + head: line == to.line ? to : Pos(line)}); + } + cm.setSelections(lineRanges, 0); + }; + + map["Shift-Tab"] = "indentLess"; + + cmds[map["Esc"] = "singleSelectionTop"] = function(cm) { + var range = cm.listSelections()[0]; + cm.setSelection(range.anchor, range.head, {scroll: false}); + }; + + cmds[map[ctrl + "L"] = "selectLine"] = function(cm) { + var ranges = cm.listSelections(), extended = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + extended.push({anchor: Pos(range.from().line, 0), + head: Pos(range.to().line + 1, 0)}); + } + cm.setSelections(extended); + }; + + map["Shift-Ctrl-K"] = "deleteLine"; + + function insertLine(cm, above) { + if (cm.isReadOnly()) return CodeMirror.Pass + cm.operation(function() { + var len = cm.listSelections().length, newSelection = [], last = -1; + for (var i = 0; i < len; i++) { + var head = cm.listSelections()[i].head; + if (head.line <= last) continue; + var at = Pos(head.line + (above ? 0 : 1), 0); + cm.replaceRange("\n", at, null, "+insertLine"); + cm.indentLine(at.line, null, true); + newSelection.push({head: at, anchor: at}); + last = head.line + 1; + } + cm.setSelections(newSelection); + }); + } + + cmds[map[ctrl + "Enter"] = "insertLineAfter"] = function(cm) { return insertLine(cm, false); }; + + cmds[map["Shift-" + ctrl + "Enter"] = "insertLineBefore"] = function(cm) { return insertLine(cm, true); }; + + function wordAt(cm, pos) { + var start = pos.ch, end = start, line = cm.getLine(pos.line); + while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start; + while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end; + return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)}; + } + + cmds[map[ctrl + "D"] = "selectNextOccurrence"] = function(cm) { + var from = cm.getCursor("from"), to = cm.getCursor("to"); + var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel; + if (CodeMirror.cmpPos(from, to) == 0) { + var word = wordAt(cm, from); + if (!word.word) return; + cm.setSelection(word.from, word.to); + fullWord = true; + } else { + var text = cm.getRange(from, to); + var query = fullWord ? new RegExp("\\b" + text + "\\b") : text; + var cur = cm.getSearchCursor(query, to); + if (cur.findNext()) { + cm.addSelection(cur.from(), cur.to()); + } else { + cur = cm.getSearchCursor(query, Pos(cm.firstLine(), 0)); + if (cur.findNext()) + cm.addSelection(cur.from(), cur.to()); + } + } + if (fullWord) + cm.state.sublimeFindFullWord = cm.doc.sel; + }; + + var mirror = "(){}[]"; + function selectBetweenBrackets(cm) { + var pos = cm.getCursor(), opening = cm.scanForBracket(pos, -1); + if (!opening) return; + for (;;) { + var closing = cm.scanForBracket(pos, 1); + if (!closing) return; + if (closing.ch == mirror.charAt(mirror.indexOf(opening.ch) + 1)) { + cm.setSelection(Pos(opening.pos.line, opening.pos.ch + 1), closing.pos, false); + return true; + } + pos = Pos(closing.pos.line, closing.pos.ch + 1); + } + } + + cmds[map["Shift-" + ctrl + "Space"] = "selectScope"] = function(cm) { + selectBetweenBrackets(cm) || cm.execCommand("selectAll"); + }; + cmds[map["Shift-" + ctrl + "M"] = "selectBetweenBrackets"] = function(cm) { + if (!selectBetweenBrackets(cm)) return CodeMirror.Pass; + }; + + cmds[map[ctrl + "M"] = "goToBracket"] = function(cm) { + cm.extendSelectionsBy(function(range) { + var next = cm.scanForBracket(range.head, 1); + if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos; + var prev = cm.scanForBracket(range.head, -1); + return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head; + }); + }; + + var swapLineCombo = mac ? "Cmd-Ctrl-" : "Shift-Ctrl-"; + + cmds[map[swapLineCombo + "Up"] = "swapLineUp"] = function(cm) { + if (cm.isReadOnly()) return CodeMirror.Pass + var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i], from = range.from().line - 1, to = range.to().line; + newSels.push({anchor: Pos(range.anchor.line - 1, range.anchor.ch), + head: Pos(range.head.line - 1, range.head.ch)}); + if (range.to().ch == 0 && !range.empty()) --to; + if (from > at) linesToMove.push(from, to); + else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to; + at = to; + } + cm.operation(function() { + for (var i = 0; i < linesToMove.length; i += 2) { + var from = linesToMove[i], to = linesToMove[i + 1]; + var line = cm.getLine(from); + cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine"); + if (to > cm.lastLine()) + cm.replaceRange("\n" + line, Pos(cm.lastLine()), null, "+swapLine"); + else + cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine"); + } + cm.setSelections(newSels); + cm.scrollIntoView(); + }); + }; + + cmds[map[swapLineCombo + "Down"] = "swapLineDown"] = function(cm) { + if (cm.isReadOnly()) return CodeMirror.Pass + var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1; + for (var i = ranges.length - 1; i >= 0; i--) { + var range = ranges[i], from = range.to().line + 1, to = range.from().line; + if (range.to().ch == 0 && !range.empty()) from--; + if (from < at) linesToMove.push(from, to); + else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to; + at = to; + } + cm.operation(function() { + for (var i = linesToMove.length - 2; i >= 0; i -= 2) { + var from = linesToMove[i], to = linesToMove[i + 1]; + var line = cm.getLine(from); + if (from == cm.lastLine()) + cm.replaceRange("", Pos(from - 1), Pos(from), "+swapLine"); + else + cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine"); + cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine"); + } + cm.scrollIntoView(); + }); + }; + + cmds[map[ctrl + "/"] = "toggleCommentIndented"] = function(cm) { + cm.toggleComment({ indent: true }); + } + + cmds[map[ctrl + "J"] = "joinLines"] = function(cm) { + var ranges = cm.listSelections(), joined = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i], from = range.from(); + var start = from.line, end = range.to().line; + while (i < ranges.length - 1 && ranges[i + 1].from().line == end) + end = ranges[++i].to().line; + joined.push({start: start, end: end, anchor: !range.empty() && from}); + } + cm.operation(function() { + var offset = 0, ranges = []; + for (var i = 0; i < joined.length; i++) { + var obj = joined[i]; + var anchor = obj.anchor && Pos(obj.anchor.line - offset, obj.anchor.ch), head; + for (var line = obj.start; line <= obj.end; line++) { + var actual = line - offset; + if (line == obj.end) head = Pos(actual, cm.getLine(actual).length + 1); + if (actual < cm.lastLine()) { + cm.replaceRange(" ", Pos(actual), Pos(actual + 1, /^\s*/.exec(cm.getLine(actual + 1))[0].length)); + ++offset; + } + } + ranges.push({anchor: anchor || head, head: head}); + } + cm.setSelections(ranges, 0); + }); + }; + + cmds[map["Shift-" + ctrl + "D"] = "duplicateLine"] = function(cm) { + cm.operation(function() { + var rangeCount = cm.listSelections().length; + for (var i = 0; i < rangeCount; i++) { + var range = cm.listSelections()[i]; + if (range.empty()) + cm.replaceRange(cm.getLine(range.head.line) + "\n", Pos(range.head.line, 0)); + else + cm.replaceRange(cm.getRange(range.from(), range.to()), range.from()); + } + cm.scrollIntoView(); + }); + }; + + map[ctrl + "T"] = "transposeChars"; + + function sortLines(cm, caseSensitive) { + if (cm.isReadOnly()) return CodeMirror.Pass + var ranges = cm.listSelections(), toSort = [], selected; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (range.empty()) continue; + var from = range.from().line, to = range.to().line; + while (i < ranges.length - 1 && ranges[i + 1].from().line == to) + to = range[++i].to().line; + toSort.push(from, to); + } + if (toSort.length) selected = true; + else toSort.push(cm.firstLine(), cm.lastLine()); + + cm.operation(function() { + var ranges = []; + for (var i = 0; i < toSort.length; i += 2) { + var from = toSort[i], to = toSort[i + 1]; + var start = Pos(from, 0), end = Pos(to); + var lines = cm.getRange(start, end, false); + if (caseSensitive) + lines.sort(); + else + lines.sort(function(a, b) { + var au = a.toUpperCase(), bu = b.toUpperCase(); + if (au != bu) { a = au; b = bu; } + return a < b ? -1 : a == b ? 0 : 1; + }); + cm.replaceRange(lines, start, end); + if (selected) ranges.push({anchor: start, head: end}); + } + if (selected) cm.setSelections(ranges, 0); + }); + } + + cmds[map["F9"] = "sortLines"] = function(cm) { sortLines(cm, true); }; + cmds[map[ctrl + "F9"] = "sortLinesInsensitive"] = function(cm) { sortLines(cm, false); }; + + cmds[map["F2"] = "nextBookmark"] = function(cm) { + var marks = cm.state.sublimeBookmarks; + if (marks) while (marks.length) { + var current = marks.shift(); + var found = current.find(); + if (found) { + marks.push(current); + return cm.setSelection(found.from, found.to); + } + } + }; + + cmds[map["Shift-F2"] = "prevBookmark"] = function(cm) { + var marks = cm.state.sublimeBookmarks; + if (marks) while (marks.length) { + marks.unshift(marks.pop()); + var found = marks[marks.length - 1].find(); + if (!found) + marks.pop(); + else + return cm.setSelection(found.from, found.to); + } + }; + + cmds[map[ctrl + "F2"] = "toggleBookmark"] = function(cm) { + var ranges = cm.listSelections(); + var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []); + for (var i = 0; i < ranges.length; i++) { + var from = ranges[i].from(), to = ranges[i].to(); + var found = cm.findMarks(from, to); + for (var j = 0; j < found.length; j++) { + if (found[j].sublimeBookmark) { + found[j].clear(); + for (var k = 0; k < marks.length; k++) + if (marks[k] == found[j]) + marks.splice(k--, 1); + break; + } + } + if (j == found.length) + marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false})); + } + }; + + cmds[map["Shift-" + ctrl + "F2"] = "clearBookmarks"] = function(cm) { + var marks = cm.state.sublimeBookmarks; + if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear(); + marks.length = 0; + }; + + cmds[map["Alt-F2"] = "selectBookmarks"] = function(cm) { + var marks = cm.state.sublimeBookmarks, ranges = []; + if (marks) for (var i = 0; i < marks.length; i++) { + var found = marks[i].find(); + if (!found) + marks.splice(i--, 0); + else + ranges.push({anchor: found.from, head: found.to}); + } + if (ranges.length) + cm.setSelections(ranges, 0); + }; + + map["Alt-Q"] = "wrapLines"; + + var cK = ctrl + "K "; + + function modifyWordOrSelection(cm, mod) { + cm.operation(function() { + var ranges = cm.listSelections(), indices = [], replacements = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (range.empty()) { indices.push(i); replacements.push(""); } + else replacements.push(mod(cm.getRange(range.from(), range.to()))); + } + cm.replaceSelections(replacements, "around", "case"); + for (var i = indices.length - 1, at; i >= 0; i--) { + var range = ranges[indices[i]]; + if (at && CodeMirror.cmpPos(range.head, at) > 0) continue; + var word = wordAt(cm, range.head); + at = word.from; + cm.replaceRange(mod(word.word), word.from, word.to); + } + }); + } + + map[cK + ctrl + "Backspace"] = "delLineLeft"; + + cmds[map["Backspace"] = "smartBackspace"] = function(cm) { + if (cm.somethingSelected()) return CodeMirror.Pass; + + var cursor = cm.getCursor(); + var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor); + var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize")); + var indentUnit = cm.getOption("indentUnit"); + + if (toStartOfLine && !/\S/.test(toStartOfLine) && column % indentUnit == 0) { + var prevIndent = new Pos(cursor.line, + CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit)); + + // If no smart delete is happening (due to tab sizing) just do a regular delete + if (prevIndent.ch == cursor.ch) return CodeMirror.Pass; + + return cm.replaceRange("", prevIndent, cursor, "+delete"); + } else { + return CodeMirror.Pass; + } + }; + + cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) { + cm.operation(function() { + var ranges = cm.listSelections(); + for (var i = ranges.length - 1; i >= 0; i--) + cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete"); + cm.scrollIntoView(); + }); + }; + + cmds[map[cK + ctrl + "U"] = "upcaseAtCursor"] = function(cm) { + modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); }); + }; + cmds[map[cK + ctrl + "L"] = "downcaseAtCursor"] = function(cm) { + modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); }); + }; + + cmds[map[cK + ctrl + "Space"] = "setSublimeMark"] = function(cm) { + if (cm.state.sublimeMark) cm.state.sublimeMark.clear(); + cm.state.sublimeMark = cm.setBookmark(cm.getCursor()); + }; + cmds[map[cK + ctrl + "A"] = "selectToSublimeMark"] = function(cm) { + var found = cm.state.sublimeMark && cm.state.sublimeMark.find(); + if (found) cm.setSelection(cm.getCursor(), found); + }; + cmds[map[cK + ctrl + "W"] = "deleteToSublimeMark"] = function(cm) { + var found = cm.state.sublimeMark && cm.state.sublimeMark.find(); + if (found) { + var from = cm.getCursor(), to = found; + if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; } + cm.state.sublimeKilled = cm.getRange(from, to); + cm.replaceRange("", from, to); + } + }; + cmds[map[cK + ctrl + "X"] = "swapWithSublimeMark"] = function(cm) { + var found = cm.state.sublimeMark && cm.state.sublimeMark.find(); + if (found) { + cm.state.sublimeMark.clear(); + cm.state.sublimeMark = cm.setBookmark(cm.getCursor()); + cm.setCursor(found); + } + }; + cmds[map[cK + ctrl + "Y"] = "sublimeYank"] = function(cm) { + if (cm.state.sublimeKilled != null) + cm.replaceSelection(cm.state.sublimeKilled, null, "paste"); + }; + + map[cK + ctrl + "G"] = "clearBookmarks"; + cmds[map[cK + ctrl + "C"] = "showInCenter"] = function(cm) { + var pos = cm.cursorCoords(null, "local"); + cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2); + }; + + cmds[map["Shift-Alt-Up"] = "selectLinesUpward"] = function(cm) { + cm.operation(function() { + var ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (range.head.line > cm.firstLine()) + cm.addSelection(Pos(range.head.line - 1, range.head.ch)); + } + }); + }; + cmds[map["Shift-Alt-Down"] = "selectLinesDownward"] = function(cm) { + cm.operation(function() { + var ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (range.head.line < cm.lastLine()) + cm.addSelection(Pos(range.head.line + 1, range.head.ch)); + } + }); + }; + + function getTarget(cm) { + var from = cm.getCursor("from"), to = cm.getCursor("to"); + if (CodeMirror.cmpPos(from, to) == 0) { + var word = wordAt(cm, from); + if (!word.word) return; + from = word.from; + to = word.to; + } + return {from: from, to: to, query: cm.getRange(from, to), word: word}; + } + + function findAndGoTo(cm, forward) { + var target = getTarget(cm); + if (!target) return; + var query = target.query; + var cur = cm.getSearchCursor(query, forward ? target.to : target.from); + + if (forward ? cur.findNext() : cur.findPrevious()) { + cm.setSelection(cur.from(), cur.to()); + } else { + cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0) + : cm.clipPos(Pos(cm.lastLine()))); + if (forward ? cur.findNext() : cur.findPrevious()) + cm.setSelection(cur.from(), cur.to()); + else if (target.word) + cm.setSelection(target.from, target.to); + } + }; + cmds[map[ctrl + "F3"] = "findUnder"] = function(cm) { findAndGoTo(cm, true); }; + cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); }; + cmds[map["Alt-F3"] = "findAllUnder"] = function(cm) { + var target = getTarget(cm); + if (!target) return; + var cur = cm.getSearchCursor(target.query); + var matches = []; + var primaryIndex = -1; + while (cur.findNext()) { + matches.push({anchor: cur.from(), head: cur.to()}); + if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch) + primaryIndex++; + } + cm.setSelections(matches, primaryIndex); + }; + + map["Shift-" + ctrl + "["] = "fold"; + map["Shift-" + ctrl + "]"] = "unfold"; + map[cK + ctrl + "0"] = map[cK + ctrl + "j"] = "unfoldAll"; + + map[ctrl + "I"] = "findIncremental"; + map["Shift-" + ctrl + "I"] = "findIncrementalReverse"; + map[ctrl + "H"] = "replace"; + map["F3"] = "findNext"; + map["Shift-F3"] = "findPrev"; + + CodeMirror.normalizeKeyMap(map); +}); diff --git a/www/code/codemirror-5.13.2/keymap/vim.js b/www/code/codemirror-5.13.2/keymap/vim.js new file mode 100644 index 000000000..5ed2d9bf7 --- /dev/null +++ b/www/code/codemirror-5.13.2/keymap/vim.js @@ -0,0 +1,5066 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +/** + * Supported keybindings: + * Too many to list. Refer to defaultKeyMap below. + * + * Supported Ex commands: + * Refer to defaultExCommandMap below. + * + * Registers: unnamed, -, a-z, A-Z, 0-9 + * (Does not respect the special case for number registers when delete + * operator is made with these commands: %, (, ), , /, ?, n, N, {, } ) + * TODO: Implement the remaining registers. + * + * Marks: a-z, A-Z, and 0-9 + * TODO: Implement the remaining special marks. They have more complex + * behavior. + * + * Events: + * 'vim-mode-change' - raised on the editor anytime the current mode changes, + * Event object: {mode: "visual", subMode: "linewise"} + * + * Code structure: + * 1. Default keymap + * 2. Variable declarations and short basic helpers + * 3. Instance (External API) implementation + * 4. Internal state tracking objects (input state, counter) implementation + * and instanstiation + * 5. Key handler (the main command dispatcher) implementation + * 6. Motion, operator, and action implementations + * 7. Helper functions for the key handler, motions, operators, and actions + * 8. Set up Vim to work as a keymap for CodeMirror. + * 9. Ex command implementations. + */ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog"), require("../addon/edit/matchbrackets.js")); + else if (typeof define == "function" && define.amd) // AMD + define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog", "../addon/edit/matchbrackets"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + 'use strict'; + + var defaultKeymap = [ + // Key to key mapping. This goes first to make it possible to override + // existing mappings. + { keys: '', type: 'keyToKey', toKeys: 'h' }, + { keys: '', type: 'keyToKey', toKeys: 'l' }, + { keys: '', type: 'keyToKey', toKeys: 'k' }, + { keys: '', type: 'keyToKey', toKeys: 'j' }, + { keys: '', type: 'keyToKey', toKeys: 'l' }, + { keys: '', type: 'keyToKey', toKeys: 'h', context: 'normal'}, + { keys: '', type: 'keyToKey', toKeys: 'W' }, + { keys: '', type: 'keyToKey', toKeys: 'B', context: 'normal' }, + { keys: '', type: 'keyToKey', toKeys: 'w' }, + { keys: '', type: 'keyToKey', toKeys: 'b', context: 'normal' }, + { keys: '', type: 'keyToKey', toKeys: 'j' }, + { keys: '', type: 'keyToKey', toKeys: 'k' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' }, + { keys: 's', type: 'keyToKey', toKeys: 'c', context: 'visual'}, + { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' }, + { keys: 'S', type: 'keyToKey', toKeys: 'VdO', context: 'visual' }, + { keys: '', type: 'keyToKey', toKeys: '0' }, + { keys: '', type: 'keyToKey', toKeys: '$' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: 'j^', context: 'normal' }, + // Motions + { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }}, + { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }}, + { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }}, + { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }}, + { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }}, + { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }}, + { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }}, + { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }}, + { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }}, + { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }}, + { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }}, + { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }}, + { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }}, + { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }}, + { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }}, + { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }}, + { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }}, + { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }}, + { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }}, + { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }}, + { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }}, + { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }}, + { keys: '0', type: 'motion', motion: 'moveToStartOfLine' }, + { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }}, + { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }}, + { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }}, + { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }}, + { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }}, + { keys: 'f', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }}, + { keys: 'F', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }}, + { keys: 't', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }}, + { keys: 'T', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }}, + { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }}, + { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }}, + { keys: '\'', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}}, + { keys: '`', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}}, + { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } }, + { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } }, + { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } }, + { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } }, + // the next two aren't motions but must come before more general motion declarations + { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}}, + { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}}, + { keys: ']', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}}, + { keys: '[', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}}, + { keys: '|', type: 'motion', motion: 'moveToColumn'}, + { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'}, + { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'}, + // Operators + { keys: 'd', type: 'operator', operator: 'delete' }, + { keys: 'y', type: 'operator', operator: 'yank' }, + { keys: 'c', type: 'operator', operator: 'change' }, + { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }}, + { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }}, + { keys: 'g~', type: 'operator', operator: 'changeCase' }, + { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true }, + { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true }, + { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }}, + { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }}, + // Operator-Motion dual commands + { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }}, + { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }}, + { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'}, + { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'}, + { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' }, + // Actions + { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }}, + { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }}, + { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }}, + { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }}, + { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' }, + { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' }, + { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' }, + { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' }, + { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' }, + { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' }, + { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' }, + { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' }, + { keys: 'v', type: 'action', action: 'toggleVisualMode' }, + { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }}, + { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }}, + { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }}, + { keys: 'gv', type: 'action', action: 'reselectLastSelection' }, + { keys: 'J', type: 'action', action: 'joinLines', isEdit: true }, + { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }}, + { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }}, + { keys: 'r', type: 'action', action: 'replace', isEdit: true }, + { keys: '@', type: 'action', action: 'replayMacro' }, + { keys: 'q', type: 'action', action: 'enterMacroRecordMode' }, + // Handle Replace-mode as a special case of insert mode. + { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }}, + { keys: 'u', type: 'action', action: 'undo', context: 'normal' }, + { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true }, + { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true }, + { keys: '', type: 'action', action: 'redo' }, + { keys: 'm', type: 'action', action: 'setMark' }, + { keys: '"', type: 'action', action: 'setRegister' }, + { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }}, + { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }}, + { keys: 'z', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }}, + { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: '.', type: 'action', action: 'repeatLastEdit' }, + { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}}, + { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}}, + // Text object motions + { keys: 'a', type: 'motion', motion: 'textObjectManipulation' }, + { keys: 'i', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }}, + // Search + { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }}, + { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }}, + { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, + { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, + { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }}, + { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }}, + // Ex command + { keys: ':', type: 'ex' } + ]; + + /** + * Ex commands + * Care must be taken when adding to the default Ex command map. For any + * pair of commands that have a shared prefix, at least one of their + * shortNames must not match the prefix of the other command. + */ + var defaultExCommandMap = [ + { name: 'colorscheme', shortName: 'colo' }, + { name: 'map' }, + { name: 'imap', shortName: 'im' }, + { name: 'nmap', shortName: 'nm' }, + { name: 'vmap', shortName: 'vm' }, + { name: 'unmap' }, + { name: 'write', shortName: 'w' }, + { name: 'undo', shortName: 'u' }, + { name: 'redo', shortName: 'red' }, + { name: 'set', shortName: 'se' }, + { name: 'set', shortName: 'se' }, + { name: 'setlocal', shortName: 'setl' }, + { name: 'setglobal', shortName: 'setg' }, + { name: 'sort', shortName: 'sor' }, + { name: 'substitute', shortName: 's', possiblyAsync: true }, + { name: 'nohlsearch', shortName: 'noh' }, + { name: 'delmarks', shortName: 'delm' }, + { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true }, + { name: 'global', shortName: 'g' } + ]; + + var Pos = CodeMirror.Pos; + + var Vim = function() { + function enterVimMode(cm) { + cm.setOption('disableInput', true); + cm.setOption('showCursorWhenSelecting', false); + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + cm.on('cursorActivity', onCursorActivity); + maybeInitVimState(cm); + CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); + } + + function leaveVimMode(cm) { + cm.setOption('disableInput', false); + cm.off('cursorActivity', onCursorActivity); + CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); + cm.state.vim = null; + } + + function detachVimMap(cm, next) { + if (this == CodeMirror.keyMap.vim) + CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor"); + + if (!next || next.attach != attachVimMap) + leaveVimMode(cm, false); + } + function attachVimMap(cm, prev) { + if (this == CodeMirror.keyMap.vim) + CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor"); + + if (!prev || prev.attach != attachVimMap) + enterVimMode(cm); + } + + // Deprecated, simply setting the keymap works again. + CodeMirror.defineOption('vimMode', false, function(cm, val, prev) { + if (val && cm.getOption("keyMap") != "vim") + cm.setOption("keyMap", "vim"); + else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap"))) + cm.setOption("keyMap", "default"); + }); + + function cmKey(key, cm) { + if (!cm) { return undefined; } + var vimKey = cmKeyToVimKey(key); + if (!vimKey) { + return false; + } + var cmd = CodeMirror.Vim.findKey(cm, vimKey); + if (typeof cmd == 'function') { + CodeMirror.signal(cm, 'vim-keypress', vimKey); + } + return cmd; + } + + var modifiers = {'Shift': 'S', 'Ctrl': 'C', 'Alt': 'A', 'Cmd': 'D', 'Mod': 'A'}; + var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del'}; + function cmKeyToVimKey(key) { + if (key.charAt(0) == '\'') { + // Keypress character binding of format "'a'" + return key.charAt(1); + } + var pieces = key.split(/-(?!$)/); + var lastPiece = pieces[pieces.length - 1]; + if (pieces.length == 1 && pieces[0].length == 1) { + // No-modifier bindings use literal character bindings above. Skip. + return false; + } else if (pieces.length == 2 && pieces[0] == 'Shift' && lastPiece.length == 1) { + // Ignore Shift+char bindings as they should be handled by literal character. + return false; + } + var hasCharacter = false; + for (var i = 0; i < pieces.length; i++) { + var piece = pieces[i]; + if (piece in modifiers) { pieces[i] = modifiers[piece]; } + else { hasCharacter = true; } + if (piece in specialKeys) { pieces[i] = specialKeys[piece]; } + } + if (!hasCharacter) { + // Vim does not support modifier only keys. + return false; + } + // TODO: Current bindings expect the character to be lower case, but + // it looks like vim key notation uses upper case. + if (isUpperCase(lastPiece)) { + pieces[pieces.length - 1] = lastPiece.toLowerCase(); + } + return '<' + pieces.join('-') + '>'; + } + + function getOnPasteFn(cm) { + var vim = cm.state.vim; + if (!vim.onPasteFn) { + vim.onPasteFn = function() { + if (!vim.insertMode) { + cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); + actions.enterInsertMode(cm, {}, vim); + } + }; + } + return vim.onPasteFn; + } + + var numberRegex = /[\d]/; + var wordCharTest = [CodeMirror.isWordChar, function(ch) { + return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch); + }], bigWordCharTest = [function(ch) { + return /\S/.test(ch); + }]; + function makeKeyRange(start, size) { + var keys = []; + for (var i = start; i < start + size; i++) { + keys.push(String.fromCharCode(i)); + } + return keys; + } + var upperCaseAlphabet = makeKeyRange(65, 26); + var lowerCaseAlphabet = makeKeyRange(97, 26); + var numbers = makeKeyRange(48, 10); + var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']); + var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':', '/']); + + function isLine(cm, line) { + return line >= cm.firstLine() && line <= cm.lastLine(); + } + function isLowerCase(k) { + return (/^[a-z]$/).test(k); + } + function isMatchableSymbol(k) { + return '()[]{}'.indexOf(k) != -1; + } + function isNumber(k) { + return numberRegex.test(k); + } + function isUpperCase(k) { + return (/^[A-Z]$/).test(k); + } + function isWhiteSpaceString(k) { + return (/^\s*$/).test(k); + } + function inArray(val, arr) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == val) { + return true; + } + } + return false; + } + + var options = {}; + function defineOption(name, defaultValue, type, aliases, callback) { + if (defaultValue === undefined && !callback) { + throw Error('defaultValue is required unless callback is provided'); + } + if (!type) { type = 'string'; } + options[name] = { + type: type, + defaultValue: defaultValue, + callback: callback + }; + if (aliases) { + for (var i = 0; i < aliases.length; i++) { + options[aliases[i]] = options[name]; + } + } + if (defaultValue) { + setOption(name, defaultValue); + } + } + + function setOption(name, value, cm, cfg) { + var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; + if (!option) { + throw Error('Unknown option: ' + name); + } + if (option.type == 'boolean') { + if (value && value !== true) { + throw Error('Invalid argument: ' + name + '=' + value); + } else if (value !== false) { + // Boolean options are set to true if value is not defined. + value = true; + } + } + if (option.callback) { + if (scope !== 'local') { + option.callback(value, undefined); + } + if (scope !== 'global' && cm) { + option.callback(value, cm); + } + } else { + if (scope !== 'local') { + option.value = option.type == 'boolean' ? !!value : value; + } + if (scope !== 'global' && cm) { + cm.state.vim.options[name] = {value: value}; + } + } + } + + function getOption(name, cm, cfg) { + var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; + if (!option) { + throw Error('Unknown option: ' + name); + } + if (option.callback) { + var local = cm && option.callback(undefined, cm); + if (scope !== 'global' && local !== undefined) { + return local; + } + if (scope !== 'local') { + return option.callback(); + } + return; + } else { + var local = (scope !== 'global') && (cm && cm.state.vim.options[name]); + return (local || (scope !== 'local') && option || {}).value; + } + } + + defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) { + // Option is local. Do nothing for global. + if (cm === undefined) { + return; + } + // The 'filetype' option proxies to the CodeMirror 'mode' option. + if (name === undefined) { + var mode = cm.getOption('mode'); + return mode == 'null' ? '' : mode; + } else { + var mode = name == '' ? 'null' : name; + cm.setOption('mode', mode); + } + }); + + var createCircularJumpList = function() { + var size = 100; + var pointer = -1; + var head = 0; + var tail = 0; + var buffer = new Array(size); + function add(cm, oldCur, newCur) { + var current = pointer % size; + var curMark = buffer[current]; + function useNextSlot(cursor) { + var next = ++pointer % size; + var trashMark = buffer[next]; + if (trashMark) { + trashMark.clear(); + } + buffer[next] = cm.setBookmark(cursor); + } + if (curMark) { + var markPos = curMark.find(); + // avoid recording redundant cursor position + if (markPos && !cursorEqual(markPos, oldCur)) { + useNextSlot(oldCur); + } + } else { + useNextSlot(oldCur); + } + useNextSlot(newCur); + head = pointer; + tail = pointer - size + 1; + if (tail < 0) { + tail = 0; + } + } + function move(cm, offset) { + pointer += offset; + if (pointer > head) { + pointer = head; + } else if (pointer < tail) { + pointer = tail; + } + var mark = buffer[(size + pointer) % size]; + // skip marks that are temporarily removed from text buffer + if (mark && !mark.find()) { + var inc = offset > 0 ? 1 : -1; + var newCur; + var oldCur = cm.getCursor(); + do { + pointer += inc; + mark = buffer[(size + pointer) % size]; + // skip marks that are the same as current position + if (mark && + (newCur = mark.find()) && + !cursorEqual(oldCur, newCur)) { + break; + } + } while (pointer < head && pointer > tail); + } + return mark; + } + return { + cachedCursor: undefined, //used for # and * jumps + add: add, + move: move + }; + }; + + // Returns an object to track the changes associated insert mode. It + // clones the object that is passed in, or creates an empty object one if + // none is provided. + var createInsertModeChanges = function(c) { + if (c) { + // Copy construction + return { + changes: c.changes, + expectCursorActivityForChange: c.expectCursorActivityForChange + }; + } + return { + // Change list + changes: [], + // Set to true on change, false on cursorActivity. + expectCursorActivityForChange: false + }; + }; + + function MacroModeState() { + this.latestRegister = undefined; + this.isPlaying = false; + this.isRecording = false; + this.replaySearchQueries = []; + this.onRecordingDone = undefined; + this.lastInsertModeChanges = createInsertModeChanges(); + } + MacroModeState.prototype = { + exitMacroRecordMode: function() { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.onRecordingDone) { + macroModeState.onRecordingDone(); // close dialog + } + macroModeState.onRecordingDone = undefined; + macroModeState.isRecording = false; + }, + enterMacroRecordMode: function(cm, registerName) { + var register = + vimGlobalState.registerController.getRegister(registerName); + if (register) { + register.clear(); + this.latestRegister = registerName; + if (cm.openDialog) { + this.onRecordingDone = cm.openDialog( + '(recording)['+registerName+']', null, {bottom:true}); + } + this.isRecording = true; + } + } + }; + + function maybeInitVimState(cm) { + if (!cm.state.vim) { + // Store instance state in the CodeMirror object. + cm.state.vim = { + inputState: new InputState(), + // Vim's input state that triggered the last edit, used to repeat + // motions and operators with '.'. + lastEditInputState: undefined, + // Vim's action command before the last edit, used to repeat actions + // with '.' and insert mode repeat. + lastEditActionCommand: undefined, + // When using jk for navigation, if you move from a longer line to a + // shorter line, the cursor may clip to the end of the shorter line. + // If j is pressed again and cursor goes to the next line, the + // cursor should go back to its horizontal position on the longer + // line if it can. This is to keep track of the horizontal position. + lastHPos: -1, + // Doing the same with screen-position for gj/gk + lastHSPos: -1, + // The last motion command run. Cleared if a non-motion command gets + // executed in between. + lastMotion: null, + marks: {}, + // Mark for rendering fake cursor for visual mode. + fakeCursor: null, + insertMode: false, + // Repeat count for changes made in insert mode, triggered by key + // sequences like 3,i. Only exists when insertMode is true. + insertModeRepeat: undefined, + visualMode: false, + // If we are in visual line mode. No effect if visualMode is false. + visualLine: false, + visualBlock: false, + lastSelection: null, + lastPastedText: null, + sel: {}, + // Buffer-local/window-local values of vim options. + options: {} + }; + } + return cm.state.vim; + } + var vimGlobalState; + function resetVimGlobalState() { + vimGlobalState = { + // The current search query. + searchQuery: null, + // Whether we are searching backwards. + searchIsReversed: false, + // Replace part of the last substituted pattern + lastSubstituteReplacePart: undefined, + jumpList: createCircularJumpList(), + macroModeState: new MacroModeState, + // Recording latest f, t, F or T motion command. + lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''}, + registerController: new RegisterController({}), + // search history buffer + searchHistoryController: new HistoryController({}), + // ex Command history buffer + exCommandHistoryController : new HistoryController({}) + }; + for (var optionName in options) { + var option = options[optionName]; + option.value = option.defaultValue; + } + } + + var lastInsertModeKeyTimer; + var vimApi= { + buildKeyMap: function() { + // TODO: Convert keymap into dictionary format for fast lookup. + }, + // Testing hook, though it might be useful to expose the register + // controller anyways. + getRegisterController: function() { + return vimGlobalState.registerController; + }, + // Testing hook. + resetVimGlobalState_: resetVimGlobalState, + + // Testing hook. + getVimGlobalState_: function() { + return vimGlobalState; + }, + + // Testing hook. + maybeInitVimState_: maybeInitVimState, + + suppressErrorLogging: false, + + InsertModeKey: InsertModeKey, + map: function(lhs, rhs, ctx) { + // Add user defined key bindings. + exCommandDispatcher.map(lhs, rhs, ctx); + }, + unmap: function(lhs, ctx) { + exCommandDispatcher.unmap(lhs, ctx); + }, + // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace + // them, or somehow make them work with the existing CodeMirror setOption/getOption API. + setOption: setOption, + getOption: getOption, + defineOption: defineOption, + defineEx: function(name, prefix, func){ + if (!prefix) { + prefix = name; + } else if (name.indexOf(prefix) !== 0) { + throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered'); + } + exCommands[name]=func; + exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'}; + }, + handleKey: function (cm, key, origin) { + var command = this.findKey(cm, key, origin); + if (typeof command === 'function') { + return command(); + } + }, + /** + * This is the outermost function called by CodeMirror, after keys have + * been mapped to their Vim equivalents. + * + * Finds a command based on the key (and cached keys if there is a + * multi-key sequence). Returns `undefined` if no key is matched, a noop + * function if a partial match is found (multi-key), and a function to + * execute the bound command if a a key is matched. The function always + * returns true. + */ + findKey: function(cm, key, origin) { + var vim = maybeInitVimState(cm); + function handleMacroRecording() { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isRecording) { + if (key == 'q') { + macroModeState.exitMacroRecordMode(); + clearInputState(cm); + return true; + } + if (origin != 'mapping') { + logKey(macroModeState, key); + } + } + } + function handleEsc() { + if (key == '') { + // Clear input state and get back to normal mode. + clearInputState(cm); + if (vim.visualMode) { + exitVisualMode(cm); + } else if (vim.insertMode) { + exitInsertMode(cm); + } + return true; + } + } + function doKeyToKey(keys) { + // TODO: prevent infinite recursion. + var match; + while (keys) { + // Pull off one command key, which is either a single character + // or a special sequence wrapped in '<' and '>', e.g. ''. + match = (/<\w+-.+?>|<\w+>|./).exec(keys); + key = match[0]; + keys = keys.substring(match.index + key.length); + CodeMirror.Vim.handleKey(cm, key, 'mapping'); + } + } + + function handleKeyInsertMode() { + if (handleEsc()) { return true; } + var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key; + var keysAreChars = key.length == 1; + var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert'); + // Need to check all key substrings in insert mode. + while (keys.length > 1 && match.type != 'full') { + var keys = vim.inputState.keyBuffer = keys.slice(1); + var thisMatch = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert'); + if (thisMatch.type != 'none') { match = thisMatch; } + } + if (match.type == 'none') { clearInputState(cm); return false; } + else if (match.type == 'partial') { + if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } + lastInsertModeKeyTimer = window.setTimeout( + function() { if (vim.insertMode && vim.inputState.keyBuffer) { clearInputState(cm); } }, + getOption('insertModeEscKeysTimeout')); + return !keysAreChars; + } + + if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } + if (keysAreChars) { + var here = cm.getCursor(); + cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input'); + } + clearInputState(cm); + return match.command; + } + + function handleKeyNonInsertMode() { + if (handleMacroRecording() || handleEsc()) { return true; }; + + var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key; + if (/^[1-9]\d*$/.test(keys)) { return true; } + + var keysMatcher = /^(\d*)(.*)$/.exec(keys); + if (!keysMatcher) { clearInputState(cm); return false; } + var context = vim.visualMode ? 'visual' : + 'normal'; + var match = commandDispatcher.matchCommand(keysMatcher[2] || keysMatcher[1], defaultKeymap, vim.inputState, context); + if (match.type == 'none') { clearInputState(cm); return false; } + else if (match.type == 'partial') { return true; } + + vim.inputState.keyBuffer = ''; + var keysMatcher = /^(\d*)(.*)$/.exec(keys); + if (keysMatcher[1] && keysMatcher[1] != '0') { + vim.inputState.pushRepeatDigit(keysMatcher[1]); + } + return match.command; + } + + var command; + if (vim.insertMode) { command = handleKeyInsertMode(); } + else { command = handleKeyNonInsertMode(); } + if (command === false) { + return undefined; + } else if (command === true) { + // TODO: Look into using CodeMirror's multi-key handling. + // Return no-op since we are caching the key. Counts as handled, but + // don't want act on it just yet. + return function() {}; + } else { + return function() { + return cm.operation(function() { + cm.curOp.isVimOp = true; + try { + if (command.type == 'keyToKey') { + doKeyToKey(command.toKeys); + } else { + commandDispatcher.processCommand(cm, vim, command); + } + } catch (e) { + // clear VIM state in case it's in a bad state. + cm.state.vim = undefined; + maybeInitVimState(cm); + if (!CodeMirror.Vim.suppressErrorLogging) { + console['log'](e); + } + throw e; + } + return true; + }); + }; + } + }, + handleEx: function(cm, input) { + exCommandDispatcher.processCommand(cm, input); + }, + + defineMotion: defineMotion, + defineAction: defineAction, + defineOperator: defineOperator, + mapCommand: mapCommand, + _mapCommand: _mapCommand, + + defineRegister: defineRegister, + + exitVisualMode: exitVisualMode, + exitInsertMode: exitInsertMode + }; + + // Represents the current input state. + function InputState() { + this.prefixRepeat = []; + this.motionRepeat = []; + + this.operator = null; + this.operatorArgs = null; + this.motion = null; + this.motionArgs = null; + this.keyBuffer = []; // For matching multi-key commands. + this.registerName = null; // Defaults to the unnamed register. + } + InputState.prototype.pushRepeatDigit = function(n) { + if (!this.operator) { + this.prefixRepeat = this.prefixRepeat.concat(n); + } else { + this.motionRepeat = this.motionRepeat.concat(n); + } + }; + InputState.prototype.getRepeat = function() { + var repeat = 0; + if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) { + repeat = 1; + if (this.prefixRepeat.length > 0) { + repeat *= parseInt(this.prefixRepeat.join(''), 10); + } + if (this.motionRepeat.length > 0) { + repeat *= parseInt(this.motionRepeat.join(''), 10); + } + } + return repeat; + }; + + function clearInputState(cm, reason) { + cm.state.vim.inputState = new InputState(); + CodeMirror.signal(cm, 'vim-command-done', reason); + } + + /* + * Register stores information about copy and paste registers. Besides + * text, a register must store whether it is linewise (i.e., when it is + * pasted, should it insert itself into a new line, or should the text be + * inserted at the cursor position.) + */ + function Register(text, linewise, blockwise) { + this.clear(); + this.keyBuffer = [text || '']; + this.insertModeChanges = []; + this.searchQueries = []; + this.linewise = !!linewise; + this.blockwise = !!blockwise; + } + Register.prototype = { + setText: function(text, linewise, blockwise) { + this.keyBuffer = [text || '']; + this.linewise = !!linewise; + this.blockwise = !!blockwise; + }, + pushText: function(text, linewise) { + // if this register has ever been set to linewise, use linewise. + if (linewise) { + if (!this.linewise) { + this.keyBuffer.push('\n'); + } + this.linewise = true; + } + this.keyBuffer.push(text); + }, + pushInsertModeChanges: function(changes) { + this.insertModeChanges.push(createInsertModeChanges(changes)); + }, + pushSearchQuery: function(query) { + this.searchQueries.push(query); + }, + clear: function() { + this.keyBuffer = []; + this.insertModeChanges = []; + this.searchQueries = []; + this.linewise = false; + }, + toString: function() { + return this.keyBuffer.join(''); + } + }; + + /** + * Defines an external register. + * + * The name should be a single character that will be used to reference the register. + * The register should support setText, pushText, clear, and toString(). See Register + * for a reference implementation. + */ + function defineRegister(name, register) { + var registers = vimGlobalState.registerController.registers[name]; + if (!name || name.length != 1) { + throw Error('Register name must be 1 character'); + } + if (registers[name]) { + throw Error('Register already defined ' + name); + } + registers[name] = register; + validRegisters.push(name); + } + + /* + * vim registers allow you to keep many independent copy and paste buffers. + * See http://usevim.com/2012/04/13/registers/ for an introduction. + * + * RegisterController keeps the state of all the registers. An initial + * state may be passed in. The unnamed register '"' will always be + * overridden. + */ + function RegisterController(registers) { + this.registers = registers; + this.unnamedRegister = registers['"'] = new Register(); + registers['.'] = new Register(); + registers[':'] = new Register(); + registers['/'] = new Register(); + } + RegisterController.prototype = { + pushText: function(registerName, operator, text, linewise, blockwise) { + if (linewise && text.charAt(0) == '\n') { + text = text.slice(1) + '\n'; + } + if (linewise && text.charAt(text.length - 1) !== '\n'){ + text += '\n'; + } + // Lowercase and uppercase registers refer to the same register. + // Uppercase just means append. + var register = this.isValidRegister(registerName) ? + this.getRegister(registerName) : null; + // if no register/an invalid register was specified, things go to the + // default registers + if (!register) { + switch (operator) { + case 'yank': + // The 0 register contains the text from the most recent yank. + this.registers['0'] = new Register(text, linewise, blockwise); + break; + case 'delete': + case 'change': + if (text.indexOf('\n') == -1) { + // Delete less than 1 line. Update the small delete register. + this.registers['-'] = new Register(text, linewise); + } else { + // Shift down the contents of the numbered registers and put the + // deleted text into register 1. + this.shiftNumericRegisters_(); + this.registers['1'] = new Register(text, linewise); + } + break; + } + // Make sure the unnamed register is set to what just happened + this.unnamedRegister.setText(text, linewise, blockwise); + return; + } + + // If we've gotten to this point, we've actually specified a register + var append = isUpperCase(registerName); + if (append) { + register.pushText(text, linewise); + } else { + register.setText(text, linewise, blockwise); + } + // The unnamed register always has the same value as the last used + // register. + this.unnamedRegister.setText(register.toString(), linewise); + }, + // Gets the register named @name. If one of @name doesn't already exist, + // create it. If @name is invalid, return the unnamedRegister. + getRegister: function(name) { + if (!this.isValidRegister(name)) { + return this.unnamedRegister; + } + name = name.toLowerCase(); + if (!this.registers[name]) { + this.registers[name] = new Register(); + } + return this.registers[name]; + }, + isValidRegister: function(name) { + return name && inArray(name, validRegisters); + }, + shiftNumericRegisters_: function() { + for (var i = 9; i >= 2; i--) { + this.registers[i] = this.getRegister('' + (i - 1)); + } + } + }; + function HistoryController() { + this.historyBuffer = []; + this.iterator; + this.initialPrefix = null; + } + HistoryController.prototype = { + // the input argument here acts a user entered prefix for a small time + // until we start autocompletion in which case it is the autocompleted. + nextMatch: function (input, up) { + var historyBuffer = this.historyBuffer; + var dir = up ? -1 : 1; + if (this.initialPrefix === null) this.initialPrefix = input; + for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i+= dir) { + var element = historyBuffer[i]; + for (var j = 0; j <= element.length; j++) { + if (this.initialPrefix == element.substring(0, j)) { + this.iterator = i; + return element; + } + } + } + // should return the user input in case we reach the end of buffer. + if (i >= historyBuffer.length) { + this.iterator = historyBuffer.length; + return this.initialPrefix; + } + // return the last autocompleted query or exCommand as it is. + if (i < 0 ) return input; + }, + pushInput: function(input) { + var index = this.historyBuffer.indexOf(input); + if (index > -1) this.historyBuffer.splice(index, 1); + if (input.length) this.historyBuffer.push(input); + }, + reset: function() { + this.initialPrefix = null; + this.iterator = this.historyBuffer.length; + } + }; + var commandDispatcher = { + matchCommand: function(keys, keyMap, inputState, context) { + var matches = commandMatches(keys, keyMap, context, inputState); + if (!matches.full && !matches.partial) { + return {type: 'none'}; + } else if (!matches.full && matches.partial) { + return {type: 'partial'}; + } + + var bestMatch; + for (var i = 0; i < matches.full.length; i++) { + var match = matches.full[i]; + if (!bestMatch) { + bestMatch = match; + } + } + if (bestMatch.keys.slice(-11) == '') { + inputState.selectedCharacter = lastChar(keys); + } + return {type: 'full', command: bestMatch}; + }, + processCommand: function(cm, vim, command) { + vim.inputState.repeatOverride = command.repeatOverride; + switch (command.type) { + case 'motion': + this.processMotion(cm, vim, command); + break; + case 'operator': + this.processOperator(cm, vim, command); + break; + case 'operatorMotion': + this.processOperatorMotion(cm, vim, command); + break; + case 'action': + this.processAction(cm, vim, command); + break; + case 'search': + this.processSearch(cm, vim, command); + break; + case 'ex': + case 'keyToEx': + this.processEx(cm, vim, command); + break; + default: + break; + } + }, + processMotion: function(cm, vim, command) { + vim.inputState.motion = command.motion; + vim.inputState.motionArgs = copyArgs(command.motionArgs); + this.evalInput(cm, vim); + }, + processOperator: function(cm, vim, command) { + var inputState = vim.inputState; + if (inputState.operator) { + if (inputState.operator == command.operator) { + // Typing an operator twice like 'dd' makes the operator operate + // linewise + inputState.motion = 'expandToLine'; + inputState.motionArgs = { linewise: true }; + this.evalInput(cm, vim); + return; + } else { + // 2 different operators in a row doesn't make sense. + clearInputState(cm); + } + } + inputState.operator = command.operator; + inputState.operatorArgs = copyArgs(command.operatorArgs); + if (vim.visualMode) { + // Operating on a selection in visual mode. We don't need a motion. + this.evalInput(cm, vim); + } + }, + processOperatorMotion: function(cm, vim, command) { + var visualMode = vim.visualMode; + var operatorMotionArgs = copyArgs(command.operatorMotionArgs); + if (operatorMotionArgs) { + // Operator motions may have special behavior in visual mode. + if (visualMode && operatorMotionArgs.visualLine) { + vim.visualLine = true; + } + } + this.processOperator(cm, vim, command); + if (!visualMode) { + this.processMotion(cm, vim, command); + } + }, + processAction: function(cm, vim, command) { + var inputState = vim.inputState; + var repeat = inputState.getRepeat(); + var repeatIsExplicit = !!repeat; + var actionArgs = copyArgs(command.actionArgs) || {}; + if (inputState.selectedCharacter) { + actionArgs.selectedCharacter = inputState.selectedCharacter; + } + // Actions may or may not have motions and operators. Do these first. + if (command.operator) { + this.processOperator(cm, vim, command); + } + if (command.motion) { + this.processMotion(cm, vim, command); + } + if (command.motion || command.operator) { + this.evalInput(cm, vim); + } + actionArgs.repeat = repeat || 1; + actionArgs.repeatIsExplicit = repeatIsExplicit; + actionArgs.registerName = inputState.registerName; + clearInputState(cm); + vim.lastMotion = null; + if (command.isEdit) { + this.recordLastEdit(vim, inputState, command); + } + actions[command.action](cm, actionArgs, vim); + }, + processSearch: function(cm, vim, command) { + if (!cm.getSearchCursor) { + // Search depends on SearchCursor. + return; + } + var forward = command.searchArgs.forward; + var wholeWordOnly = command.searchArgs.wholeWordOnly; + getSearchState(cm).setReversed(!forward); + var promptPrefix = (forward) ? '/' : '?'; + var originalQuery = getSearchState(cm).getQuery(); + var originalScrollPos = cm.getScrollInfo(); + function handleQuery(query, ignoreCase, smartCase) { + vimGlobalState.searchHistoryController.pushInput(query); + vimGlobalState.searchHistoryController.reset(); + try { + updateSearchQuery(cm, query, ignoreCase, smartCase); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + query); + clearInputState(cm); + return; + } + commandDispatcher.processMotion(cm, vim, { + type: 'motion', + motion: 'findNext', + motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist } + }); + } + function onPromptClose(query) { + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + handleQuery(query, true /** ignoreCase */, true /** smartCase */); + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isRecording) { + logSearchQuery(macroModeState, query); + } + } + function onPromptKeyUp(e, query, close) { + var keyName = CodeMirror.keyName(e), up; + if (keyName == 'Up' || keyName == 'Down') { + up = keyName == 'Up' ? true : false; + query = vimGlobalState.searchHistoryController.nextMatch(query, up) || ''; + close(query); + } else { + if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift') + vimGlobalState.searchHistoryController.reset(); + } + var parsedQuery; + try { + parsedQuery = updateSearchQuery(cm, query, + true /** ignoreCase */, true /** smartCase */); + } catch (e) { + // Swallow bad regexes for incremental search. + } + if (parsedQuery) { + cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30); + } else { + clearSearchHighlight(cm); + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + } + } + function onPromptKeyDown(e, query, close) { + var keyName = CodeMirror.keyName(e); + if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' || + (keyName == 'Backspace' && query == '')) { + vimGlobalState.searchHistoryController.pushInput(query); + vimGlobalState.searchHistoryController.reset(); + updateSearchQuery(cm, originalQuery); + clearSearchHighlight(cm); + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + CodeMirror.e_stop(e); + clearInputState(cm); + close(); + cm.focus(); + } else if (keyName == 'Ctrl-U') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); + } + } + switch (command.searchArgs.querySrc) { + case 'prompt': + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { + var query = macroModeState.replaySearchQueries.shift(); + handleQuery(query, true /** ignoreCase */, false /** smartCase */); + } else { + showPrompt(cm, { + onClose: onPromptClose, + prefix: promptPrefix, + desc: searchPromptDesc, + onKeyUp: onPromptKeyUp, + onKeyDown: onPromptKeyDown + }); + } + break; + case 'wordUnderCursor': + var word = expandWordUnderCursor(cm, false /** inclusive */, + true /** forward */, false /** bigWord */, + true /** noSymbol */); + var isKeyword = true; + if (!word) { + word = expandWordUnderCursor(cm, false /** inclusive */, + true /** forward */, false /** bigWord */, + false /** noSymbol */); + isKeyword = false; + } + if (!word) { + return; + } + var query = cm.getLine(word.start.line).substring(word.start.ch, + word.end.ch); + if (isKeyword && wholeWordOnly) { + query = '\\b' + query + '\\b'; + } else { + query = escapeRegex(query); + } + + // cachedCursor is used to save the old position of the cursor + // when * or # causes vim to seek for the nearest word and shift + // the cursor before entering the motion. + vimGlobalState.jumpList.cachedCursor = cm.getCursor(); + cm.setCursor(word.start); + + handleQuery(query, true /** ignoreCase */, false /** smartCase */); + break; + } + }, + processEx: function(cm, vim, command) { + function onPromptClose(input) { + // Give the prompt some time to close so that if processCommand shows + // an error, the elements don't overlap. + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); + exCommandDispatcher.processCommand(cm, input); + } + function onPromptKeyDown(e, input, close) { + var keyName = CodeMirror.keyName(e), up; + if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' || + (keyName == 'Backspace' && input == '')) { + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); + CodeMirror.e_stop(e); + clearInputState(cm); + close(); + cm.focus(); + } + if (keyName == 'Up' || keyName == 'Down') { + up = keyName == 'Up' ? true : false; + input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || ''; + close(input); + } else if (keyName == 'Ctrl-U') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); + } else { + if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift') + vimGlobalState.exCommandHistoryController.reset(); + } + } + if (command.type == 'keyToEx') { + // Handle user defined Ex to Ex mappings + exCommandDispatcher.processCommand(cm, command.exArgs.input); + } else { + if (vim.visualMode) { + showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>', + onKeyDown: onPromptKeyDown}); + } else { + showPrompt(cm, { onClose: onPromptClose, prefix: ':', + onKeyDown: onPromptKeyDown}); + } + } + }, + evalInput: function(cm, vim) { + // If the motion comand is set, execute both the operator and motion. + // Otherwise return. + var inputState = vim.inputState; + var motion = inputState.motion; + var motionArgs = inputState.motionArgs || {}; + var operator = inputState.operator; + var operatorArgs = inputState.operatorArgs || {}; + var registerName = inputState.registerName; + var sel = vim.sel; + // TODO: Make sure cm and vim selections are identical outside visual mode. + var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head')); + var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor')); + var oldHead = copyCursor(origHead); + var oldAnchor = copyCursor(origAnchor); + var newHead, newAnchor; + var repeat; + if (operator) { + this.recordLastEdit(vim, inputState); + } + if (inputState.repeatOverride !== undefined) { + // If repeatOverride is specified, that takes precedence over the + // input state's repeat. Used by Ex mode and can be user defined. + repeat = inputState.repeatOverride; + } else { + repeat = inputState.getRepeat(); + } + if (repeat > 0 && motionArgs.explicitRepeat) { + motionArgs.repeatIsExplicit = true; + } else if (motionArgs.noRepeat || + (!motionArgs.explicitRepeat && repeat === 0)) { + repeat = 1; + motionArgs.repeatIsExplicit = false; + } + if (inputState.selectedCharacter) { + // If there is a character input, stick it in all of the arg arrays. + motionArgs.selectedCharacter = operatorArgs.selectedCharacter = + inputState.selectedCharacter; + } + motionArgs.repeat = repeat; + clearInputState(cm); + if (motion) { + var motionResult = motions[motion](cm, origHead, motionArgs, vim); + vim.lastMotion = motions[motion]; + if (!motionResult) { + return; + } + if (motionArgs.toJumplist) { + var jumpList = vimGlobalState.jumpList; + // if the current motion is # or *, use cachedCursor + var cachedCursor = jumpList.cachedCursor; + if (cachedCursor) { + recordJumpPosition(cm, cachedCursor, motionResult); + delete jumpList.cachedCursor; + } else { + recordJumpPosition(cm, origHead, motionResult); + } + } + if (motionResult instanceof Array) { + newAnchor = motionResult[0]; + newHead = motionResult[1]; + } else { + newHead = motionResult; + } + // TODO: Handle null returns from motion commands better. + if (!newHead) { + newHead = copyCursor(origHead); + } + if (vim.visualMode) { + if (!(vim.visualBlock && newHead.ch === Infinity)) { + newHead = clipCursorToContent(cm, newHead, vim.visualBlock); + } + if (newAnchor) { + newAnchor = clipCursorToContent(cm, newAnchor, true); + } + newAnchor = newAnchor || oldAnchor; + sel.anchor = newAnchor; + sel.head = newHead; + updateCmSelection(cm); + updateMark(cm, vim, '<', + cursorIsBefore(newAnchor, newHead) ? newAnchor + : newHead); + updateMark(cm, vim, '>', + cursorIsBefore(newAnchor, newHead) ? newHead + : newAnchor); + } else if (!operator) { + newHead = clipCursorToContent(cm, newHead); + cm.setCursor(newHead.line, newHead.ch); + } + } + if (operator) { + if (operatorArgs.lastSel) { + // Replaying a visual mode operation + newAnchor = oldAnchor; + var lastSel = operatorArgs.lastSel; + var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line); + var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch); + if (lastSel.visualLine) { + // Linewise Visual mode: The same number of lines. + newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch); + } else if (lastSel.visualBlock) { + // Blockwise Visual mode: The same number of lines and columns. + newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset); + } else if (lastSel.head.line == lastSel.anchor.line) { + // Normal Visual mode within one line: The same number of characters. + newHead = Pos(oldAnchor.line, oldAnchor.ch + chOffset); + } else { + // Normal Visual mode with several lines: The same number of lines, in the + // last line the same number of characters as in the last line the last time. + newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch); + } + vim.visualMode = true; + vim.visualLine = lastSel.visualLine; + vim.visualBlock = lastSel.visualBlock; + sel = vim.sel = { + anchor: newAnchor, + head: newHead + }; + updateCmSelection(cm); + } else if (vim.visualMode) { + operatorArgs.lastSel = { + anchor: copyCursor(sel.anchor), + head: copyCursor(sel.head), + visualBlock: vim.visualBlock, + visualLine: vim.visualLine + }; + } + var curStart, curEnd, linewise, mode; + var cmSel; + if (vim.visualMode) { + // Init visual op + curStart = cursorMin(sel.head, sel.anchor); + curEnd = cursorMax(sel.head, sel.anchor); + linewise = vim.visualLine || operatorArgs.linewise; + mode = vim.visualBlock ? 'block' : + linewise ? 'line' : + 'char'; + cmSel = makeCmSelection(cm, { + anchor: curStart, + head: curEnd + }, mode); + if (linewise) { + var ranges = cmSel.ranges; + if (mode == 'block') { + // Linewise operators in visual block mode extend to end of line + for (var i = 0; i < ranges.length; i++) { + ranges[i].head.ch = lineLength(cm, ranges[i].head.line); + } + } else if (mode == 'line') { + ranges[0].head = Pos(ranges[0].head.line + 1, 0); + } + } + } else { + // Init motion op + curStart = copyCursor(newAnchor || oldAnchor); + curEnd = copyCursor(newHead || oldHead); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curStart; + curStart = curEnd; + curEnd = tmp; + } + linewise = motionArgs.linewise || operatorArgs.linewise; + if (linewise) { + // Expand selection to entire line. + expandSelectionToLine(cm, curStart, curEnd); + } else if (motionArgs.forward) { + // Clip to trailing newlines only if the motion goes forward. + clipToLine(cm, curStart, curEnd); + } + mode = 'char'; + var exclusive = !motionArgs.inclusive || linewise; + cmSel = makeCmSelection(cm, { + anchor: curStart, + head: curEnd + }, mode, exclusive); + } + cm.setSelections(cmSel.ranges, cmSel.primary); + vim.lastMotion = null; + operatorArgs.repeat = repeat; // For indent in visual mode. + operatorArgs.registerName = registerName; + // Keep track of linewise as it affects how paste and change behave. + operatorArgs.linewise = linewise; + var operatorMoveTo = operators[operator]( + cm, operatorArgs, cmSel.ranges, oldAnchor, newHead); + if (vim.visualMode) { + exitVisualMode(cm, operatorMoveTo != null); + } + if (operatorMoveTo) { + cm.setCursor(operatorMoveTo); + } + } + }, + recordLastEdit: function(vim, inputState, actionCommand) { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { return; } + vim.lastEditInputState = inputState; + vim.lastEditActionCommand = actionCommand; + macroModeState.lastInsertModeChanges.changes = []; + macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false; + } + }; + + /** + * typedef {Object{line:number,ch:number}} Cursor An object containing the + * position of the cursor. + */ + // All of the functions below return Cursor objects. + var motions = { + moveToTopLine: function(cm, _head, motionArgs) { + var line = getUserVisibleLines(cm).top + motionArgs.repeat -1; + return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + moveToMiddleLine: function(cm) { + var range = getUserVisibleLines(cm); + var line = Math.floor((range.top + range.bottom) * 0.5); + return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + moveToBottomLine: function(cm, _head, motionArgs) { + var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1; + return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + expandToLine: function(_cm, head, motionArgs) { + // Expands forward to end of line, and then to next line if repeat is + // >1. Does not handle backward motion! + var cur = head; + return Pos(cur.line + motionArgs.repeat - 1, Infinity); + }, + findNext: function(cm, _head, motionArgs) { + var state = getSearchState(cm); + var query = state.getQuery(); + if (!query) { + return; + } + var prev = !motionArgs.forward; + // If search is initiated with ? instead of /, negate direction. + prev = (state.isReversed()) ? !prev : prev; + highlightSearchMatches(cm, query); + return findNext(cm, prev/** prev */, query, motionArgs.repeat); + }, + goToMark: function(cm, _head, motionArgs, vim) { + var mark = vim.marks[motionArgs.selectedCharacter]; + if (mark) { + var pos = mark.find(); + return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos; + } + return null; + }, + moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) { + if (vim.visualBlock && motionArgs.sameLine) { + var sel = vim.sel; + return [ + clipCursorToContent(cm, Pos(sel.anchor.line, sel.head.ch)), + clipCursorToContent(cm, Pos(sel.head.line, sel.anchor.ch)) + ]; + } else { + return ([vim.sel.head, vim.sel.anchor]); + } + }, + jumpToMark: function(cm, head, motionArgs, vim) { + var best = head; + for (var i = 0; i < motionArgs.repeat; i++) { + var cursor = best; + for (var key in vim.marks) { + if (!isLowerCase(key)) { + continue; + } + var mark = vim.marks[key].find(); + var isWrongDirection = (motionArgs.forward) ? + cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark); + + if (isWrongDirection) { + continue; + } + if (motionArgs.linewise && (mark.line == cursor.line)) { + continue; + } + + var equal = cursorEqual(cursor, best); + var between = (motionArgs.forward) ? + cursorIsBetween(cursor, mark, best) : + cursorIsBetween(best, mark, cursor); + + if (equal || between) { + best = mark; + } + } + } + + if (motionArgs.linewise) { + // Vim places the cursor on the first non-whitespace character of + // the line if there is one, else it places the cursor at the end + // of the line, regardless of whether a mark was found. + best = Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line))); + } + return best; + }, + moveByCharacters: function(_cm, head, motionArgs) { + var cur = head; + var repeat = motionArgs.repeat; + var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat; + return Pos(cur.line, ch); + }, + moveByLines: function(cm, head, motionArgs, vim) { + var cur = head; + var endCh = cur.ch; + // Depending what our last motion was, we may want to do different + // things. If our last motion was moving vertically, we want to + // preserve the HPos from our last horizontal move. If our last motion + // was going to the end of a line, moving vertically we should go to + // the end of the line, etc. + switch (vim.lastMotion) { + case this.moveByLines: + case this.moveByDisplayLines: + case this.moveByScroll: + case this.moveToColumn: + case this.moveToEol: + endCh = vim.lastHPos; + break; + default: + vim.lastHPos = endCh; + } + var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0); + var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat; + var first = cm.firstLine(); + var last = cm.lastLine(); + // Vim go to line begin or line end when cursor at first/last line and + // move to previous/next line is triggered. + if (line < first && cur.line == first){ + return this.moveToStartOfLine(cm, head, motionArgs, vim); + }else if (line > last && cur.line == last){ + return this.moveToEol(cm, head, motionArgs, vim); + } + if (motionArgs.toFirstChar){ + endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line)); + vim.lastHPos = endCh; + } + vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left; + return Pos(line, endCh); + }, + moveByDisplayLines: function(cm, head, motionArgs, vim) { + var cur = head; + switch (vim.lastMotion) { + case this.moveByDisplayLines: + case this.moveByScroll: + case this.moveByLines: + case this.moveToColumn: + case this.moveToEol: + break; + default: + vim.lastHSPos = cm.charCoords(cur,'div').left; + } + var repeat = motionArgs.repeat; + var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos); + if (res.hitSide) { + if (motionArgs.forward) { + var lastCharCoords = cm.charCoords(res, 'div'); + var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos }; + var res = cm.coordsChar(goalCoords, 'div'); + } else { + var resCoords = cm.charCoords(Pos(cm.firstLine(), 0), 'div'); + resCoords.left = vim.lastHSPos; + res = cm.coordsChar(resCoords, 'div'); + } + } + vim.lastHPos = res.ch; + return res; + }, + moveByPage: function(cm, head, motionArgs) { + // CodeMirror only exposes functions that move the cursor page down, so + // doing this bad hack to move the cursor and move it back. evalInput + // will move the cursor to where it should be in the end. + var curStart = head; + var repeat = motionArgs.repeat; + return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page'); + }, + moveByParagraph: function(cm, head, motionArgs) { + var dir = motionArgs.forward ? 1 : -1; + return findParagraph(cm, head, motionArgs.repeat, dir); + }, + moveByScroll: function(cm, head, motionArgs, vim) { + var scrollbox = cm.getScrollInfo(); + var curEnd = null; + var repeat = motionArgs.repeat; + if (!repeat) { + repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight()); + } + var orig = cm.charCoords(head, 'local'); + motionArgs.repeat = repeat; + var curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim); + if (!curEnd) { + return null; + } + var dest = cm.charCoords(curEnd, 'local'); + cm.scrollTo(null, scrollbox.top + dest.top - orig.top); + return curEnd; + }, + moveByWords: function(cm, head, motionArgs) { + return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward, + !!motionArgs.wordEnd, !!motionArgs.bigWord); + }, + moveTillCharacter: function(cm, _head, motionArgs) { + var repeat = motionArgs.repeat; + var curEnd = moveToCharacter(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter); + var increment = motionArgs.forward ? -1 : 1; + recordLastCharacterSearch(increment, motionArgs); + if (!curEnd) return null; + curEnd.ch += increment; + return curEnd; + }, + moveToCharacter: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + recordLastCharacterSearch(0, motionArgs); + return moveToCharacter(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter) || head; + }, + moveToSymbol: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + return findSymbol(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter) || head; + }, + moveToColumn: function(cm, head, motionArgs, vim) { + var repeat = motionArgs.repeat; + // repeat is equivalent to which column we want to move to! + vim.lastHPos = repeat - 1; + vim.lastHSPos = cm.charCoords(head,'div').left; + return moveToColumn(cm, repeat); + }, + moveToEol: function(cm, head, motionArgs, vim) { + var cur = head; + vim.lastHPos = Infinity; + var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity); + var end=cm.clipPos(retval); + end.ch--; + vim.lastHSPos = cm.charCoords(end,'div').left; + return retval; + }, + moveToFirstNonWhiteSpaceCharacter: function(cm, head) { + // Go to the start of the line where the text begins, or the end for + // whitespace-only lines + var cursor = head; + return Pos(cursor.line, + findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line))); + }, + moveToMatchedSymbol: function(cm, head) { + var cursor = head; + var line = cursor.line; + var ch = cursor.ch; + var lineText = cm.getLine(line); + var symbol; + do { + symbol = lineText.charAt(ch++); + if (symbol && isMatchableSymbol(symbol)) { + var style = cm.getTokenTypeAt(Pos(line, ch)); + if (style !== "string" && style !== "comment") { + break; + } + } + } while (symbol); + if (symbol) { + var matched = cm.findMatchingBracket(Pos(line, ch)); + return matched.to; + } else { + return cursor; + } + }, + moveToStartOfLine: function(_cm, head) { + return Pos(head.line, 0); + }, + moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) { + var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine(); + if (motionArgs.repeatIsExplicit) { + lineNum = motionArgs.repeat - cm.getOption('firstLineNumber'); + } + return Pos(lineNum, + findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum))); + }, + textObjectManipulation: function(cm, head, motionArgs, vim) { + // TODO: lots of possible exceptions that can be thrown here. Try da( + // outside of a () block. + + // TODO: adding <> >< to this map doesn't work, presumably because + // they're operators + var mirroredPairs = {'(': ')', ')': '(', + '{': '}', '}': '{', + '[': ']', ']': '['}; + var selfPaired = {'\'': true, '"': true}; + + var character = motionArgs.selectedCharacter; + // 'b' refers to '()' block. + // 'B' refers to '{}' block. + if (character == 'b') { + character = '('; + } else if (character == 'B') { + character = '{'; + } + + // Inclusive is the difference between a and i + // TODO: Instead of using the additional text object map to perform text + // object operations, merge the map into the defaultKeyMap and use + // motionArgs to define behavior. Define separate entries for 'aw', + // 'iw', 'a[', 'i[', etc. + var inclusive = !motionArgs.textObjectInner; + + var tmp; + if (mirroredPairs[character]) { + tmp = selectCompanionObject(cm, head, character, inclusive); + } else if (selfPaired[character]) { + tmp = findBeginningAndEnd(cm, head, character, inclusive); + } else if (character === 'W') { + tmp = expandWordUnderCursor(cm, inclusive, true /** forward */, + true /** bigWord */); + } else if (character === 'w') { + tmp = expandWordUnderCursor(cm, inclusive, true /** forward */, + false /** bigWord */); + } else if (character === 'p') { + tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive); + motionArgs.linewise = true; + if (vim.visualMode) { + if (!vim.visualLine) { vim.visualLine = true; } + } else { + var operatorArgs = vim.inputState.operatorArgs; + if (operatorArgs) { operatorArgs.linewise = true; } + tmp.end.line--; + } + } else { + // No text object defined for this, don't move. + return null; + } + + if (!cm.state.vim.visualMode) { + return [tmp.start, tmp.end]; + } else { + return expandSelection(cm, tmp.start, tmp.end); + } + }, + + repeatLastCharacterSearch: function(cm, head, motionArgs) { + var lastSearch = vimGlobalState.lastChararacterSearch; + var repeat = motionArgs.repeat; + var forward = motionArgs.forward === lastSearch.forward; + var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1); + cm.moveH(-increment, 'char'); + motionArgs.inclusive = forward ? true : false; + var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter); + if (!curEnd) { + cm.moveH(increment, 'char'); + return head; + } + curEnd.ch += increment; + return curEnd; + } + }; + + function defineMotion(name, fn) { + motions[name] = fn; + } + + function fillArray(val, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(val); + } + return arr; + } + /** + * An operator acts on a text selection. It receives the list of selections + * as input. The corresponding CodeMirror selection is guaranteed to + * match the input selection. + */ + var operators = { + change: function(cm, args, ranges) { + var finalHead, text; + var vim = cm.state.vim; + vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock = vim.visualBlock; + if (!vim.visualMode) { + var anchor = ranges[0].anchor, + head = ranges[0].head; + text = cm.getRange(anchor, head); + var lastState = vim.lastEditInputState || {}; + if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) { + // Exclude trailing whitespace if the range is not all whitespace. + var match = (/\s+$/).exec(text); + if (match && lastState.motionArgs && lastState.motionArgs.forward) { + head = offsetCursor(head, 0, - match[0].length); + text = text.slice(0, - match[0].length); + } + } + var prevLineEnd = new Pos(anchor.line - 1, Number.MAX_VALUE); + var wasLastLine = cm.firstLine() == cm.lastLine(); + if (head.line > cm.lastLine() && args.linewise && !wasLastLine) { + cm.replaceRange('', prevLineEnd, head); + } else { + cm.replaceRange('', anchor, head); + } + if (args.linewise) { + // Push the next line back down, if there is a next line. + if (!wasLastLine) { + cm.setCursor(prevLineEnd); + CodeMirror.commands.newlineAndIndent(cm); + } + // make sure cursor ends up at the end of the line. + anchor.ch = Number.MAX_VALUE; + } + finalHead = anchor; + } else { + text = cm.getSelection(); + var replacement = fillArray('', ranges.length); + cm.replaceSelections(replacement); + finalHead = cursorMin(ranges[0].head, ranges[0].anchor); + } + vimGlobalState.registerController.pushText( + args.registerName, 'change', text, + args.linewise, ranges.length > 1); + actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim); + }, + // delete is a javascript keyword. + 'delete': function(cm, args, ranges) { + var finalHead, text; + var vim = cm.state.vim; + if (!vim.visualBlock) { + var anchor = ranges[0].anchor, + head = ranges[0].head; + if (args.linewise && + head.line != cm.firstLine() && + anchor.line == cm.lastLine() && + anchor.line == head.line - 1) { + // Special case for dd on last line (and first line). + if (anchor.line == cm.firstLine()) { + anchor.ch = 0; + } else { + anchor = Pos(anchor.line - 1, lineLength(cm, anchor.line - 1)); + } + } + text = cm.getRange(anchor, head); + cm.replaceRange('', anchor, head); + finalHead = anchor; + if (args.linewise) { + finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor); + } + } else { + text = cm.getSelection(); + var replacement = fillArray('', ranges.length); + cm.replaceSelections(replacement); + finalHead = ranges[0].anchor; + } + vimGlobalState.registerController.pushText( + args.registerName, 'delete', text, + args.linewise, vim.visualBlock); + return clipCursorToContent(cm, finalHead); + }, + indent: function(cm, args, ranges) { + var vim = cm.state.vim; + var startLine = ranges[0].anchor.line; + var endLine = vim.visualBlock ? + ranges[ranges.length - 1].anchor.line : + ranges[0].head.line; + // In visual mode, n> shifts the selection right n times, instead of + // shifting n lines right once. + var repeat = (vim.visualMode) ? args.repeat : 1; + if (args.linewise) { + // The only way to delete a newline is to delete until the start of + // the next line, so in linewise mode evalInput will include the next + // line. We don't want this in indent, so we go back a line. + endLine--; + } + for (var i = startLine; i <= endLine; i++) { + for (var j = 0; j < repeat; j++) { + cm.indentLine(i, args.indentRight); + } + } + return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); + }, + changeCase: function(cm, args, ranges, oldAnchor, newHead) { + var selections = cm.getSelections(); + var swapped = []; + var toLower = args.toLower; + for (var j = 0; j < selections.length; j++) { + var toSwap = selections[j]; + var text = ''; + if (toLower === true) { + text = toSwap.toLowerCase(); + } else if (toLower === false) { + text = toSwap.toUpperCase(); + } else { + for (var i = 0; i < toSwap.length; i++) { + var character = toSwap.charAt(i); + text += isUpperCase(character) ? character.toLowerCase() : + character.toUpperCase(); + } + } + swapped.push(text); + } + cm.replaceSelections(swapped); + if (args.shouldMoveCursor){ + return newHead; + } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) { + return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor); + } else if (args.linewise){ + return oldAnchor; + } else { + return cursorMin(ranges[0].anchor, ranges[0].head); + } + }, + yank: function(cm, args, ranges, oldAnchor) { + var vim = cm.state.vim; + var text = cm.getSelection(); + var endPos = vim.visualMode + ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor) + : oldAnchor; + vimGlobalState.registerController.pushText( + args.registerName, 'yank', + text, args.linewise, vim.visualBlock); + return endPos; + } + }; + + function defineOperator(name, fn) { + operators[name] = fn; + } + + var actions = { + jumpListWalk: function(cm, actionArgs, vim) { + if (vim.visualMode) { + return; + } + var repeat = actionArgs.repeat; + var forward = actionArgs.forward; + var jumpList = vimGlobalState.jumpList; + + var mark = jumpList.move(cm, forward ? repeat : -repeat); + var markPos = mark ? mark.find() : undefined; + markPos = markPos ? markPos : cm.getCursor(); + cm.setCursor(markPos); + }, + scroll: function(cm, actionArgs, vim) { + if (vim.visualMode) { + return; + } + var repeat = actionArgs.repeat || 1; + var lineHeight = cm.defaultTextHeight(); + var top = cm.getScrollInfo().top; + var delta = lineHeight * repeat; + var newPos = actionArgs.forward ? top + delta : top - delta; + var cursor = copyCursor(cm.getCursor()); + var cursorCoords = cm.charCoords(cursor, 'local'); + if (actionArgs.forward) { + if (newPos > cursorCoords.top) { + cursor.line += (newPos - cursorCoords.top) / lineHeight; + cursor.line = Math.ceil(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo(null, cursorCoords.top); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } else { + var newBottom = newPos + cm.getScrollInfo().clientHeight; + if (newBottom < cursorCoords.bottom) { + cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight; + cursor.line = Math.floor(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo( + null, cursorCoords.bottom - cm.getScrollInfo().clientHeight); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } + }, + scrollToCursor: function(cm, actionArgs) { + var lineNum = cm.getCursor().line; + var charCoords = cm.charCoords(Pos(lineNum, 0), 'local'); + var height = cm.getScrollInfo().clientHeight; + var y = charCoords.top; + var lineHeight = charCoords.bottom - y; + switch (actionArgs.position) { + case 'center': y = y - (height / 2) + lineHeight; + break; + case 'bottom': y = y - height + lineHeight; + break; + } + cm.scrollTo(null, y); + }, + replayMacro: function(cm, actionArgs, vim) { + var registerName = actionArgs.selectedCharacter; + var repeat = actionArgs.repeat; + var macroModeState = vimGlobalState.macroModeState; + if (registerName == '@') { + registerName = macroModeState.latestRegister; + } + while(repeat--){ + executeMacroRegister(cm, vim, macroModeState, registerName); + } + }, + enterMacroRecordMode: function(cm, actionArgs) { + var macroModeState = vimGlobalState.macroModeState; + var registerName = actionArgs.selectedCharacter; + macroModeState.enterMacroRecordMode(cm, registerName); + }, + enterInsertMode: function(cm, actionArgs, vim) { + if (cm.getOption('readOnly')) { return; } + vim.insertMode = true; + vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1; + var insertAt = (actionArgs) ? actionArgs.insertAt : null; + var sel = vim.sel; + var head = actionArgs.head || cm.getCursor('head'); + var height = cm.listSelections().length; + if (insertAt == 'eol') { + head = Pos(head.line, lineLength(cm, head.line)); + } else if (insertAt == 'charAfter') { + head = offsetCursor(head, 0, 1); + } else if (insertAt == 'firstNonBlank') { + head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head); + } else if (insertAt == 'startOfSelectedArea') { + if (!vim.visualBlock) { + if (sel.head.line < sel.anchor.line) { + head = sel.head; + } else { + head = Pos(sel.anchor.line, 0); + } + } else { + head = Pos( + Math.min(sel.head.line, sel.anchor.line), + Math.min(sel.head.ch, sel.anchor.ch)); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == 'endOfSelectedArea') { + if (!vim.visualBlock) { + if (sel.head.line >= sel.anchor.line) { + head = offsetCursor(sel.head, 0, 1); + } else { + head = Pos(sel.anchor.line, 0); + } + } else { + head = Pos( + Math.min(sel.head.line, sel.anchor.line), + Math.max(sel.head.ch + 1, sel.anchor.ch)); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == 'inplace') { + if (vim.visualMode){ + return; + } + } + cm.setOption('keyMap', 'vim-insert'); + cm.setOption('disableInput', false); + if (actionArgs && actionArgs.replace) { + // Handle Replace-mode as a special case of insert mode. + cm.toggleOverwrite(true); + cm.setOption('keyMap', 'vim-replace'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"}); + } else { + cm.setOption('keyMap', 'vim-insert'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"}); + } + if (!vimGlobalState.macroModeState.isPlaying) { + // Only record if not replaying. + cm.on('change', onChange); + CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); + } + if (vim.visualMode) { + exitVisualMode(cm); + } + selectForInsert(cm, head, height); + }, + toggleVisualMode: function(cm, actionArgs, vim) { + var repeat = actionArgs.repeat; + var anchor = cm.getCursor(); + var head; + // TODO: The repeat should actually select number of characters/lines + // equal to the repeat times the size of the previous visual + // operation. + if (!vim.visualMode) { + // Entering visual mode + vim.visualMode = true; + vim.visualLine = !!actionArgs.linewise; + vim.visualBlock = !!actionArgs.blockwise; + head = clipCursorToContent( + cm, Pos(anchor.line, anchor.ch + repeat - 1), + true /** includeLineBreak */); + vim.sel = { + anchor: anchor, + head: head + }; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); + updateCmSelection(cm); + updateMark(cm, vim, '<', cursorMin(anchor, head)); + updateMark(cm, vim, '>', cursorMax(anchor, head)); + } else if (vim.visualLine ^ actionArgs.linewise || + vim.visualBlock ^ actionArgs.blockwise) { + // Toggling between modes + vim.visualLine = !!actionArgs.linewise; + vim.visualBlock = !!actionArgs.blockwise; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); + updateCmSelection(cm); + } else { + exitVisualMode(cm); + } + }, + reselectLastSelection: function(cm, _actionArgs, vim) { + var lastSelection = vim.lastSelection; + if (vim.visualMode) { + updateLastSelection(cm, vim); + } + if (lastSelection) { + var anchor = lastSelection.anchorMark.find(); + var head = lastSelection.headMark.find(); + if (!anchor || !head) { + // If the marks have been destroyed due to edits, do nothing. + return; + } + vim.sel = { + anchor: anchor, + head: head + }; + vim.visualMode = true; + vim.visualLine = lastSelection.visualLine; + vim.visualBlock = lastSelection.visualBlock; + updateCmSelection(cm); + updateMark(cm, vim, '<', cursorMin(anchor, head)); + updateMark(cm, vim, '>', cursorMax(anchor, head)); + CodeMirror.signal(cm, 'vim-mode-change', { + mode: 'visual', + subMode: vim.visualLine ? 'linewise' : + vim.visualBlock ? 'blockwise' : ''}); + } + }, + joinLines: function(cm, actionArgs, vim) { + var curStart, curEnd; + if (vim.visualMode) { + curStart = cm.getCursor('anchor'); + curEnd = cm.getCursor('head'); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curEnd; + curEnd = curStart; + curStart = tmp; + } + curEnd.ch = lineLength(cm, curEnd.line) - 1; + } else { + // Repeat is the number of lines to join. Minimum 2 lines. + var repeat = Math.max(actionArgs.repeat, 2); + curStart = cm.getCursor(); + curEnd = clipCursorToContent(cm, Pos(curStart.line + repeat - 1, + Infinity)); + } + var finalCh = 0; + for (var i = curStart.line; i < curEnd.line; i++) { + finalCh = lineLength(cm, curStart.line); + var tmp = Pos(curStart.line + 1, + lineLength(cm, curStart.line + 1)); + var text = cm.getRange(curStart, tmp); + text = text.replace(/\n\s*/g, ' '); + cm.replaceRange(text, curStart, tmp); + } + var curFinalPos = Pos(curStart.line, finalCh); + if (vim.visualMode) { + exitVisualMode(cm, false); + } + cm.setCursor(curFinalPos); + }, + newLineAndEnterInsertMode: function(cm, actionArgs, vim) { + vim.insertMode = true; + var insertAt = copyCursor(cm.getCursor()); + if (insertAt.line === cm.firstLine() && !actionArgs.after) { + // Special case for inserting newline before start of document. + cm.replaceRange('\n', Pos(cm.firstLine(), 0)); + cm.setCursor(cm.firstLine(), 0); + } else { + insertAt.line = (actionArgs.after) ? insertAt.line : + insertAt.line - 1; + insertAt.ch = lineLength(cm, insertAt.line); + cm.setCursor(insertAt); + var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment || + CodeMirror.commands.newlineAndIndent; + newlineFn(cm); + } + this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim); + }, + paste: function(cm, actionArgs, vim) { + var cur = copyCursor(cm.getCursor()); + var register = vimGlobalState.registerController.getRegister( + actionArgs.registerName); + var text = register.toString(); + if (!text) { + return; + } + if (actionArgs.matchIndent) { + var tabSize = cm.getOption("tabSize"); + // length that considers tabs and tabSize + var whitespaceLength = function(str) { + var tabs = (str.split("\t").length - 1); + var spaces = (str.split(" ").length - 1); + return tabs * tabSize + spaces * 1; + }; + var currentLine = cm.getLine(cm.getCursor().line); + var indent = whitespaceLength(currentLine.match(/^\s*/)[0]); + // chomp last newline b/c don't want it to match /^\s*/gm + var chompedText = text.replace(/\n$/, ''); + var wasChomped = text !== chompedText; + var firstIndent = whitespaceLength(text.match(/^\s*/)[0]); + var text = chompedText.replace(/^\s*/gm, function(wspace) { + var newIndent = indent + (whitespaceLength(wspace) - firstIndent); + if (newIndent < 0) { + return ""; + } + else if (cm.getOption("indentWithTabs")) { + var quotient = Math.floor(newIndent / tabSize); + return Array(quotient + 1).join('\t'); + } + else { + return Array(newIndent + 1).join(' '); + } + }); + text += wasChomped ? "\n" : ""; + } + if (actionArgs.repeat > 1) { + var text = Array(actionArgs.repeat + 1).join(text); + } + var linewise = register.linewise; + var blockwise = register.blockwise; + if (linewise) { + if(vim.visualMode) { + text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n'; + } else if (actionArgs.after) { + // Move the newline at the end to the start instead, and paste just + // before the newline character of the line we are on right now. + text = '\n' + text.slice(0, text.length - 1); + cur.ch = lineLength(cm, cur.line); + } else { + cur.ch = 0; + } + } else { + if (blockwise) { + text = text.split('\n'); + for (var i = 0; i < text.length; i++) { + text[i] = (text[i] == '') ? ' ' : text[i]; + } + } + cur.ch += actionArgs.after ? 1 : 0; + } + var curPosFinal; + var idx; + if (vim.visualMode) { + // save the pasted text for reselection if the need arises + vim.lastPastedText = text; + var lastSelectionCurEnd; + var selectedArea = getSelectedAreaRange(cm, vim); + var selectionStart = selectedArea[0]; + var selectionEnd = selectedArea[1]; + var selectedText = cm.getSelection(); + var selections = cm.listSelections(); + var emptyStrings = new Array(selections.length).join('1').split('1'); + // save the curEnd marker before it get cleared due to cm.replaceRange. + if (vim.lastSelection) { + lastSelectionCurEnd = vim.lastSelection.headMark.find(); + } + // push the previously selected text to unnamed register + vimGlobalState.registerController.unnamedRegister.setText(selectedText); + if (blockwise) { + // first delete the selected text + cm.replaceSelections(emptyStrings); + // Set new selections as per the block length of the yanked text + selectionEnd = Pos(selectionStart.line + text.length-1, selectionStart.ch); + cm.setCursor(selectionStart); + selectBlock(cm, selectionEnd); + cm.replaceSelections(text); + curPosFinal = selectionStart; + } else if (vim.visualBlock) { + cm.replaceSelections(emptyStrings); + cm.setCursor(selectionStart); + cm.replaceRange(text, selectionStart, selectionStart); + curPosFinal = selectionStart; + } else { + cm.replaceRange(text, selectionStart, selectionEnd); + curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1); + } + // restore the the curEnd marker + if(lastSelectionCurEnd) { + vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd); + } + if (linewise) { + curPosFinal.ch=0; + } + } else { + if (blockwise) { + cm.setCursor(cur); + for (var i = 0; i < text.length; i++) { + var line = cur.line+i; + if (line > cm.lastLine()) { + cm.replaceRange('\n', Pos(line, 0)); + } + var lastCh = lineLength(cm, line); + if (lastCh < cur.ch) { + extendLineToColumn(cm, line, cur.ch); + } + } + cm.setCursor(cur); + selectBlock(cm, Pos(cur.line + text.length-1, cur.ch)); + cm.replaceSelections(text); + curPosFinal = cur; + } else { + cm.replaceRange(text, cur); + // Now fine tune the cursor to where we want it. + if (linewise && actionArgs.after) { + curPosFinal = Pos( + cur.line + 1, + findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1))); + } else if (linewise && !actionArgs.after) { + curPosFinal = Pos( + cur.line, + findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line))); + } else if (!linewise && actionArgs.after) { + idx = cm.indexFromPos(cur); + curPosFinal = cm.posFromIndex(idx + text.length - 1); + } else { + idx = cm.indexFromPos(cur); + curPosFinal = cm.posFromIndex(idx + text.length); + } + } + } + if (vim.visualMode) { + exitVisualMode(cm, false); + } + cm.setCursor(curPosFinal); + }, + undo: function(cm, actionArgs) { + cm.operation(function() { + repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)(); + cm.setCursor(cm.getCursor('anchor')); + }); + }, + redo: function(cm, actionArgs) { + repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)(); + }, + setRegister: function(_cm, actionArgs, vim) { + vim.inputState.registerName = actionArgs.selectedCharacter; + }, + setMark: function(cm, actionArgs, vim) { + var markName = actionArgs.selectedCharacter; + updateMark(cm, vim, markName, cm.getCursor()); + }, + replace: function(cm, actionArgs, vim) { + var replaceWith = actionArgs.selectedCharacter; + var curStart = cm.getCursor(); + var replaceTo; + var curEnd; + var selections = cm.listSelections(); + if (vim.visualMode) { + curStart = cm.getCursor('start'); + curEnd = cm.getCursor('end'); + } else { + var line = cm.getLine(curStart.line); + replaceTo = curStart.ch + actionArgs.repeat; + if (replaceTo > line.length) { + replaceTo=line.length; + } + curEnd = Pos(curStart.line, replaceTo); + } + if (replaceWith=='\n') { + if (!vim.visualMode) cm.replaceRange('', curStart, curEnd); + // special case, where vim help says to replace by just one line-break + (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm); + } else { + var replaceWithStr = cm.getRange(curStart, curEnd); + //replace all characters in range by selected, but keep linebreaks + replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith); + if (vim.visualBlock) { + // Tabs are split in visua block before replacing + var spaces = new Array(cm.getOption("tabSize")+1).join(' '); + replaceWithStr = cm.getSelection(); + replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n'); + cm.replaceSelections(replaceWithStr); + } else { + cm.replaceRange(replaceWithStr, curStart, curEnd); + } + if (vim.visualMode) { + curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ? + selections[0].anchor : selections[0].head; + cm.setCursor(curStart); + exitVisualMode(cm, false); + } else { + cm.setCursor(offsetCursor(curEnd, 0, -1)); + } + } + }, + incrementNumberToken: function(cm, actionArgs) { + var cur = cm.getCursor(); + var lineStr = cm.getLine(cur.line); + var re = /-?\d+/g; + var match; + var start; + var end; + var numberStr; + var token; + while ((match = re.exec(lineStr)) !== null) { + token = match[0]; + start = match.index; + end = start + token.length; + if (cur.ch < end)break; + } + if (!actionArgs.backtrack && (end <= cur.ch))return; + if (token) { + var increment = actionArgs.increase ? 1 : -1; + var number = parseInt(token) + (increment * actionArgs.repeat); + var from = Pos(cur.line, start); + var to = Pos(cur.line, end); + numberStr = number.toString(); + cm.replaceRange(numberStr, from, to); + } else { + return; + } + cm.setCursor(Pos(cur.line, start + numberStr.length - 1)); + }, + repeatLastEdit: function(cm, actionArgs, vim) { + var lastEditInputState = vim.lastEditInputState; + if (!lastEditInputState) { return; } + var repeat = actionArgs.repeat; + if (repeat && actionArgs.repeatIsExplicit) { + vim.lastEditInputState.repeatOverride = repeat; + } else { + repeat = vim.lastEditInputState.repeatOverride || repeat; + } + repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */); + }, + exitInsertMode: exitInsertMode + }; + + function defineAction(name, fn) { + actions[name] = fn; + } + + /* + * Below are miscellaneous utility functions used by vim.js + */ + + /** + * Clips cursor to ensure that line is within the buffer's range + * If includeLineBreak is true, then allow cur.ch == lineLength. + */ + function clipCursorToContent(cm, cur, includeLineBreak) { + var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() ); + var maxCh = lineLength(cm, line) - 1; + maxCh = (includeLineBreak) ? maxCh + 1 : maxCh; + var ch = Math.min(Math.max(0, cur.ch), maxCh); + return Pos(line, ch); + } + function copyArgs(args) { + var ret = {}; + for (var prop in args) { + if (args.hasOwnProperty(prop)) { + ret[prop] = args[prop]; + } + } + return ret; + } + function offsetCursor(cur, offsetLine, offsetCh) { + if (typeof offsetLine === 'object') { + offsetCh = offsetLine.ch; + offsetLine = offsetLine.line; + } + return Pos(cur.line + offsetLine, cur.ch + offsetCh); + } + function getOffset(anchor, head) { + return { + line: head.line - anchor.line, + ch: head.line - anchor.line + }; + } + function commandMatches(keys, keyMap, context, inputState) { + // Partial matches are not applied. They inform the key handler + // that the current key sequence is a subsequence of a valid key + // sequence, so that the key buffer is not cleared. + var match, partial = [], full = []; + for (var i = 0; i < keyMap.length; i++) { + var command = keyMap[i]; + if (context == 'insert' && command.context != 'insert' || + command.context && command.context != context || + inputState.operator && command.type == 'action' || + !(match = commandMatch(keys, command.keys))) { continue; } + if (match == 'partial') { partial.push(command); } + if (match == 'full') { full.push(command); } + } + return { + partial: partial.length && partial, + full: full.length && full + }; + } + function commandMatch(pressed, mapped) { + if (mapped.slice(-11) == '') { + // Last character matches anything. + var prefixLen = mapped.length - 11; + var pressedPrefix = pressed.slice(0, prefixLen); + var mappedPrefix = mapped.slice(0, prefixLen); + return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' : + mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false; + } else { + return pressed == mapped ? 'full' : + mapped.indexOf(pressed) == 0 ? 'partial' : false; + } + } + function lastChar(keys) { + var match = /^.*(<[\w\-]+>)$/.exec(keys); + var selectedCharacter = match ? match[1] : keys.slice(-1); + if (selectedCharacter.length > 1){ + switch(selectedCharacter){ + case '': + selectedCharacter='\n'; + break; + case '': + selectedCharacter=' '; + break; + default: + break; + } + } + return selectedCharacter; + } + function repeatFn(cm, fn, repeat) { + return function() { + for (var i = 0; i < repeat; i++) { + fn(cm); + } + }; + } + function copyCursor(cur) { + return Pos(cur.line, cur.ch); + } + function cursorEqual(cur1, cur2) { + return cur1.ch == cur2.ch && cur1.line == cur2.line; + } + function cursorIsBefore(cur1, cur2) { + if (cur1.line < cur2.line) { + return true; + } + if (cur1.line == cur2.line && cur1.ch < cur2.ch) { + return true; + } + return false; + } + function cursorMin(cur1, cur2) { + if (arguments.length > 2) { + cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur1 : cur2; + } + function cursorMax(cur1, cur2) { + if (arguments.length > 2) { + cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur2 : cur1; + } + function cursorIsBetween(cur1, cur2, cur3) { + // returns true if cur2 is between cur1 and cur3. + var cur1before2 = cursorIsBefore(cur1, cur2); + var cur2before3 = cursorIsBefore(cur2, cur3); + return cur1before2 && cur2before3; + } + function lineLength(cm, lineNum) { + return cm.getLine(lineNum).length; + } + function trim(s) { + if (s.trim) { + return s.trim(); + } + return s.replace(/^\s+|\s+$/g, ''); + } + function escapeRegex(s) { + return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1'); + } + function extendLineToColumn(cm, lineNum, column) { + var endCh = lineLength(cm, lineNum); + var spaces = new Array(column-endCh+1).join(' '); + cm.setCursor(Pos(lineNum, endCh)); + cm.replaceRange(spaces, cm.getCursor()); + } + // This functions selects a rectangular block + // of text with selectionEnd as any of its corner + // Height of block: + // Difference in selectionEnd.line and first/last selection.line + // Width of the block: + // Distance between selectionEnd.ch and any(first considered here) selection.ch + function selectBlock(cm, selectionEnd) { + var selections = [], ranges = cm.listSelections(); + var head = copyCursor(cm.clipPos(selectionEnd)); + var isClipped = !cursorEqual(selectionEnd, head); + var curHead = cm.getCursor('head'); + var primIndex = getIndex(ranges, curHead); + var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor); + var max = ranges.length - 1; + var index = max - primIndex > primIndex ? max : 0; + var base = ranges[index].anchor; + + var firstLine = Math.min(base.line, head.line); + var lastLine = Math.max(base.line, head.line); + var baseCh = base.ch, headCh = head.ch; + + var dir = ranges[index].head.ch - baseCh; + var newDir = headCh - baseCh; + if (dir > 0 && newDir <= 0) { + baseCh++; + if (!isClipped) { headCh--; } + } else if (dir < 0 && newDir >= 0) { + baseCh--; + if (!wasClipped) { headCh++; } + } else if (dir < 0 && newDir == -1) { + baseCh--; + headCh++; + } + for (var line = firstLine; line <= lastLine; line++) { + var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)}; + selections.push(range); + } + primIndex = head.line == lastLine ? selections.length - 1 : 0; + cm.setSelections(selections); + selectionEnd.ch = headCh; + base.ch = baseCh; + return base; + } + function selectForInsert(cm, head, height) { + var sel = []; + for (var i = 0; i < height; i++) { + var lineHead = offsetCursor(head, i, 0); + sel.push({anchor: lineHead, head: lineHead}); + } + cm.setSelections(sel, 0); + } + // getIndex returns the index of the cursor in the selections. + function getIndex(ranges, cursor, end) { + for (var i = 0; i < ranges.length; i++) { + var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor); + var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor); + if (atAnchor || atHead) { + return i; + } + } + return -1; + } + function getSelectedAreaRange(cm, vim) { + var lastSelection = vim.lastSelection; + var getCurrentSelectedAreaRange = function() { + var selections = cm.listSelections(); + var start = selections[0]; + var end = selections[selections.length-1]; + var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head; + var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; + return [selectionStart, selectionEnd]; + }; + var getLastSelectedAreaRange = function() { + var selectionStart = cm.getCursor(); + var selectionEnd = cm.getCursor(); + var block = lastSelection.visualBlock; + if (block) { + var width = block.width; + var height = block.height; + selectionEnd = Pos(selectionStart.line + height, selectionStart.ch + width); + var selections = []; + // selectBlock creates a 'proper' rectangular block. + // We do not want that in all cases, so we manually set selections. + for (var i = selectionStart.line; i < selectionEnd.line; i++) { + var anchor = Pos(i, selectionStart.ch); + var head = Pos(i, selectionEnd.ch); + var range = {anchor: anchor, head: head}; + selections.push(range); + } + cm.setSelections(selections); + } else { + var start = lastSelection.anchorMark.find(); + var end = lastSelection.headMark.find(); + var line = end.line - start.line; + var ch = end.ch - start.ch; + selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch}; + if (lastSelection.visualLine) { + selectionStart = Pos(selectionStart.line, 0); + selectionEnd = Pos(selectionEnd.line, lineLength(cm, selectionEnd.line)); + } + cm.setSelection(selectionStart, selectionEnd); + } + return [selectionStart, selectionEnd]; + }; + if (!vim.visualMode) { + // In case of replaying the action. + return getLastSelectedAreaRange(); + } else { + return getCurrentSelectedAreaRange(); + } + } + // Updates the previous selection with the current selection's values. This + // should only be called in visual mode. + function updateLastSelection(cm, vim) { + var anchor = vim.sel.anchor; + var head = vim.sel.head; + // To accommodate the effect of lastPastedText in the last selection + if (vim.lastPastedText) { + head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length); + vim.lastPastedText = null; + } + vim.lastSelection = {'anchorMark': cm.setBookmark(anchor), + 'headMark': cm.setBookmark(head), + 'anchor': copyCursor(anchor), + 'head': copyCursor(head), + 'visualMode': vim.visualMode, + 'visualLine': vim.visualLine, + 'visualBlock': vim.visualBlock}; + } + function expandSelection(cm, start, end) { + var sel = cm.state.vim.sel; + var head = sel.head; + var anchor = sel.anchor; + var tmp; + if (cursorIsBefore(end, start)) { + tmp = end; + end = start; + start = tmp; + } + if (cursorIsBefore(head, anchor)) { + head = cursorMin(start, head); + anchor = cursorMax(anchor, end); + } else { + anchor = cursorMin(start, anchor); + head = cursorMax(head, end); + head = offsetCursor(head, 0, -1); + if (head.ch == -1 && head.line != cm.firstLine()) { + head = Pos(head.line - 1, lineLength(cm, head.line - 1)); + } + } + return [anchor, head]; + } + /** + * Updates the CodeMirror selection to match the provided vim selection. + * If no arguments are given, it uses the current vim selection state. + */ + function updateCmSelection(cm, sel, mode) { + var vim = cm.state.vim; + sel = sel || vim.sel; + var mode = mode || + vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char'; + var cmSel = makeCmSelection(cm, sel, mode); + cm.setSelections(cmSel.ranges, cmSel.primary); + updateFakeCursor(cm); + } + function makeCmSelection(cm, sel, mode, exclusive) { + var head = copyCursor(sel.head); + var anchor = copyCursor(sel.anchor); + if (mode == 'char') { + var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + head = offsetCursor(sel.head, 0, headOffset); + anchor = offsetCursor(sel.anchor, 0, anchorOffset); + return { + ranges: [{anchor: anchor, head: head}], + primary: 0 + }; + } else if (mode == 'line') { + if (!cursorIsBefore(sel.head, sel.anchor)) { + anchor.ch = 0; + + var lastLine = cm.lastLine(); + if (head.line > lastLine) { + head.line = lastLine; + } + head.ch = lineLength(cm, head.line); + } else { + head.ch = 0; + anchor.ch = lineLength(cm, anchor.line); + } + return { + ranges: [{anchor: anchor, head: head}], + primary: 0 + }; + } else if (mode == 'block') { + var top = Math.min(anchor.line, head.line), + left = Math.min(anchor.ch, head.ch), + bottom = Math.max(anchor.line, head.line), + right = Math.max(anchor.ch, head.ch) + 1; + var height = bottom - top + 1; + var primary = head.line == top ? 0 : height - 1; + var ranges = []; + for (var i = 0; i < height; i++) { + ranges.push({ + anchor: Pos(top + i, left), + head: Pos(top + i, right) + }); + } + return { + ranges: ranges, + primary: primary + }; + } + } + function getHead(cm) { + var cur = cm.getCursor('head'); + if (cm.getSelection().length == 1) { + // Small corner case when only 1 character is selected. The "real" + // head is the left of head and anchor. + cur = cursorMin(cur, cm.getCursor('anchor')); + } + return cur; + } + + /** + * If moveHead is set to false, the CodeMirror selection will not be + * touched. The caller assumes the responsibility of putting the cursor + * in the right place. + */ + function exitVisualMode(cm, moveHead) { + var vim = cm.state.vim; + if (moveHead !== false) { + cm.setCursor(clipCursorToContent(cm, vim.sel.head)); + } + updateLastSelection(cm, vim); + vim.visualMode = false; + vim.visualLine = false; + vim.visualBlock = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + if (vim.fakeCursor) { + vim.fakeCursor.clear(); + } + } + + // Remove any trailing newlines from the selection. For + // example, with the caret at the start of the last word on the line, + // 'dw' should word, but not the newline, while 'w' should advance the + // caret to the first character of the next line. + function clipToLine(cm, curStart, curEnd) { + var selection = cm.getRange(curStart, curEnd); + // Only clip if the selection ends with trailing newline + whitespace + if (/\n\s*$/.test(selection)) { + var lines = selection.split('\n'); + // We know this is all whitepsace. + lines.pop(); + + // Cases: + // 1. Last word is an empty line - do not clip the trailing '\n' + // 2. Last word is not an empty line - clip the trailing '\n' + var line; + // Find the line containing the last word, and clip all whitespace up + // to it. + for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) { + curEnd.line--; + curEnd.ch = 0; + } + // If the last word is not an empty line, clip an additional newline + if (line) { + curEnd.line--; + curEnd.ch = lineLength(cm, curEnd.line); + } else { + curEnd.ch = 0; + } + } + } + + // Expand the selection to line ends. + function expandSelectionToLine(_cm, curStart, curEnd) { + curStart.ch = 0; + curEnd.ch = 0; + curEnd.line++; + } + + function findFirstNonWhiteSpaceCharacter(text) { + if (!text) { + return 0; + } + var firstNonWS = text.search(/\S/); + return firstNonWS == -1 ? text.length : firstNonWS; + } + + function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) { + var cur = getHead(cm); + var line = cm.getLine(cur.line); + var idx = cur.ch; + + // Seek to first word or non-whitespace character, depending on if + // noSymbol is true. + var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0]; + while (!test(line.charAt(idx))) { + idx++; + if (idx >= line.length) { return null; } + } + + if (bigWord) { + test = bigWordCharTest[0]; + } else { + test = wordCharTest[0]; + if (!test(line.charAt(idx))) { + test = wordCharTest[1]; + } + } + + var end = idx, start = idx; + while (test(line.charAt(end)) && end < line.length) { end++; } + while (test(line.charAt(start)) && start >= 0) { start--; } + start++; + + if (inclusive) { + // If present, include all whitespace after word. + // Otherwise, include all whitespace before word, except indentation. + var wordEnd = end; + while (/\s/.test(line.charAt(end)) && end < line.length) { end++; } + if (wordEnd == end) { + var wordStart = start; + while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; } + if (!start) { start = wordStart; } + } + } + return { start: Pos(cur.line, start), end: Pos(cur.line, end) }; + } + + function recordJumpPosition(cm, oldCur, newCur) { + if (!cursorEqual(oldCur, newCur)) { + vimGlobalState.jumpList.add(cm, oldCur, newCur); + } + } + + function recordLastCharacterSearch(increment, args) { + vimGlobalState.lastChararacterSearch.increment = increment; + vimGlobalState.lastChararacterSearch.forward = args.forward; + vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter; + } + + var symbolToMode = { + '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket', + '[': 'section', ']': 'section', + '*': 'comment', '/': 'comment', + 'm': 'method', 'M': 'method', + '#': 'preprocess' + }; + var findSymbolModes = { + bracket: { + isComplete: function(state) { + if (state.nextCh === state.symb) { + state.depth++; + if (state.depth >= 1)return true; + } else if (state.nextCh === state.reverseSymb) { + state.depth--; + } + return false; + } + }, + section: { + init: function(state) { + state.curMoveThrough = true; + state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}'; + }, + isComplete: function(state) { + return state.index === 0 && state.nextCh === state.symb; + } + }, + comment: { + isComplete: function(state) { + var found = state.lastCh === '*' && state.nextCh === '/'; + state.lastCh = state.nextCh; + return found; + } + }, + // TODO: The original Vim implementation only operates on level 1 and 2. + // The current implementation doesn't check for code block level and + // therefore it operates on any levels. + method: { + init: function(state) { + state.symb = (state.symb === 'm' ? '{' : '}'); + state.reverseSymb = state.symb === '{' ? '}' : '{'; + }, + isComplete: function(state) { + if (state.nextCh === state.symb)return true; + return false; + } + }, + preprocess: { + init: function(state) { + state.index = 0; + }, + isComplete: function(state) { + if (state.nextCh === '#') { + var token = state.lineText.match(/#(\w+)/)[1]; + if (token === 'endif') { + if (state.forward && state.depth === 0) { + return true; + } + state.depth++; + } else if (token === 'if') { + if (!state.forward && state.depth === 0) { + return true; + } + state.depth--; + } + if (token === 'else' && state.depth === 0)return true; + } + return false; + } + } + }; + function findSymbol(cm, repeat, forward, symb) { + var cur = copyCursor(cm.getCursor()); + var increment = forward ? 1 : -1; + var endLine = forward ? cm.lineCount() : -1; + var curCh = cur.ch; + var line = cur.line; + var lineText = cm.getLine(line); + var state = { + lineText: lineText, + nextCh: lineText.charAt(curCh), + lastCh: null, + index: curCh, + symb: symb, + reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb], + forward: forward, + depth: 0, + curMoveThrough: false + }; + var mode = symbolToMode[symb]; + if (!mode)return cur; + var init = findSymbolModes[mode].init; + var isComplete = findSymbolModes[mode].isComplete; + if (init) { init(state); } + while (line !== endLine && repeat) { + state.index += increment; + state.nextCh = state.lineText.charAt(state.index); + if (!state.nextCh) { + line += increment; + state.lineText = cm.getLine(line) || ''; + if (increment > 0) { + state.index = 0; + } else { + var lineLen = state.lineText.length; + state.index = (lineLen > 0) ? (lineLen-1) : 0; + } + state.nextCh = state.lineText.charAt(state.index); + } + if (isComplete(state)) { + cur.line = line; + cur.ch = state.index; + repeat--; + } + } + if (state.nextCh || state.curMoveThrough) { + return Pos(line, state.index); + } + return cur; + } + + /** + * Returns the boundaries of the next word. If the cursor in the middle of + * the word, then returns the boundaries of the current word, starting at + * the cursor. If the cursor is at the start/end of a word, and we are going + * forward/backward, respectively, find the boundaries of the next word. + * + * @param {CodeMirror} cm CodeMirror object. + * @param {Cursor} cur The cursor position. + * @param {boolean} forward True to search forward. False to search + * backward. + * @param {boolean} bigWord True if punctuation count as part of the word. + * False if only [a-zA-Z0-9] characters count as part of the word. + * @param {boolean} emptyLineIsWord True if empty lines should be treated + * as words. + * @return {Object{from:number, to:number, line: number}} The boundaries of + * the word, or null if there are no more words. + */ + function findWord(cm, cur, forward, bigWord, emptyLineIsWord) { + var lineNum = cur.line; + var pos = cur.ch; + var line = cm.getLine(lineNum); + var dir = forward ? 1 : -1; + var charTests = bigWord ? bigWordCharTest: wordCharTest; + + if (emptyLineIsWord && line == '') { + lineNum += dir; + line = cm.getLine(lineNum); + if (!isLine(cm, lineNum)) { + return null; + } + pos = (forward) ? 0 : line.length; + } + + while (true) { + if (emptyLineIsWord && line == '') { + return { from: 0, to: 0, line: lineNum }; + } + var stop = (dir > 0) ? line.length : -1; + var wordStart = stop, wordEnd = stop; + // Find bounds of next word. + while (pos != stop) { + var foundWord = false; + for (var i = 0; i < charTests.length && !foundWord; ++i) { + if (charTests[i](line.charAt(pos))) { + wordStart = pos; + // Advance to end of word. + while (pos != stop && charTests[i](line.charAt(pos))) { + pos += dir; + } + wordEnd = pos; + foundWord = wordStart != wordEnd; + if (wordStart == cur.ch && lineNum == cur.line && + wordEnd == wordStart + dir) { + // We started at the end of a word. Find the next one. + continue; + } else { + return { + from: Math.min(wordStart, wordEnd + 1), + to: Math.max(wordStart, wordEnd), + line: lineNum }; + } + } + } + if (!foundWord) { + pos += dir; + } + } + // Advance to next/prev line. + lineNum += dir; + if (!isLine(cm, lineNum)) { + return null; + } + line = cm.getLine(lineNum); + pos = (dir > 0) ? 0 : line.length; + } + // Should never get here. + throw new Error('The impossible happened.'); + } + + /** + * @param {CodeMirror} cm CodeMirror object. + * @param {Pos} cur The position to start from. + * @param {int} repeat Number of words to move past. + * @param {boolean} forward True to search forward. False to search + * backward. + * @param {boolean} wordEnd True to move to end of word. False to move to + * beginning of word. + * @param {boolean} bigWord True if punctuation count as part of the word. + * False if only alphabet characters count as part of the word. + * @return {Cursor} The position the cursor should move to. + */ + function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) { + var curStart = copyCursor(cur); + var words = []; + if (forward && !wordEnd || !forward && wordEnd) { + repeat++; + } + // For 'e', empty lines are not considered words, go figure. + var emptyLineIsWord = !(forward && wordEnd); + for (var i = 0; i < repeat; i++) { + var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord); + if (!word) { + var eodCh = lineLength(cm, cm.lastLine()); + words.push(forward + ? {line: cm.lastLine(), from: eodCh, to: eodCh} + : {line: 0, from: 0, to: 0}); + break; + } + words.push(word); + cur = Pos(word.line, forward ? (word.to - 1) : word.from); + } + var shortCircuit = words.length != repeat; + var firstWord = words[0]; + var lastWord = words.pop(); + if (forward && !wordEnd) { + // w + if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) { + // We did not start in the middle of a word. Discard the extra word at the end. + lastWord = words.pop(); + } + return Pos(lastWord.line, lastWord.from); + } else if (forward && wordEnd) { + return Pos(lastWord.line, lastWord.to - 1); + } else if (!forward && wordEnd) { + // ge + if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) { + // We did not start in the middle of a word. Discard the extra word at the end. + lastWord = words.pop(); + } + return Pos(lastWord.line, lastWord.to); + } else { + // b + return Pos(lastWord.line, lastWord.from); + } + } + + function moveToCharacter(cm, repeat, forward, character) { + var cur = cm.getCursor(); + var start = cur.ch; + var idx; + for (var i = 0; i < repeat; i ++) { + var line = cm.getLine(cur.line); + idx = charIdxInLine(start, line, character, forward, true); + if (idx == -1) { + return null; + } + start = idx; + } + return Pos(cm.getCursor().line, idx); + } + + function moveToColumn(cm, repeat) { + // repeat is always >= 1, so repeat - 1 always corresponds + // to the column we want to go to. + var line = cm.getCursor().line; + return clipCursorToContent(cm, Pos(line, repeat - 1)); + } + + function updateMark(cm, vim, markName, pos) { + if (!inArray(markName, validMarks)) { + return; + } + if (vim.marks[markName]) { + vim.marks[markName].clear(); + } + vim.marks[markName] = cm.setBookmark(pos); + } + + function charIdxInLine(start, line, character, forward, includeChar) { + // Search for char in line. + // motion_options: {forward, includeChar} + // If includeChar = true, include it too. + // If forward = true, search forward, else search backwards. + // If char is not found on this line, do nothing + var idx; + if (forward) { + idx = line.indexOf(character, start + 1); + if (idx != -1 && !includeChar) { + idx -= 1; + } + } else { + idx = line.lastIndexOf(character, start - 1); + if (idx != -1 && !includeChar) { + idx += 1; + } + } + return idx; + } + + function findParagraph(cm, head, repeat, dir, inclusive) { + var line = head.line; + var min = cm.firstLine(); + var max = cm.lastLine(); + var start, end, i = line; + function isEmpty(i) { return !cm.getLine(i); } + function isBoundary(i, dir, any) { + if (any) { return isEmpty(i) != isEmpty(i + dir); } + return !isEmpty(i) && isEmpty(i + dir); + } + if (dir) { + while (min <= i && i <= max && repeat > 0) { + if (isBoundary(i, dir)) { repeat--; } + i += dir; + } + return new Pos(i, 0); + } + + var vim = cm.state.vim; + if (vim.visualLine && isBoundary(line, 1, true)) { + var anchor = vim.sel.anchor; + if (isBoundary(anchor.line, -1, true)) { + if (!inclusive || anchor.line != line) { + line += 1; + } + } + } + var startState = isEmpty(line); + for (i = line; i <= max && repeat; i++) { + if (isBoundary(i, 1, true)) { + if (!inclusive || isEmpty(i) != startState) { + repeat--; + } + } + } + end = new Pos(i, 0); + // select boundary before paragraph for the last one + if (i > max && !startState) { startState = true; } + else { inclusive = false; } + for (i = line; i > min; i--) { + if (!inclusive || isEmpty(i) == startState || i == line) { + if (isBoundary(i, -1, true)) { break; } + } + } + start = new Pos(i, 0); + return { start: start, end: end }; + } + + // TODO: perhaps this finagling of start and end positions belonds + // in codmirror/replaceRange? + function selectCompanionObject(cm, head, symb, inclusive) { + var cur = head, start, end; + + var bracketRegexp = ({ + '(': /[()]/, ')': /[()]/, + '[': /[[\]]/, ']': /[[\]]/, + '{': /[{}]/, '}': /[{}]/})[symb]; + var openSym = ({ + '(': '(', ')': '(', + '[': '[', ']': '[', + '{': '{', '}': '{'})[symb]; + var curChar = cm.getLine(cur.line).charAt(cur.ch); + // Due to the behavior of scanForBracket, we need to add an offset if the + // cursor is on a matching open bracket. + var offset = curChar === openSym ? 1 : 0; + + start = cm.scanForBracket(Pos(cur.line, cur.ch + offset), -1, null, {'bracketRegex': bracketRegexp}); + end = cm.scanForBracket(Pos(cur.line, cur.ch + offset), 1, null, {'bracketRegex': bracketRegexp}); + + if (!start || !end) { + return { start: cur, end: cur }; + } + + start = start.pos; + end = end.pos; + + if ((start.line == end.line && start.ch > end.ch) + || (start.line > end.line)) { + var tmp = start; + start = end; + end = tmp; + } + + if (inclusive) { + end.ch += 1; + } else { + start.ch += 1; + } + + return { start: start, end: end }; + } + + // Takes in a symbol and a cursor and tries to simulate text objects that + // have identical opening and closing symbols + // TODO support across multiple lines + function findBeginningAndEnd(cm, head, symb, inclusive) { + var cur = copyCursor(head); + var line = cm.getLine(cur.line); + var chars = line.split(''); + var start, end, i, len; + var firstIndex = chars.indexOf(symb); + + // the decision tree is to always look backwards for the beginning first, + // but if the cursor is in front of the first instance of the symb, + // then move the cursor forward + if (cur.ch < firstIndex) { + cur.ch = firstIndex; + // Why is this line even here??? + // cm.setCursor(cur.line, firstIndex+1); + } + // otherwise if the cursor is currently on the closing symbol + else if (firstIndex < cur.ch && chars[cur.ch] == symb) { + end = cur.ch; // assign end to the current cursor + --cur.ch; // make sure to look backwards + } + + // if we're currently on the symbol, we've got a start + if (chars[cur.ch] == symb && !end) { + start = cur.ch + 1; // assign start to ahead of the cursor + } else { + // go backwards to find the start + for (i = cur.ch; i > -1 && !start; i--) { + if (chars[i] == symb) { + start = i + 1; + } + } + } + + // look forwards for the end symbol + if (start && !end) { + for (i = start, len = chars.length; i < len && !end; i++) { + if (chars[i] == symb) { + end = i; + } + } + } + + // nothing found + if (!start || !end) { + return { start: cur, end: cur }; + } + + // include the symbols + if (inclusive) { + --start; ++end; + } + + return { + start: Pos(cur.line, start), + end: Pos(cur.line, end) + }; + } + + // Search functions + defineOption('pcre', true, 'boolean'); + function SearchState() {} + SearchState.prototype = { + getQuery: function() { + return vimGlobalState.query; + }, + setQuery: function(query) { + vimGlobalState.query = query; + }, + getOverlay: function() { + return this.searchOverlay; + }, + setOverlay: function(overlay) { + this.searchOverlay = overlay; + }, + isReversed: function() { + return vimGlobalState.isReversed; + }, + setReversed: function(reversed) { + vimGlobalState.isReversed = reversed; + }, + getScrollbarAnnotate: function() { + return this.annotate; + }, + setScrollbarAnnotate: function(annotate) { + this.annotate = annotate; + } + }; + function getSearchState(cm) { + var vim = cm.state.vim; + return vim.searchState_ || (vim.searchState_ = new SearchState()); + } + function dialog(cm, template, shortText, onClose, options) { + if (cm.openDialog) { + cm.openDialog(template, onClose, { bottom: true, value: options.value, + onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp, + selectValueOnOpen: false}); + } + else { + onClose(prompt(shortText, '')); + } + } + function splitBySlash(argString) { + var slashes = findUnescapedSlashes(argString) || []; + if (!slashes.length) return []; + var tokens = []; + // in case of strings like foo/bar + if (slashes[0] !== 0) return; + for (var i = 0; i < slashes.length; i++) { + if (typeof slashes[i] == 'number') + tokens.push(argString.substring(slashes[i] + 1, slashes[i+1])); + } + return tokens; + } + + function findUnescapedSlashes(str) { + var escapeNextChar = false; + var slashes = []; + for (var i = 0; i < str.length; i++) { + var c = str.charAt(i); + if (!escapeNextChar && c == '/') { + slashes.push(i); + } + escapeNextChar = !escapeNextChar && (c == '\\'); + } + return slashes; + } + + // Translates a search string from ex (vim) syntax into javascript form. + function translateRegex(str) { + // When these match, add a '\' if unescaped or remove one if escaped. + var specials = '|(){'; + // Remove, but never add, a '\' for these. + var unescape = '}'; + var escapeNextChar = false; + var out = []; + for (var i = -1; i < str.length; i++) { + var c = str.charAt(i) || ''; + var n = str.charAt(i+1) || ''; + var specialComesNext = (n && specials.indexOf(n) != -1); + if (escapeNextChar) { + if (c !== '\\' || !specialComesNext) { + out.push(c); + } + escapeNextChar = false; + } else { + if (c === '\\') { + escapeNextChar = true; + // Treat the unescape list as special for removing, but not adding '\'. + if (n && unescape.indexOf(n) != -1) { + specialComesNext = true; + } + // Not passing this test means removing a '\'. + if (!specialComesNext || n === '\\') { + out.push(c); + } + } else { + out.push(c); + if (specialComesNext && n !== '\\') { + out.push('\\'); + } + } + } + } + return out.join(''); + } + + // Translates the replace part of a search and replace from ex (vim) syntax into + // javascript form. Similar to translateRegex, but additionally fixes back references + // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'. + var charUnescapes = {'\\n': '\n', '\\r': '\r', '\\t': '\t'}; + function translateRegexReplace(str) { + var escapeNextChar = false; + var out = []; + for (var i = -1; i < str.length; i++) { + var c = str.charAt(i) || ''; + var n = str.charAt(i+1) || ''; + if (charUnescapes[c + n]) { + out.push(charUnescapes[c+n]); + i++; + } else if (escapeNextChar) { + // At any point in the loop, escapeNextChar is true if the previous + // character was a '\' and was not escaped. + out.push(c); + escapeNextChar = false; + } else { + if (c === '\\') { + escapeNextChar = true; + if ((isNumber(n) || n === '$')) { + out.push('$'); + } else if (n !== '/' && n !== '\\') { + out.push('\\'); + } + } else { + if (c === '$') { + out.push('$'); + } + out.push(c); + if (n === '/') { + out.push('\\'); + } + } + } + } + return out.join(''); + } + + // Unescape \ and / in the replace part, for PCRE mode. + var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t'}; + function unescapeRegexReplace(str) { + var stream = new CodeMirror.StringStream(str); + var output = []; + while (!stream.eol()) { + // Search for \. + while (stream.peek() && stream.peek() != '\\') { + output.push(stream.next()); + } + var matched = false; + for (var matcher in unescapes) { + if (stream.match(matcher, true)) { + matched = true; + output.push(unescapes[matcher]); + break; + } + } + if (!matched) { + // Don't change anything + output.push(stream.next()); + } + } + return output.join(''); + } + + /** + * Extract the regular expression from the query and return a Regexp object. + * Returns null if the query is blank. + * If ignoreCase is passed in, the Regexp object will have the 'i' flag set. + * If smartCase is passed in, and the query contains upper case letters, + * then ignoreCase is overridden, and the 'i' flag will not be set. + * If the query contains the /i in the flag part of the regular expression, + * then both ignoreCase and smartCase are ignored, and 'i' will be passed + * through to the Regex object. + */ + function parseQuery(query, ignoreCase, smartCase) { + // First update the last search register + var lastSearchRegister = vimGlobalState.registerController.getRegister('/'); + lastSearchRegister.setText(query); + // Check if the query is already a regex. + if (query instanceof RegExp) { return query; } + // First try to extract regex + flags from the input. If no flags found, + // extract just the regex. IE does not accept flags directly defined in + // the regex string in the form /regex/flags + var slashes = findUnescapedSlashes(query); + var regexPart; + var forceIgnoreCase; + if (!slashes.length) { + // Query looks like 'regexp' + regexPart = query; + } else { + // Query looks like 'regexp/...' + regexPart = query.substring(0, slashes[0]); + var flagsPart = query.substring(slashes[0]); + forceIgnoreCase = (flagsPart.indexOf('i') != -1); + } + if (!regexPart) { + return null; + } + if (!getOption('pcre')) { + regexPart = translateRegex(regexPart); + } + if (smartCase) { + ignoreCase = (/^[^A-Z]*$/).test(regexPart); + } + var regexp = new RegExp(regexPart, + (ignoreCase || forceIgnoreCase) ? 'i' : undefined); + return regexp; + } + function showConfirm(cm, text) { + if (cm.openNotification) { + cm.openNotification('' + text + '', + {bottom: true, duration: 5000}); + } else { + alert(text); + } + } + function makePrompt(prefix, desc) { + var raw = ''; + if (prefix) { + raw += '' + prefix + ''; + } + raw += ' ' + + ''; + if (desc) { + raw += ''; + raw += desc; + raw += ''; + } + return raw; + } + var searchPromptDesc = '(Javascript regexp)'; + function showPrompt(cm, options) { + var shortText = (options.prefix || '') + ' ' + (options.desc || ''); + var prompt = makePrompt(options.prefix, options.desc); + dialog(cm, prompt, shortText, options.onClose, options); + } + function regexEqual(r1, r2) { + if (r1 instanceof RegExp && r2 instanceof RegExp) { + var props = ['global', 'multiline', 'ignoreCase', 'source']; + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + if (r1[prop] !== r2[prop]) { + return false; + } + } + return true; + } + return false; + } + // Returns true if the query is valid. + function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) { + if (!rawQuery) { + return; + } + var state = getSearchState(cm); + var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase); + if (!query) { + return; + } + highlightSearchMatches(cm, query); + if (regexEqual(query, state.getQuery())) { + return query; + } + state.setQuery(query); + return query; + } + function searchOverlay(query) { + if (query.source.charAt(0) == '^') { + var matchSol = true; + } + return { + token: function(stream) { + if (matchSol && !stream.sol()) { + stream.skipToEnd(); + return; + } + var match = stream.match(query, false); + if (match) { + if (match[0].length == 0) { + // Matched empty string, skip to next. + stream.next(); + return 'searching'; + } + if (!stream.sol()) { + // Backtrack 1 to match \b + stream.backUp(1); + if (!query.exec(stream.next() + match[0])) { + stream.next(); + return null; + } + } + stream.match(query); + return 'searching'; + } + while (!stream.eol()) { + stream.next(); + if (stream.match(query, false)) break; + } + }, + query: query + }; + } + function highlightSearchMatches(cm, query) { + var searchState = getSearchState(cm); + var overlay = searchState.getOverlay(); + if (!overlay || query != overlay.query) { + if (overlay) { + cm.removeOverlay(overlay); + } + overlay = searchOverlay(query); + cm.addOverlay(overlay); + if (cm.showMatchesOnScrollbar) { + if (searchState.getScrollbarAnnotate()) { + searchState.getScrollbarAnnotate().clear(); + } + searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query)); + } + searchState.setOverlay(overlay); + } + } + function findNext(cm, prev, query, repeat) { + if (repeat === undefined) { repeat = 1; } + return cm.operation(function() { + var pos = cm.getCursor(); + var cursor = cm.getSearchCursor(query, pos); + for (var i = 0; i < repeat; i++) { + var found = cursor.find(prev); + if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); } + if (!found) { + // SearchCursor may have returned null because it hit EOF, wrap + // around and try again. + cursor = cm.getSearchCursor(query, + (prev) ? Pos(cm.lastLine()) : Pos(cm.firstLine(), 0) ); + if (!cursor.find(prev)) { + return; + } + } + } + return cursor.from(); + }); + } + function clearSearchHighlight(cm) { + var state = getSearchState(cm); + cm.removeOverlay(getSearchState(cm).getOverlay()); + state.setOverlay(null); + if (state.getScrollbarAnnotate()) { + state.getScrollbarAnnotate().clear(); + state.setScrollbarAnnotate(null); + } + } + /** + * Check if pos is in the specified range, INCLUSIVE. + * Range can be specified with 1 or 2 arguments. + * If the first range argument is an array, treat it as an array of line + * numbers. Match pos against any of the lines. + * If the first range argument is a number, + * if there is only 1 range argument, check if pos has the same line + * number + * if there are 2 range arguments, then check if pos is in between the two + * range arguments. + */ + function isInRange(pos, start, end) { + if (typeof pos != 'number') { + // Assume it is a cursor position. Get the line number. + pos = pos.line; + } + if (start instanceof Array) { + return inArray(pos, start); + } else { + if (end) { + return (pos >= start && pos <= end); + } else { + return pos == start; + } + } + } + function getUserVisibleLines(cm) { + var scrollInfo = cm.getScrollInfo(); + var occludeToleranceTop = 6; + var occludeToleranceBottom = 10; + var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local'); + var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top; + var to = cm.coordsChar({left:0, top: bottomY}, 'local'); + return {top: from.line, bottom: to.line}; + } + + var ExCommandDispatcher = function() { + this.buildCommandMap_(); + }; + ExCommandDispatcher.prototype = { + processCommand: function(cm, input, opt_params) { + var that = this; + cm.operation(function () { + cm.curOp.isVimOp = true; + that._processCommand(cm, input, opt_params); + }); + }, + _processCommand: function(cm, input, opt_params) { + var vim = cm.state.vim; + var commandHistoryRegister = vimGlobalState.registerController.getRegister(':'); + var previousCommand = commandHistoryRegister.toString(); + if (vim.visualMode) { + exitVisualMode(cm); + } + var inputStream = new CodeMirror.StringStream(input); + // update ": with the latest command whether valid or invalid + commandHistoryRegister.setText(input); + var params = opt_params || {}; + params.input = input; + try { + this.parseInput_(cm, inputStream, params); + } catch(e) { + showConfirm(cm, e); + throw e; + } + var command; + var commandName; + if (!params.commandName) { + // If only a line range is defined, move to the line. + if (params.line !== undefined) { + commandName = 'move'; + } + } else { + command = this.matchCommand_(params.commandName); + if (command) { + commandName = command.name; + if (command.excludeFromCommandHistory) { + commandHistoryRegister.setText(previousCommand); + } + this.parseCommandArgs_(inputStream, params, command); + if (command.type == 'exToKey') { + // Handle Ex to Key mapping. + for (var i = 0; i < command.toKeys.length; i++) { + CodeMirror.Vim.handleKey(cm, command.toKeys[i], 'mapping'); + } + return; + } else if (command.type == 'exToEx') { + // Handle Ex to Ex mapping. + this.processCommand(cm, command.toInput); + return; + } + } + } + if (!commandName) { + showConfirm(cm, 'Not an editor command ":' + input + '"'); + return; + } + try { + exCommands[commandName](cm, params); + // Possibly asynchronous commands (e.g. substitute, which might have a + // user confirmation), are responsible for calling the callback when + // done. All others have it taken care of for them here. + if ((!command || !command.possiblyAsync) && params.callback) { + params.callback(); + } + } catch(e) { + showConfirm(cm, e); + throw e; + } + }, + parseInput_: function(cm, inputStream, result) { + inputStream.eatWhile(':'); + // Parse range. + if (inputStream.eat('%')) { + result.line = cm.firstLine(); + result.lineEnd = cm.lastLine(); + } else { + result.line = this.parseLineSpec_(cm, inputStream); + if (result.line !== undefined && inputStream.eat(',')) { + result.lineEnd = this.parseLineSpec_(cm, inputStream); + } + } + + // Parse command name. + var commandMatch = inputStream.match(/^(\w+)/); + if (commandMatch) { + result.commandName = commandMatch[1]; + } else { + result.commandName = inputStream.match(/.*/)[0]; + } + + return result; + }, + parseLineSpec_: function(cm, inputStream) { + var numberMatch = inputStream.match(/^(\d+)/); + if (numberMatch) { + return parseInt(numberMatch[1], 10) - 1; + } + switch (inputStream.next()) { + case '.': + return cm.getCursor().line; + case '$': + return cm.lastLine(); + case '\'': + var mark = cm.state.vim.marks[inputStream.next()]; + if (mark && mark.find()) { + return mark.find().line; + } + throw new Error('Mark not set'); + default: + inputStream.backUp(1); + return undefined; + } + }, + parseCommandArgs_: function(inputStream, params, command) { + if (inputStream.eol()) { + return; + } + params.argString = inputStream.match(/.*/)[0]; + // Parse command-line arguments + var delim = command.argDelimiter || /\s+/; + var args = trim(params.argString).split(delim); + if (args.length && args[0]) { + params.args = args; + } + }, + matchCommand_: function(commandName) { + // Return the command in the command map that matches the shortest + // prefix of the passed in command name. The match is guaranteed to be + // unambiguous if the defaultExCommandMap's shortNames are set up + // correctly. (see @code{defaultExCommandMap}). + for (var i = commandName.length; i > 0; i--) { + var prefix = commandName.substring(0, i); + if (this.commandMap_[prefix]) { + var command = this.commandMap_[prefix]; + if (command.name.indexOf(commandName) === 0) { + return command; + } + } + } + return null; + }, + buildCommandMap_: function() { + this.commandMap_ = {}; + for (var i = 0; i < defaultExCommandMap.length; i++) { + var command = defaultExCommandMap[i]; + var key = command.shortName || command.name; + this.commandMap_[key] = command; + } + }, + map: function(lhs, rhs, ctx) { + if (lhs != ':' && lhs.charAt(0) == ':') { + if (ctx) { throw Error('Mode not supported for ex mappings'); } + var commandName = lhs.substring(1); + if (rhs != ':' && rhs.charAt(0) == ':') { + // Ex to Ex mapping + this.commandMap_[commandName] = { + name: commandName, + type: 'exToEx', + toInput: rhs.substring(1), + user: true + }; + } else { + // Ex to key mapping + this.commandMap_[commandName] = { + name: commandName, + type: 'exToKey', + toKeys: rhs, + user: true + }; + } + } else { + if (rhs != ':' && rhs.charAt(0) == ':') { + // Key to Ex mapping. + var mapping = { + keys: lhs, + type: 'keyToEx', + exArgs: { input: rhs.substring(1) }, + user: true}; + if (ctx) { mapping.context = ctx; } + defaultKeymap.unshift(mapping); + } else { + // Key to key mapping + var mapping = { + keys: lhs, + type: 'keyToKey', + toKeys: rhs, + user: true + }; + if (ctx) { mapping.context = ctx; } + defaultKeymap.unshift(mapping); + } + } + }, + unmap: function(lhs, ctx) { + if (lhs != ':' && lhs.charAt(0) == ':') { + // Ex to Ex or Ex to key mapping + if (ctx) { throw Error('Mode not supported for ex mappings'); } + var commandName = lhs.substring(1); + if (this.commandMap_[commandName] && this.commandMap_[commandName].user) { + delete this.commandMap_[commandName]; + return; + } + } else { + // Key to Ex or key to key mapping + var keys = lhs; + for (var i = 0; i < defaultKeymap.length; i++) { + if (keys == defaultKeymap[i].keys + && defaultKeymap[i].context === ctx + && defaultKeymap[i].user) { + defaultKeymap.splice(i, 1); + return; + } + } + } + throw Error('No such mapping.'); + } + }; + + var exCommands = { + colorscheme: function(cm, params) { + if (!params.args || params.args.length < 1) { + showConfirm(cm, cm.getOption('theme')); + return; + } + cm.setOption('theme', params.args[0]); + }, + map: function(cm, params, ctx) { + var mapArgs = params.args; + if (!mapArgs || mapArgs.length < 2) { + if (cm) { + showConfirm(cm, 'Invalid mapping: ' + params.input); + } + return; + } + exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx); + }, + imap: function(cm, params) { this.map(cm, params, 'insert'); }, + nmap: function(cm, params) { this.map(cm, params, 'normal'); }, + vmap: function(cm, params) { this.map(cm, params, 'visual'); }, + unmap: function(cm, params, ctx) { + var mapArgs = params.args; + if (!mapArgs || mapArgs.length < 1) { + if (cm) { + showConfirm(cm, 'No such mapping: ' + params.input); + } + return; + } + exCommandDispatcher.unmap(mapArgs[0], ctx); + }, + move: function(cm, params) { + commandDispatcher.processCommand(cm, cm.state.vim, { + type: 'motion', + motion: 'moveToLineOrEdgeOfDocument', + motionArgs: { forward: false, explicitRepeat: true, + linewise: true }, + repeatOverride: params.line+1}); + }, + set: function(cm, params) { + var setArgs = params.args; + // Options passed through to the setOption/getOption calls. May be passed in by the + // local/global versions of the set command + var setCfg = params.setCfg || {}; + if (!setArgs || setArgs.length < 1) { + if (cm) { + showConfirm(cm, 'Invalid mapping: ' + params.input); + } + return; + } + var expr = setArgs[0].split('='); + var optionName = expr[0]; + var value = expr[1]; + var forceGet = false; + + if (optionName.charAt(optionName.length - 1) == '?') { + // If post-fixed with ?, then the set is actually a get. + if (value) { throw Error('Trailing characters: ' + params.argString); } + optionName = optionName.substring(0, optionName.length - 1); + forceGet = true; + } + if (value === undefined && optionName.substring(0, 2) == 'no') { + // To set boolean options to false, the option name is prefixed with + // 'no'. + optionName = optionName.substring(2); + value = false; + } + + var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean'; + if (optionIsBoolean && value == undefined) { + // Calling set with a boolean option sets it to true. + value = true; + } + // If no value is provided, then we assume this is a get. + if (!optionIsBoolean && value === undefined || forceGet) { + var oldValue = getOption(optionName, cm, setCfg); + if (oldValue === true || oldValue === false) { + showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName); + } else { + showConfirm(cm, ' ' + optionName + '=' + oldValue); + } + } else { + setOption(optionName, value, cm, setCfg); + } + }, + setlocal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'local'}; + this.set(cm, params); + }, + setglobal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'global'}; + this.set(cm, params); + }, + registers: function(cm, params) { + var regArgs = params.args; + var registers = vimGlobalState.registerController.registers; + var regInfo = '----------Registers----------

'; + if (!regArgs) { + for (var registerName in registers) { + var text = registers[registerName].toString(); + if (text.length) { + regInfo += '"' + registerName + ' ' + text + '
'; + } + } + } else { + var registerName; + regArgs = regArgs.join(''); + for (var i = 0; i < regArgs.length; i++) { + registerName = regArgs.charAt(i); + if (!vimGlobalState.registerController.isValidRegister(registerName)) { + continue; + } + var register = registers[registerName] || new Register(); + regInfo += '"' + registerName + ' ' + register.toString() + '
'; + } + } + showConfirm(cm, regInfo); + }, + sort: function(cm, params) { + var reverse, ignoreCase, unique, number; + function parseArgs() { + if (params.argString) { + var args = new CodeMirror.StringStream(params.argString); + if (args.eat('!')) { reverse = true; } + if (args.eol()) { return; } + if (!args.eatSpace()) { return 'Invalid arguments'; } + var opts = args.match(/[a-z]+/); + if (opts) { + opts = opts[0]; + ignoreCase = opts.indexOf('i') != -1; + unique = opts.indexOf('u') != -1; + var decimal = opts.indexOf('d') != -1 && 1; + var hex = opts.indexOf('x') != -1 && 1; + var octal = opts.indexOf('o') != -1 && 1; + if (decimal + hex + octal > 1) { return 'Invalid arguments'; } + number = decimal && 'decimal' || hex && 'hex' || octal && 'octal'; + } + if (args.match(/\/.*\//)) { return 'patterns not supported'; } + } + } + var err = parseArgs(); + if (err) { + showConfirm(cm, err + ': ' + params.argString); + return; + } + var lineStart = params.line || cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + if (lineStart == lineEnd) { return; } + var curStart = Pos(lineStart, 0); + var curEnd = Pos(lineEnd, lineLength(cm, lineEnd)); + var text = cm.getRange(curStart, curEnd).split('\n'); + var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ : + (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i : + (number == 'octal') ? /([0-7]+)/ : null; + var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null; + var numPart = [], textPart = []; + if (number) { + for (var i = 0; i < text.length; i++) { + if (numberRegex.exec(text[i])) { + numPart.push(text[i]); + } else { + textPart.push(text[i]); + } + } + } else { + textPart = text; + } + function compareFn(a, b) { + if (reverse) { var tmp; tmp = a; a = b; b = tmp; } + if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); } + var anum = number && numberRegex.exec(a); + var bnum = number && numberRegex.exec(b); + if (!anum) { return a < b ? -1 : 1; } + anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix); + bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix); + return anum - bnum; + } + numPart.sort(compareFn); + textPart.sort(compareFn); + text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart); + if (unique) { // Remove duplicate lines + var textOld = text; + var lastLine; + text = []; + for (var i = 0; i < textOld.length; i++) { + if (textOld[i] != lastLine) { + text.push(textOld[i]); + } + lastLine = textOld[i]; + } + } + cm.replaceRange(text.join('\n'), curStart, curEnd); + }, + global: function(cm, params) { + // a global command is of the form + // :[range]g/pattern/[cmd] + // argString holds the string /pattern/[cmd] + var argString = params.argString; + if (!argString) { + showConfirm(cm, 'Regular Expression missing from global'); + return; + } + // range is specified here + var lineStart = (params.line !== undefined) ? params.line : cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + // get the tokens from argString + var tokens = splitBySlash(argString); + var regexPart = argString, cmd; + if (tokens.length) { + regexPart = tokens[0]; + cmd = tokens.slice(1, tokens.length).join('/'); + } + if (regexPart) { + // If regex part is empty, then use the previous query. Otherwise + // use the regex part as the new query. + try { + updateSearchQuery(cm, regexPart, true /** ignoreCase */, + true /** smartCase */); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + regexPart); + return; + } + } + // now that we have the regexPart, search for regex matches in the + // specified range of lines + var query = getSearchState(cm).getQuery(); + var matchedLines = [], content = ''; + for (var i = lineStart; i <= lineEnd; i++) { + var matched = query.test(cm.getLine(i)); + if (matched) { + matchedLines.push(i+1); + content+= cm.getLine(i) + '
'; + } + } + // if there is no [cmd], just display the list of matched lines + if (!cmd) { + showConfirm(cm, content); + return; + } + var index = 0; + var nextCommand = function() { + if (index < matchedLines.length) { + var command = matchedLines[index] + cmd; + exCommandDispatcher.processCommand(cm, command, { + callback: nextCommand + }); + } + index++; + }; + nextCommand(); + }, + substitute: function(cm, params) { + if (!cm.getSearchCursor) { + throw new Error('Search feature not available. Requires searchcursor.js or ' + + 'any other getSearchCursor implementation.'); + } + var argString = params.argString; + var tokens = argString ? splitBySlash(argString) : []; + var regexPart, replacePart = '', trailing, flagsPart, count; + var confirm = false; // Whether to confirm each replace. + var global = false; // True to replace all instances on a line, false to replace only 1. + if (tokens.length) { + regexPart = tokens[0]; + replacePart = tokens[1]; + if (replacePart !== undefined) { + if (getOption('pcre')) { + replacePart = unescapeRegexReplace(replacePart); + } else { + replacePart = translateRegexReplace(replacePart); + } + vimGlobalState.lastSubstituteReplacePart = replacePart; + } + trailing = tokens[2] ? tokens[2].split(' ') : []; + } else { + // either the argString is empty or its of the form ' hello/world' + // actually splitBySlash returns a list of tokens + // only if the string starts with a '/' + if (argString && argString.length) { + showConfirm(cm, 'Substitutions should be of the form ' + + ':s/pattern/replace/'); + return; + } + } + // After the 3rd slash, we can have flags followed by a space followed + // by count. + if (trailing) { + flagsPart = trailing[0]; + count = parseInt(trailing[1]); + if (flagsPart) { + if (flagsPart.indexOf('c') != -1) { + confirm = true; + flagsPart.replace('c', ''); + } + if (flagsPart.indexOf('g') != -1) { + global = true; + flagsPart.replace('g', ''); + } + regexPart = regexPart + '/' + flagsPart; + } + } + if (regexPart) { + // If regex part is empty, then use the previous query. Otherwise use + // the regex part as the new query. + try { + updateSearchQuery(cm, regexPart, true /** ignoreCase */, + true /** smartCase */); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + regexPart); + return; + } + } + replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart; + if (replacePart === undefined) { + showConfirm(cm, 'No previous substitute regular expression'); + return; + } + var state = getSearchState(cm); + var query = state.getQuery(); + var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line; + var lineEnd = params.lineEnd || lineStart; + if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) { + lineEnd = Infinity; + } + if (count) { + lineStart = lineEnd; + lineEnd = lineStart + count - 1; + } + var startPos = clipCursorToContent(cm, Pos(lineStart, 0)); + var cursor = cm.getSearchCursor(query, startPos); + doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback); + }, + redo: CodeMirror.commands.redo, + undo: CodeMirror.commands.undo, + write: function(cm) { + if (CodeMirror.commands.save) { + // If a save command is defined, call it. + CodeMirror.commands.save(cm); + } else { + // Saves to text area if no save command is defined. + cm.save(); + } + }, + nohlsearch: function(cm) { + clearSearchHighlight(cm); + }, + delmarks: function(cm, params) { + if (!params.argString || !trim(params.argString)) { + showConfirm(cm, 'Argument required'); + return; + } + + var state = cm.state.vim; + var stream = new CodeMirror.StringStream(trim(params.argString)); + while (!stream.eol()) { + stream.eatSpace(); + + // Record the streams position at the beginning of the loop for use + // in error messages. + var count = stream.pos; + + if (!stream.match(/[a-zA-Z]/, false)) { + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); + return; + } + + var sym = stream.next(); + // Check if this symbol is part of a range + if (stream.match('-', true)) { + // This symbol is part of a range. + + // The range must terminate at an alphabetic character. + if (!stream.match(/[a-zA-Z]/, false)) { + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); + return; + } + + var startMark = sym; + var finishMark = stream.next(); + // The range must terminate at an alphabetic character which + // shares the same case as the start of the range. + if (isLowerCase(startMark) && isLowerCase(finishMark) || + isUpperCase(startMark) && isUpperCase(finishMark)) { + var start = startMark.charCodeAt(0); + var finish = finishMark.charCodeAt(0); + if (start >= finish) { + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); + return; + } + + // Because marks are always ASCII values, and we have + // determined that they are the same case, we can use + // their char codes to iterate through the defined range. + for (var j = 0; j <= finish - start; j++) { + var mark = String.fromCharCode(start + j); + delete state.marks[mark]; + } + } else { + showConfirm(cm, 'Invalid argument: ' + startMark + '-'); + return; + } + } else { + // This symbol is a valid mark, and is not part of a range. + delete state.marks[sym]; + } + } + } + }; + + var exCommandDispatcher = new ExCommandDispatcher(); + + /** + * @param {CodeMirror} cm CodeMirror instance we are in. + * @param {boolean} confirm Whether to confirm each replace. + * @param {Cursor} lineStart Line to start replacing from. + * @param {Cursor} lineEnd Line to stop replacing at. + * @param {RegExp} query Query for performing matches with. + * @param {string} replaceWith Text to replace matches with. May contain $1, + * $2, etc for replacing captured groups using Javascript replace. + * @param {function()} callback A callback for when the replace is done. + */ + function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query, + replaceWith, callback) { + // Set up all the functions. + cm.state.vim.exMode = true; + var done = false; + var lastPos = searchCursor.from(); + function replaceAll() { + cm.operation(function() { + while (!done) { + replace(); + next(); + } + stop(); + }); + } + function replace() { + var text = cm.getRange(searchCursor.from(), searchCursor.to()); + var newText = text.replace(query, replaceWith); + searchCursor.replace(newText); + } + function next() { + // The below only loops to skip over multiple occurrences on the same + // line when 'global' is not true. + while(searchCursor.findNext() && + isInRange(searchCursor.from(), lineStart, lineEnd)) { + if (!global && lastPos && searchCursor.from().line == lastPos.line) { + continue; + } + cm.scrollIntoView(searchCursor.from(), 30); + cm.setSelection(searchCursor.from(), searchCursor.to()); + lastPos = searchCursor.from(); + done = false; + return; + } + done = true; + } + function stop(close) { + if (close) { close(); } + cm.focus(); + if (lastPos) { + cm.setCursor(lastPos); + var vim = cm.state.vim; + vim.exMode = false; + vim.lastHPos = vim.lastHSPos = lastPos.ch; + } + if (callback) { callback(); } + } + function onPromptKeyDown(e, _value, close) { + // Swallow all keys. + CodeMirror.e_stop(e); + var keyName = CodeMirror.keyName(e); + switch (keyName) { + case 'Y': + replace(); next(); break; + case 'N': + next(); break; + case 'A': + // replaceAll contains a call to close of its own. We don't want it + // to fire too early or multiple times. + var savedCallback = callback; + callback = undefined; + cm.operation(replaceAll); + callback = savedCallback; + break; + case 'L': + replace(); + // fall through and exit. + case 'Q': + case 'Esc': + case 'Ctrl-C': + case 'Ctrl-[': + stop(close); + break; + } + if (done) { stop(close); } + return true; + } + + // Actually do replace. + next(); + if (done) { + showConfirm(cm, 'No matches for ' + query.source); + return; + } + if (!confirm) { + replaceAll(); + if (callback) { callback(); }; + return; + } + showPrompt(cm, { + prefix: 'replace with ' + replaceWith + ' (y/n/a/q/l)', + onKeyDown: onPromptKeyDown + }); + } + + CodeMirror.keyMap.vim = { + attach: attachVimMap, + detach: detachVimMap, + call: cmKey + }; + + function exitInsertMode(cm) { + var vim = cm.state.vim; + var macroModeState = vimGlobalState.macroModeState; + var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.'); + var isPlaying = macroModeState.isPlaying; + var lastChange = macroModeState.lastInsertModeChanges; + // In case of visual block, the insertModeChanges are not saved as a + // single word, so we convert them to a single word + // so as to update the ". register as expected in real vim. + var text = []; + if (!isPlaying) { + var selLength = lastChange.inVisualBlock ? vim.lastSelection.visualBlock.height : 1; + var changes = lastChange.changes; + var text = []; + var i = 0; + // In case of multiple selections in blockwise visual, + // the inserted text, for example: 'foo', is stored as + // 'f', 'f', InsertModeKey 'o', 'o', 'o', 'o'. (if you have a block with 2 lines). + // We push the contents of the changes array as per the following: + // 1. In case of InsertModeKey, just increment by 1. + // 2. In case of a character, jump by selLength (2 in the example). + while (i < changes.length) { + // This loop will convert 'ffoooo' to 'foo'. + text.push(changes[i]); + if (changes[i] instanceof InsertModeKey) { + i++; + } else { + i+= selLength; + } + } + lastChange.changes = text; + cm.off('change', onChange); + CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); + } + if (!isPlaying && vim.insertModeRepeat > 1) { + // Perform insert mode repeat for commands like 3,a and 3,o. + repeatLastEdit(cm, vim, vim.insertModeRepeat - 1, + true /** repeatForInsert */); + vim.lastEditInputState.repeatOverride = vim.insertModeRepeat; + } + delete vim.insertModeRepeat; + vim.insertMode = false; + cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1); + cm.setOption('keyMap', 'vim'); + cm.setOption('disableInput', true); + cm.toggleOverwrite(false); // exit replace mode if we were in it. + // update the ". register before exiting insert mode + insertModeChangeRegister.setText(lastChange.changes.join('')); + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + if (macroModeState.isRecording) { + logInsertModeChange(macroModeState); + } + } + + function _mapCommand(command) { + defaultKeymap.unshift(command); + } + + function mapCommand(keys, type, name, args, extra) { + var command = {keys: keys, type: type}; + command[type] = name; + command[type + "Args"] = args; + for (var key in extra) + command[key] = extra[key]; + _mapCommand(command); + } + + // The timeout in milliseconds for the two-character ESC keymap should be + // adjusted according to your typing speed to prevent false positives. + defineOption('insertModeEscKeysTimeout', 200, 'number'); + + CodeMirror.keyMap['vim-insert'] = { + // TODO: override navigation keys so that Esc will cancel automatic + // indentation from o, O, i_ + 'Ctrl-N': 'autocomplete', + 'Ctrl-P': 'autocomplete', + 'Enter': function(cm) { + var fn = CodeMirror.commands.newlineAndIndentContinueComment || + CodeMirror.commands.newlineAndIndent; + fn(cm); + }, + fallthrough: ['default'], + attach: attachVimMap, + detach: detachVimMap, + call: cmKey + }; + + CodeMirror.keyMap['vim-replace'] = { + 'Backspace': 'goCharLeft', + fallthrough: ['vim-insert'], + attach: attachVimMap, + detach: detachVimMap, + call: cmKey + }; + + function executeMacroRegister(cm, vim, macroModeState, registerName) { + var register = vimGlobalState.registerController.getRegister(registerName); + if (registerName == ':') { + // Read-only register containing last Ex command. + if (register.keyBuffer[0]) { + exCommandDispatcher.processCommand(cm, register.keyBuffer[0]); + } + macroModeState.isPlaying = false; + return; + } + var keyBuffer = register.keyBuffer; + var imc = 0; + macroModeState.isPlaying = true; + macroModeState.replaySearchQueries = register.searchQueries.slice(0); + for (var i = 0; i < keyBuffer.length; i++) { + var text = keyBuffer[i]; + var match, key; + while (text) { + // Pull off one command key, which is either a single character + // or a special sequence wrapped in '<' and '>', e.g. ''. + match = (/<\w+-.+?>|<\w+>|./).exec(text); + key = match[0]; + text = text.substring(match.index + key.length); + CodeMirror.Vim.handleKey(cm, key, 'macro'); + if (vim.insertMode) { + var changes = register.insertModeChanges[imc++].changes; + vimGlobalState.macroModeState.lastInsertModeChanges.changes = + changes; + repeatInsertModeChanges(cm, changes, 1); + exitInsertMode(cm); + } + } + }; + macroModeState.isPlaying = false; + } + + function logKey(macroModeState, key) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register) { + register.pushText(key); + } + } + + function logInsertModeChange(macroModeState) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register && register.pushInsertModeChanges) { + register.pushInsertModeChanges(macroModeState.lastInsertModeChanges); + } + } + + function logSearchQuery(macroModeState, query) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register && register.pushSearchQuery) { + register.pushSearchQuery(query); + } + } + + /** + * Listens for changes made in insert mode. + * Should only be active in insert mode. + */ + function onChange(_cm, changeObj) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + if (!macroModeState.isPlaying) { + while(changeObj) { + lastChange.expectCursorActivityForChange = true; + if (changeObj.origin == '+input' || changeObj.origin == 'paste' + || changeObj.origin === undefined /* only in testing */) { + var text = changeObj.text.join('\n'); + lastChange.changes.push(text); + } + // Change objects may be chained with next. + changeObj = changeObj.next; + } + } + } + + /** + * Listens for any kind of cursor activity on CodeMirror. + */ + function onCursorActivity(cm) { + var vim = cm.state.vim; + if (vim.insertMode) { + // Tracking cursor activity in insert mode (for macro support). + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { return; } + var lastChange = macroModeState.lastInsertModeChanges; + if (lastChange.expectCursorActivityForChange) { + lastChange.expectCursorActivityForChange = false; + } else { + // Cursor moved outside the context of an edit. Reset the change. + lastChange.changes = []; + } + } else if (!cm.curOp.isVimOp) { + handleExternalSelection(cm, vim); + } + if (vim.visualMode) { + updateFakeCursor(cm); + } + } + function updateFakeCursor(cm) { + var vim = cm.state.vim; + var from = clipCursorToContent(cm, copyCursor(vim.sel.head)); + var to = offsetCursor(from, 0, 1); + if (vim.fakeCursor) { + vim.fakeCursor.clear(); + } + vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'}); + } + function handleExternalSelection(cm, vim) { + var anchor = cm.getCursor('anchor'); + var head = cm.getCursor('head'); + // Enter or exit visual mode to match mouse selection. + if (vim.visualMode && !cm.somethingSelected()) { + exitVisualMode(cm, false); + } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) { + vim.visualMode = true; + vim.visualLine = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); + } + if (vim.visualMode) { + // Bind CodeMirror selection model to vim selection model. + // Mouse selections are considered visual characterwise. + var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; + var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; + head = offsetCursor(head, 0, headOffset); + anchor = offsetCursor(anchor, 0, anchorOffset); + vim.sel = { + anchor: anchor, + head: head + }; + updateMark(cm, vim, '<', cursorMin(head, anchor)); + updateMark(cm, vim, '>', cursorMax(head, anchor)); + } else if (!vim.insertMode) { + // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse. + vim.lastHPos = cm.getCursor().ch; + } + } + + /** Wrapper for special keys pressed in insert mode */ + function InsertModeKey(keyName) { + this.keyName = keyName; + } + + /** + * Handles raw key down events from the text area. + * - Should only be active in insert mode. + * - For recording deletes in insert mode. + */ + function onKeyEventTargetKeyDown(e) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + var keyName = CodeMirror.keyName(e); + if (!keyName) { return; } + function onKeyFound() { + lastChange.changes.push(new InsertModeKey(keyName)); + return true; + } + if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) { + CodeMirror.lookupKey(keyName, 'vim-insert', onKeyFound); + } + } + + /** + * Repeats the last edit, which includes exactly 1 command and at most 1 + * insert. Operator and motion commands are read from lastEditInputState, + * while action commands are read from lastEditActionCommand. + * + * If repeatForInsert is true, then the function was called by + * exitInsertMode to repeat the insert mode changes the user just made. The + * corresponding enterInsertMode call was made with a count. + */ + function repeatLastEdit(cm, vim, repeat, repeatForInsert) { + var macroModeState = vimGlobalState.macroModeState; + macroModeState.isPlaying = true; + var isAction = !!vim.lastEditActionCommand; + var cachedInputState = vim.inputState; + function repeatCommand() { + if (isAction) { + commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand); + } else { + commandDispatcher.evalInput(cm, vim); + } + } + function repeatInsert(repeat) { + if (macroModeState.lastInsertModeChanges.changes.length > 0) { + // For some reason, repeat cw in desktop VIM does not repeat + // insert mode changes. Will conform to that behavior. + repeat = !vim.lastEditActionCommand ? 1 : repeat; + var changeObject = macroModeState.lastInsertModeChanges; + repeatInsertModeChanges(cm, changeObject.changes, repeat); + } + } + vim.inputState = vim.lastEditInputState; + if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) { + // o and O repeat have to be interlaced with insert repeats so that the + // insertions appear on separate lines instead of the last line. + for (var i = 0; i < repeat; i++) { + repeatCommand(); + repeatInsert(1); + } + } else { + if (!repeatForInsert) { + // Hack to get the cursor to end up at the right place. If I is + // repeated in insert mode repeat, cursor will be 1 insert + // change set left of where it should be. + repeatCommand(); + } + repeatInsert(repeat); + } + vim.inputState = cachedInputState; + if (vim.insertMode && !repeatForInsert) { + // Don't exit insert mode twice. If repeatForInsert is set, then we + // were called by an exitInsertMode call lower on the stack. + exitInsertMode(cm); + } + macroModeState.isPlaying = false; + }; + + function repeatInsertModeChanges(cm, changes, repeat) { + function keyHandler(binding) { + if (typeof binding == 'string') { + CodeMirror.commands[binding](cm); + } else { + binding(cm); + } + return true; + } + var head = cm.getCursor('head'); + var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock; + if (inVisualBlock) { + // Set up block selection again for repeating the changes. + var vim = cm.state.vim; + var lastSel = vim.lastSelection; + var offset = getOffset(lastSel.anchor, lastSel.head); + selectForInsert(cm, head, offset.line + 1); + repeat = cm.listSelections().length; + cm.setCursor(head); + } + for (var i = 0; i < repeat; i++) { + if (inVisualBlock) { + cm.setCursor(offsetCursor(head, i, 0)); + } + for (var j = 0; j < changes.length; j++) { + var change = changes[j]; + if (change instanceof InsertModeKey) { + CodeMirror.lookupKey(change.keyName, 'vim-insert', keyHandler); + } else { + var cur = cm.getCursor(); + cm.replaceRange(change, cur, cur); + } + } + } + if (inVisualBlock) { + cm.setCursor(offsetCursor(head, 0, 1)); + } + } + + resetVimGlobalState(); + return vimApi; + }; + // Initialize Vim and make it available as an API. + CodeMirror.Vim = Vim(); +}); diff --git a/www/code/codemirror.css b/www/code/codemirror-5.13.2/lib/codemirror.css similarity index 97% rename from www/code/codemirror.css rename to www/code/codemirror-5.13.2/lib/codemirror.css index 3543523e6..1cf66a9fa 100644 --- a/www/code/codemirror.css +++ b/www/code/codemirror-5.13.2/lib/codemirror.css @@ -165,7 +165,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} } /* The fake, visible scrollbars. Used to force redraw during scrolling - before actuall scrolling happens, thus preventing shaking and + before actual scrolling happens, thus preventing shaking and flickering artifacts. */ .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { position: absolute; @@ -191,12 +191,14 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-gutters { position: absolute; left: 0; top: 0; + min-height: 100%; z-index: 3; } .CodeMirror-gutter { white-space: normal; height: 100%; display: inline-block; + vertical-align: top; margin-bottom: -30px; /* Hack to make IE7 behave */ *zoom:1; @@ -244,6 +246,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} position: relative; overflow: visible; -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: none; + font-variant-ligatures: none; } .CodeMirror-wrap pre { word-wrap: break-word; diff --git a/www/code/codemirror.js b/www/code/codemirror-5.13.2/lib/codemirror.js similarity index 97% rename from www/code/codemirror.js rename to www/code/codemirror-5.13.2/lib/codemirror.js index c6f9af859..ec49e3a33 100644 --- a/www/code/codemirror.js +++ b/www/code/codemirror-5.13.2/lib/codemirror.js @@ -13,7 +13,7 @@ else if (typeof define == "function" && define.amd) // AMD return define([], mod); else // Plain browser env - this.CodeMirror = mod(); + (this || window).CodeMirror = mod(); })(function() { "use strict"; @@ -21,27 +21,29 @@ // Kludges for bugs and behavior differences that can't be feature // detected are enabled based on userAgent etc sniffing. + var userAgent = navigator.userAgent; + var platform = navigator.platform; - var gecko = /gecko\/\d/i.test(navigator.userAgent); - var ie_upto10 = /MSIE \d/.test(navigator.userAgent); - var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent); + var gecko = /gecko\/\d/i.test(userAgent); + var ie_upto10 = /MSIE \d/.test(userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); var ie = ie_upto10 || ie_11up; var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); - var webkit = /WebKit\//.test(navigator.userAgent); - var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); - var chrome = /Chrome\//.test(navigator.userAgent); - var presto = /Opera\//.test(navigator.userAgent); + var webkit = /WebKit\//.test(userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); + var chrome = /Chrome\//.test(userAgent); + var presto = /Opera\//.test(userAgent); var safari = /Apple Computer/.test(navigator.vendor); - var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); - var phantom = /PhantomJS/.test(navigator.userAgent); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); + var phantom = /PhantomJS/.test(userAgent); - var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); // This is woefully incomplete. Suggestions for alternative methods welcome. - var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); - var mac = ios || /Mac/.test(navigator.platform); - var windows = /win/i.test(navigator.platform); + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); + var mac = ios || /Mac/.test(platform); + var windows = /win/i.test(platform); - var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/); + var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); if (presto_version) presto_version = Number(presto_version[1]); if (presto_version && presto_version >= 15) { presto = false; webkit = true; } // Some browsers use the wrong event properties to signal cmd/ctrl on OS X @@ -408,7 +410,7 @@ if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); }); - this.checkedOverlay = false; + this.checkedZeroWidth = false; // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; } @@ -443,29 +445,43 @@ this.horiz.firstChild.style.width = "0"; } - if (!this.checkedOverlay && measure.clientHeight > 0) { - if (sWidth == 0) this.overlayHack(); - this.checkedOverlay = true; + if (!this.checkedZeroWidth && measure.clientHeight > 0) { + if (sWidth == 0) this.zeroWidthHack(); + this.checkedZeroWidth = true; } return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; }, setScrollLeft: function(pos) { if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; + if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz); }, setScrollTop: function(pos) { if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; + if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert); }, - overlayHack: function() { + zeroWidthHack: function() { var w = mac && !mac_geMountainLion ? "12px" : "18px"; - this.horiz.style.minHeight = this.vert.style.minWidth = w; - var self = this; - var barMouseDown = function(e) { - if (e_target(e) != self.vert && e_target(e) != self.horiz) - operation(self.cm, onMouseDown)(e); - }; - on(this.vert, "mousedown", barMouseDown); - on(this.horiz, "mousedown", barMouseDown); + this.horiz.style.height = this.vert.style.width = w; + this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; + this.disableHoriz = new Delayed; + this.disableVert = new Delayed; + }, + enableZeroWidthBar: function(bar, delay) { + bar.style.pointerEvents = "auto"; + function maybeDisable() { + // To find out whether the scrollbar is still visible, we + // check whether the element under the pixel in the bottom + // left corner of the scrollbar box is the scrollbar box + // itself (when the bar is still visible) or its filler child + // (when the bar is hidden). If it is still visible, we keep + // it enabled, if it's hidden, we disable pointer events. + var box = bar.getBoundingClientRect(); + var elt = document.elementFromPoint(box.left + 1, box.bottom - 1); + if (elt != bar) bar.style.pointerEvents = "none"; + else delay.set(1000, maybeDisable); + } + delay.set(1000, maybeDisable); }, clear: function() { var parent = this.horiz.parentNode; @@ -527,6 +543,7 @@ d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" if (sizes.right && sizes.bottom) { d.scrollbarFiller.style.display = "block"; @@ -730,6 +747,7 @@ function postUpdateDisplay(cm, update) { var viewport = update.viewport; + for (var first = true;; first = false) { if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { // Clip forced viewport to actual scrollable area. @@ -745,8 +763,8 @@ updateHeightsInViewport(cm); var barMeasure = measureForScrollbars(cm); updateSelection(cm); - setDocumentHeight(cm, barMeasure); updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); } update.signal(cm, "update", cm); @@ -763,17 +781,16 @@ postUpdateDisplay(cm, update); var barMeasure = measureForScrollbars(cm); updateSelection(cm); - setDocumentHeight(cm, barMeasure); updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); update.finish(); } } function setDocumentHeight(cm, measure) { cm.display.sizer.style.minHeight = measure.docHeight + "px"; - var total = measure.docHeight + cm.display.barHeight; - cm.display.heightForcer.style.top = total + "px"; - cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px"; + cm.display.heightForcer.style.top = measure.docHeight + "px"; + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"; } // Read the actual heights of the rendered lines, and update their @@ -807,7 +824,7 @@ // given line. function updateWidgetHeight(line) { if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) - line.widgets[i].height = line.widgets[i].node.offsetHeight; + line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight; } // Do a bulk-read of the DOM positions and sizes needed to draw the @@ -1078,10 +1095,6 @@ if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } } - function isReadOnly(cm) { - return cm.options.readOnly || cm.doc.cantEdit; - } - // This will be set to an array of strings when copying, so that, // when pasting, we know what kind of selections the copied text // was made out of. @@ -1136,7 +1149,7 @@ var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); if (pasted) { e.preventDefault(); - if (!isReadOnly(cm) && !cm.options.disableInput) + if (!cm.isReadOnly() && !cm.options.disableInput) runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); }); return true; } @@ -1239,13 +1252,14 @@ }); on(te, "paste", function(e) { - if (handlePaste(e, cm)) return true; + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return cm.state.pasteIncoming = true; input.fastPoll(); }); function prepareCopyCut(e) { + if (signalDOMEvent(cm, e)) return if (cm.somethingSelected()) { lastCopied = cm.getSelections(); if (input.inaccurateSelection) { @@ -1273,7 +1287,7 @@ on(te, "copy", prepareCopyCut); on(display.scroller, "paste", function(e) { - if (eventInWidget(display, e)) return; + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return; cm.state.pasteIncoming = true; input.focus(); }); @@ -1407,7 +1421,7 @@ // in which case reading its value would be expensive. if (this.contextMenuPending || !cm.state.focused || (hasSelection(input) && !prevInput && !this.composing) || - isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq) + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) return false; var text = input.value; @@ -1469,10 +1483,11 @@ if (reset && cm.doc.sel.contains(pos) == -1) operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); - var oldCSS = te.style.cssText; - input.wrapper.style.position = "absolute"; - te.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + - "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + + var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText; + input.wrapper.style.cssText = "position: absolute" + var wrapperBox = input.wrapper.getBoundingClientRect() + te.style.cssText = "position: absolute; width: 30px; height: 30px; top: " + (e.clientY - wrapperBox.top - 5) + + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px; z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) @@ -1503,7 +1518,7 @@ } function rehide() { input.contextMenuPending = false; - input.wrapper.style.position = "relative"; + input.wrapper.style.cssText = oldWrapperCSS te.style.cssText = oldCSS; if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); @@ -1558,7 +1573,9 @@ var div = input.div = display.lineDiv; disableBrowserMagic(div); - on(div, "paste", function(e) { handlePaste(e, cm); }) + on(div, "paste", function(e) { + if (!signalDOMEvent(cm, e)) handlePaste(e, cm); + }) on(div, "compositionstart", function(e) { var data = e.data; @@ -1596,11 +1613,12 @@ on(div, "input", function() { if (input.composing) return; - if (isReadOnly(cm) || !input.pollContent()) + if (cm.isReadOnly() || !input.pollContent()) runInOp(input.cm, function() {regChange(cm);}); }); function onCopyCut(e) { + if (signalDOMEvent(cm, e)) return if (cm.somethingSelected()) { lastCopied = cm.getSelections(); if (e.type == "cut") cm.replaceSelection("", null, "cut"); @@ -1676,8 +1694,13 @@ try { var rng = range(start.node, start.offset, end.offset, end.node); } catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible if (rng) { - sel.removeAllRanges(); - sel.addRange(rng); + if (!gecko && this.cm.state.focused) { + sel.collapse(start.node, start.offset); + if (!rng.collapsed) sel.addRange(rng); + } else { + sel.removeAllRanges(); + sel.addRange(rng); + } if (old && sel.anchorNode == null) sel.addRange(old); else if (gecko) this.startGracePeriod(); } @@ -1821,7 +1844,7 @@ this.div.focus(); }, applyComposition: function(composing) { - if (isReadOnly(this.cm)) + if (this.cm.isReadOnly()) operation(this.cm, regChange)(this.cm) else if (composing.data && composing.data != composing.startData) operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel); @@ -1833,7 +1856,7 @@ onKeyPress: function(e) { e.preventDefault(); - if (!isReadOnly(this.cm)) + if (!this.cm.isReadOnly()) operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); }, @@ -2138,7 +2161,7 @@ // Give beforeSelectionChange handlers a change to influence a // selection update. - function filterSelectionChange(doc, sel) { + function filterSelectionChange(doc, sel, options) { var obj = { ranges: sel.ranges, update: function(ranges) { @@ -2146,7 +2169,8 @@ for (var i = 0; i < ranges.length; i++) this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), clipPos(doc, ranges[i].head)); - } + }, + origin: options && options.origin }; signal(doc, "beforeSelectionChange", doc, obj); if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); @@ -2172,7 +2196,7 @@ function setSelectionNoUndo(doc, sel, options) { if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) - sel = filterSelectionChange(doc, sel); + sel = filterSelectionChange(doc, sel, options); var bias = options && options.bias || (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); @@ -2206,8 +2230,9 @@ var out; for (var i = 0; i < sel.ranges.length; i++) { var range = sel.ranges[i]; - var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear); - var newHead = skipAtomic(doc, range.head, bias, mayClear); + var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear); if (out || newAnchor != range.anchor || newHead != range.head) { if (!out) out = sel.ranges.slice(0, i); out[i] = new Range(newAnchor, newHead); @@ -2216,54 +2241,61 @@ return out ? normalizeSelection(out, sel.primIndex) : sel; } - // Ensure a given position is not inside an atomic range. - function skipAtomic(doc, pos, bias, mayClear) { - var flipped = false, curPos = pos; - var dir = bias || 1; - doc.cantEdit = false; - search: for (;;) { - var line = getLine(doc, curPos.line); - if (line.markedSpans) { - for (var i = 0; i < line.markedSpans.length; ++i) { - var sp = line.markedSpans[i], m = sp.marker; - if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && - (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { - if (mayClear) { - signal(m, "beforeCursorEnter"); - if (m.explicitlyCleared) { - if (!line.markedSpans) break; - else {--i; continue;} - } - } - if (!m.atomic) continue; - var newPos = m.find(dir < 0 ? -1 : 1); - if (cmp(newPos, curPos) == 0) { - newPos.ch += dir; - if (newPos.ch < 0) { - if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); - else newPos = null; - } else if (newPos.ch > line.text.length) { - if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); - else newPos = null; - } - if (!newPos) { - if (flipped) { - // Driven in a corner -- no valid cursor position found at all - // -- try again *with* clearing, if we didn't already - if (!mayClear) return skipAtomic(doc, pos, bias, true); - // Otherwise, turn off editing until further notice, and return the start of the doc - doc.cantEdit = true; - return Pos(doc.first, 0); - } - flipped = true; newPos = pos; dir = -dir; - } - } - curPos = newPos; - continue search; + function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { + var line = getLine(doc, pos.line); + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} } } + if (!m.atomic) continue; + + if (oldPos) { + var near = m.find(dir < 0 ? 1 : -1), diff; + if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) + near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) + return skipAtomicInner(doc, near, pos, dir, mayClear); + } + + var far = m.find(dir < 0 ? -1 : 1); + if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) + far = movePos(doc, far, dir, far.line == pos.line ? line : null); + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null; } - return curPos; + } + return pos; + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, oldPos, bias, mayClear) { + var dir = bias || 1; + var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); + if (!found) { + doc.cantEdit = true; + return Pos(doc.first, 0); + } + return found; + } + + function movePos(doc, pos, dir, line) { + if (dir < 0 && pos.ch == 0) { + if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)); + else return null; + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { + if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0); + else return null; + } else { + return new Pos(pos.line, pos.ch + dir); } } @@ -2281,6 +2313,7 @@ for (var i = 0; i < doc.sel.ranges.length; i++) { if (primary === false && i == doc.sel.primIndex) continue; var range = doc.sel.ranges[i]; + if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue; var collapsed = range.empty(); if (collapsed || cm.options.showCursorWhenSelecting) drawSelectionCursor(cm, range.head, curFragment); @@ -3082,16 +3115,17 @@ if (op.preparedSelection) cm.display.input.showSelection(op.preparedSelection); - if (op.updatedDisplay) - setDocumentHeight(cm, op.barMeasure); if (op.updatedDisplay || op.startHeight != cm.doc.height) updateScrollbars(cm, op.barMeasure); + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure); if (op.selectionChanged) restartBlink(cm); if (cm.state.focused && op.updateInput) cm.display.input.reset(op.typing); - if (op.focus && op.focus == activeElt()) ensureFocus(op.cm); + if (op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus())) + ensureFocus(op.cm); } function endOperation_finish(op) { @@ -3110,7 +3144,7 @@ display.scroller.scrollTop = doc.scrollTop; } if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { - doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displayWidth(cm), op.scrollLeft)); + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)); display.scrollbars.setScrollLeft(doc.scrollLeft); display.scroller.scrollLeft = doc.scrollLeft; alignHorizontally(cm); @@ -3406,7 +3440,7 @@ return dx * dx + dy * dy > 20 * 20; } on(d.scroller, "touchstart", function(e) { - if (!isMouseLikeTouchEvent(e)) { + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { clearTimeout(touchFinished); var now = +new Date; d.activeTouch = {start: now, moved: false, @@ -3461,7 +3495,7 @@ over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }}, start: function(e){onDragStart(cm, e);}, drop: operation(cm, onDrop), - leave: function() {clearDragCursor(cm);} + leave: function(e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} }; var inp = d.input.getField(); @@ -3535,7 +3569,7 @@ // not interfere with, such as a scrollbar or widget. function onMouseDown(e) { var cm = this, display = cm.display; - if (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return; + if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return; display.shift = e.shiftKey; if (eventInWidget(display, e)) { @@ -3591,7 +3625,7 @@ } var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; - if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && type == "single" && (contained = sel.contains(start)) > -1 && (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) && (cmp(contained.to(), start) > 0 || start.xRel < 0)) @@ -3776,7 +3810,7 @@ // Determines whether an event happened in the gutter, and fires the // handlers for the corresponding event. - function gutterEvent(cm, e, type, prevent, signalfn) { + function gutterEvent(cm, e, type, prevent) { try { var mX = e.clientX, mY = e.clientY; } catch(e) { return false; } if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; @@ -3793,14 +3827,14 @@ if (g && g.getBoundingClientRect().right >= mX) { var line = lineAtHeight(cm.doc, mY); var gutter = cm.options.gutters[i]; - signalfn(cm, type, cm, line, gutter, e); + signal(cm, type, cm, line, gutter, e); return e_defaultPrevented(e); } } } function clickInGutter(cm, e) { - return gutterEvent(cm, e, "gutterClick", true, signalLater); + return gutterEvent(cm, e, "gutterClick", true); } // Kludge to work around strange IE behavior where it'll sometimes @@ -3815,15 +3849,21 @@ e_preventDefault(e); if (ie) lastDrop = +new Date; var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; - if (!pos || isReadOnly(cm)) return; + if (!pos || cm.isReadOnly()) return; // Might be a file drop, in which case we simply extract the text // and insert it. if (files && files.length && window.FileReader && window.File) { var n = files.length, text = Array(n), read = 0; var loadFile = function(file, i) { + if (cm.options.allowDropFileTypes && + indexOf(cm.options.allowDropFileTypes, file.type) == -1) + return; + var reader = new FileReader; reader.onload = operation(cm, function() { - text[i] = reader.result; + var content = reader.result; + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = ""; + text[i] = content; if (++read == n) { pos = clipPos(cm.doc, pos); var change = {from: pos, to: pos, @@ -3965,8 +4005,9 @@ var display = cm.display, scroll = display.scroller; // Quit if there's nothing to scroll here - if (!(dx && scroll.scrollWidth > scroll.clientWidth || - dy && scroll.scrollHeight > scroll.clientHeight)) return; + var canScrollX = scroll.scrollWidth > scroll.clientWidth; + var canScrollY = scroll.scrollHeight > scroll.clientHeight; + if (!(dx && canScrollX || dy && canScrollY)) return; // Webkit browsers on OS X abort momentum scrolls when the target // of the scroll event is removed from the scrollable element. @@ -3990,10 +4031,15 @@ // scrolling entirely here. It'll be slightly off from native, but // better than glitching out. if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { - if (dy) + if (dy && canScrollY) setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); - e_preventDefault(e); + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + e_preventDefault(e); display.wheelStartX = null; // Abort measurement, if in progress return; } @@ -4042,7 +4088,7 @@ cm.display.input.ensurePolled(); var prevShift = cm.display.shift, done = false; try { - if (isReadOnly(cm)) cm.state.suppressEdits = true; + if (cm.isReadOnly()) cm.state.suppressEdits = true; if (dropShift) cm.display.shift = false; done = bound(cm) != Pass; } finally { @@ -4221,12 +4267,13 @@ // right-click take effect on it. function onContextMenu(cm, e) { if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; + if (signalDOMEvent(cm, e, "contextmenu")) return; cm.display.input.onContextMenu(e); } function contextMenuInGutter(cm, e) { if (!hasHandler(cm, "gutterContextMenu")) return false; - return gutterEvent(cm, e, "gutterContextMenu", false, signal); + return gutterEvent(cm, e, "gutterContextMenu", false); } // UPDATING @@ -4774,10 +4821,9 @@ function findPosH(doc, pos, dir, unit, visually) { var line = pos.line, ch = pos.ch, origDir = dir; var lineObj = getLine(doc, line); - var possible = true; function findNextLine() { var l = line + dir; - if (l < doc.first || l >= doc.first + doc.size) return (possible = false); + if (l < doc.first || l >= doc.first + doc.size) return false line = l; return lineObj = getLine(doc, l); } @@ -4787,14 +4833,16 @@ if (!boundToLine && findNextLine()) { if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); else ch = dir < 0 ? lineObj.text.length : 0; - } else return (possible = false); + } else return false } else ch = next; return true; } - if (unit == "char") moveOnce(); - else if (unit == "column") moveOnce(true); - else if (unit == "word" || unit == "group") { + if (unit == "char") { + moveOnce() + } else if (unit == "column") { + moveOnce(true) + } else if (unit == "word" || unit == "group") { var sawType = null, group = unit == "group"; var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); for (var first = true;; first = false) { @@ -4814,8 +4862,8 @@ if (dir > 0 && !moveOnce(!first)) break; } } - var result = skipAtomic(doc, Pos(line, ch), origDir, true); - if (!possible) result.hitSide = true; + var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true); + if (!cmp(pos, result)) result.hitSide = true; return result; } @@ -5202,6 +5250,7 @@ signal(this, "overwriteToggle", this, this.state.overwrite); }, hasFocus: function() { return this.display.input.getField() == activeElt(); }, + isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit); }, scrollTo: methodOp(function(x, y) { if (x != null || y != null) resolveScrollToPos(this); @@ -5404,6 +5453,7 @@ }); option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true); option("dragDrop", true, dragDropChanged); + option("allowDropFileTypes", null); option("cursorBlinkRate", 530); option("cursorScrollMargin", 0); @@ -5708,8 +5758,8 @@ var range = cm.listSelections()[i]; cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+input"); cm.indentLine(range.from().line + 1, null, true); - ensureCursorVisible(cm); } + ensureCursorVisible(cm); }); }, toggleOverwrite: function(cm) {cm.toggleOverwrite();} @@ -6637,7 +6687,7 @@ parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); } - return widget.height = widget.node.offsetHeight; + return widget.height = widget.node.parentNode.offsetHeight; } function addLineWidget(doc, handle, node, options) { @@ -7047,7 +7097,7 @@ if (nextChange == pos) { // Update current marker set spanStyle = spanEndStyle = spanStartStyle = title = css = ""; collapsed = null; nextChange = Infinity; - var foundBookmarks = []; + var foundBookmarks = [], endStyles for (var j = 0; j < spans.length; ++j) { var sp = spans[j], m = sp.marker; if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { @@ -7058,9 +7108,9 @@ spanEndStyle = ""; } if (m.className) spanStyle += " " + m.className; - if (m.css) css = m.css; + if (m.css) css = (css ? css + ";" : "") + m.css; if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; - if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; + if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to) if (m.title && !title) title = m.title; if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) collapsed = sp; @@ -7068,14 +7118,17 @@ nextChange = sp.from; } } + if (endStyles) for (var j = 0; j < endStyles.length; j += 2) + if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j] + + if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); if (collapsed && (collapsed.from || 0) == pos) { buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, collapsed.marker, collapsed.from == null); if (collapsed.to == null) return; if (collapsed.to == pos) collapsed = false; } - if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) - buildCollapsedSpan(builder, 0, foundBookmarks[j]); } if (pos >= len) break; @@ -7327,6 +7380,7 @@ this.id = ++nextDocId; this.modeOption = mode; this.lineSep = lineSep; + this.extend = false; if (typeof text == "string") text = this.splitLines(text); updateDoc(this, {from: start, to: start, text: text}); @@ -7414,10 +7468,11 @@ extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); }), extendSelections: docMethodOp(function(heads, options) { - extendSelections(this, clipPosArray(this, heads, options)); + extendSelections(this, clipPosArray(this, heads), options); }), extendSelectionsBy: docMethodOp(function(f, options) { - extendSelections(this, map(this.sel.ranges, f), options); + var heads = map(this.sel.ranges, f); + extendSelections(this, clipPosArray(this, heads), options); }), setSelections: docMethodOp(function(ranges, primary, options) { if (!ranges.length) return; @@ -7542,7 +7597,7 @@ removeLineWidget: function(widget) { widget.clear(); }, markText: function(from, to, options) { - return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range"); }, setBookmark: function(pos, options) { var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), @@ -7570,9 +7625,9 @@ var spans = line.markedSpans; if (spans) for (var i = 0; i < spans.length; i++) { var span = spans[i]; - if (!(lineNo == from.line && from.ch > span.to || - span.from == null && lineNo != from.line|| - lineNo == to.line && span.from > to.ch) && + if (!(span.to != null && lineNo == from.line && from.ch > span.to || + span.from == null && lineNo != from.line || + span.from != null && lineNo == to.line && span.from > to.ch) && (!filter || filter(span.marker))) found.push(span.marker.parent || span.marker); } @@ -8103,24 +8158,30 @@ } }; + var noHandlers = [] + function getHandlers(emitter, type, copy) { + var arr = emitter._handlers && emitter._handlers[type] + if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers + else return arr || noHandlers + } + var off = CodeMirror.off = function(emitter, type, f) { if (emitter.removeEventListener) emitter.removeEventListener(type, f, false); else if (emitter.detachEvent) emitter.detachEvent("on" + type, f); else { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; - for (var i = 0; i < arr.length; ++i) - if (arr[i] == f) { arr.splice(i, 1); break; } + var handlers = getHandlers(emitter, type, false) + for (var i = 0; i < handlers.length; ++i) + if (handlers[i] == f) { handlers.splice(i, 1); break; } } }; var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; + var handlers = getHandlers(emitter, type, true) + if (!handlers.length) return; var args = Array.prototype.slice.call(arguments, 2); - for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); + for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args); }; var orphanDelayedCallbacks = null; @@ -8133,8 +8194,8 @@ // them to be executed when the last operation ends, or, if no // operation is active, when a timeout fires. function signalLater(emitter, type /*, values...*/) { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; + var arr = getHandlers(emitter, type, false) + if (!arr.length) return; var args = Array.prototype.slice.call(arguments, 2), list; if (operationGroup) { list = operationGroup.delayedCallbacks; @@ -8174,8 +8235,7 @@ } function hasHandler(emitter, type) { - var arr = emitter._handlers && emitter._handlers[type]; - return arr && arr.length > 0; + return getHandlers(emitter, type).length > 0 } // Add on and off methods to a constructor's prototype, to make @@ -8829,7 +8889,7 @@ // THE END - CodeMirror.version = "5.7.0"; + CodeMirror.version = "5.13.2"; return CodeMirror; }); diff --git a/www/code/mode/apl/apl.js b/www/code/codemirror-5.13.2/mode/apl/apl.js similarity index 100% rename from www/code/mode/apl/apl.js rename to www/code/codemirror-5.13.2/mode/apl/apl.js diff --git a/www/code/mode/apl/index.html b/www/code/codemirror-5.13.2/mode/apl/index.html similarity index 100% rename from www/code/mode/apl/index.html rename to www/code/codemirror-5.13.2/mode/apl/index.html diff --git a/www/code/mode/asciiarmor/asciiarmor.js b/www/code/codemirror-5.13.2/mode/asciiarmor/asciiarmor.js similarity index 100% rename from www/code/mode/asciiarmor/asciiarmor.js rename to www/code/codemirror-5.13.2/mode/asciiarmor/asciiarmor.js diff --git a/www/code/mode/asciiarmor/index.html b/www/code/codemirror-5.13.2/mode/asciiarmor/index.html similarity index 100% rename from www/code/mode/asciiarmor/index.html rename to www/code/codemirror-5.13.2/mode/asciiarmor/index.html diff --git a/www/code/mode/asn.1/asn.1.js b/www/code/codemirror-5.13.2/mode/asn.1/asn.1.js similarity index 100% rename from www/code/mode/asn.1/asn.1.js rename to www/code/codemirror-5.13.2/mode/asn.1/asn.1.js diff --git a/www/code/mode/asn.1/index.html b/www/code/codemirror-5.13.2/mode/asn.1/index.html similarity index 98% rename from www/code/mode/asn.1/index.html rename to www/code/codemirror-5.13.2/mode/asn.1/index.html index 8346f8e50..699fd4473 100644 --- a/www/code/mode/asn.1/index.html +++ b/www/code/codemirror-5.13.2/mode/asn.1/index.html @@ -1,4 +1,4 @@ - + CodeMirror: ASN.1 mode @@ -73,6 +73,5 @@

The development of this mode has been sponsored by Ericsson .

Coded by Asmelash Tsegay Gebretsadkan

- diff --git a/www/code/mode/asterisk/asterisk.js b/www/code/codemirror-5.13.2/mode/asterisk/asterisk.js similarity index 100% rename from www/code/mode/asterisk/asterisk.js rename to www/code/codemirror-5.13.2/mode/asterisk/asterisk.js diff --git a/www/code/mode/asterisk/index.html b/www/code/codemirror-5.13.2/mode/asterisk/index.html similarity index 100% rename from www/code/mode/asterisk/index.html rename to www/code/codemirror-5.13.2/mode/asterisk/index.html diff --git a/www/code/mode/brainfuck/brainfuck.js b/www/code/codemirror-5.13.2/mode/brainfuck/brainfuck.js similarity index 100% rename from www/code/mode/brainfuck/brainfuck.js rename to www/code/codemirror-5.13.2/mode/brainfuck/brainfuck.js diff --git a/www/code/mode/brainfuck/index.html b/www/code/codemirror-5.13.2/mode/brainfuck/index.html similarity index 100% rename from www/code/mode/brainfuck/index.html rename to www/code/codemirror-5.13.2/mode/brainfuck/index.html diff --git a/www/code/mode/clike/clike.js b/www/code/codemirror-5.13.2/mode/clike/clike.js similarity index 70% rename from www/code/mode/clike/clike.js rename to www/code/codemirror-5.13.2/mode/clike/clike.js index 50f1ea734..34d3a5afd 100644 --- a/www/code/mode/clike/clike.js +++ b/www/code/codemirror-5.13.2/mode/clike/clike.js @@ -11,6 +11,42 @@ })(function(CodeMirror) { "use strict"; +function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; +} +function isStatement(type) { + return type == "statement" || type == "switchstatement" || type == "namespace"; +} +function pushContext(state, col, type) { + var indent = state.indented; + if (state.context && isStatement(state.context.type) && !isStatement(type)) + indent = state.context.indented; + return state.context = new Context(indent, col, type, null, state.context); +} +function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; +} + +function typeBefore(stream, state) { + if (state.prevToken == "variable" || state.prevToken == "variable-3") return true; + if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, stream.start))) return true; +} + +function isTopScope(context) { + for (;;) { + if (!context || context.type == "top") return true; + if (context.type == "}" && context.prev.type != "namespace") return false; + context = context.prev; + } +} + CodeMirror.defineMode("clike", function(config, parserConfig) { var indentUnit = config.indentUnit, statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, @@ -25,8 +61,12 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { multiLineStrings = parserConfig.multiLineStrings, indentStatements = parserConfig.indentStatements !== false, indentSwitch = parserConfig.indentSwitch !== false, - namespaceSeparator = parserConfig.namespaceSeparator; - var isOperatorChar = /[+\-*&%=<>!?|\/]/; + namespaceSeparator = parserConfig.namespaceSeparator, + isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/, + numberStart = parserConfig.numberStart || /[\d\.]/, + number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i, + isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/, + endStatement = parserConfig.endStatement || /^[;:,]$/; var curPunc, isDefKeyword; @@ -40,13 +80,14 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + if (isPunctuationChar.test(ch)) { curPunc = ch; return null; } - if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; + if (numberStart.test(ch)) { + stream.backUp(1) + if (stream.match(number)) return "number" + stream.next() } if (ch == "/") { if (stream.eat("*")) { @@ -59,7 +100,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { } } if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); + while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {} return "operator"; } stream.eatWhile(/[\w\$_\xa1-\uffff]/); @@ -67,17 +108,17 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { stream.eatWhile(/[\w\$_\xa1-\uffff]/); var cur = stream.current(); - if (keywords.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - if (defKeywords.propertyIsEnumerable(cur)) isDefKeyword = true; + if (contains(keywords, cur)) { + if (contains(blockKeywords, cur)) curPunc = "newstatement"; + if (contains(defKeywords, cur)) isDefKeyword = true; return "keyword"; } - if (types.propertyIsEnumerable(cur)) return "variable-3"; - if (builtin.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + if (contains(types, cur)) return "variable-3"; + if (contains(builtin, cur)) { + if (contains(blockKeywords, cur)) curPunc = "newstatement"; return "builtin"; } - if (atoms.propertyIsEnumerable(cur)) return "atom"; + if (contains(atoms, cur)) return "atom"; return "variable"; } @@ -106,42 +147,6 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { return "comment"; } - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - function isStatement(type) { - return type == "statement" || type == "switchstatement" || type == "namespace"; - } - function pushContext(state, col, type) { - var indent = state.indented; - if (state.context && isStatement(state.context.type) && !isStatement(type)) - indent = state.context.indented; - return state.context = new Context(indent, col, type, null, state.context); - } - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - function typeBefore(stream, state) { - if (state.prevToken == "variable" || state.prevToken == "variable-3") return true; - if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, stream.start))) return true; - } - - function isTopScope(context) { - for (;;) { - if (!context || context.type == "top") return true; - if (context.type == "}" && context.prev.type != "namespace") return false; - context = context.prev; - } - } - // Interface return { @@ -168,8 +173,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { if (style == "comment" || style == "meta") return style; if (ctx.align == null) ctx.align = true; - if ((curPunc == ";" || curPunc == ":" || curPunc == ",")) - while (isStatement(state.context.type)) popContext(state); + if (endStatement.test(curPunc)) while (isStatement(state.context.type)) popContext(state); else if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); @@ -212,8 +216,16 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); if (isStatement(ctx.type) && firstChar == "}") ctx = ctx.prev; + if (hooks.indent) { + var hook = hooks.indent(state, ctx, textAfter); + if (typeof hook == "number") return hook + } var closing = firstChar == ctx.type; var switchBlock = ctx.prev && ctx.prev.type == "switchstatement"; + if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) { + while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev + return ctx.indented + } if (isStatement(ctx.type)) return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); if (ctx.align && (!dontAlignCalls || ctx.type != ")")) @@ -238,27 +250,30 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } + function contains(words, word) { + if (typeof words === "function") { + return words(word); + } else { + return words.propertyIsEnumerable(word); + } + } var cKeywords = "auto if break case register continue return default do sizeof " + - "static else struct switch extern typedef float union for " + - "goto while enum const volatile"; + "static else struct switch extern typedef union for goto while enum const volatile"; var cTypes = "int long char short double float unsigned signed void size_t ptrdiff_t"; function cppHook(stream, state) { - if (!state.startOfLine) return false; - for (;;) { - if (stream.skipTo("\\")) { - stream.next(); - if (stream.eol()) { - state.tokenize = cppHook; - break; - } - } else { - stream.skipToEnd(); - state.tokenize = null; - break; + if (!state.startOfLine) return false + for (var ch, next = null; ch = stream.peek();) { + if (ch == "\\" && stream.match(/^.$/)) { + next = cppHook + break + } else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) { + break } + stream.next() } - return "meta"; + state.tokenize = next + return "meta" } function pointerHook(_stream, state) { @@ -413,6 +428,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { defKeywords: words("class interface package enum"), typeFirstDefinitions: true, atoms: words("true false null"), + endStatement: /^[;:]$/, hooks: { "@": function(stream) { stream.eatWhile(/[\w\$_]/); @@ -468,7 +484,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { keywords: words( /* scala */ - "abstract case catch class def do else extends false final finally for forSome if " + + "abstract case catch class def do else extends final finally for forSome if " + "implicit import lazy match new null object override package private protected return " + "sealed super this throw trait try type val var while with yield _ : = => <- <: " + "<% >: # @ " + @@ -481,7 +497,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { ), types: words( "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + - "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " + + "Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable " + "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector " + @@ -511,6 +527,68 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { "'": function(stream) { stream.eatWhile(/[\w\$_\xa1-\uffff]/); return "atom"; + }, + "=": function(stream, state) { + var cx = state.context + if (cx.type == "}" && cx.align && stream.eat(">")) { + state.context = new Context(cx.indented, cx.column, cx.type, null, cx.prev) + return "operator" + } else { + return false + } + } + }, + modeProps: {closeBrackets: {triples: '"'}} + }); + + function tokenKotlinString(tripleString){ + return function (stream, state) { + var escaped = false, next, end = false; + while (!stream.eol()) { + if (!tripleString && !escaped && stream.match('"') ) {end = true; break;} + if (tripleString && stream.match('"""')) {end = true; break;} + next = stream.next(); + if(!escaped && next == "$" && stream.match('{')) + stream.skipTo("}"); + escaped = !escaped && next == "\\" && !tripleString; + } + if (end || !tripleString) + state.tokenize = null; + return "string"; + } + } + + def("text/x-kotlin", { + name: "clike", + keywords: words( + /*keywords*/ + "package as typealias class interface this super val " + + "var fun for is in This throw return " + + "break continue object if else while do try when !in !is as? " + + + /*soft keywords*/ + "file import where by get set abstract enum open inner override private public internal " + + "protected catch finally out final vararg reified dynamic companion constructor init " + + "sealed field property receiver param sparam lateinit data inline noinline tailrec " + + "external annotation crossinline const operator infix" + ), + types: words( + /* package java.lang */ + "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + + "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + + "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + + "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" + ), + intendSwitch: false, + indentStatements: false, + multiLineStrings: true, + blockKeywords: words("catch class do else finally for if where try while enum"), + defKeywords: words("class val var object package interface fun"), + atoms: words("true false null this"), + hooks: { + '"': function(stream, state) { + state.tokenize = tokenKotlinString(stream.match('""')); + return state.tokenize(stream, state); } }, modeProps: {closeBrackets: {triples: '"'}} @@ -598,7 +676,10 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { stream.eatWhile(/[\w\$]/); return "keyword"; }, - "#": cppHook + "#": cppHook, + indent: function(_state, ctx, textAfter) { + if (ctx.type == "statement" && /^@\w/.test(textAfter)) return ctx.indented + } }, modeProps: {fold: "brace"} }); @@ -616,4 +697,85 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { modeProps: {fold: ["brace", "include"]} }); + // Ceylon Strings need to deal with interpolation + var stringTokenizer = null; + function tokenCeylonString(type) { + return function(stream, state) { + var escaped = false, next, end = false; + while (!stream.eol()) { + if (!escaped && stream.match('"') && + (type == "single" || stream.match('""'))) { + end = true; + break; + } + if (!escaped && stream.match('``')) { + stringTokenizer = tokenCeylonString(type); + end = true; + break; + } + next = stream.next(); + escaped = type == "single" && !escaped && next == "\\"; + } + if (end) + state.tokenize = null; + return "string"; + } + } + + def("text/x-ceylon", { + name: "clike", + keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" + + " exists extends finally for function given if import in interface is let module new" + + " nonempty object of out outer package return satisfies super switch then this throw" + + " try value void while"), + types: function(word) { + // In Ceylon all identifiers that start with an uppercase are types + var first = word.charAt(0); + return (first === first.toUpperCase() && first !== first.toLowerCase()); + }, + blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"), + defKeywords: words("class dynamic function interface module object package value"), + builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" + + " native optional sealed see serializable shared suppressWarnings tagged throws variable"), + isPunctuationChar: /[\[\]{}\(\),;\:\.`]/, + isOperatorChar: /[+\-*&%=<>!?|^~:\/]/, + numberStart: /[\d#$]/, + number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i, + multiLineStrings: true, + typeFirstDefinitions: true, + atoms: words("true false null larger smaller equal empty finished"), + indentSwitch: false, + styleDefs: false, + hooks: { + "@": function(stream) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + }, + '"': function(stream, state) { + state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single"); + return state.tokenize(stream, state); + }, + '`': function(stream, state) { + if (!stringTokenizer || !stream.match('`')) return false; + state.tokenize = stringTokenizer; + stringTokenizer = null; + return state.tokenize(stream, state); + }, + "'": function(stream) { + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + return "atom"; + }, + token: function(_stream, state, style) { + if ((style == "variable" || style == "variable-3") && + state.prevToken == ".") { + return "variable-2"; + } + } + }, + modeProps: { + fold: ["brace", "import"], + closeBrackets: {triples: '"'} + } + }); + }); diff --git a/www/code/mode/clike/index.html b/www/code/codemirror-5.13.2/mode/clike/index.html similarity index 65% rename from www/code/mode/clike/index.html rename to www/code/codemirror-5.13.2/mode/clike/index.html index 226c6ce84..45c670ae5 100644 --- a/www/code/mode/clike/index.html +++ b/www/code/codemirror-5.13.2/mode/clike/index.html @@ -206,6 +206,103 @@ object FilterTest extends App { } +

Kotlin mode

+ +
+ +

Ceylon mode

+ +
+ @@ -247,6 +354,7 @@ object FilterTest extends App { (Java), text/x-csharp (C#), text/x-objectivec (Objective-C), text/x-scala (Scala), text/x-vertex - and x-shader/x-fragment (shader programs), - text/x-squirrel (Squirrel).

+ x-shader/x-fragment (shader programs), + text/x-squirrel (Squirrel) and + text/x-ceylon (Ceylon)

diff --git a/www/code/mode/clike/scala.html b/www/code/codemirror-5.13.2/mode/clike/scala.html similarity index 100% rename from www/code/mode/clike/scala.html rename to www/code/codemirror-5.13.2/mode/clike/scala.html diff --git a/www/code/mode/clike/test.js b/www/code/codemirror-5.13.2/mode/clike/test.js similarity index 83% rename from www/code/mode/clike/test.js rename to www/code/codemirror-5.13.2/mode/clike/test.js index c84d22e18..c26003266 100644 --- a/www/code/mode/clike/test.js +++ b/www/code/codemirror-5.13.2/mode/clike/test.js @@ -31,6 +31,15 @@ " [variable x][operator ++];", "[keyword return];"); + MT("preprocessor", + "[meta #define FOO 3]", + "[variable-3 int] [variable foo];", + "[meta #define BAR\\]", + "[meta 4]", + "[variable-3 unsigned] [variable-3 int] [variable bar] [operator =] [number 8];", + "[meta #include ][comment // comment]") + + var mode_cpp = CodeMirror.getMode({indentUnit: 2}, "text/x-c++src"); function MTCPP(name) { test.mode(name, mode_cpp, Array.prototype.slice.call(arguments, 1)); } diff --git a/www/code/mode/clojure/clojure.js b/www/code/codemirror-5.13.2/mode/clojure/clojure.js similarity index 96% rename from www/code/mode/clojure/clojure.js rename to www/code/codemirror-5.13.2/mode/clojure/clojure.js index d531022a2..cd8129f75 100644 --- a/www/code/mode/clojure/clojure.js +++ b/www/code/codemirror-5.13.2/mode/clojure/clojure.js @@ -59,7 +59,8 @@ CodeMirror.defineMode("clojure", function (options) { sign: /[+-]/, exponent: /e/i, keyword_char: /[^\s\(\[\;\)\]]/, - symbol: /[\w*+!\-\._?:<>\/\xa1-\uffff]/ + symbol: /[\w*+!\-\._?:<>\/\xa1-\uffff]/, + block_indent: /^(?:def|with)[^\/]+$|\/(?:def|with)/ }; function stateStack(indent, type, prev) { // represents a state stack object @@ -96,6 +97,9 @@ CodeMirror.defineMode("clojure", function (options) { if ( '.' == stream.peek() ) { stream.eat('.'); stream.eatWhile(tests.digit); + } else if ('/' == stream.peek() ) { + stream.eat('/'); + stream.eatWhile(tests.digit); } if ( stream.eat(tests.exponent) ) { @@ -139,7 +143,7 @@ CodeMirror.defineMode("clojure", function (options) { } // skip spaces - if (stream.eatSpace()) { + if (state.mode != "string" && stream.eatSpace()) { return null; } var returnType = null; @@ -187,7 +191,7 @@ CodeMirror.defineMode("clojure", function (options) { } if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) || - /^(?:def|with)/.test(keyWord))) { // indent-word + tests.block_indent.test(keyWord))) { // indent-word pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); } else { // non-indent word // we continue eating the spaces @@ -240,5 +244,7 @@ CodeMirror.defineMode("clojure", function (options) { }); CodeMirror.defineMIME("text/x-clojure", "clojure"); +CodeMirror.defineMIME("text/x-clojurescript", "clojure"); +CodeMirror.defineMIME("application/edn", "clojure"); }); diff --git a/www/code/mode/clojure/index.html b/www/code/codemirror-5.13.2/mode/clojure/index.html similarity index 97% rename from www/code/mode/clojure/index.html rename to www/code/codemirror-5.13.2/mode/clojure/index.html index 3ecf4c486..81294bc14 100644 --- a/www/code/mode/clojure/index.html +++ b/www/code/codemirror-5.13.2/mode/clojure/index.html @@ -78,6 +78,9 @@ \tab \return \backspace \u1000 \uAaAa \u9F9F) +;; Let's play with numbers +(+ 1 -1 1/2 -1/2 -0.5 0.5) + + + + + + + +
+

Crystal mode

+
+ + +

MIME types defined: text/x-crystal.

+
diff --git a/www/code/mode/css/css.js b/www/code/codemirror-5.13.2/mode/css/css.js similarity index 96% rename from www/code/mode/css/css.js rename to www/code/codemirror-5.13.2/mode/css/css.js index 730085039..e9656e3dc 100644 --- a/www/code/mode/css/css.js +++ b/www/code/codemirror-5.13.2/mode/css/css.js @@ -12,9 +12,8 @@ "use strict"; CodeMirror.defineMode("css", function(config, parserConfig) { - var provided = parserConfig; + var inline = parserConfig.inline if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css"); - parserConfig.inline = provided.inline; var indentUnit = config.indentUnit, tokenHooks = parserConfig.tokenHooks, @@ -228,7 +227,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { if (type == "}" || type == "{") return popAndPass(type, stream, state); if (type == "(") return pushContext(state, stream, "parens"); - if (type == "hash" && !/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { + if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) { override += " error"; } else if (type == "word") { wordAsValue(stream); @@ -275,6 +274,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { if (type == "}" || type == ";") return popAndPass(type, stream, state); if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top"); + if (type == "interpolation") return pushContext(state, stream, "interpolation"); + if (type == "word") { var word = stream.current().toLowerCase(); if (word == "only" || word == "not" || word == "and" || word == "or") @@ -366,9 +367,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return { startState: function(base) { return {tokenize: null, - state: parserConfig.inline ? "block" : "top", + state: inline ? "block" : "top", stateArg: null, - context: new Context(parserConfig.inline ? "block" : "top", base || 0, null)}; + context: new Context(inline ? "block" : "top", base || 0, null)}; }, token: function(stream, state) { @@ -451,8 +452,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "animation-direction", "animation-duration", "animation-fill-mode", "animation-iteration-count", "animation-name", "animation-play-state", "animation-timing-function", "appearance", "azimuth", "backface-visibility", - "background", "background-attachment", "background-clip", "background-color", - "background-image", "background-origin", "background-position", + "background", "background-attachment", "background-blend-mode", "background-clip", + "background-color", "background-image", "background-origin", "background-position", "background-repeat", "background-size", "baseline-shift", "binding", "bleed", "bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target", "border", "border-bottom", "border-bottom-color", @@ -596,11 +597,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", - "col-resize", "collapse", "column", "column-reverse", "compact", "condensed", "contain", "content", + "col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse", + "compact", "condensed", "contain", "content", "content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop", - "cross", "crosshair", "currentcolor", "cursive", "cyclic", "dashed", "decimal", + "cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal", "decimal-leading-zero", "default", "default-button", "destination-atop", - "destination-in", "destination-out", "destination-over", "devanagari", + "destination-in", "destination-out", "destination-over", "devanagari", "difference", "disc", "discard", "disclosure-closed", "disclosure-open", "document", "dot-dash", "dot-dot-dash", "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", @@ -611,23 +613,23 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "ethiopic-halehame-gez", "ethiopic-halehame-om-et", "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", - "ethiopic-numeric", "ew-resize", "expanded", "extends", "extra-condensed", + "ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed", "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes", "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove", - "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", + "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew", "help", "hidden", "hide", "higher", "highlight", "highlighttext", - "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", + "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore", "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", "inline-block", "inline-flex", "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "japanese-formal", "japanese-informal", "justify", "kannada", "katakana", "katakana-iroha", "keep-all", "khmer", "korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal", - "landscape", "lao", "large", "larger", "left", "level", "lighter", + "landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten", "line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", - "lower-roman", "lowercase", "ltr", "malayalam", "match", "matrix", "matrix3d", + "lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d", "media-controls-background", "media-current-time-display", "media-fullscreen-button", "media-mute-button", "media-play-button", "media-return-to-realtime-button", "media-rewind-button", @@ -636,7 +638,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "media-volume-slider-container", "media-volume-sliderthumb", "medium", "menu", "menulist", "menulist-button", "menulist-text", "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", - "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize", + "mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize", "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", @@ -650,7 +652,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY", "rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running", - "s-resize", "sans-serif", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", + "s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen", "scroll", "scrollbar", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", "searchfield-results-decoration", @@ -658,7 +660,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "simp-chinese-formal", "simp-chinese-informal", "single", "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", - "small", "small-caps", "small-caption", "smaller", "solid", "somali", + "small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali", "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "spell-out", "square", "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", "subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "table", diff --git a/www/code/mode/css/gss.html b/www/code/codemirror-5.13.2/mode/css/gss.html similarity index 100% rename from www/code/mode/css/gss.html rename to www/code/codemirror-5.13.2/mode/css/gss.html diff --git a/www/code/mode/css/gss_test.js b/www/code/codemirror-5.13.2/mode/css/gss_test.js similarity index 100% rename from www/code/mode/css/gss_test.js rename to www/code/codemirror-5.13.2/mode/css/gss_test.js diff --git a/www/code/mode/css/index.html b/www/code/codemirror-5.13.2/mode/css/index.html similarity index 100% rename from www/code/mode/css/index.html rename to www/code/codemirror-5.13.2/mode/css/index.html diff --git a/www/code/mode/css/less.html b/www/code/codemirror-5.13.2/mode/css/less.html similarity index 100% rename from www/code/mode/css/less.html rename to www/code/codemirror-5.13.2/mode/css/less.html diff --git a/www/code/mode/css/less_test.js b/www/code/codemirror-5.13.2/mode/css/less_test.js similarity index 91% rename from www/code/mode/css/less_test.js rename to www/code/codemirror-5.13.2/mode/css/less_test.js index 7b77f5848..dd821558b 100644 --- a/www/code/mode/css/less_test.js +++ b/www/code/codemirror-5.13.2/mode/css/less_test.js @@ -26,10 +26,10 @@ MT("mixin", "[qualifier .mixin] ([variable dark]; [variable-2 @color]) {", - " [property color]: [variable darken]([variable-2 @color], [number 10%]);", + " [property color]: [atom darken]([variable-2 @color], [number 10%]);", "}", "[qualifier .mixin] ([variable light]; [variable-2 @color]) {", - " [property color]: [variable lighten]([variable-2 @color], [number 10%]);", + " [property color]: [atom lighten]([variable-2 @color], [number 10%]);", "}", "[qualifier .mixin] ([variable-2 @_]; [variable-2 @color]) {", " [property display]: [atom block];", diff --git a/www/code/mode/css/scss.html b/www/code/codemirror-5.13.2/mode/css/scss.html similarity index 100% rename from www/code/mode/css/scss.html rename to www/code/codemirror-5.13.2/mode/css/scss.html diff --git a/www/code/mode/css/scss_test.js b/www/code/codemirror-5.13.2/mode/css/scss_test.js similarity index 97% rename from www/code/mode/css/scss_test.js rename to www/code/codemirror-5.13.2/mode/css/scss_test.js index 26c226a1e..785921b39 100644 --- a/www/code/mode/css/scss_test.js +++ b/www/code/codemirror-5.13.2/mode/css/scss_test.js @@ -95,7 +95,7 @@ MT('indent_parentheses', "[tag foo] {", - " [property color]: [variable darken]([variable-2 $blue],", + " [property color]: [atom darken]([variable-2 $blue],", " [number 9%]);", "}"); diff --git a/www/code/mode/css/test.js b/www/code/codemirror-5.13.2/mode/css/test.js similarity index 96% rename from www/code/mode/css/test.js rename to www/code/codemirror-5.13.2/mode/css/test.js index 60f9c837f..7a496fb09 100644 --- a/www/code/mode/css/test.js +++ b/www/code/codemirror-5.13.2/mode/css/test.js @@ -55,11 +55,17 @@ MT("tagColorHex3", "[tag foo] { [property background]: [atom #fff]; }"); + MT("tagColorHex4", + "[tag foo] { [property background]: [atom #ffff]; }"); + MT("tagColorHex6", "[tag foo] { [property background]: [atom #ffffff]; }"); - MT("tagColorHex4", - "[tag foo] { [property background]: [atom&error #ffff]; }"); + MT("tagColorHex8", + "[tag foo] { [property background]: [atom #ffffffff]; }"); + + MT("tagColorHex5Invalid", + "[tag foo] { [property background]: [atom&error #fffff]; }"); MT("tagColorHexInvalid", "[tag foo] { [property background]: [atom&error #ffg]; }"); diff --git a/www/code/mode/cypher/cypher.js b/www/code/codemirror-5.13.2/mode/cypher/cypher.js similarity index 86% rename from www/code/mode/cypher/cypher.js rename to www/code/codemirror-5.13.2/mode/cypher/cypher.js index 79048b440..107e4f6d2 100644 --- a/www/code/mode/cypher/cypher.js +++ b/www/code/codemirror-5.13.2/mode/cypher/cypher.js @@ -60,9 +60,9 @@ }; var indentUnit = config.indentUnit; var curPunc; - var funcs = wordRegexp(["abs", "acos", "allShortestPaths", "asin", "atan", "atan2", "avg", "ceil", "coalesce", "collect", "cos", "cot", "count", "degrees", "e", "endnode", "exp", "extract", "filter", "floor", "haversin", "head", "id", "keys", "labels", "last", "left", "length", "log", "log10", "lower", "ltrim", "max", "min", "node", "nodes", "percentileCont", "percentileDisc", "pi", "radians", "rand", "range", "reduce", "rel", "relationship", "relationships", "replace", "right", "round", "rtrim", "shortestPath", "sign", "sin", "split", "sqrt", "startnode", "stdev", "stdevp", "str", "substring", "sum", "tail", "tan", "timestamp", "toFloat", "toInt", "trim", "type", "upper"]); - var preds = wordRegexp(["all", "and", "any", "has", "in", "none", "not", "or", "single", "xor", "like", "ilike", "exists"]); - var keywords = wordRegexp(["as", "asc", "ascending", "assert", "by", "case", "commit", "constraint", "create", "csv", "cypher", "delete", "desc", "descending", "distinct", "drop", "else", "end", "explain", "false", "fieldterminator", "foreach", "from", "headers", "in", "index", "is", "join", "limit", "load", "match", "merge", "null", "on", "optional", "order", "periodic", "profile", "remove", "return", "scan", "set", "skip", "start", "then", "true", "union", "unique", "unwind", "using", "when", "where", "with"]); + var funcs = wordRegexp(["abs", "acos", "allShortestPaths", "asin", "atan", "atan2", "avg", "ceil", "coalesce", "collect", "cos", "cot", "count", "degrees", "e", "endnode", "exp", "extract", "filter", "floor", "haversin", "head", "id", "keys", "labels", "last", "left", "length", "log", "log10", "lower", "ltrim", "max", "min", "node", "nodes", "percentileCont", "percentileDisc", "pi", "radians", "rand", "range", "reduce", "rel", "relationship", "relationships", "replace", "reverse", "right", "round", "rtrim", "shortestPath", "sign", "sin", "size", "split", "sqrt", "startnode", "stdev", "stdevp", "str", "substring", "sum", "tail", "tan", "timestamp", "toFloat", "toInt", "toString", "trim", "type", "upper"]); + var preds = wordRegexp(["all", "and", "any", "contains", "exists", "has", "in", "none", "not", "or", "single", "xor"]); + var keywords = wordRegexp(["as", "asc", "ascending", "assert", "by", "case", "commit", "constraint", "create", "csv", "cypher", "delete", "desc", "descending", "detach", "distinct", "drop", "else", "end", "ends", "explain", "false", "fieldterminator", "foreach", "from", "headers", "in", "index", "is", "join", "limit", "load", "match", "merge", "null", "on", "optional", "order", "periodic", "profile", "remove", "return", "scan", "set", "skip", "start", "starts", "then", "true", "union", "unique", "unwind", "using", "when", "where", "with"]); var operatorChars = /[*+\-<>=&|~%^]/; return { diff --git a/www/code/mode/cypher/index.html b/www/code/codemirror-5.13.2/mode/cypher/index.html similarity index 100% rename from www/code/mode/cypher/index.html rename to www/code/codemirror-5.13.2/mode/cypher/index.html diff --git a/www/code/mode/d/d.js b/www/code/codemirror-5.13.2/mode/d/d.js similarity index 100% rename from www/code/mode/d/d.js rename to www/code/codemirror-5.13.2/mode/d/d.js diff --git a/www/code/mode/d/index.html b/www/code/codemirror-5.13.2/mode/d/index.html similarity index 100% rename from www/code/mode/d/index.html rename to www/code/codemirror-5.13.2/mode/d/index.html diff --git a/www/code/codemirror-5.13.2/mode/dart/dart.js b/www/code/codemirror-5.13.2/mode/dart/dart.js new file mode 100644 index 000000000..8d383a95e --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/dart/dart.js @@ -0,0 +1,157 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../clike/clike")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../clike/clike"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var keywords = ("this super static final const abstract class extends external factory " + + "implements get native operator set typedef with enum throw rethrow " + + "assert break case continue default in return new deferred async await " + + "try catch finally do else for if switch while import library export " + + "part of show hide is as").split(" "); + var blockKeywords = "try catch finally do else for if switch while".split(" "); + var atoms = "true false null".split(" "); + var builtins = "void bool num int double dynamic var String".split(" "); + + function set(words) { + var obj = {}; + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + function pushInterpolationStack(state) { + (state.interpolationStack || (state.interpolationStack = [])).push(state.tokenize); + } + + function popInterpolationStack(state) { + return (state.interpolationStack || (state.interpolationStack = [])).pop(); + } + + function sizeInterpolationStack(state) { + return state.interpolationStack ? state.interpolationStack.length : 0; + } + + CodeMirror.defineMIME("application/dart", { + name: "clike", + keywords: set(keywords), + blockKeywords: set(blockKeywords), + builtin: set(builtins), + atoms: set(atoms), + hooks: { + "@": function(stream) { + stream.eatWhile(/[\w\$_\.]/); + return "meta"; + }, + + // custom string handling to deal with triple-quoted strings and string interpolation + "'": function(stream, state) { + return tokenString("'", stream, state, false); + }, + "\"": function(stream, state) { + return tokenString("\"", stream, state, false); + }, + "r": function(stream, state) { + var peek = stream.peek(); + if (peek == "'" || peek == "\"") { + return tokenString(stream.next(), stream, state, true); + } + return false; + }, + + "}": function(_stream, state) { + // "}" is end of interpolation, if interpolation stack is non-empty + if (sizeInterpolationStack(state) > 0) { + state.tokenize = popInterpolationStack(state); + return null; + } + return false; + }, + + "/": function(stream, state) { + if (!stream.eat("*")) return false + state.tokenize = tokenNestedComment(1) + return state.tokenize(stream, state) + } + } + }); + + function tokenString(quote, stream, state, raw) { + var tripleQuoted = false; + if (stream.eat(quote)) { + if (stream.eat(quote)) tripleQuoted = true; + else return "string"; //empty string + } + function tokenStringHelper(stream, state) { + var escaped = false; + while (!stream.eol()) { + if (!raw && !escaped && stream.peek() == "$") { + pushInterpolationStack(state); + state.tokenize = tokenInterpolation; + return "string"; + } + var next = stream.next(); + if (next == quote && !escaped && (!tripleQuoted || stream.match(quote + quote))) { + state.tokenize = null; + break; + } + escaped = !raw && !escaped && next == "\\"; + } + return "string"; + } + state.tokenize = tokenStringHelper; + return tokenStringHelper(stream, state); + } + + function tokenInterpolation(stream, state) { + stream.eat("$"); + if (stream.eat("{")) { + // let clike handle the content of ${...}, + // we take over again when "}" appears (see hooks). + state.tokenize = null; + } else { + state.tokenize = tokenInterpolationIdentifier; + } + return null; + } + + function tokenInterpolationIdentifier(stream, state) { + stream.eatWhile(/[\w_]/); + state.tokenize = popInterpolationStack(state); + return "variable"; + } + + function tokenNestedComment(depth) { + return function (stream, state) { + var ch + while (ch = stream.next()) { + if (ch == "*" && stream.eat("/")) { + if (depth == 1) { + state.tokenize = null + break + } else { + state.tokenize = tokenNestedComment(depth - 1) + return state.tokenize(stream, state) + } + } else if (ch == "/" && stream.eat("*")) { + state.tokenize = tokenNestedComment(depth + 1) + return state.tokenize(stream, state) + } + } + return "comment" + } + } + + CodeMirror.registerHelper("hintWords", "application/dart", keywords.concat(atoms).concat(builtins)); + + // This is needed to make loading through meta.js work. + CodeMirror.defineMode("dart", function(conf) { + return CodeMirror.getMode(conf, "application/dart"); + }, "clike"); +}); diff --git a/www/code/mode/dart/index.html b/www/code/codemirror-5.13.2/mode/dart/index.html similarity index 100% rename from www/code/mode/dart/index.html rename to www/code/codemirror-5.13.2/mode/dart/index.html diff --git a/www/code/mode/diff/diff.js b/www/code/codemirror-5.13.2/mode/diff/diff.js similarity index 100% rename from www/code/mode/diff/diff.js rename to www/code/codemirror-5.13.2/mode/diff/diff.js diff --git a/www/code/mode/diff/index.html b/www/code/codemirror-5.13.2/mode/diff/index.html similarity index 100% rename from www/code/mode/diff/index.html rename to www/code/codemirror-5.13.2/mode/diff/index.html diff --git a/www/code/mode/django/django.js b/www/code/codemirror-5.13.2/mode/django/django.js similarity index 90% rename from www/code/mode/django/django.js rename to www/code/codemirror-5.13.2/mode/django/django.js index 2f08b0631..a8a7d8311 100644 --- a/www/code/mode/django/django.js +++ b/www/code/codemirror-5.13.2/mode/django/django.js @@ -14,14 +14,14 @@ "use strict"; CodeMirror.defineMode("django:inner", function() { - var keywords = ["block", "endblock", "for", "endfor", "true", "false", - "loop", "none", "self", "super", "if", "endif", "as", - "else", "import", "with", "endwith", "without", "context", "ifequal", "endifequal", - "ifnotequal", "endifnotequal", "extends", "include", "load", "comment", - "endcomment", "empty", "url", "static", "trans", "blocktrans", "now", "regroup", - "lorem", "ifchanged", "endifchanged", "firstof", "debug", "cycle", "csrf_token", - "autoescape", "endautoescape", "spaceless", "ssi", "templatetag", - "verbatim", "endverbatim", "widthratio"], + var keywords = ["block", "endblock", "for", "endfor", "true", "false", "filter", "endfilter", + "loop", "none", "self", "super", "if", "elif", "endif", "as", "else", "import", + "with", "endwith", "without", "context", "ifequal", "endifequal", "ifnotequal", + "endifnotequal", "extends", "include", "load", "comment", "endcomment", + "empty", "url", "static", "trans", "blocktrans", "endblocktrans", "now", + "regroup", "lorem", "ifchanged", "endifchanged", "firstof", "debug", "cycle", + "csrf_token", "autoescape", "endautoescape", "spaceless", "endspaceless", + "ssi", "templatetag", "verbatim", "endverbatim", "widthratio"], filters = ["add", "addslashes", "capfirst", "center", "cut", "date", "default", "default_if_none", "dictsort", "dictsortreversed", "divisibleby", "escape", "escapejs", @@ -35,11 +35,13 @@ "truncatechars_html", "truncatewords", "truncatewords_html", "unordered_list", "upper", "urlencode", "urlize", "urlizetrunc", "wordcount", "wordwrap", "yesno"], - operators = ["==", "!=", "<", ">", "<=", ">=", "in", "not", "or", "and"]; + operators = ["==", "!=", "<", ">", "<=", ">="], + wordOperators = ["in", "not", "or", "and"]; keywords = new RegExp("^\\b(" + keywords.join("|") + ")\\b"); filters = new RegExp("^\\b(" + filters.join("|") + ")\\b"); operators = new RegExp("^\\b(" + operators.join("|") + ")\\b"); + wordOperators = new RegExp("^\\b(" + wordOperators.join("|") + ")\\b"); // We have to return "null" instead of null, in order to avoid string // styling as the default, when using Django templates inside HTML @@ -59,7 +61,7 @@ // Ignore completely any stream series that do not match the // Django template opening tags. - while (stream.next() != null && !stream.match("{{", false) && !stream.match("{%", false)) {} + while (stream.next() != null && !stream.match(/\{[{%#]/, false)) {} return null; } @@ -270,6 +272,11 @@ return "operator"; } + // Attempt to match a word operator + if (stream.match(wordOperators)) { + return "keyword"; + } + // Attempt to match a keyword var keywordMatch = stream.match(keywords); if (keywordMatch) { @@ -310,9 +317,8 @@ // Mark everything as comment inside the tag and the tag itself. function inComment (stream, state) { - if (stream.match("#}")) { - state.tokenize = tokenBase; - } + if (stream.match(/^.*?#\}/)) state.tokenize = tokenBase + else stream.skipToEnd() return "comment"; } diff --git a/www/code/mode/django/index.html b/www/code/codemirror-5.13.2/mode/django/index.html similarity index 100% rename from www/code/mode/django/index.html rename to www/code/codemirror-5.13.2/mode/django/index.html diff --git a/www/code/mode/dockerfile/dockerfile.js b/www/code/codemirror-5.13.2/mode/dockerfile/dockerfile.js similarity index 100% rename from www/code/mode/dockerfile/dockerfile.js rename to www/code/codemirror-5.13.2/mode/dockerfile/dockerfile.js diff --git a/www/code/mode/dockerfile/index.html b/www/code/codemirror-5.13.2/mode/dockerfile/index.html similarity index 100% rename from www/code/mode/dockerfile/index.html rename to www/code/codemirror-5.13.2/mode/dockerfile/index.html diff --git a/www/code/mode/dtd/dtd.js b/www/code/codemirror-5.13.2/mode/dtd/dtd.js similarity index 100% rename from www/code/mode/dtd/dtd.js rename to www/code/codemirror-5.13.2/mode/dtd/dtd.js diff --git a/www/code/mode/dtd/index.html b/www/code/codemirror-5.13.2/mode/dtd/index.html similarity index 100% rename from www/code/mode/dtd/index.html rename to www/code/codemirror-5.13.2/mode/dtd/index.html diff --git a/www/code/mode/dylan/dylan.js b/www/code/codemirror-5.13.2/mode/dylan/dylan.js similarity index 80% rename from www/code/mode/dylan/dylan.js rename to www/code/codemirror-5.13.2/mode/dylan/dylan.js index 85f0166c1..1b46bc828 100644 --- a/www/code/mode/dylan/dylan.js +++ b/www/code/codemirror-5.13.2/mode/dylan/dylan.js @@ -169,15 +169,16 @@ CodeMirror.defineMode("dylan", function(_config) { } else if (stream.eat("/")) { stream.skipToEnd(); return "comment"; - } else { - stream.skipTo(" "); - return "operator"; } + stream.backUp(1); } // Decimal - else if (/\d/.test(ch)) { - stream.match(/^\d*(?:\.\d*)?(?:e[+\-]?\d+)?/); - return "number"; + else if (/[+\-\d\.]/.test(ch)) { + if (stream.match(/^[+-]?[0-9]*\.[0-9]*([esdx][+-]?[0-9]+)?/i) || + stream.match(/^[+-]?[0-9]+([esdx][+-]?[0-9]+)/i) || + stream.match(/^[+-]?\d+/)) { + return "number"; + } } // Hash else if (ch == "#") { @@ -186,7 +187,7 @@ CodeMirror.defineMode("dylan", function(_config) { ch = stream.peek(); if (ch == '"') { stream.next(); - return chain(stream, state, tokenString('"', "string-2")); + return chain(stream, state, tokenString('"', "string")); } // Binary number else if (ch == "b") { @@ -206,11 +207,51 @@ CodeMirror.defineMode("dylan", function(_config) { stream.eatWhile(/[0-7]/); return "number"; } + // Token concatenation in macros + else if (ch == '#') { + stream.next(); + return "punctuation"; + } + // Sequence literals + else if ((ch == '[') || (ch == '(')) { + stream.next(); + return "bracket"; // Hash symbol - else { + } else if (stream.match(/f|t|all-keys|include|key|next|rest/i)) { + return "atom"; + } else { stream.eatWhile(/[-a-zA-Z]/); - return "keyword"; + return "error"; + } + } else if (ch == "~") { + stream.next(); + ch = stream.peek(); + if (ch == "=") { + stream.next(); + ch = stream.peek(); + if (ch == "=") { + stream.next(); + return "operator"; + } + return "operator"; } + return "operator"; + } else if (ch == ":") { + stream.next(); + ch = stream.peek(); + if (ch == "=") { + stream.next(); + return "operator"; + } else if (ch == ":") { + stream.next(); + return "punctuation"; + } + } else if ("[](){}".indexOf(ch) != -1) { + stream.next(); + return "bracket"; + } else if (".,".indexOf(ch) != -1) { + stream.next(); + return "punctuation"; } else if (stream.match("end")) { return "keyword"; } @@ -223,6 +264,10 @@ CodeMirror.defineMode("dylan", function(_config) { return patternStyles[name]; } } + if (/[+\-*\/^=<>&|]/.test(ch)) { + stream.next(); + return "operator"; + } if (stream.match("define")) { return "def"; } else { @@ -240,29 +285,37 @@ CodeMirror.defineMode("dylan", function(_config) { } function tokenComment(stream, state) { - var maybeEnd = false, - ch; + var maybeEnd = false, maybeNested = false, nestedCount = 0, ch; while ((ch = stream.next())) { if (ch == "/" && maybeEnd) { - state.tokenize = tokenBase; - break; + if (nestedCount > 0) { + nestedCount--; + } else { + state.tokenize = tokenBase; + break; + } + } else if (ch == "*" && maybeNested) { + nestedCount++; } maybeEnd = (ch == "*"); + maybeNested = (ch == "/"); } return "comment"; } function tokenString(quote, style) { return function(stream, state) { - var next, end = false; + var escaped = false, next, end = false; while ((next = stream.next()) != null) { - if (next == quote) { + if (next == quote && !escaped) { end = true; break; } + escaped = !escaped && next == "\\"; } - if (end) + if (end || !escaped) { state.tokenize = tokenBase; + } return style; }; } diff --git a/www/code/mode/dylan/index.html b/www/code/codemirror-5.13.2/mode/dylan/index.html similarity index 100% rename from www/code/mode/dylan/index.html rename to www/code/codemirror-5.13.2/mode/dylan/index.html diff --git a/www/code/codemirror-5.13.2/mode/dylan/test.js b/www/code/codemirror-5.13.2/mode/dylan/test.js new file mode 100644 index 000000000..bf25be27e --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/dylan/test.js @@ -0,0 +1,88 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "dylan"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + MT('comments', + '[comment // This is a line comment]', + '[comment /* This is a block comment */]', + '[comment /* This is a multi]', + '[comment line comment]', + '[comment */]', + '[comment /* And this is a /*]', + '[comment /* nested */ comment */]'); + + MT('unary_operators', + '[operator -][variable a]', + '[operator -] [variable a]', + '[operator ~][variable a]', + '[operator ~] [variable a]'); + + MT('binary_operators', + '[variable a] [operator +] [variable b]', + '[variable a] [operator -] [variable b]', + '[variable a] [operator *] [variable b]', + '[variable a] [operator /] [variable b]', + '[variable a] [operator ^] [variable b]', + '[variable a] [operator =] [variable b]', + '[variable a] [operator ==] [variable b]', + '[variable a] [operator ~=] [variable b]', + '[variable a] [operator ~==] [variable b]', + '[variable a] [operator <] [variable b]', + '[variable a] [operator <=] [variable b]', + '[variable a] [operator >] [variable b]', + '[variable a] [operator >=] [variable b]', + '[variable a] [operator &] [variable b]', + '[variable a] [operator |] [variable b]', + '[variable a] [operator :=] [variable b]'); + + MT('integers', + '[number 1]', + '[number 123]', + '[number -123]', + '[number +456]', + '[number #b010]', + '[number #o073]', + '[number #xabcDEF123]'); + + MT('floats', + '[number .3]', + '[number -1.]', + '[number -2.335]', + '[number +3.78d1]', + '[number 3.78s-1]', + '[number -3.32e+5]'); + + MT('characters_and_strings', + "[string 'a']", + "[string '\\\\'']", + '[string ""]', + '[string "a"]', + '[string "abc def"]', + '[string "More escaped characters: \\\\\\\\ \\\\a \\\\b \\\\e \\\\f \\\\n \\\\r \\\\t \\\\0 ..."]'); + + MT('brackets', + '[bracket #[[]]]', + '[bracket #()]', + '[bracket #(][number 1][bracket )]', + '[bracket [[][number 1][punctuation ,] [number 3][bracket ]]]', + '[bracket ()]', + '[bracket {}]', + '[keyword if] [bracket (][variable foo][bracket )]', + '[bracket (][number 1][bracket )]', + '[bracket [[][number 1][bracket ]]]'); + + MT('hash_words', + '[punctuation ##]', + '[atom #f]', '[atom #F]', + '[atom #t]', '[atom #T]', + '[atom #all-keys]', + '[atom #include]', + '[atom #key]', + '[atom #next]', + '[atom #rest]', + '[string #"foo"]', + '[error #invalid]'); +})(); diff --git a/www/code/mode/ebnf/ebnf.js b/www/code/codemirror-5.13.2/mode/ebnf/ebnf.js similarity index 100% rename from www/code/mode/ebnf/ebnf.js rename to www/code/codemirror-5.13.2/mode/ebnf/ebnf.js diff --git a/www/code/mode/ebnf/index.html b/www/code/codemirror-5.13.2/mode/ebnf/index.html similarity index 100% rename from www/code/mode/ebnf/index.html rename to www/code/codemirror-5.13.2/mode/ebnf/index.html diff --git a/www/code/mode/ecl/ecl.js b/www/code/codemirror-5.13.2/mode/ecl/ecl.js similarity index 100% rename from www/code/mode/ecl/ecl.js rename to www/code/codemirror-5.13.2/mode/ecl/ecl.js diff --git a/www/code/mode/ecl/index.html b/www/code/codemirror-5.13.2/mode/ecl/index.html similarity index 100% rename from www/code/mode/ecl/index.html rename to www/code/codemirror-5.13.2/mode/ecl/index.html diff --git a/www/code/mode/eiffel/eiffel.js b/www/code/codemirror-5.13.2/mode/eiffel/eiffel.js similarity index 100% rename from www/code/mode/eiffel/eiffel.js rename to www/code/codemirror-5.13.2/mode/eiffel/eiffel.js diff --git a/www/code/mode/eiffel/index.html b/www/code/codemirror-5.13.2/mode/eiffel/index.html similarity index 100% rename from www/code/mode/eiffel/index.html rename to www/code/codemirror-5.13.2/mode/eiffel/index.html diff --git a/www/code/mode/elm/elm.js b/www/code/codemirror-5.13.2/mode/elm/elm.js similarity index 100% rename from www/code/mode/elm/elm.js rename to www/code/codemirror-5.13.2/mode/elm/elm.js diff --git a/www/code/mode/elm/index.html b/www/code/codemirror-5.13.2/mode/elm/index.html similarity index 100% rename from www/code/mode/elm/index.html rename to www/code/codemirror-5.13.2/mode/elm/index.html diff --git a/www/code/mode/erlang/erlang.js b/www/code/codemirror-5.13.2/mode/erlang/erlang.js similarity index 100% rename from www/code/mode/erlang/erlang.js rename to www/code/codemirror-5.13.2/mode/erlang/erlang.js diff --git a/www/code/mode/erlang/index.html b/www/code/codemirror-5.13.2/mode/erlang/index.html similarity index 100% rename from www/code/mode/erlang/index.html rename to www/code/codemirror-5.13.2/mode/erlang/index.html diff --git a/www/code/mode/factor/factor.js b/www/code/codemirror-5.13.2/mode/factor/factor.js similarity index 100% rename from www/code/mode/factor/factor.js rename to www/code/codemirror-5.13.2/mode/factor/factor.js diff --git a/www/code/mode/factor/index.html b/www/code/codemirror-5.13.2/mode/factor/index.html similarity index 100% rename from www/code/mode/factor/index.html rename to www/code/codemirror-5.13.2/mode/factor/index.html diff --git a/www/code/codemirror-5.13.2/mode/fcl/fcl.js b/www/code/codemirror-5.13.2/mode/fcl/fcl.js new file mode 100644 index 000000000..518116976 --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/fcl/fcl.js @@ -0,0 +1,173 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(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("fcl", function(config) { + var indentUnit = config.indentUnit; + + var keywords = { + "term": true, + "method": true, "accu": true, + "rule": true, "then": true, "is": true, "and": true, "or": true, + "if": true, "default": true + }; + + var start_blocks = { + "var_input": true, + "var_output": true, + "fuzzify": true, + "defuzzify": true, + "function_block": true, + "ruleblock": true + }; + + var end_blocks = { + "end_ruleblock": true, + "end_defuzzify": true, + "end_function_block": true, + "end_fuzzify": true, + "end_var": true + }; + + var atoms = { + "true": true, "false": true, "nan": true, + "real": true, "min": true, "max": true, "cog": true, "cogs": true + }; + + var isOperatorChar = /[+\-*&^%:=<>!|\/]/; + + function tokenBase(stream, state) { + var ch = stream.next(); + + if (/[\d\.]/.test(ch)) { + if (ch == ".") { + stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); + } else if (ch == "0") { + stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); + } else { + stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); + } + return "number"; + } + + if (ch == "/" || ch == "(") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + + var cur = stream.current().toLowerCase(); + if (keywords.propertyIsEnumerable(cur) || + start_blocks.propertyIsEnumerable(cur) || + end_blocks.propertyIsEnumerable(cur)) { + return "keyword"; + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return "variable"; + } + + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if ((ch == "/" || ch == ")") && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + + function popContext(state) { + if (!state.context.prev) return; + var t = state.context.type; + if (t == "end_block") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) return null; + + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + + var cur = stream.current().toLowerCase(); + + if (start_blocks.propertyIsEnumerable(cur)) pushContext(state, stream.column(), "end_block"); + else if (end_blocks.propertyIsEnumerable(cur)) popContext(state); + + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context; + + var closing = end_blocks.propertyIsEnumerable(textAfter); + if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "ryk", + fold: "brace", + blockCommentStart: "(*", + blockCommentEnd: "*)", + lineComment: "//" + }; +}); + +CodeMirror.defineMIME("text/x-fcl", "fcl"); +}); diff --git a/www/code/codemirror-5.13.2/mode/fcl/index.html b/www/code/codemirror-5.13.2/mode/fcl/index.html new file mode 100644 index 000000000..3c18d0b3c --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/fcl/index.html @@ -0,0 +1,108 @@ + + +CodeMirror: FCL mode + + + + + + + + + + + +
+

FCL mode

+
+ + + +

MIME type: text/x-fcl

+
diff --git a/www/code/mode/forth/forth.js b/www/code/codemirror-5.13.2/mode/forth/forth.js similarity index 100% rename from www/code/mode/forth/forth.js rename to www/code/codemirror-5.13.2/mode/forth/forth.js diff --git a/www/code/mode/forth/index.html b/www/code/codemirror-5.13.2/mode/forth/index.html similarity index 100% rename from www/code/mode/forth/index.html rename to www/code/codemirror-5.13.2/mode/forth/index.html diff --git a/www/code/mode/fortran/fortran.js b/www/code/codemirror-5.13.2/mode/fortran/fortran.js similarity index 100% rename from www/code/mode/fortran/fortran.js rename to www/code/codemirror-5.13.2/mode/fortran/fortran.js diff --git a/www/code/mode/fortran/index.html b/www/code/codemirror-5.13.2/mode/fortran/index.html similarity index 100% rename from www/code/mode/fortran/index.html rename to www/code/codemirror-5.13.2/mode/fortran/index.html diff --git a/www/code/mode/gas/gas.js b/www/code/codemirror-5.13.2/mode/gas/gas.js similarity index 100% rename from www/code/mode/gas/gas.js rename to www/code/codemirror-5.13.2/mode/gas/gas.js diff --git a/www/code/mode/gas/index.html b/www/code/codemirror-5.13.2/mode/gas/index.html similarity index 100% rename from www/code/mode/gas/index.html rename to www/code/codemirror-5.13.2/mode/gas/index.html diff --git a/www/code/mode/gfm/gfm.js b/www/code/codemirror-5.13.2/mode/gfm/gfm.js similarity index 100% rename from www/code/mode/gfm/gfm.js rename to www/code/codemirror-5.13.2/mode/gfm/gfm.js diff --git a/www/code/mode/gfm/index.html b/www/code/codemirror-5.13.2/mode/gfm/index.html similarity index 100% rename from www/code/mode/gfm/index.html rename to www/code/codemirror-5.13.2/mode/gfm/index.html diff --git a/www/code/mode/gfm/test.js b/www/code/codemirror-5.13.2/mode/gfm/test.js similarity index 100% rename from www/code/mode/gfm/test.js rename to www/code/codemirror-5.13.2/mode/gfm/test.js diff --git a/www/code/mode/gherkin/gherkin.js b/www/code/codemirror-5.13.2/mode/gherkin/gherkin.js similarity index 100% rename from www/code/mode/gherkin/gherkin.js rename to www/code/codemirror-5.13.2/mode/gherkin/gherkin.js diff --git a/www/code/mode/gherkin/index.html b/www/code/codemirror-5.13.2/mode/gherkin/index.html similarity index 100% rename from www/code/mode/gherkin/index.html rename to www/code/codemirror-5.13.2/mode/gherkin/index.html diff --git a/www/code/mode/go/go.js b/www/code/codemirror-5.13.2/mode/go/go.js similarity index 100% rename from www/code/mode/go/go.js rename to www/code/codemirror-5.13.2/mode/go/go.js diff --git a/www/code/mode/go/index.html b/www/code/codemirror-5.13.2/mode/go/index.html similarity index 100% rename from www/code/mode/go/index.html rename to www/code/codemirror-5.13.2/mode/go/index.html diff --git a/www/code/mode/groovy/groovy.js b/www/code/codemirror-5.13.2/mode/groovy/groovy.js similarity index 100% rename from www/code/mode/groovy/groovy.js rename to www/code/codemirror-5.13.2/mode/groovy/groovy.js diff --git a/www/code/mode/groovy/index.html b/www/code/codemirror-5.13.2/mode/groovy/index.html similarity index 100% rename from www/code/mode/groovy/index.html rename to www/code/codemirror-5.13.2/mode/groovy/index.html diff --git a/www/code/mode/haml/haml.js b/www/code/codemirror-5.13.2/mode/haml/haml.js similarity index 97% rename from www/code/mode/haml/haml.js rename to www/code/codemirror-5.13.2/mode/haml/haml.js index 8fe63b020..03ce83355 100644 --- a/www/code/mode/haml/haml.js +++ b/www/code/codemirror-5.13.2/mode/haml/haml.js @@ -85,8 +85,10 @@ state.tokenize = rubyInQuote(")"); return state.tokenize(stream, state); } else if (ch == "{") { - state.tokenize = rubyInQuote("}"); - return state.tokenize(stream, state); + if (!stream.match(/^\{%.*/)) { + state.tokenize = rubyInQuote("}"); + return state.tokenize(stream, state); + } } } diff --git a/www/code/mode/haml/index.html b/www/code/codemirror-5.13.2/mode/haml/index.html similarity index 100% rename from www/code/mode/haml/index.html rename to www/code/codemirror-5.13.2/mode/haml/index.html diff --git a/www/code/mode/haml/test.js b/www/code/codemirror-5.13.2/mode/haml/test.js similarity index 100% rename from www/code/mode/haml/test.js rename to www/code/codemirror-5.13.2/mode/haml/test.js diff --git a/www/code/mode/handlebars/handlebars.js b/www/code/codemirror-5.13.2/mode/handlebars/handlebars.js similarity index 67% rename from www/code/mode/handlebars/handlebars.js rename to www/code/codemirror-5.13.2/mode/handlebars/handlebars.js index 40dfea42a..2174e5384 100644 --- a/www/code/mode/handlebars/handlebars.js +++ b/www/code/codemirror-5.13.2/mode/handlebars/handlebars.js @@ -3,15 +3,15 @@ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); + mod(require("../../lib/codemirror"), require("../../addon/mode/simple"), require("../../addon/mode/multiplex")); else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror", "../../addon/mode/simple"], mod); + define(["../../lib/codemirror", "../../addon/mode/simple", "../../addon/mode/multiplex"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; - CodeMirror.defineSimpleMode("handlebars", { + CodeMirror.defineSimpleMode("handlebars-tags", { start: [ { regex: /\{\{!--/, push: "dash_comment", token: "comment" }, { regex: /\{\{!/, push: "comment", token: "comment" }, @@ -21,8 +21,8 @@ { regex: /\}\}/, pop: true, token: "tag" }, // Double and single quotes - { regex: /"(?:[^\\]|\\.)*?"/, token: "string" }, - { regex: /'(?:[^\\]|\\.)*?'/, token: "string" }, + { regex: /"(?:[^\\"]|\\.)*"?/, token: "string" }, + { regex: /'(?:[^\\']|\\.)*'?/, token: "string" }, // Handlebars keywords { regex: />|[#\/]([A-Za-z_]\w*)/, token: "keyword" }, @@ -49,5 +49,14 @@ ] }); + CodeMirror.defineMode("handlebars", function(config, parserConfig) { + var handlebars = CodeMirror.getMode(config, "handlebars-tags"); + if (!parserConfig || !parserConfig.base) return handlebars; + return CodeMirror.multiplexingMode( + CodeMirror.getMode(config, parserConfig.base), + {open: "{{", close: "}}", mode: handlebars, parseDelimiters: true} + ); + }); + CodeMirror.defineMIME("text/x-handlebars-template", "handlebars"); }); diff --git a/www/code/mode/handlebars/index.html b/www/code/codemirror-5.13.2/mode/handlebars/index.html similarity index 84% rename from www/code/mode/handlebars/index.html rename to www/code/codemirror-5.13.2/mode/handlebars/index.html index beaef87a1..b1bfad1ca 100644 --- a/www/code/mode/handlebars/index.html +++ b/www/code/codemirror-5.13.2/mode/handlebars/index.html @@ -61,23 +61,19 @@ - - +

Handlebars syntax highlighting for CodeMirror.

MIME types defined: text/x-handlebars-template

+ +

Supported options: base to set the mode to + wrap. For example, use

+
mode: {name: "handlebars", base: "text/html"}
+

to highlight an HTML template.

diff --git a/www/code/codemirror-5.13.2/mode/haskell-literate/haskell-literate.js b/www/code/codemirror-5.13.2/mode/haskell-literate/haskell-literate.js new file mode 100644 index 000000000..906415b4c --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/haskell-literate/haskell-literate.js @@ -0,0 +1,43 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function (mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../haskell/haskell")) + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../haskell/haskell"], mod) + else // Plain browser env + mod(CodeMirror) +})(function (CodeMirror) { + "use strict" + + CodeMirror.defineMode("haskell-literate", function (config, parserConfig) { + var baseMode = CodeMirror.getMode(config, (parserConfig && parserConfig.base) || "haskell") + + return { + startState: function () { + return { + inCode: false, + baseState: CodeMirror.startState(baseMode) + } + }, + token: function (stream, state) { + if (stream.sol()) { + if (state.inCode = stream.eat(">")) + return "meta" + } + if (state.inCode) { + return baseMode.token(stream, state.baseState) + } else { + stream.skipToEnd() + return "comment" + } + }, + innerMode: function (state) { + return state.inCode ? {state: state.baseState, mode: baseMode} : null + } + } + }, "haskell") + + CodeMirror.defineMIME("text/x-literate-haskell", "haskell-literate") +}); diff --git a/www/code/codemirror-5.13.2/mode/haskell-literate/index.html b/www/code/codemirror-5.13.2/mode/haskell-literate/index.html new file mode 100644 index 000000000..8c9bc60d1 --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/haskell-literate/index.html @@ -0,0 +1,282 @@ + + +CodeMirror: Haskell-literate mode + + + + + + + + + + +
+

Haskell literate mode

+
+ +
+ +

MIME types + defined: text/x-literate-haskell.

+ +

Parser configuration parameters recognized: base to + set the base mode (defaults to "haskell").

+ + + +
diff --git a/www/code/mode/haskell/haskell.js b/www/code/codemirror-5.13.2/mode/haskell/haskell.js similarity index 100% rename from www/code/mode/haskell/haskell.js rename to www/code/codemirror-5.13.2/mode/haskell/haskell.js diff --git a/www/code/mode/haskell/index.html b/www/code/codemirror-5.13.2/mode/haskell/index.html similarity index 100% rename from www/code/mode/haskell/index.html rename to www/code/codemirror-5.13.2/mode/haskell/index.html diff --git a/www/code/mode/haxe/haxe.js b/www/code/codemirror-5.13.2/mode/haxe/haxe.js similarity index 97% rename from www/code/mode/haxe/haxe.js rename to www/code/codemirror-5.13.2/mode/haxe/haxe.js index 73cd6213a..a9573dd71 100644 --- a/www/code/mode/haxe/haxe.js +++ b/www/code/codemirror-5.13.2/mode/haxe/haxe.js @@ -191,13 +191,20 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) { pass.apply(null, arguments); return true; } + function inList(name, list) { + for (var v = list; v; v = v.next) + if (v.name == name) return true; + return false; + } function register(varname) { var state = cx.state; if (state.context) { cx.marked = "def"; - for (var v = state.localVars; v; v = v.next) - if (v.name == varname) return; + if (inList(varname, state.localVars)) return; state.localVars = {name: varname, next: state.localVars}; + } else if (state.globalVars) { + if (inList(varname, state.globalVars)) return; + state.globalVars = {name: varname, next: state.globalVars}; } } @@ -380,11 +387,10 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) { } // Interface - return { startState: function(basecolumn) { var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"]; - return { + var state = { tokenize: haxeTokenBase, reAllowed: true, kwAllowed: true, @@ -395,6 +401,9 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) { context: parserConfig.localVars && {vars: parserConfig.localVars}, indented: 0 }; + if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") + state.globalVars = parserConfig.globalVars; + return state; }, token: function(stream, state) { diff --git a/www/code/mode/haxe/index.html b/www/code/codemirror-5.13.2/mode/haxe/index.html similarity index 100% rename from www/code/mode/haxe/index.html rename to www/code/codemirror-5.13.2/mode/haxe/index.html diff --git a/www/code/mode/htmlembedded/htmlembedded.js b/www/code/codemirror-5.13.2/mode/htmlembedded/htmlembedded.js similarity index 100% rename from www/code/mode/htmlembedded/htmlembedded.js rename to www/code/codemirror-5.13.2/mode/htmlembedded/htmlembedded.js diff --git a/www/code/mode/htmlembedded/index.html b/www/code/codemirror-5.13.2/mode/htmlembedded/index.html similarity index 100% rename from www/code/mode/htmlembedded/index.html rename to www/code/codemirror-5.13.2/mode/htmlembedded/index.html diff --git a/www/code/mode/htmlmixed/htmlmixed.js b/www/code/codemirror-5.13.2/mode/htmlmixed/htmlmixed.js similarity index 76% rename from www/code/mode/htmlmixed/htmlmixed.js rename to www/code/codemirror-5.13.2/mode/htmlmixed/htmlmixed.js index 670fd62bf..6574fbd56 100644 --- a/www/code/mode/htmlmixed/htmlmixed.js +++ b/www/code/codemirror-5.13.2/mode/htmlmixed/htmlmixed.js @@ -44,13 +44,9 @@ return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*"); } - function getAttrValue(stream, attr) { - var pos = stream.pos, match; - while (pos >= 0 && stream.string.charAt(pos) !== "<") pos--; - if (pos < 0) return pos; - if (match = stream.string.slice(pos, stream.pos).match(getAttrRegexp(attr))) - return match[2]; - return ""; + function getAttrValue(text, attr) { + var match = text.match(getAttrRegexp(attr)) + return match ? match[2] : "" } function getTagRegexp(tagName, anchored) { @@ -66,10 +62,10 @@ } } - function findMatchingMode(tagInfo, stream) { + function findMatchingMode(tagInfo, tagText) { for (var i = 0; i < tagInfo.length; i++) { var spec = tagInfo[i]; - if (!spec[0] || spec[1].test(getAttrValue(stream, spec[0]))) return spec[2]; + if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2]; } } @@ -89,15 +85,17 @@ tags.script.unshift(["type", configScript[i].matches, configScript[i].mode]) function html(stream, state) { - var tagName = state.htmlState.tagName; - var tagInfo = tagName && tags[tagName.toLowerCase()]; - - var style = htmlMode.token(stream, state.htmlState), modeSpec; - - if (tagInfo && /\btag\b/.test(style) && stream.current() === ">" && - (modeSpec = findMatchingMode(tagInfo, stream))) { - var mode = CodeMirror.getMode(config, modeSpec); - var endTagA = getTagRegexp(tagName, true), endTag = getTagRegexp(tagName, false); + var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName + if (tag && !/[<>\s\/]/.test(stream.current()) && + (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) && + tags.hasOwnProperty(tagName)) { + state.inTag = tagName + " " + } else if (state.inTag && tag && />$/.test(stream.current())) { + var inTag = /^([\S]+) (.*)/.exec(state.inTag) + state.inTag = null + var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2]) + var mode = CodeMirror.getMode(config, modeSpec) + var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false); state.token = function (stream, state) { if (stream.match(endTagA, false)) { state.token = html; @@ -108,6 +106,9 @@ }; state.localMode = mode; state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "")); + } else if (state.inTag) { + state.inTag += stream.current() + if (stream.eol()) state.inTag += " " } return style; }; @@ -115,7 +116,7 @@ return { startState: function () { var state = htmlMode.startState(); - return {token: html, localMode: null, localState: null, htmlState: state}; + return {token: html, inTag: null, localMode: null, localState: null, htmlState: state}; }, copyState: function (state) { @@ -123,7 +124,8 @@ if (state.localState) { local = CodeMirror.copyState(state.localMode, state.localState); } - return {token: state.token, localMode: state.localMode, localState: local, + return {token: state.token, inTag: state.inTag, + localMode: state.localMode, localState: local, htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; }, diff --git a/www/code/mode/htmlmixed/index.html b/www/code/codemirror-5.13.2/mode/htmlmixed/index.html similarity index 100% rename from www/code/mode/htmlmixed/index.html rename to www/code/codemirror-5.13.2/mode/htmlmixed/index.html diff --git a/www/code/mode/http/http.js b/www/code/codemirror-5.13.2/mode/http/http.js similarity index 100% rename from www/code/mode/http/http.js rename to www/code/codemirror-5.13.2/mode/http/http.js diff --git a/www/code/mode/http/index.html b/www/code/codemirror-5.13.2/mode/http/index.html similarity index 100% rename from www/code/mode/http/index.html rename to www/code/codemirror-5.13.2/mode/http/index.html diff --git a/www/code/mode/idl/idl.js b/www/code/codemirror-5.13.2/mode/idl/idl.js similarity index 100% rename from www/code/mode/idl/idl.js rename to www/code/codemirror-5.13.2/mode/idl/idl.js diff --git a/www/code/mode/idl/index.html b/www/code/codemirror-5.13.2/mode/idl/index.html similarity index 100% rename from www/code/mode/idl/index.html rename to www/code/codemirror-5.13.2/mode/idl/index.html diff --git a/www/code/mode/index.html b/www/code/codemirror-5.13.2/mode/index.html similarity index 92% rename from www/code/mode/index.html rename to www/code/codemirror-5.13.2/mode/index.html index f63e83684..5e77655bf 100644 --- a/www/code/mode/index.html +++ b/www/code/codemirror-5.13.2/mode/index.html @@ -35,12 +35,14 @@ option.

  • Asterisk dialplan
  • Brainfuck
  • C, C++, C#
  • +
  • Ceylon
  • Clojure
  • Closure Stylesheets (GSS)
  • CMake
  • COBOL
  • CoffeeScript
  • Common Lisp
  • +
  • Crystal
  • CSS
  • Cypher
  • Cython
  • @@ -57,6 +59,7 @@ option.

  • Elm
  • Erlang
  • Factor
  • +
  • FCL
  • Forth
  • Fortran
  • F#
  • @@ -66,7 +69,7 @@ option.

  • Groovy
  • HAML
  • Handlebars
  • -
  • Haskell
  • +
  • Haskell (Literate)
  • Haxe
  • HTML embedded (JSP, ASP.NET)
  • HTML mixed-mode
  • @@ -74,7 +77,7 @@ option.

  • IDL
  • Java
  • Jade
  • -
  • JavaScript
  • +
  • JavaScript (JSX)
  • Jinja2
  • Julia
  • Kotlin
  • @@ -85,8 +88,10 @@ option.

  • Mathematica
  • mIRC
  • Modelica
  • +
  • MscGen
  • MUMPS
  • Nginx
  • +
  • NSIS
  • NTriples
  • Objective C
  • OCaml
  • @@ -99,6 +104,7 @@ option.

  • PHP
  • Pig Latin
  • Properties files
  • +
  • ProtoBuf
  • Puppet
  • Python
  • Q
  • @@ -145,6 +151,7 @@ option.

  • XML/HTML
  • XQuery
  • YAML
  • +
  • YAML frontmatter
  • Z80
  • diff --git a/www/code/mode/jade/index.html b/www/code/codemirror-5.13.2/mode/jade/index.html similarity index 100% rename from www/code/mode/jade/index.html rename to www/code/codemirror-5.13.2/mode/jade/index.html diff --git a/www/code/mode/jade/jade.js b/www/code/codemirror-5.13.2/mode/jade/jade.js similarity index 99% rename from www/code/mode/jade/jade.js rename to www/code/codemirror-5.13.2/mode/jade/jade.js index 96fadb19e..1db069a90 100644 --- a/www/code/mode/jade/jade.js +++ b/www/code/codemirror-5.13.2/mode/jade/jade.js @@ -74,7 +74,7 @@ CodeMirror.defineMode('jade', function (config) { res.javaScriptArguments = this.javaScriptArguments; res.javaScriptArgumentsDepth = this.javaScriptArgumentsDepth; res.isInterpolating = this.isInterpolating; - res.interpolationNesting = this.intpolationNesting; + res.interpolationNesting = this.interpolationNesting; res.jsState = CodeMirror.copyState(jsMode, this.jsState); @@ -167,7 +167,7 @@ CodeMirror.defineMode('jade', function (config) { if (state.interpolationNesting < 0) { stream.next(); state.isInterpolating = false; - return 'puncutation'; + return 'punctuation'; } } else if (stream.peek() === '{') { state.interpolationNesting++; @@ -583,7 +583,7 @@ CodeMirror.defineMode('jade', function (config) { copyState: copyState, token: nextToken }; -}); +}, 'javascript', 'css', 'htmlmixed'); CodeMirror.defineMIME('text/x-jade', 'jade'); diff --git a/www/code/mode/javascript/index.html b/www/code/codemirror-5.13.2/mode/javascript/index.html similarity index 100% rename from www/code/mode/javascript/index.html rename to www/code/codemirror-5.13.2/mode/javascript/index.html diff --git a/www/code/mode/javascript/javascript.js b/www/code/codemirror-5.13.2/mode/javascript/javascript.js similarity index 91% rename from www/code/mode/javascript/javascript.js rename to www/code/codemirror-5.13.2/mode/javascript/javascript.js index 032c20759..fa5721d5d 100644 --- a/www/code/mode/javascript/javascript.js +++ b/www/code/codemirror-5.13.2/mode/javascript/javascript.js @@ -13,6 +13,11 @@ })(function(CodeMirror) { "use strict"; +function expressionAllowed(stream, state, backUp) { + return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) || + (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) +} + CodeMirror.defineMode("javascript", function(config, parserConfig) { var indentUnit = config.indentUnit; var statementIndent = parserConfig.statementIndent; @@ -30,14 +35,14 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var jsKeywords = { "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, - "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C, + "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C, "var": kw("var"), "const": kw("var"), "let": kw("var"), - "async": kw("async"), "function": kw("function"), "catch": kw("catch"), + "function": kw("function"), "catch": kw("catch"), "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), "in": operator, "typeof": operator, "instanceof": operator, "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, "this": kw("this"), "class": kw("class"), "super": kw("atom"), - "await": C, "yield": C, "export": kw("export"), "import": kw("import"), "extends": C + "yield": C, "export": kw("export"), "import": kw("import"), "extends": C }; // Extend the 'normal' keywords with the TypeScript language extensions @@ -45,18 +50,23 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var type = {type: "variable", style: "variable-3"}; var tsKeywords = { // object-like things - "interface": kw("interface"), - "extends": kw("extends"), - "constructor": kw("constructor"), + "interface": kw("class"), + "implements": C, + "namespace": C, + "module": kw("module"), + "enum": kw("module"), // scope modifiers - "public": kw("public"), - "private": kw("private"), - "protected": kw("protected"), - "static": kw("static"), + "public": kw("modifier"), + "private": kw("modifier"), + "protected": kw("modifier"), + "abstract": kw("modifier"), + + // operators + "as": operator, // types - "string": type, "number": type, "bool": type, "any": type + "string": type, "number": type, "boolean": type, "any": type }; for (var attr in tsKeywords) { @@ -121,8 +131,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } else if (stream.eat("/")) { stream.skipToEnd(); return ret("comment", "comment"); - } else if (state.lastType == "operator" || state.lastType == "keyword c" || - state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) { + } else if (expressionAllowed(stream, state, 1)) { readRegexp(stream); stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); return ret("regexp", "string-2"); @@ -281,8 +290,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return false; } var state = cx.state; + cx.marked = "def"; if (state.context) { - cx.marked = "def"; if (inList(state.localVars)) return; state.localVars = {name: varname, next: state.localVars}; } else { @@ -356,6 +365,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "class") return cont(pushlex("form"), className, poplex); if (type == "export") return cont(pushlex("stat"), afterExport, poplex); if (type == "import") return cont(pushlex("stat"), afterImport, poplex); + if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex) return pass(pushlex("stat"), expression, expect(";"), poplex); } function expression(type) { @@ -373,14 +383,14 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); - if (type == "async") return cont(expression); if (type == "function") return cont(functiondef, maybeop); if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); if (type == "{") return contCommasep(objprop, "}", null, maybeop); - if (type == "quasi") { return pass(quasi, maybeop); } + if (type == "quasi") return pass(quasi, maybeop); + if (type == "new") return cont(maybeTarget(noComma)); return cont(); } function maybeexpression(type) { @@ -431,6 +441,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { findFatArrow(cx.stream, cx.state); return pass(type == "{" ? statement : expressionNoComma); } + function maybeTarget(noComma) { + return function(type) { + if (type == ".") return cont(noComma ? targetNoComma : target); + else return pass(noComma ? expressionNoComma : expression); + }; + } + function target(_, value) { + if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } + } + function targetNoComma(_, value) { + if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } + } function maybelabel(type) { if (type == ":") return cont(poplex, statement); return pass(maybeoperatorComma, expect(";"), poplex); @@ -439,9 +461,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "variable") {cx.marked = "property"; return cont();} } function objprop(type, value) { - if (type == "async") { - return cont(objprop); - } else if (type == "variable" || cx.style == "keyword") { + if (type == "variable" || cx.style == "keyword") { cx.marked = "property"; if (value == "get" || value == "set") return cont(getterSetter); return cont(afterprop); @@ -450,8 +470,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return cont(afterprop); } else if (type == "jsonld-keyword") { return cont(afterprop); + } else if (type == "modifier") { + return cont(objprop) } else if (type == "[") { return cont(expression, expect("]"), afterprop); + } else if (type == "spread") { + return cont(expression); } } function getterSetter(type) { @@ -500,7 +524,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return pass(pattern, maybetype, maybeAssign, vardefCont); } function pattern(type, value) { + if (type == "modifier") return cont(pattern) if (type == "variable") { register(value); return cont(); } + if (type == "spread") return cont(pattern); if (type == "[") return contCommasep(pattern, "]"); if (type == "{") return contCommasep(proppattern, "}"); } @@ -510,6 +536,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return cont(maybeAssign); } if (type == "variable") cx.marked = "property"; + if (type == "spread") return cont(pattern); + if (type == "}") return pass(); return cont(expect(":"), pattern, maybeAssign); } function maybeAssign(_type, value) { @@ -632,7 +660,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), localVars: parserConfig.localVars, context: parserConfig.localVars && {vars: parserConfig.localVars}, - indented: 0 + indented: basecolumn || 0 }; if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") state.globalVars = parserConfig.globalVars; @@ -688,7 +716,13 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { helperType: jsonMode ? "json" : "javascript", jsonldMode: jsonldMode, - jsonMode: jsonMode + jsonMode: jsonMode, + + expressionAllowed: expressionAllowed, + skipExpression: function(state) { + var top = state.cc[state.cc.length - 1] + if (top == expression || top == expressionNoComma) state.cc.pop() + } }; }); diff --git a/www/code/mode/javascript/json-ld.html b/www/code/codemirror-5.13.2/mode/javascript/json-ld.html similarity index 100% rename from www/code/mode/javascript/json-ld.html rename to www/code/codemirror-5.13.2/mode/javascript/json-ld.html diff --git a/www/code/mode/javascript/test.js b/www/code/codemirror-5.13.2/mode/javascript/test.js similarity index 76% rename from www/code/mode/javascript/test.js rename to www/code/codemirror-5.13.2/mode/javascript/test.js index 452de5fd6..cb43d0894 100644 --- a/www/code/mode/javascript/test.js +++ b/www/code/codemirror-5.13.2/mode/javascript/test.js @@ -6,7 +6,7 @@ function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("locals", - "[keyword function] [variable foo]([def a], [def b]) { [keyword var] [def c] [operator =] [number 10]; [keyword return] [variable-2 a] [operator +] [variable-2 c] [operator +] [variable d]; }"); + "[keyword function] [def foo]([def a], [def b]) { [keyword var] [def c] [operator =] [number 10]; [keyword return] [variable-2 a] [operator +] [variable-2 c] [operator +] [variable d]; }"); MT("comma-and-binop", "[keyword function](){ [keyword var] [def x] [operator =] [number 1] [operator +] [number 2], [def y]; }"); @@ -17,8 +17,12 @@ " [[[variable-2 c], [variable y] ]] [operator =] [variable-2 c];", "})();"); + MT("destructure_trailing_comma", + "[keyword let] {[def a], [def b],} [operator =] [variable foo];", + "[keyword let] [def c];"); // Parser still in good state? + MT("class_body", - "[keyword class] [variable Foo] {", + "[keyword class] [def Foo] {", " [property constructor]() {}", " [property sayName]() {", " [keyword return] [string-2 `foo${][variable foo][string-2 }oo`];", @@ -26,7 +30,7 @@ "}"); MT("class", - "[keyword class] [variable Point] [keyword extends] [variable SuperThing] {", + "[keyword class] [def Point] [keyword extends] [variable SuperThing] {", " [property get] [property prop]() { [keyword return] [number 24]; }", " [property constructor]([def x], [def y]) {", " [keyword super]([string 'something']);", @@ -35,44 +39,44 @@ "}"); MT("import", - "[keyword function] [variable foo]() {", + "[keyword function] [def foo]() {", " [keyword import] [def $] [keyword from] [string 'jquery'];", " [keyword import] { [def encrypt], [def decrypt] } [keyword from] [string 'crypto'];", "}"); MT("const", - "[keyword function] [variable f]() {", + "[keyword function] [def f]() {", " [keyword const] [[ [def a], [def b] ]] [operator =] [[ [number 1], [number 2] ]];", "}"); MT("for/of", - "[keyword for]([keyword let] [variable of] [keyword of] [variable something]) {}"); + "[keyword for]([keyword let] [def of] [keyword of] [variable something]) {}"); MT("generator", - "[keyword function*] [variable repeat]([def n]) {", + "[keyword function*] [def repeat]([def n]) {", " [keyword for]([keyword var] [def i] [operator =] [number 0]; [variable-2 i] [operator <] [variable-2 n]; [operator ++][variable-2 i])", " [keyword yield] [variable-2 i];", "}"); MT("quotedStringAddition", - "[keyword let] [variable f] [operator =] [variable a] [operator +] [string 'fatarrow'] [operator +] [variable c];"); + "[keyword let] [def f] [operator =] [variable a] [operator +] [string 'fatarrow'] [operator +] [variable c];"); MT("quotedFatArrow", - "[keyword let] [variable f] [operator =] [variable a] [operator +] [string '=>'] [operator +] [variable c];"); + "[keyword let] [def f] [operator =] [variable a] [operator +] [string '=>'] [operator +] [variable c];"); MT("fatArrow", "[variable array].[property filter]([def a] [operator =>] [variable-2 a] [operator +] [number 1]);", "[variable a];", // No longer in scope - "[keyword let] [variable f] [operator =] ([[ [def a], [def b] ]], [def c]) [operator =>] [variable-2 a] [operator +] [variable-2 c];", + "[keyword let] [def f] [operator =] ([[ [def a], [def b] ]], [def c]) [operator =>] [variable-2 a] [operator +] [variable-2 c];", "[variable c];"); MT("spread", - "[keyword function] [variable f]([def a], [meta ...][def b]) {", + "[keyword function] [def f]([def a], [meta ...][def b]) {", " [variable something]([variable-2 a], [meta ...][variable-2 b]);", "}"); MT("comprehension", - "[keyword function] [variable f]() {", + "[keyword function] [def f]() {", " [[([variable x] [operator +] [number 1]) [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];", " ([variable u] [keyword for] ([keyword var] [def u] [keyword of] [variable generateValues]()) [keyword if] ([variable-2 u].[property color] [operator ===] [string 'blue']));", "}"); @@ -84,7 +88,7 @@ "[variable x] [operator =] [string-2 `fofdlakj${][variable x] [operator +] [string-2 `foo`] [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]"); MT("indent_statement", - "[keyword var] [variable x] [operator =] [number 10]", + "[keyword var] [def x] [operator =] [number 10]", "[variable x] [operator +=] [variable y] [operator +]", " [atom Infinity]", "[keyword debugger];"); @@ -105,14 +109,14 @@ "}"); MT("indent_for", - "[keyword for] ([keyword var] [variable i] [operator =] [number 0];", + "[keyword for] ([keyword var] [def i] [operator =] [number 0];", " [variable i] [operator <] [number 100];", " [variable i][operator ++])", " [variable doSomething]([variable i]);", "[keyword debugger];"); MT("indent_c_style", - "[keyword function] [variable foo]()", + "[keyword function] [def foo]()", "{", " [keyword debugger];", "}"); @@ -140,24 +144,32 @@ "[number 2];"); MT("multilinestring", - "[keyword var] [variable x] [operator =] [string 'foo\\]", + "[keyword var] [def x] [operator =] [string 'foo\\]", "[string bar'];"); MT("scary_regexp", "[string-2 /foo[[/]]bar/];"); MT("indent_strange_array", - "[keyword var] [variable x] [operator =] [[", + "[keyword var] [def x] [operator =] [[", " [number 1],,", " [number 2],", "]];", "[number 10];"); MT("param_default", - "[keyword function] [variable foo]([def x] [operator =] [string-2 `foo${][number 10][string-2 }bar`]) {", + "[keyword function] [def foo]([def x] [operator =] [string-2 `foo${][number 10][string-2 }bar`]) {", " [keyword return] [variable-2 x];", "}"); + MT("new_target", + "[keyword function] [def F]([def target]) {", + " [keyword if] ([variable-2 target] [operator &&] [keyword new].[keyword target].[property name]) {", + " [keyword return] [keyword new]", + " .[keyword target];", + " }", + "}"); + var jsonld_mode = CodeMirror.getMode( {indentUnit: 2}, {name: "javascript", jsonld: true} diff --git a/www/code/mode/javascript/typescript.html b/www/code/codemirror-5.13.2/mode/javascript/typescript.html similarity index 100% rename from www/code/mode/javascript/typescript.html rename to www/code/codemirror-5.13.2/mode/javascript/typescript.html diff --git a/www/code/mode/jinja2/index.html b/www/code/codemirror-5.13.2/mode/jinja2/index.html similarity index 100% rename from www/code/mode/jinja2/index.html rename to www/code/codemirror-5.13.2/mode/jinja2/index.html diff --git a/www/code/mode/jinja2/jinja2.js b/www/code/codemirror-5.13.2/mode/jinja2/jinja2.js similarity index 100% rename from www/code/mode/jinja2/jinja2.js rename to www/code/codemirror-5.13.2/mode/jinja2/jinja2.js diff --git a/www/code/codemirror-5.13.2/mode/jsx/index.html b/www/code/codemirror-5.13.2/mode/jsx/index.html new file mode 100644 index 000000000..cb51edb36 --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/jsx/index.html @@ -0,0 +1,89 @@ + + +CodeMirror: JSX mode + + + + + + + + + + + +
    +

    JSX mode

    + +
    + + + +

    JSX Mode for React's +JavaScript syntax extension.

    + +

    MIME types defined: text/jsx.

    + +
    diff --git a/www/code/codemirror-5.13.2/mode/jsx/jsx.js b/www/code/codemirror-5.13.2/mode/jsx/jsx.js new file mode 100644 index 000000000..aff01b8d3 --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/jsx/jsx.js @@ -0,0 +1,147 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript")) + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript"], mod) + else // Plain browser env + mod(CodeMirror) +})(function(CodeMirror) { + "use strict" + + // Depth means the amount of open braces in JS context, in XML + // context 0 means not in tag, 1 means in tag, and 2 means in tag + // and js block comment. + function Context(state, mode, depth, prev) { + this.state = state; this.mode = mode; this.depth = depth; this.prev = prev + } + + function copyContext(context) { + return new Context(CodeMirror.copyState(context.mode, context.state), + context.mode, + context.depth, + context.prev && copyContext(context.prev)) + } + + CodeMirror.defineMode("jsx", function(config, modeConfig) { + var xmlMode = CodeMirror.getMode(config, {name: "xml", allowMissing: true, multilineTagIndentPastTag: false}) + var jsMode = CodeMirror.getMode(config, modeConfig && modeConfig.base || "javascript") + + function flatXMLIndent(state) { + var tagName = state.tagName + state.tagName = null + var result = xmlMode.indent(state, "") + state.tagName = tagName + return result + } + + function token(stream, state) { + if (state.context.mode == xmlMode) + return xmlToken(stream, state, state.context) + else + return jsToken(stream, state, state.context) + } + + function xmlToken(stream, state, cx) { + if (cx.depth == 2) { // Inside a JS /* */ comment + if (stream.match(/^.*?\*\//)) cx.depth = 1 + else stream.skipToEnd() + return "comment" + } + + if (stream.peek() == "{") { + xmlMode.skipAttribute(cx.state) + + var indent = flatXMLIndent(cx.state), xmlContext = cx.state.context + // If JS starts on same line as tag + if (xmlContext && stream.match(/^[^>]*>\s*$/, false)) { + while (xmlContext.prev && !xmlContext.startOfLine) + xmlContext = xmlContext.prev + // If tag starts the line, use XML indentation level + if (xmlContext.startOfLine) indent -= config.indentUnit + // Else use JS indentation level + else if (cx.prev.state.lexical) indent = cx.prev.state.lexical.indented + // Else if inside of tag + } else if (cx.depth == 1) { + indent += config.indentUnit + } + + state.context = new Context(CodeMirror.startState(jsMode, indent), + jsMode, 0, state.context) + return null + } + + if (cx.depth == 1) { // Inside of tag + if (stream.peek() == "<") { // Tag inside of tag + xmlMode.skipAttribute(cx.state) + state.context = new Context(CodeMirror.startState(xmlMode, flatXMLIndent(cx.state)), + xmlMode, 0, state.context) + return null + } else if (stream.match("//")) { + stream.skipToEnd() + return "comment" + } else if (stream.match("/*")) { + cx.depth = 2 + return token(stream, state) + } + } + + var style = xmlMode.token(stream, cx.state), cur = stream.current(), stop + if (/\btag\b/.test(style)) { + if (/>$/.test(cur)) { + if (cx.state.context) cx.depth = 0 + else state.context = state.context.prev + } else if (/^ -1) { + stream.backUp(cur.length - stop) + } + return style + } + + function jsToken(stream, state, cx) { + if (stream.peek() == "<" && jsMode.expressionAllowed(stream, cx.state)) { + jsMode.skipExpression(cx.state) + state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "")), + xmlMode, 0, state.context) + return null + } + + var style = jsMode.token(stream, cx.state) + if (!style && cx.depth != null) { + var cur = stream.current() + if (cur == "{") { + cx.depth++ + } else if (cur == "}") { + if (--cx.depth == 0) state.context = state.context.prev + } + } + return style + } + + return { + startState: function() { + return {context: new Context(CodeMirror.startState(jsMode), jsMode)} + }, + + copyState: function(state) { + return {context: copyContext(state.context)} + }, + + token: token, + + indent: function(state, textAfter, fullLine) { + return state.context.mode.indent(state.context.state, textAfter, fullLine) + }, + + innerMode: function(state) { + return state.context + } + } + }, "xml", "javascript") + + CodeMirror.defineMIME("text/jsx", "jsx") +}); diff --git a/www/code/codemirror-5.13.2/mode/jsx/test.js b/www/code/codemirror-5.13.2/mode/jsx/test.js new file mode 100644 index 000000000..c54a8b24c --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/jsx/test.js @@ -0,0 +1,69 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "jsx") + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)) } + + MT("selfclose", + "[keyword var] [def x] [operator =] [bracket&tag <] [tag foo] [bracket&tag />] [operator +] [number 1];") + + MT("openclose", + "([bracket&tag <][tag foo][bracket&tag >]hello [atom &][bracket&tag ][operator ++])") + + MT("attr", + "([bracket&tag <][tag foo] [attribute abc]=[string 'value'][bracket&tag >]hello [atom &][bracket&tag ][operator ++])") + + MT("braced_attr", + "([bracket&tag <][tag foo] [attribute abc]={[number 10]}[bracket&tag >]hello [atom &][bracket&tag ][operator ++])") + + MT("braced_text", + "([bracket&tag <][tag foo][bracket&tag >]hello {[number 10]} [atom &][bracket&tag ][operator ++])") + + MT("nested_tag", + "([bracket&tag <][tag foo][bracket&tag ><][tag bar][bracket&tag >][operator ++])") + + MT("nested_jsx", + "[keyword return] (", + " [bracket&tag <][tag foo][bracket&tag >]", + " say {[number 1] [operator +] [bracket&tag <][tag bar] [attribute attr]={[number 10]}[bracket&tag />]}!", + " [bracket&tag ][operator ++]", + ")") + + MT("preserve_js_context", + "[variable x] [operator =] [string-2 `quasi${][bracket&tag <][tag foo][bracket&tag />][string-2 }quoted`]") + + MT("line_comment", + "([bracket&tag <][tag foo] [comment // hello]", + " [bracket&tag >][operator ++])") + + MT("line_comment_not_in_tag", + "([bracket&tag <][tag foo][bracket&tag >] // hello", + " [bracket&tag ][operator ++])") + + MT("block_comment", + "([bracket&tag <][tag foo] [comment /* hello]", + "[comment line 2]", + "[comment line 3 */] [bracket&tag >][operator ++])") + + MT("block_comment_not_in_tag", + "([bracket&tag <][tag foo][bracket&tag >]/* hello", + " line 2", + " line 3 */ [bracket&tag ][operator ++])") + + MT("missing_attr", + "([bracket&tag <][tag foo] [attribute selected][bracket&tag />][operator ++])") + + MT("indent_js", + "([bracket&tag <][tag foo][bracket&tag >]", + " [bracket&tag <][tag bar] [attribute baz]={[keyword function]() {", + " [keyword return] [number 10]", + " }}[bracket&tag />]", + " [bracket&tag ])") + + MT("spread", + "([bracket&tag <][tag foo] [attribute bar]={[meta ...][variable baz] [operator /][number 2]}[bracket&tag />])") + + MT("tag_attribute", + "([bracket&tag <][tag foo] [attribute bar]=[bracket&tag <][tag foo][bracket&tag />/>][operator ++])") +})() diff --git a/www/code/mode/julia/index.html b/www/code/codemirror-5.13.2/mode/julia/index.html similarity index 100% rename from www/code/mode/julia/index.html rename to www/code/codemirror-5.13.2/mode/julia/index.html diff --git a/www/code/codemirror-5.13.2/mode/julia/julia.js b/www/code/codemirror-5.13.2/mode/julia/julia.js new file mode 100644 index 000000000..004de4431 --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/julia/julia.js @@ -0,0 +1,392 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(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("julia", function(_conf, parserConf) { + var ERRORCLASS = 'error'; + + function wordRegexp(words, end) { + if (typeof end === 'undefined') { end = "\\b"; } + return new RegExp("^((" + words.join(")|(") + "))" + end); + } + + var octChar = "\\\\[0-7]{1,3}"; + var hexChar = "\\\\x[A-Fa-f0-9]{1,2}"; + var specialChar = "\\\\[abfnrtv0%?'\"\\\\]"; + var singleChar = "([^\\u0027\\u005C\\uD800-\\uDFFF]|[\\uD800-\\uDFFF][\\uDC00-\\uDFFF])"; + var operators = parserConf.operators || /^\.?[|&^\\%*+\-<>!=\/]=?|\?|~|:|\$|\.[<>]|<<=?|>>>?=?|\.[<>=]=|->?|\/\/|\bin\b(?!\()|[\u2208\u2209](?!\()/; + var delimiters = parserConf.delimiters || /^[;,()[\]{}]/; + var identifiers = parserConf.identifiers || /^[_A-Za-z\u00A1-\uFFFF][\w\u00A1-\uFFFF]*!*/; + var charsList = [octChar, hexChar, specialChar, singleChar]; + var blockOpeners = ["begin", "function", "type", "immutable", "let", "macro", "for", "while", "quote", "if", "else", "elseif", "try", "finally", "catch", "do"]; + var blockClosers = ["end", "else", "elseif", "catch", "finally"]; + var keywordList = ['if', 'else', 'elseif', 'while', 'for', 'begin', 'let', 'end', 'do', 'try', 'catch', 'finally', 'return', 'break', 'continue', 'global', 'local', 'const', 'export', 'import', 'importall', 'using', 'function', 'macro', 'module', 'baremodule', 'type', 'immutable', 'quote', 'typealias', 'abstract', 'bitstype']; + var builtinList = ['true', 'false', 'nothing', 'NaN', 'Inf']; + + //var stringPrefixes = new RegExp("^[br]?('|\")") + var stringPrefixes = /^(`|"{3}|([brv]?"))/; + var chars = wordRegexp(charsList, "'"); + var keywords = wordRegexp(keywordList); + var builtins = wordRegexp(builtinList); + var openers = wordRegexp(blockOpeners); + var closers = wordRegexp(blockClosers); + var macro = /^@[_A-Za-z][\w]*/; + var symbol = /^:[_A-Za-z\u00A1-\uFFFF][\w\u00A1-\uFFFF]*!*/; + var typeAnnotation = /^::[^,;"{()=$\s]+({[^}]*}+)*/; + + function inArray(state) { + var ch = currentScope(state); + if (ch == '[') { + return true; + } + return false; + } + + function currentScope(state) { + if (state.scopes.length == 0) { + return null; + } + return state.scopes[state.scopes.length - 1]; + } + + // tokenizers + function tokenBase(stream, state) { + // Handle multiline comments + if (stream.match(/^#=/, false)) { + state.tokenize = tokenComment; + return state.tokenize(stream, state); + } + + // Handle scope changes + var leavingExpr = state.leavingExpr; + if (stream.sol()) { + leavingExpr = false; + } + state.leavingExpr = false; + if (leavingExpr) { + if (stream.match(/^'+/)) { + return 'operator'; + } + } + + if (stream.match(/^\.{2,3}/)) { + return 'operator'; + } + + if (stream.eatSpace()) { + return null; + } + + var ch = stream.peek(); + + // Handle single line comments + if (ch === '#') { + stream.skipToEnd(); + return 'comment'; + } + + if (ch === '[') { + state.scopes.push('['); + } + + if (ch === '(') { + state.scopes.push('('); + } + + var scope = currentScope(state); + + if (scope == '[' && ch === ']') { + state.scopes.pop(); + state.leavingExpr = true; + } + + if (scope == '(' && ch === ')') { + state.scopes.pop(); + state.leavingExpr = true; + } + + var match; + if (!inArray(state) && (match=stream.match(openers, false))) { + state.scopes.push(match); + } + + if (!inArray(state) && stream.match(closers, false)) { + state.scopes.pop(); + } + + if (inArray(state)) { + if (state.lastToken == 'end' && stream.match(/^:/)) { + return 'operator'; + } + if (stream.match(/^end/)) { + return 'number'; + } + } + + if (stream.match(/^=>/)) { + return 'operator'; + } + + // Handle Number Literals + if (stream.match(/^[0-9\.]/, false)) { + var imMatcher = RegExp(/^im\b/); + var numberLiteral = false; + // Floats + if (stream.match(/^\d*\.(?!\.)\d*([Eef][\+\-]?\d+)?/i)) { numberLiteral = true; } + if (stream.match(/^\d+\.(?!\.)\d*/)) { numberLiteral = true; } + if (stream.match(/^\.\d+/)) { numberLiteral = true; } + if (stream.match(/^0x\.[0-9a-f]+p[\+\-]?\d+/i)) { numberLiteral = true; } + // Integers + if (stream.match(/^0x[0-9a-f]+/i)) { numberLiteral = true; } // Hex + if (stream.match(/^0b[01]+/i)) { numberLiteral = true; } // Binary + if (stream.match(/^0o[0-7]+/i)) { numberLiteral = true; } // Octal + if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) { numberLiteral = true; } // Decimal + // Zero by itself with no other piece of number. + if (stream.match(/^0(?![\dx])/i)) { numberLiteral = true; } + if (numberLiteral) { + // Integer literals may be "long" + stream.match(imMatcher); + state.leavingExpr = true; + return 'number'; + } + } + + if (stream.match(/^<:/)) { + return 'operator'; + } + + if (stream.match(typeAnnotation)) { + return 'builtin'; + } + + // Handle symbols + if (!leavingExpr && stream.match(symbol) || stream.match(/:\./)) { + return 'builtin'; + } + + // Handle parametric types + if (stream.match(/^{[^}]*}(?=\()/)) { + return 'builtin'; + } + + // Handle operators and Delimiters + if (stream.match(operators)) { + return 'operator'; + } + + // Handle Chars + if (stream.match(/^'/)) { + state.tokenize = tokenChar; + return state.tokenize(stream, state); + } + + // Handle Strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenStringFactory(stream.current()); + return state.tokenize(stream, state); + } + + if (stream.match(macro)) { + return 'meta'; + } + + if (stream.match(delimiters)) { + return null; + } + + if (stream.match(keywords)) { + return 'keyword'; + } + + if (stream.match(builtins)) { + return 'builtin'; + } + + var isDefinition = state.isDefinition || + state.lastToken == 'function' || + state.lastToken == 'macro' || + state.lastToken == 'type' || + state.lastToken == 'immutable'; + + if (stream.match(identifiers)) { + if (isDefinition) { + if (stream.peek() === '.') { + state.isDefinition = true; + return 'variable'; + } + state.isDefinition = false; + return 'def'; + } + if (stream.match(/^({[^}]*})*\(/, false)) { + return callOrDef(stream, state); + } + state.leavingExpr = true; + return 'variable'; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; + } + + function callOrDef(stream, state) { + var match = stream.match(/^(\(\s*)/); + if (match) { + if (state.firstParenPos < 0) + state.firstParenPos = state.scopes.length; + state.scopes.push('('); + state.charsAdvanced += match[1].length; + } + if (currentScope(state) == '(' && stream.match(/^\)/)) { + state.scopes.pop(); + state.charsAdvanced += 1; + if (state.scopes.length <= state.firstParenPos) { + var isDefinition = stream.match(/^\s*?=(?!=)/, false); + stream.backUp(state.charsAdvanced); + state.firstParenPos = -1; + state.charsAdvanced = 0; + if (isDefinition) + return 'def'; + return 'builtin'; + } + } + // Unfortunately javascript does not support multiline strings, so we have + // to undo anything done upto here if a function call or definition splits + // over two or more lines. + if (stream.match(/^$/g, false)) { + stream.backUp(state.charsAdvanced); + while (state.scopes.length > state.firstParenPos) + state.scopes.pop(); + state.firstParenPos = -1; + state.charsAdvanced = 0; + return 'builtin'; + } + state.charsAdvanced += stream.match(/^([^()]*)/)[1].length; + return callOrDef(stream, state); + } + + function tokenComment(stream, state) { + if (stream.match(/^#=/)) { + state.weakScopes++; + } + if (!stream.match(/.*?(?=(#=|=#))/)) { + stream.skipToEnd(); + } + if (stream.match(/^=#/)) { + state.weakScopes--; + if (state.weakScopes == 0) + state.tokenize = tokenBase; + } + return 'comment'; + } + + function tokenChar(stream, state) { + var isChar = false, match; + if (stream.match(chars)) { + isChar = true; + } else if (match = stream.match(/\\u([a-f0-9]{1,4})(?=')/i)) { + var value = parseInt(match[1], 16); + if (value <= 55295 || value >= 57344) { // (U+0,U+D7FF), (U+E000,U+FFFF) + isChar = true; + stream.next(); + } + } else if (match = stream.match(/\\U([A-Fa-f0-9]{5,8})(?=')/)) { + var value = parseInt(match[1], 16); + if (value <= 1114111) { // U+10FFFF + isChar = true; + stream.next(); + } + } + if (isChar) { + state.leavingExpr = true; + state.tokenize = tokenBase; + return 'string'; + } + if (!stream.match(/^[^']+(?=')/)) { stream.skipToEnd(); } + if (stream.match(/^'/)) { state.tokenize = tokenBase; } + return ERRORCLASS; + } + + function tokenStringFactory(delimiter) { + while ('bruv'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) { + delimiter = delimiter.substr(1); + } + var OUTCLASS = 'string'; + + function tokenString(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^"\\]/); + if (stream.eat('\\')) { + stream.next(); + } else if (stream.match(delimiter)) { + state.tokenize = tokenBase; + state.leavingExpr = true; + return OUTCLASS; + } else { + stream.eat(/["]/); + } + } + return OUTCLASS; + } + tokenString.isString = true; + return tokenString; + } + + var external = { + startState: function() { + return { + tokenize: tokenBase, + scopes: [], + weakScopes: 0, + lastToken: null, + leavingExpr: false, + isDefinition: false, + charsAdvanced: 0, + firstParenPos: -1 + }; + }, + + token: function(stream, state) { + var style = state.tokenize(stream, state); + var current = stream.current(); + + if (current && style) { + state.lastToken = current; + } + + // Handle '.' connected identifiers + if (current === '.') { + style = stream.match(identifiers, false) || stream.match(macro, false) || + stream.match(/\(/, false) ? 'operator' : ERRORCLASS; + } + return style; + }, + + indent: function(state, textAfter) { + var delta = 0; + if (textAfter == "]" || textAfter == ")" || textAfter == "end" || textAfter == "else" || textAfter == "elseif" || textAfter == "catch" || textAfter == "finally") { + delta = -1; + } + return (state.scopes.length + delta) * _conf.indentUnit; + }, + + electricInput: /(end|else(if)?|catch|finally)$/, + lineComment: "#", + fold: "indent" + }; + return external; +}); + + +CodeMirror.defineMIME("text/x-julia", "julia"); + +}); diff --git a/www/code/mode/livescript/index.html b/www/code/codemirror-5.13.2/mode/livescript/index.html similarity index 100% rename from www/code/mode/livescript/index.html rename to www/code/codemirror-5.13.2/mode/livescript/index.html diff --git a/www/code/mode/livescript/livescript.js b/www/code/codemirror-5.13.2/mode/livescript/livescript.js similarity index 100% rename from www/code/mode/livescript/livescript.js rename to www/code/codemirror-5.13.2/mode/livescript/livescript.js diff --git a/www/code/mode/lua/index.html b/www/code/codemirror-5.13.2/mode/lua/index.html similarity index 100% rename from www/code/mode/lua/index.html rename to www/code/codemirror-5.13.2/mode/lua/index.html diff --git a/www/code/mode/lua/lua.js b/www/code/codemirror-5.13.2/mode/lua/lua.js similarity index 100% rename from www/code/mode/lua/lua.js rename to www/code/codemirror-5.13.2/mode/lua/lua.js diff --git a/www/code/mode/markdown/index.html b/www/code/codemirror-5.13.2/mode/markdown/index.html similarity index 100% rename from www/code/mode/markdown/index.html rename to www/code/codemirror-5.13.2/mode/markdown/index.html diff --git a/www/code/mode/markdown/markdown.js b/www/code/codemirror-5.13.2/mode/markdown/markdown.js similarity index 83% rename from www/code/mode/markdown/markdown.js rename to www/code/codemirror-5.13.2/mode/markdown/markdown.js index 9d4036818..a6942b39c 100644 --- a/www/code/mode/markdown/markdown.js +++ b/www/code/codemirror-5.13.2/mode/markdown/markdown.js @@ -13,8 +13,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { - var htmlFound = CodeMirror.modes.hasOwnProperty("xml"); - var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: "xml", htmlMode: true} : "text/plain"); + var htmlMode = CodeMirror.getMode(cmCfg, "text/html"); + var htmlModeMissing = htmlMode.name == "null" function getMode(name) { if (CodeMirror.findModeByName) { @@ -51,24 +51,34 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { if (modeCfg.strikethrough === undefined) modeCfg.strikethrough = false; - var codeDepth = 0; - - var header = 'header' - , code = 'comment' - , quote = 'quote' - , list1 = 'variable-2' - , list2 = 'variable-3' - , list3 = 'keyword' - , hr = 'hr' - , image = 'tag' - , formatting = 'formatting' - , linkinline = 'link' - , linkemail = 'link' - , linktext = 'link' - , linkhref = 'string' - , em = 'em' - , strong = 'strong' - , strikethrough = 'strikethrough'; + // Allow token types to be overridden by user-provided token types. + if (modeCfg.tokenTypeOverrides === undefined) + modeCfg.tokenTypeOverrides = {}; + + var tokenTypes = { + header: "header", + code: "comment", + quote: "quote", + list1: "variable-2", + list2: "variable-3", + list3: "keyword", + hr: "hr", + image: "tag", + formatting: "formatting", + linkInline: "link", + linkEmail: "link", + linkText: "link", + linkHref: "string", + em: "em", + strong: "strong", + strikethrough: "strikethrough" + }; + + for (var tokenType in tokenTypes) { + if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) { + tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType]; + } + } var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/ , ulRE = /^[*\-+]\s+/ @@ -109,7 +119,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.quote = 0; // Reset state.indentedCode state.indentedCode = false; - if (!htmlFound && state.f == htmlBlock) { + if (htmlModeMissing && state.f == htmlBlock) { state.f = inlineNormal; state.block = blockNormal; } @@ -139,10 +149,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.list = null; } else if (state.indentation > 0) { state.list = null; - state.listDepth = Math.floor(state.indentation / 4); } else { // No longer a list state.list = false; - state.listDepth = 0; } } @@ -152,7 +160,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { if (prevLineIsIndentedCode || lineIsEmpty(state.prevLine)) { state.indentation -= 4; state.indentedCode = true; - return code; + return tokenTypes.code; } else { return null; } @@ -178,7 +186,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return switchInline(stream, state, footnoteLink); } else if (stream.match(hrRE, true)) { state.hr = true; - return hr; + return tokenTypes.hr; } else if ((lineIsEmpty(state.prevLine) || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) { var listType = null; if (stream.match(ulRE, true)) { @@ -189,7 +197,17 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } state.indentation = stream.column() + stream.current().length; state.list = true; - state.listDepth++; + + // While this list item's marker's indentation + // is less than the deepest list item's content's indentation, + // pop the deepest list item indentation off the stack. + while (state.listStack && stream.column() < state.listStack[state.listStack.length - 1]) { + state.listStack.pop(); + } + + // Add this list item's content's indentation to the stack + state.listStack.push(state.indentation); + if (modeCfg.taskLists && stream.match(taskListRE, false)) { state.taskList = true; } @@ -203,7 +221,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { if (state.localMode) state.localState = state.localMode.startState(); state.f = state.block = local; if (modeCfg.highlightFormatting) state.formatting = "code-block"; - state.code = true; + state.code = -1 return getType(state); } @@ -212,18 +230,21 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { function htmlBlock(stream, state) { var style = htmlMode.token(stream, state.htmlState); - if ((htmlFound && state.htmlState.tagStart === null && - (!state.htmlState.context && state.htmlState.tokenize.isInText)) || - (state.md_inside && stream.current().indexOf(">") > -1)) { - state.f = inlineNormal; - state.block = blockNormal; - state.htmlState = null; + if (!htmlModeMissing) { + var inner = CodeMirror.innerMode(htmlMode, state.htmlState) + if ((inner.mode.name == "xml" && inner.state.tagStart === null && + (!inner.state.context && inner.state.tokenize.isInText)) || + (state.md_inside && stream.current().indexOf(">") > -1)) { + state.f = inlineNormal; + state.block = blockNormal; + state.htmlState = null; + } } return style; } function local(stream, state) { - if (stream.sol() && state.fencedChars && stream.match(state.fencedChars, false)) { + if (state.fencedChars && stream.match(state.fencedChars, false)) { state.localMode = state.localState = null; state.f = state.block = leavingLocal; return null; @@ -231,7 +252,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return state.localMode.token(stream, state.localState); } else { stream.skipToEnd(); - return code; + return tokenTypes.code; } } @@ -241,9 +262,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.f = inlineNormal; state.fencedChars = null; if (modeCfg.highlightFormatting) state.formatting = "code-block"; - state.code = true; + state.code = 1 var returnType = getType(state); - state.code = false; + state.code = 0 return returnType; } @@ -252,22 +273,22 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { var styles = []; if (state.formatting) { - styles.push(formatting); + styles.push(tokenTypes.formatting); if (typeof state.formatting === "string") state.formatting = [state.formatting]; for (var i = 0; i < state.formatting.length; i++) { - styles.push(formatting + "-" + state.formatting[i]); + styles.push(tokenTypes.formatting + "-" + state.formatting[i]); if (state.formatting[i] === "header") { - styles.push(formatting + "-" + state.formatting[i] + "-" + state.header); + styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.header); } // Add `formatting-quote` and `formatting-quote-#` for blockquotes // Add `error` instead if the maximum blockquote nesting depth is passed if (state.formatting[i] === "quote") { if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { - styles.push(formatting + "-" + state.formatting[i] + "-" + state.quote); + styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.quote); } else { styles.push("error"); } @@ -285,38 +306,36 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } if (state.linkHref) { - styles.push(linkhref, "url"); + styles.push(tokenTypes.linkHref, "url"); } else { // Only apply inline styles to non-url text - if (state.strong) { styles.push(strong); } - if (state.em) { styles.push(em); } - if (state.strikethrough) { styles.push(strikethrough); } - - if (state.linkText) { styles.push(linktext); } - - if (state.code) { styles.push(code); } + if (state.strong) { styles.push(tokenTypes.strong); } + if (state.em) { styles.push(tokenTypes.em); } + if (state.strikethrough) { styles.push(tokenTypes.strikethrough); } + if (state.linkText) { styles.push(tokenTypes.linkText); } + if (state.code) { styles.push(tokenTypes.code); } } - if (state.header) { styles.push(header); styles.push(header + "-" + state.header); } + if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); } if (state.quote) { - styles.push(quote); + styles.push(tokenTypes.quote); // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { - styles.push(quote + "-" + state.quote); + styles.push(tokenTypes.quote + "-" + state.quote); } else { - styles.push(quote + "-" + modeCfg.maxBlockquoteDepth); + styles.push(tokenTypes.quote + "-" + modeCfg.maxBlockquoteDepth); } } if (state.list !== false) { - var listMod = (state.listDepth - 1) % 3; + var listMod = (state.listStack.length - 1) % 3; if (!listMod) { - styles.push(list1); + styles.push(tokenTypes.list1); } else if (listMod === 1) { - styles.push(list2); + styles.push(tokenTypes.list2); } else { - styles.push(list3); + styles.push(tokenTypes.list3); } } @@ -368,14 +387,6 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { var ch = stream.next(); - if (ch === '\\') { - stream.next(); - if (modeCfg.highlightFormatting) { - var type = getType(state); - return type ? type + " formatting-escape" : "formatting-escape"; - } - } - // Matches link titles present on next line if (state.linkTitle) { state.linkTitle = false; @@ -386,7 +397,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; if (stream.match(new RegExp(regex), true)) { - return linkhref; + return tokenTypes.linkHref; } } @@ -394,30 +405,36 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { if (ch === '`') { var previousFormatting = state.formatting; if (modeCfg.highlightFormatting) state.formatting = "code"; - var t = getType(state); - var before = stream.pos; stream.eatWhile('`'); - var difference = 1 + stream.pos - before; - if (!state.code) { - codeDepth = difference; - state.code = true; - return getType(state); + var count = stream.current().length + if (state.code == 0) { + state.code = count + return getType(state) + } else if (count == state.code) { // Must be exact + var t = getType(state) + state.code = 0 + return t } else { - if (difference === codeDepth) { // Must be exact - state.code = false; - return t; - } - state.formatting = previousFormatting; - return getType(state); + state.formatting = previousFormatting + return getType(state) } } else if (state.code) { return getType(state); } + if (ch === '\\') { + stream.next(); + if (modeCfg.highlightFormatting) { + var type = getType(state); + var formattingEscape = tokenTypes.formatting + "-escape"; + return type ? type + " " + formattingEscape : formattingEscape; + } + } + if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { stream.match(/\[[^\]]*\]/); state.inline = state.f = linkHref; - return image; + return tokenTypes.image; } if (ch === '[' && stream.match(/.*\](\(.*\)| ?\[.*\])/, false)) { @@ -443,7 +460,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } else { type = ""; } - return type + linkinline; + return type + tokenTypes.linkInline; } if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) { @@ -455,7 +472,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } else { type = ""; } - return type + linkemail; + return type + tokenTypes.linkEmail; } if (ch === '<' && stream.match(/^(!--|\w)/, false)) { @@ -564,12 +581,12 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } else { type = ""; } - return type + linkinline; + return type + tokenTypes.linkInline; } stream.match(/^[^>]+/, true); - return linkinline; + return tokenTypes.linkInline; } function linkHref(stream, state) { @@ -609,7 +626,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } function footnoteLink(stream, state) { - if (stream.match(/^[^\]]*\]:/, false)) { + if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) { state.f = footnoteLinkInside; stream.next(); // Consume [ if (modeCfg.highlightFormatting) state.formatting = "link"; @@ -628,9 +645,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return returnType; } - stream.match(/^[^\]]+/, true); + stream.match(/^([^\]\\]|\\.)+/, true); - return linktext; + return tokenTypes.linkText; } function footnoteUrl(stream, state) { @@ -647,7 +664,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); } state.f = state.inline = inlineNormal; - return linkhref + " url"; + return tokenTypes.linkHref + " url"; } var savedInlineRE = []; @@ -681,13 +698,14 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { linkText: false, linkHref: false, linkTitle: false, + code: 0, em: false, strong: false, header: 0, hr: false, taskList: false, list: false, - listDepth: 0, + listStack: [], quote: 0, trailingSpace: 0, trailingSpaceNewLine: false, @@ -701,7 +719,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { f: s.f, prevLine: s.prevLine, - thisLine: s.this, + thisLine: s.thisLine, block: s.block, htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState), @@ -722,7 +740,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { hr: s.hr, taskList: s.taskList, list: s.list, - listDepth: s.listDepth, + listStack: s.listStack.slice(0), quote: s.quote, indentedCode: s.indentedCode, trailingSpace: s.trailingSpace, @@ -762,11 +780,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.f = state.block; var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; - var difference = Math.floor((indentation - state.indentation) / 4) * 4; - if (difference > 4) difference = 4; - var adjustedIndentation = state.indentation + difference; - state.indentationDiff = adjustedIndentation - state.indentation; - state.indentation = adjustedIndentation; + state.indentationDiff = Math.min(indentation - state.indentation, 4); + state.indentation = state.indentation + state.indentationDiff; if (indentation > 0) return null; } return state.f(stream, state); diff --git a/www/code/mode/markdown/test.js b/www/code/codemirror-5.13.2/mode/markdown/test.js similarity index 83% rename from www/code/mode/markdown/test.js rename to www/code/codemirror-5.13.2/mode/markdown/test.js index 78b6c685a..a48d15310 100644 --- a/www/code/mode/markdown/test.js +++ b/www/code/codemirror-5.13.2/mode/markdown/test.js @@ -10,6 +10,35 @@ function AtxNoSpaceTest(name) { test.mode(name, modeAtxNoSpace, Array.prototype.slice.call(arguments, 1)); } var modeFenced = CodeMirror.getMode({tabSize: 4}, {name: "markdown", fencedCodeBlocks: true}); function FencedTest(name) { test.mode(name, modeFenced, Array.prototype.slice.call(arguments, 1)); } + var modeOverrideClasses = CodeMirror.getMode({tabsize: 4}, { + name: "markdown", + strikethrough: true, + tokenTypeOverrides: { + "header" : "override-header", + "code" : "override-code", + "quote" : "override-quote", + "list1" : "override-list1", + "list2" : "override-list2", + "list3" : "override-list3", + "hr" : "override-hr", + "image" : "override-image", + "linkInline" : "override-link-inline", + "linkEmail" : "override-link-email", + "linkText" : "override-link-text", + "linkHref" : "override-link-href", + "em" : "override-em", + "strong" : "override-strong", + "strikethrough" : "override-strikethrough" + }}); + function TokenTypeOverrideTest(name) { test.mode(name, modeOverrideClasses, Array.prototype.slice.call(arguments, 1)); } + var modeFormattingOverride = CodeMirror.getMode({tabsize: 4}, { + name: "markdown", + highlightFormatting: true, + tokenTypeOverrides: { + "formatting" : "override-formatting" + }}); + function FormatTokenTypeOverrideTest(name) { test.mode(name, modeFormattingOverride, Array.prototype.slice.call(arguments, 1)); } + FT("formatting_emAsterisk", "[em&formatting&formatting-em *][em foo][em&formatting&formatting-em *]"); @@ -423,6 +452,18 @@ "", "hello"); + MT("listCommonMarkIndentationCode", + "[variable-2 * Code blocks also affect]", + " [variable-3 * The next level starts where the contents start.]", + " [variable-3 * Anything less than that will keep the item on the same level.]", + " [variable-3 * Each list item can indent the first level further and further.]", + " [variable-3 * For the most part, this makes sense while writing a list.]", + " [keyword * This means two items with same indentation can be different levels.]", + " [keyword * Each level has an indent requirement that can change between items.]", + " [keyword * A list item that meets this will be part of the next level.]", + " [variable-3 * Otherwise, it will be part of the level where it does meet this.]", + " [variable-2 * World]"); + // Blockquote MT("blockquote", "[variable-2 * foo]", @@ -667,6 +708,15 @@ "[link [[foo]]:] [string&url http://example.com/]", "(bar\" hello"); + MT("labelEscape", + "[link [[foo \\]] ]]:] [string&url http://example.com/]"); + + MT("labelEscapeColon", + "[link [[foo \\]]: bar]]:] [string&url http://example.com/]"); + + MT("labelEscapeEnd", + "[[foo\\]]: http://example.com/"); + MT("linkWeb", "[link ] foo"); @@ -774,6 +824,76 @@ "\\", "[em *foo*]"); + // Class override tests + TokenTypeOverrideTest("overrideHeader1", + "[override-header&override-header-1 # Foo]"); + + TokenTypeOverrideTest("overrideHeader2", + "[override-header&override-header-2 ## Foo]"); + + TokenTypeOverrideTest("overrideHeader3", + "[override-header&override-header-3 ### Foo]"); + + TokenTypeOverrideTest("overrideHeader4", + "[override-header&override-header-4 #### Foo]"); + + TokenTypeOverrideTest("overrideHeader5", + "[override-header&override-header-5 ##### Foo]"); + + TokenTypeOverrideTest("overrideHeader6", + "[override-header&override-header-6 ###### Foo]"); + + TokenTypeOverrideTest("overrideCode", + "[override-code `foo`]"); + + TokenTypeOverrideTest("overrideCodeBlock", + "[override-code ```]", + "[override-code foo]", + "[override-code ```]"); + + TokenTypeOverrideTest("overrideQuote", + "[override-quote&override-quote-1 > foo]", + "[override-quote&override-quote-1 > bar]"); + + TokenTypeOverrideTest("overrideQuoteNested", + "[override-quote&override-quote-1 > foo]", + "[override-quote&override-quote-1 >][override-quote&override-quote-2 > bar]", + "[override-quote&override-quote-1 >][override-quote&override-quote-2 >][override-quote&override-quote-3 > baz]"); + + TokenTypeOverrideTest("overrideLists", + "[override-list1 - foo]", + "", + " [override-list2 + bar]", + "", + " [override-list3 * baz]", + "", + " [override-list1 1. qux]", + "", + " [override-list2 - quux]"); + + TokenTypeOverrideTest("overrideHr", + "[override-hr * * *]"); + + TokenTypeOverrideTest("overrideImage", + "[override-image ![[foo]]][override-link-href&url (http://example.com/)]") + + TokenTypeOverrideTest("overrideLinkText", + "[override-link-text [[foo]]][override-link-href&url (http://example.com)]"); + + TokenTypeOverrideTest("overrideLinkEmailAndInline", + "[override-link-email <][override-link-inline foo@example.com>]"); + + TokenTypeOverrideTest("overrideEm", + "[override-em *foo*]"); + + TokenTypeOverrideTest("overrideStrong", + "[override-strong **foo**]"); + + TokenTypeOverrideTest("overrideStrikethrough", + "[override-strikethrough ~~foo~~]"); + + FormatTokenTypeOverrideTest("overrideFormatting", + "[override-formatting-escape \\*]"); // Tests to make sure GFM-specific things aren't getting through diff --git a/www/code/mode/mathematica/index.html b/www/code/codemirror-5.13.2/mode/mathematica/index.html similarity index 100% rename from www/code/mode/mathematica/index.html rename to www/code/codemirror-5.13.2/mode/mathematica/index.html diff --git a/www/code/mode/mathematica/mathematica.js b/www/code/codemirror-5.13.2/mode/mathematica/mathematica.js similarity index 100% rename from www/code/mode/mathematica/mathematica.js rename to www/code/codemirror-5.13.2/mode/mathematica/mathematica.js diff --git a/www/code/mode/meta.js b/www/code/codemirror-5.13.2/mode/meta.js similarity index 92% rename from www/code/mode/meta.js rename to www/code/codemirror-5.13.2/mode/meta.js index 3a14dfc58..3cb9de21e 100644 --- a/www/code/mode/meta.js +++ b/www/code/codemirror-5.13.2/mode/meta.js @@ -21,13 +21,15 @@ {name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx"], alias: ["cpp"]}, {name: "Cobol", mime: "text/x-cobol", mode: "cobol", ext: ["cob", "cpy"]}, {name: "C#", mime: "text/x-csharp", mode: "clike", ext: ["cs"], alias: ["csharp"]}, - {name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj"]}, + {name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj", "cljc", "cljx"]}, + {name: "ClojureScript", mime: "text/x-clojurescript", mode: "clojure", ext: ["cljs"]}, {name: "Closure Stylesheets (GSS)", mime: "text/x-gss", mode: "css", ext: ["gss"]}, {name: "CMake", mime: "text/x-cmake", mode: "cmake", ext: ["cmake", "cmake.in"], file: /^CMakeLists.txt$/}, {name: "CoffeeScript", mime: "text/x-coffeescript", mode: "coffeescript", ext: ["coffee"], alias: ["coffee", "coffee-script"]}, {name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"], alias: ["lisp"]}, {name: "Cypher", mime: "application/x-cypher-query", mode: "cypher", ext: ["cyp", "cypher"]}, {name: "Cython", mime: "text/x-cython", mode: "python", ext: ["pyx", "pxd", "pxi"]}, + {name: "Crystal", mime: "text/x-crystal", mode: "crystal", ext: ["cr"]}, {name: "CSS", mime: "text/css", mode: "css", ext: ["css"]}, {name: "CQL", mime: "text/x-cassandra", mode: "sql", ext: ["cql"]}, {name: "D", mime: "text/x-d", mode: "d", ext: ["d"]}, @@ -39,12 +41,14 @@ {name: "Dylan", mime: "text/x-dylan", mode: "dylan", ext: ["dylan", "dyl", "intr"]}, {name: "EBNF", mime: "text/x-ebnf", mode: "ebnf"}, {name: "ECL", mime: "text/x-ecl", mode: "ecl", ext: ["ecl"]}, + {name: "edn", mime: "application/edn", mode: "clojure", ext: ["edn"]}, {name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel", ext: ["e"]}, {name: "Elm", mime: "text/x-elm", mode: "elm", ext: ["elm"]}, {name: "Embedded Javascript", mime: "application/x-ejs", mode: "htmlembedded", ext: ["ejs"]}, {name: "Embedded Ruby", mime: "application/x-erb", mode: "htmlembedded", ext: ["erb"]}, {name: "Erlang", mime: "text/x-erlang", mode: "erlang", ext: ["erl"]}, {name: "Factor", mime: "text/x-factor", mode: "factor", ext: ["factor"]}, + {name: "FCL", mime: "text/x-fcl", mode: "fcl"}, {name: "Forth", mime: "text/x-forth", mode: "forth", ext: ["forth", "fth", "4th"]}, {name: "Fortran", mime: "text/x-fortran", mode: "fortran", ext: ["f", "for", "f77", "f90"]}, {name: "F#", mime: "text/x-fsharp", mode: "mllike", ext: ["fs"], alias: ["fsharp"]}, @@ -52,9 +56,10 @@ {name: "Gherkin", mime: "text/x-feature", mode: "gherkin", ext: ["feature"]}, {name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm", file: /^(readme|contributing|history).md$/i}, {name: "Go", mime: "text/x-go", mode: "go", ext: ["go"]}, - {name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy"]}, + {name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy", "gradle"]}, {name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]}, {name: "Haskell", mime: "text/x-haskell", mode: "haskell", ext: ["hs"]}, + {name: "Haskell (Literate)", mime: "text/x-literate-haskell", mode: "haskell-literate", ext: ["lhs"]}, {name: "Haxe", mime: "text/x-haxe", mode: "haxe", ext: ["hx"]}, {name: "HXML", mime: "text/x-hxml", mode: "haxe", ext: ["hxml"]}, {name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded", ext: ["aspx"], alias: ["asp", "aspx"]}, @@ -68,9 +73,10 @@ mode: "javascript", ext: ["js"], alias: ["ecmascript", "js", "node"]}, {name: "JSON", mimes: ["application/json", "application/x-json"], mode: "javascript", ext: ["json", "map"], alias: ["json5"]}, {name: "JSON-LD", mime: "application/ld+json", mode: "javascript", ext: ["jsonld"], alias: ["jsonld"]}, + {name: "JSX", mime: "text/jsx", mode: "jsx", ext: ["jsx"]}, {name: "Jinja2", mime: "null", mode: "jinja2"}, {name: "Julia", mime: "text/x-julia", mode: "julia", ext: ["jl"]}, - {name: "Kotlin", mime: "text/x-kotlin", mode: "kotlin", ext: ["kt"]}, + {name: "Kotlin", mime: "text/x-kotlin", mode: "clike", ext: ["kt"]}, {name: "LESS", mime: "text/x-less", mode: "css", ext: ["less"]}, {name: "LiveScript", mime: "text/x-livescript", mode: "livescript", ext: ["ls"], alias: ["ls"]}, {name: "Lua", mime: "text/x-lua", mode: "lua", ext: ["lua"]}, @@ -79,10 +85,11 @@ {name: "MariaDB SQL", mime: "text/x-mariadb", mode: "sql"}, {name: "Mathematica", mime: "text/x-mathematica", mode: "mathematica", ext: ["m", "nb"]}, {name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]}, - {name: "MUMPS", mime: "text/x-mumps", mode: "mumps"}, + {name: "MUMPS", mime: "text/x-mumps", mode: "mumps", ext: ["mps"]}, {name: "MS SQL", mime: "text/x-mssql", mode: "sql"}, {name: "MySQL", mime: "text/x-mysql", mode: "sql"}, {name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx", file: /nginx.*\.conf$/i}, + {name: "NSIS", mime: "text/x-nsis", mode: "nsis", ext: ["nsh", "nsi"]}, {name: "NTriples", mime: "text/n-triples", mode: "ntriples", ext: ["nt"]}, {name: "Objective C", mime: "text/x-objectivec", mode: "clike", ext: ["m", "mm"]}, {name: "OCaml", mime: "text/x-ocaml", mode: "mllike", ext: ["ml", "mli", "mll", "mly"]}, @@ -96,6 +103,7 @@ {name: "Plain Text", mime: "text/plain", mode: "null", ext: ["txt", "text", "conf", "def", "list", "log"]}, {name: "PLSQL", mime: "text/x-plsql", mode: "sql", ext: ["pls"]}, {name: "Properties files", mime: "text/x-properties", mode: "properties", ext: ["properties", "ini", "in"], alias: ["ini", "properties"]}, + {name: "ProtoBuf", mime: "text/x-protobuf", mode: "protobuf", ext: ["proto"]}, {name: "Python", mime: "text/x-python", mode: "python", ext: ["py", "pyw"]}, {name: "Puppet", mime: "text/x-puppet", mode: "puppet", ext: ["pp"]}, {name: "Q", mime: "text/x-q", mode: "q", ext: ["q"]}, @@ -109,7 +117,7 @@ {name: "Scala", mime: "text/x-scala", mode: "clike", ext: ["scala"]}, {name: "Scheme", mime: "text/x-scheme", mode: "scheme", ext: ["scm", "ss"]}, {name: "SCSS", mime: "text/x-scss", mode: "css", ext: ["scss"]}, - {name: "Shell", mime: "text/x-sh", mode: "shell", ext: ["sh", "ksh", "bash"], alias: ["bash", "sh", "zsh"]}, + {name: "Shell", mime: "text/x-sh", mode: "shell", ext: ["sh", "ksh", "bash"], alias: ["bash", "sh", "zsh"], file: /^PKGBUILD$/}, {name: "Sieve", mime: "application/sieve", mode: "sieve", ext: ["siv", "sieve"]}, {name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim", ext: ["slim"]}, {name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk", ext: ["st"]}, @@ -121,7 +129,6 @@ {name: "SQL", mime: "text/x-sql", mode: "sql", ext: ["sql"]}, {name: "Squirrel", mime: "text/x-squirrel", mode: "clike", ext: ["nut"]}, {name: "Swift", mime: "text/x-swift", mode: "swift", ext: ["swift"]}, - {name: "MariaDB", mime: "text/x-mariadb", mode: "sql"}, {name: "sTeX", mime: "text/x-stex", mode: "stex"}, {name: "LaTeX", mime: "text/x-latex", mode: "stex", ext: ["text", "ltx"], alias: ["tex"]}, {name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog", ext: ["v"]}, @@ -131,7 +138,7 @@ {name: "Tiki wiki", mime: "text/tiki", mode: "tiki"}, {name: "TOML", mime: "text/x-toml", mode: "toml", ext: ["toml"]}, {name: "Tornado", mime: "text/x-tornado", mode: "tornado"}, - {name: "troff", mime: "troff", mode: "troff", ext: ["1", "2", "3", "4", "5", "6", "7", "8", "9"]}, + {name: "troff", mime: "text/troff", mode: "troff", ext: ["1", "2", "3", "4", "5", "6", "7", "8", "9"]}, {name: "TTCN", mime: "text/x-ttcn", mode: "ttcn", ext: ["ttcn", "ttcn3", "ttcnpp"]}, {name: "TTCN_CFG", mime: "text/x-ttcn-cfg", mode: "ttcn-cfg", ext: ["cfg"]}, {name: "Turtle", mime: "text/turtle", mode: "turtle", ext: ["ttl"]}, diff --git a/www/code/mode/mirc/index.html b/www/code/codemirror-5.13.2/mode/mirc/index.html similarity index 100% rename from www/code/mode/mirc/index.html rename to www/code/codemirror-5.13.2/mode/mirc/index.html diff --git a/www/code/mode/mirc/mirc.js b/www/code/codemirror-5.13.2/mode/mirc/mirc.js similarity index 100% rename from www/code/mode/mirc/mirc.js rename to www/code/codemirror-5.13.2/mode/mirc/mirc.js diff --git a/www/code/mode/mllike/index.html b/www/code/codemirror-5.13.2/mode/mllike/index.html similarity index 100% rename from www/code/mode/mllike/index.html rename to www/code/codemirror-5.13.2/mode/mllike/index.html diff --git a/www/code/mode/mllike/mllike.js b/www/code/codemirror-5.13.2/mode/mllike/mllike.js similarity index 100% rename from www/code/mode/mllike/mllike.js rename to www/code/codemirror-5.13.2/mode/mllike/mllike.js diff --git a/www/code/mode/modelica/index.html b/www/code/codemirror-5.13.2/mode/modelica/index.html similarity index 100% rename from www/code/mode/modelica/index.html rename to www/code/codemirror-5.13.2/mode/modelica/index.html diff --git a/www/code/mode/modelica/modelica.js b/www/code/codemirror-5.13.2/mode/modelica/modelica.js similarity index 100% rename from www/code/mode/modelica/modelica.js rename to www/code/codemirror-5.13.2/mode/modelica/modelica.js diff --git a/www/code/codemirror-5.13.2/mode/mscgen/index.html b/www/code/codemirror-5.13.2/mode/mscgen/index.html new file mode 100644 index 000000000..8c28ee620 --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/mscgen/index.html @@ -0,0 +1,151 @@ + + +CodeMirror: MscGen mode + + + + + + + + + +
    +

    MscGen mode

    + +
    + +

    Xù mode

    + +
    + +

    MsGenny mode

    +
    + +

    + Simple mode for highlighting MscGen and two derived sequence + chart languages. +

    + + + +

    MIME types defined: + text/x-mscgen + text/x-xu + text/x-msgenny +

    + +
    diff --git a/www/code/mode/mscgen/mscgen.js b/www/code/codemirror-5.13.2/mode/mscgen/mscgen.js similarity index 98% rename from www/code/mode/mscgen/mscgen.js rename to www/code/codemirror-5.13.2/mode/mscgen/mscgen.js index 3cf4eb089..d61b47065 100644 --- a/www/code/mode/mscgen/mscgen.js +++ b/www/code/codemirror-5.13.2/mode/mscgen/mscgen.js @@ -69,11 +69,11 @@ CodeMirror.defineMIME("text/x-msgenny", {name: "mscgen", language: "msgenny"}); function wordRegexpBoundary(pWords) { - return new RegExp("\\b((" + pWords.join(")|(") + "))\\b", "i"); + return new RegExp("\\b(" + pWords.join("|") + ")\\b", "i"); } function wordRegexp(pWords) { - return new RegExp("((" + pWords.join(")|(") + "))", "i"); + return new RegExp("(" + pWords.join("|") + ")", "i"); } function startStateFn() { diff --git a/www/code/mode/mscgen/mscgen_test.js b/www/code/codemirror-5.13.2/mode/mscgen/mscgen_test.js similarity index 100% rename from www/code/mode/mscgen/mscgen_test.js rename to www/code/codemirror-5.13.2/mode/mscgen/mscgen_test.js diff --git a/www/code/mode/mscgen/msgenny_test.js b/www/code/codemirror-5.13.2/mode/mscgen/msgenny_test.js similarity index 100% rename from www/code/mode/mscgen/msgenny_test.js rename to www/code/codemirror-5.13.2/mode/mscgen/msgenny_test.js diff --git a/www/code/mode/mscgen/xu_test.js b/www/code/codemirror-5.13.2/mode/mscgen/xu_test.js similarity index 100% rename from www/code/mode/mscgen/xu_test.js rename to www/code/codemirror-5.13.2/mode/mscgen/xu_test.js diff --git a/www/code/mode/mumps/index.html b/www/code/codemirror-5.13.2/mode/mumps/index.html similarity index 98% rename from www/code/mode/mumps/index.html rename to www/code/codemirror-5.13.2/mode/mumps/index.html index bd1f69aef..b1f92c213 100644 --- a/www/code/mode/mumps/index.html +++ b/www/code/codemirror-5.13.2/mode/mumps/index.html @@ -1,4 +1,4 @@ - + CodeMirror: MUMPS mode @@ -73,7 +73,7 @@ SET2() ;EF. Return error code (also called from XUSRB) IF '$LENGTH($PIECE(XUSER(1),U,2)) QUIT 21 ;p419, p434 Q 0 ; - + + + + + + + +
    +

    NSIS mode

    + + + + + + +

    MIME types defined: text/x-nsis.

    +
    \ No newline at end of file diff --git a/www/code/codemirror-5.13.2/mode/nsis/nsis.js b/www/code/codemirror-5.13.2/mode/nsis/nsis.js new file mode 100644 index 000000000..172207c5a --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/nsis/nsis.js @@ -0,0 +1,95 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Author: Jan T. Sott (http://github.com/idleberg) + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../../addon/mode/simple"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineSimpleMode("nsis",{ + start:[ + // Numbers + {regex: /(?:[+-]?)(?:0x[\d,a-f]+)|(?:0o[0-7]+)|(?:0b[0,1]+)|(?:\d+.?\d*)/, token: "number"}, + + // Strings + { regex: /"(?:[^\\"]|\\.)*"?/, token: "string" }, + { regex: /'(?:[^\\']|\\.)*'?/, token: "string" }, + { regex: /`(?:[^\\`]|\\.)*`?/, token: "string" }, + + // Compile Time Commands + {regex: /(?:\!(include|addincludedir|addplugindir|appendfile|cd|delfile|echo|error|execute|packhdr|finalize|getdllversion|system|tempfile|warning|verbose|define|undef|insertmacro|makensis|searchparse|searchreplace))\b/, token: "keyword"}, + + // Conditional Compilation + {regex: /(?:\!(if(?:n?def)?|ifmacron?def|macro))\b/, token: "keyword", indent: true}, + {regex: /(?:\!(else|endif|macroend))\b/, token: "keyword", dedent: true}, + + // Runtime Commands + {regex: /\b(?:Abort|AddBrandingImage|AddSize|AllowRootDirInstall|AllowSkipFiles|AutoCloseWindow|BGFont|BGGradient|BrandingText|BringToFront|Call|CallInstDLL|Caption|ChangeUI|CheckBitmap|ClearErrors|CompletedText|ComponentText|CopyFiles|CRCCheck|CreateDirectory|CreateFont|CreateShortCut|Delete|DeleteINISec|DeleteINIStr|DeleteRegKey|DeleteRegValue|DetailPrint|DetailsButtonText|DirText|DirVar|DirVerify|EnableWindow|EnumRegKey|EnumRegValue|Exch|Exec|ExecShell|ExecWait|ExpandEnvStrings|File|FileBufSize|FileClose|FileErrorText|FileOpen|FileRead|FileReadByte|FileReadUTF16LE|FileReadWord|FileWriteUTF16LE|FileSeek|FileWrite|FileWriteByte|FileWriteWord|FindClose|FindFirst|FindNext|FindWindow|FlushINI|GetCurInstType|GetCurrentAddress|GetDlgItem|GetDLLVersion|GetDLLVersionLocal|GetErrorLevel|GetFileTime|GetFileTimeLocal|GetFullPathName|GetFunctionAddress|GetInstDirError|GetLabelAddress|GetTempFileName|Goto|HideWindow|Icon|IfAbort|IfErrors|IfFileExists|IfRebootFlag|IfSilent|InitPluginsDir|InstallButtonText|InstallColors|InstallDir|InstallDirRegKey|InstProgressFlags|InstType|InstTypeGetText|InstTypeSetText|IntCmp|IntCmpU|IntFmt|IntOp|IsWindow|LangString|LicenseBkColor|LicenseData|LicenseForceSelection|LicenseLangString|LicenseText|LoadLanguageFile|LockWindow|LogSet|LogText|ManifestDPIAware|ManifestSupportedOS|MessageBox|MiscButtonText|Name|Nop|OutFile|Page|PageCallbacks|Pop|Push|Quit|ReadEnvStr|ReadINIStr|ReadRegDWORD|ReadRegStr|Reboot|RegDLL|Rename|RequestExecutionLevel|ReserveFile|Return|RMDir|SearchPath|SectionGetFlags|SectionGetInstTypes|SectionGetSize|SectionGetText|SectionIn|SectionSetFlags|SectionSetInstTypes|SectionSetSize|SectionSetText|SendMessage|SetAutoClose|SetBrandingImage|SetCompress|SetCompressor|SetCompressorDictSize|SetCtlColors|SetCurInstType|SetDatablockOptimize|SetDateSave|SetDetailsPrint|SetDetailsView|SetErrorLevel|SetErrors|SetFileAttributes|SetFont|SetOutPath|SetOverwrite|SetPluginUnload|SetRebootFlag|SetRegView|SetShellVarContext|SetSilent|ShowInstDetails|ShowUninstDetails|ShowWindow|SilentInstall|SilentUnInstall|Sleep|SpaceTexts|StrCmp|StrCmpS|StrCpy|StrLen|SubCaption|Unicode|UninstallButtonText|UninstallCaption|UninstallIcon|UninstallSubCaption|UninstallText|UninstPage|UnRegDLL|Var|VIAddVersionKey|VIFileVersion|VIProductVersion|WindowIcon|WriteINIStr|WriteRegBin|WriteRegDWORD|WriteRegExpandStr|WriteRegStr|WriteUninstaller|XPStyle)\b/, token: "keyword"}, + {regex: /\b(?:Function|PageEx|Section(?:Group)?)\b/, token: "keyword", indent: true}, + {regex: /\b(?:(Function|PageEx|Section(?:Group)?)End)\b/, token: "keyword", dedent: true}, + + // Command Options + {regex: /\b(?:ARCHIVE|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_TEMPORARY|HIDDEN|HKCC|HKCR|HKCU|HKDD|HKEY_CLASSES_ROOT|HKEY_CURRENT_CONFIG|HKEY_CURRENT_USER|HKEY_DYN_DATA|HKEY_LOCAL_MACHINE|HKEY_PERFORMANCE_DATA|HKEY_USERS|HKLM|HKPD|HKU|IDABORT|IDCANCEL|IDD_DIR|IDD_INST|IDD_INSTFILES|IDD_LICENSE|IDD_SELCOM|IDD_UNINST|IDD_VERIFY|IDIGNORE|IDNO|IDOK|IDRETRY|IDYES|MB_ABORTRETRYIGNORE|MB_DEFBUTTON1|MB_DEFBUTTON2|MB_DEFBUTTON3|MB_DEFBUTTON4|MB_ICONEXCLAMATION|MB_ICONINFORMATION|MB_ICONQUESTION|MB_ICONSTOP|MB_OK|MB_OKCANCEL|MB_RETRYCANCEL|MB_RIGHT|MB_RTLREADING|MB_SETFOREGROUND|MB_TOPMOST|MB_USERICON|MB_YESNO|MB_YESNOCANCEL|NORMAL|OFFLINE|READONLY|SHCTX|SHELL_CONTEXT|SW_HIDE|SW_SHOWDEFAULT|SW_SHOWMAXIMIZED|SW_SHOWMINIMIZED|SW_SHOWNORMAL|SYSTEM|TEMPORARY)\b/, token: "atom"}, + {regex: /\b(?:admin|all|auto|both|bottom|bzip2|components|current|custom|directory|force|hide|highest|ifdiff|ifnewer|instfiles|lastused|leave|left|license|listonly|lzma|nevershow|none|normal|notset|right|show|silent|silentlog|textonly|top|try|un\.components|un\.custom|un\.directory|un\.instfiles|un\.license|uninstConfirm|user|Win10|Win7|Win8|WinVista|zlib)\b/, token: "builtin"}, + + // LogicLib.nsh + {regex: /\$\{(?:And(?:If(?:Not)?|Unless)|Break|Case(?:Else)?|Continue|Default|Do(?:Until|While)?|Else(?:If(?:Not)?|Unless)?|End(?:If|Select|Switch)|Exit(?:Do|For|While)|For(?:Each)?|If(?:Cmd|Not(?:Then)?|Then)?|Loop(?:Until|While)?|Or(?:If(?:Not)?|Unless)|Select|Switch|Unless|While)\}/, token: "variable-2", indent: true}, + + // FileFunc.nsh + {regex: /\$\{(?:BannerTrimPath|DirState|DriveSpace|Get(BaseName|Drives|ExeName|ExePath|FileAttributes|FileExt|FileName|FileVersion|Options|OptionsS|Parameters|Parent|Root|Size|Time)|Locate|RefreshShellIcons)\}/, token: "variable-2", dedent: true}, + + // Memento.nsh + {regex: /\$\{(?:Memento(?:Section(?:Done|End|Restore|Save)?|UnselectedSection))\}/, token: "variable-2", dedent: true}, + + // TextFunc.nsh + {regex: /\$\{(?:Config(?:Read|ReadS|Write|WriteS)|File(?:Join|ReadFromEnd|Recode)|Line(?:Find|Read|Sum)|Text(?:Compare|CompareS)|TrimNewLines)\}/, token: "variable-2", dedent: true}, + + // WinVer.nsh + {regex: /\$\{(?:(?:At(?:Least|Most)|Is)(?:ServicePack|Win(?:7|8|10|95|98|200(?:0|3|8(?:R2)?)|ME|NT4|Vista|XP))|Is(?:NT|Server))\}/, token: "variable", dedent: true}, + + // WordFunc.nsh + {regex: /\$\{(?:StrFilterS?|Version(?:Compare|Convert)|Word(?:AddS?|Find(?:(?:2|3)X)?S?|InsertS?|ReplaceS?))\}/, token: "variable-2", dedent: true}, + + // x64.nsh + {regex: /\$\{(?:RunningX64)\}/, token: "variable", dedent: true}, + {regex: /\$\{(?:Disable|Enable)X64FSRedirection\}/, token: "variable-2", dedent: true}, + + // Line Comment + {regex: /(#|;).*/, token: "comment"}, + + // Block Comment + {regex: /\/\*/, token: "comment", next: "comment"}, + + // Operator + {regex: /[-+\/*=<>!]+/, token: "operator"}, + + // Variable + {regex: /\$[\w]+/, token: "variable"}, + + // Constant + {regex: /\${[\w]+}/,token: "variable-2"}, + + // Language String + {regex: /\$\([\w]+\)/,token: "variable-3"} + ], + comment: [ + {regex: /.*?\*\//, token: "comment", next: "start"}, + {regex: /.*/, token: "comment"} + ], + meta: { + electricInput: /^\s*((Function|PageEx|Section|Section(Group)?)End|(\!(endif|macroend))|\$\{(End(If|Unless|While)|Loop(Until)|Next)\})$/, + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: ["#", ";"] + } +}); + +CodeMirror.defineMIME("text/x-nsis", "nsis"); +}); diff --git a/www/code/mode/ntriples/index.html b/www/code/codemirror-5.13.2/mode/ntriples/index.html similarity index 100% rename from www/code/mode/ntriples/index.html rename to www/code/codemirror-5.13.2/mode/ntriples/index.html diff --git a/www/code/mode/ntriples/ntriples.js b/www/code/codemirror-5.13.2/mode/ntriples/ntriples.js similarity index 100% rename from www/code/mode/ntriples/ntriples.js rename to www/code/codemirror-5.13.2/mode/ntriples/ntriples.js diff --git a/www/code/mode/octave/index.html b/www/code/codemirror-5.13.2/mode/octave/index.html similarity index 100% rename from www/code/mode/octave/index.html rename to www/code/codemirror-5.13.2/mode/octave/index.html diff --git a/www/code/mode/octave/octave.js b/www/code/codemirror-5.13.2/mode/octave/octave.js similarity index 100% rename from www/code/mode/octave/octave.js rename to www/code/codemirror-5.13.2/mode/octave/octave.js diff --git a/www/code/mode/oz/index.html b/www/code/codemirror-5.13.2/mode/oz/index.html similarity index 100% rename from www/code/mode/oz/index.html rename to www/code/codemirror-5.13.2/mode/oz/index.html diff --git a/www/code/mode/oz/oz.js b/www/code/codemirror-5.13.2/mode/oz/oz.js similarity index 100% rename from www/code/mode/oz/oz.js rename to www/code/codemirror-5.13.2/mode/oz/oz.js diff --git a/www/code/mode/pascal/index.html b/www/code/codemirror-5.13.2/mode/pascal/index.html similarity index 100% rename from www/code/mode/pascal/index.html rename to www/code/codemirror-5.13.2/mode/pascal/index.html diff --git a/www/code/mode/pascal/pascal.js b/www/code/codemirror-5.13.2/mode/pascal/pascal.js similarity index 100% rename from www/code/mode/pascal/pascal.js rename to www/code/codemirror-5.13.2/mode/pascal/pascal.js diff --git a/www/code/mode/pegjs/index.html b/www/code/codemirror-5.13.2/mode/pegjs/index.html similarity index 100% rename from www/code/mode/pegjs/index.html rename to www/code/codemirror-5.13.2/mode/pegjs/index.html diff --git a/www/code/mode/pegjs/pegjs.js b/www/code/codemirror-5.13.2/mode/pegjs/pegjs.js similarity index 100% rename from www/code/mode/pegjs/pegjs.js rename to www/code/codemirror-5.13.2/mode/pegjs/pegjs.js diff --git a/www/code/mode/perl/index.html b/www/code/codemirror-5.13.2/mode/perl/index.html similarity index 100% rename from www/code/mode/perl/index.html rename to www/code/codemirror-5.13.2/mode/perl/index.html diff --git a/www/code/mode/perl/perl.js b/www/code/codemirror-5.13.2/mode/perl/perl.js similarity index 100% rename from www/code/mode/perl/perl.js rename to www/code/codemirror-5.13.2/mode/perl/perl.js diff --git a/www/code/mode/php/index.html b/www/code/codemirror-5.13.2/mode/php/index.html similarity index 100% rename from www/code/mode/php/index.html rename to www/code/codemirror-5.13.2/mode/php/index.html diff --git a/www/code/mode/php/php.js b/www/code/codemirror-5.13.2/mode/php/php.js similarity index 100% rename from www/code/mode/php/php.js rename to www/code/codemirror-5.13.2/mode/php/php.js diff --git a/www/code/mode/php/test.js b/www/code/codemirror-5.13.2/mode/php/test.js similarity index 100% rename from www/code/mode/php/test.js rename to www/code/codemirror-5.13.2/mode/php/test.js diff --git a/www/code/mode/pig/index.html b/www/code/codemirror-5.13.2/mode/pig/index.html similarity index 98% rename from www/code/mode/pig/index.html rename to www/code/codemirror-5.13.2/mode/pig/index.html index 984c9a183..ea77f7044 100644 --- a/www/code/mode/pig/index.html +++ b/www/code/codemirror-5.13.2/mode/pig/index.html @@ -1,5 +1,4 @@ - - + CodeMirror: Pig Latin mode @@ -51,5 +50,4 @@ STORE c INTO "\path\to\output";

    MIME type defined: text/x-pig (PIG code) - diff --git a/www/code/mode/pig/pig.js b/www/code/codemirror-5.13.2/mode/pig/pig.js similarity index 100% rename from www/code/mode/pig/pig.js rename to www/code/codemirror-5.13.2/mode/pig/pig.js diff --git a/www/code/mode/properties/index.html b/www/code/codemirror-5.13.2/mode/properties/index.html similarity index 100% rename from www/code/mode/properties/index.html rename to www/code/codemirror-5.13.2/mode/properties/index.html diff --git a/www/code/mode/properties/properties.js b/www/code/codemirror-5.13.2/mode/properties/properties.js similarity index 100% rename from www/code/mode/properties/properties.js rename to www/code/codemirror-5.13.2/mode/properties/properties.js diff --git a/www/code/codemirror-5.13.2/mode/protobuf/index.html b/www/code/codemirror-5.13.2/mode/protobuf/index.html new file mode 100644 index 000000000..cfe7b9dcd --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/protobuf/index.html @@ -0,0 +1,64 @@ + + +CodeMirror: ProtoBuf mode + + + + + + + +

    + +
    +

    ProtoBuf mode

    +
    + + +

    MIME types defined: text/x-protobuf.

    + +
    diff --git a/www/code/codemirror-5.13.2/mode/protobuf/protobuf.js b/www/code/codemirror-5.13.2/mode/protobuf/protobuf.js new file mode 100644 index 000000000..bcae276e8 --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/protobuf/protobuf.js @@ -0,0 +1,68 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(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"; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); + }; + + var keywordArray = [ + "package", "message", "import", "syntax", + "required", "optional", "repeated", "reserved", "default", "extensions", "packed", + "bool", "bytes", "double", "enum", "float", "string", + "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64" + ]; + var keywords = wordRegexp(keywordArray); + + CodeMirror.registerHelper("hintWords", "protobuf", keywordArray); + + var identifiers = new RegExp("^[_A-Za-z\xa1-\uffff][_A-Za-z0-9\xa1-\uffff]*"); + + function tokenBase(stream) { + // whitespaces + if (stream.eatSpace()) return null; + + // Handle one line Comments + if (stream.match("//")) { + stream.skipToEnd(); + return "comment"; + } + + // Handle Number Literals + if (stream.match(/^[0-9\.+-]/, false)) { + if (stream.match(/^[+-]?0x[0-9a-fA-F]+/)) + return "number"; + if (stream.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?/)) + return "number"; + if (stream.match(/^[+-]?\d+([EeDd][+-]?\d+)?/)) + return "number"; + } + + // Handle Strings + if (stream.match(/^"([^"]|(""))*"/)) { return "string"; } + if (stream.match(/^'([^']|(''))*'/)) { return "string"; } + + // Handle words + if (stream.match(keywords)) { return "keyword"; } + if (stream.match(identifiers)) { return "variable"; } ; + + // Handle non-detected items + stream.next(); + return null; + }; + + CodeMirror.defineMode("protobuf", function() { + return {token: tokenBase}; + }); + + CodeMirror.defineMIME("text/x-protobuf", "protobuf"); +}); diff --git a/www/code/mode/puppet/index.html b/www/code/codemirror-5.13.2/mode/puppet/index.html similarity index 100% rename from www/code/mode/puppet/index.html rename to www/code/codemirror-5.13.2/mode/puppet/index.html diff --git a/www/code/mode/puppet/puppet.js b/www/code/codemirror-5.13.2/mode/puppet/puppet.js similarity index 100% rename from www/code/mode/puppet/puppet.js rename to www/code/codemirror-5.13.2/mode/puppet/puppet.js diff --git a/www/code/mode/python/index.html b/www/code/codemirror-5.13.2/mode/python/index.html similarity index 100% rename from www/code/mode/python/index.html rename to www/code/codemirror-5.13.2/mode/python/index.html diff --git a/www/code/mode/python/python.js b/www/code/codemirror-5.13.2/mode/python/python.js similarity index 78% rename from www/code/mode/python/python.js rename to www/code/codemirror-5.13.2/mode/python/python.js index e5a09719b..196b026c6 100644 --- a/www/code/mode/python/python.js +++ b/www/code/codemirror-5.13.2/mode/python/python.js @@ -48,29 +48,29 @@ CodeMirror.defineMode("python", function(conf, parserConf) { var ERRORCLASS = "error"; - var singleDelimiters = parserConf.singleDelimiters || new RegExp("^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]"); - var doubleOperators = parserConf.doubleOperators || new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); - var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); - var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); + var singleDelimiters = parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.]/; + var doubleOperators = parserConf.doubleOperators || /^([!<>]==|<>|<<|>>|\/\/|\*\*)/; + var doubleDelimiters = parserConf.doubleDelimiters || /^(\+=|\-=|\*=|%=|\/=|&=|\|=|\^=)/; + var tripleDelimiters = parserConf.tripleDelimiters || /^(\/\/=|>>=|<<=|\*\*=)/; - if (parserConf.version && parseInt(parserConf.version, 10) == 3){ + if (parserConf.version && parseInt(parserConf.version, 10) == 3) { // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator - var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!@]"); - var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*"); + var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!@]/; + var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/; } else { - var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!]"); - var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); + var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!]/; + var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/; } var hangingIndent = parserConf.hangingIndent || conf.indentUnit; var myKeywords = commonKeywords, myBuiltins = commonBuiltins; - if(parserConf.extra_keywords != undefined){ + if (parserConf.extra_keywords != undefined) myKeywords = myKeywords.concat(parserConf.extra_keywords); - } - if(parserConf.extra_builtins != undefined){ + + if (parserConf.extra_builtins != undefined) myBuiltins = myBuiltins.concat(parserConf.extra_builtins); - } + if (parserConf.version && parseInt(parserConf.version, 10) == 3) { myKeywords = myKeywords.concat(py3.keywords); myBuiltins = myBuiltins.concat(py3.builtins); @@ -85,13 +85,14 @@ // tokenizers function tokenBase(stream, state) { + if (stream.sol()) state.indent = stream.indentation() // Handle scope changes if (stream.sol() && top(state).type == "py") { var scopeOffset = top(state).offset; if (stream.eatSpace()) { var lineOffset = stream.indentation(); if (lineOffset > scopeOffset) - pushScope(stream, state, "py"); + pushPyScope(state); else if (lineOffset < scopeOffset && dedent(stream, state)) state.errorToken = true; return null; @@ -160,13 +161,16 @@ // Handle operators and Delimiters if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) - return null; + return "punctuation"; if (stream.match(doubleOperators) || stream.match(singleOperators)) return "operator"; if (stream.match(singleDelimiters)) - return null; + return "punctuation"; + + if (state.lastToken == "." && stream.match(identifiers)) + return "property"; if (stream.match(keywords) || stream.match(wordOperators)) return "keyword"; @@ -221,16 +225,18 @@ return tokenString; } - function pushScope(stream, state, type) { - var offset = 0, align = null; - if (type == "py") { - while (top(state).type != "py") - state.scopes.pop(); - } - offset = top(state).offset + (type == "py" ? conf.indentUnit : hangingIndent); - if (type != "py" && !stream.match(/^(\s|#.*)*$/, false)) - align = stream.column() + 1; - state.scopes.push({offset: offset, type: type, align: align}); + function pushPyScope(state) { + while (top(state).type != "py") state.scopes.pop() + state.scopes.push({offset: top(state).offset + conf.indentUnit, + type: "py", + align: null}) + } + + function pushBracketScope(stream, state, type) { + var align = stream.match(/^([\s\[\{\(]|#.*)*$/, false) ? null : stream.column() + 1 + state.scopes.push({offset: state.indent + hangingIndent, + type: type, + align: align}) } function dedent(stream, state) { @@ -246,28 +252,16 @@ 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; - } - // Handle decorators - if (current == "@"){ - if(parserConf.version && parseInt(parserConf.version, 10) == 3){ - return stream.match(identifiers, false) ? "meta" : "operator"; - } else { - return stream.match(identifiers, false) ? "meta" : ERRORCLASS; - } + if (current == "@") { + if (parserConf.version && parseInt(parserConf.version, 10) == 3) + return stream.match(identifiers, false) ? "meta" : "operator"; + else + return stream.match(identifiers, false) ? "meta" : ERRORCLASS; } if ((style == "variable" || style == "builtin") - && state.lastStyle == "meta") + && state.lastToken == "meta") style = "meta"; // Handle scope changes. @@ -276,15 +270,15 @@ if (current == "lambda") state.lambda = true; if (current == ":" && !state.lambda && top(state).type == "py") - pushScope(stream, state, "py"); + pushPyScope(state); var delimiter_index = current.length == 1 ? "[({".indexOf(current) : -1; if (delimiter_index != -1) - pushScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); + pushBracketScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); delimiter_index = "])}".indexOf(current); if (delimiter_index != -1) { - if (top(state).type == current) state.scopes.pop(); + if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent else return ERRORCLASS; } if (state.dedent > 0 && stream.eol() && top(state).type == "py") { @@ -300,7 +294,7 @@ return { tokenize: tokenBase, scopes: [{offset: basecolumn || 0, type: "py", align: null}], - lastStyle: null, + indent: basecolumn || 0, lastToken: null, lambda: false, dedent: 0 @@ -312,11 +306,9 @@ if (addErr) state.errorToken = false; var style = tokenLexer(stream, state); - state.lastStyle = style; - - var current = stream.current(); - if (current && style) - state.lastToken = current; + if (style && style != "comment") + state.lastToken = (style == "keyword" || style == "punctuation") ? stream.current() : style; + if (style == "punctuation") style = null; if (stream.eol() && state.lambda) state.lambda = false; @@ -327,16 +319,14 @@ if (state.tokenize != tokenBase) return state.tokenize.isString ? CodeMirror.Pass : 0; - var scope = top(state); - var closing = textAfter && textAfter.charAt(0) == scope.type; + var scope = top(state), closing = scope.type == textAfter.charAt(0) if (scope.align != null) - return scope.align - (closing ? 1 : 0); - else if (closing && state.scopes.length > 1) - return state.scopes[state.scopes.length - 2].offset; + return scope.align - (closing ? 1 : 0) else - return scope.offset; + return scope.offset - (closing ? hangingIndent : 0) }, + electricInput: /^\s*[\}\]\)]$/, closeBrackets: {triples: "'\""}, lineComment: "#", fold: "indent" diff --git a/www/code/mode/q/index.html b/www/code/codemirror-5.13.2/mode/q/index.html similarity index 100% rename from www/code/mode/q/index.html rename to www/code/codemirror-5.13.2/mode/q/index.html diff --git a/www/code/mode/q/q.js b/www/code/codemirror-5.13.2/mode/q/q.js similarity index 100% rename from www/code/mode/q/q.js rename to www/code/codemirror-5.13.2/mode/q/q.js diff --git a/www/code/mode/r/index.html b/www/code/codemirror-5.13.2/mode/r/index.html similarity index 100% rename from www/code/mode/r/index.html rename to www/code/codemirror-5.13.2/mode/r/index.html diff --git a/www/code/mode/r/r.js b/www/code/codemirror-5.13.2/mode/r/r.js similarity index 99% rename from www/code/mode/r/r.js rename to www/code/codemirror-5.13.2/mode/r/r.js index 1ab4a9565..d41d1c54c 100644 --- a/www/code/mode/r/r.js +++ b/www/code/codemirror-5.13.2/mode/r/r.js @@ -11,6 +11,8 @@ })(function(CodeMirror) { "use strict"; +CodeMirror.registerHelper("wordChars", "r", /[\w.]/); + CodeMirror.defineMode("r", function(config) { function wordObj(str) { var words = str.split(" "), res = {}; diff --git a/www/code/mode/rpm/changes/index.html b/www/code/codemirror-5.13.2/mode/rpm/changes/index.html similarity index 100% rename from www/code/mode/rpm/changes/index.html rename to www/code/codemirror-5.13.2/mode/rpm/changes/index.html diff --git a/www/code/mode/rpm/index.html b/www/code/codemirror-5.13.2/mode/rpm/index.html similarity index 100% rename from www/code/mode/rpm/index.html rename to www/code/codemirror-5.13.2/mode/rpm/index.html diff --git a/www/code/mode/rpm/rpm.js b/www/code/codemirror-5.13.2/mode/rpm/rpm.js similarity index 80% rename from www/code/mode/rpm/rpm.js rename to www/code/codemirror-5.13.2/mode/rpm/rpm.js index 3bb7cd2f6..87cde591a 100644 --- a/www/code/mode/rpm/rpm.js +++ b/www/code/codemirror-5.13.2/mode/rpm/rpm.js @@ -34,10 +34,10 @@ CodeMirror.defineMIME("text/x-rpm-changes", "rpm-changes"); // Quick and dirty spec file highlighting CodeMirror.defineMode("rpm-spec", function() { - var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; + var arch = /^(i386|i586|i686|x86_64|ppc64le|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; - var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/; - var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/; + var preamble = /^[a-zA-Z0-9()]+:/; + var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pretrans|posttrans|pre|post|triggerin|triggerun|verifyscript|check|triggerpostun|triggerprein|trigger)/; var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros var control_flow_simple = /^%(else|endif)/; // rpm control flow macros var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros @@ -55,8 +55,8 @@ CodeMirror.defineMode("rpm-spec", function() { if (ch == "#") { stream.skipToEnd(); return "comment"; } if (stream.sol()) { - if (stream.match(preamble)) { return "preamble"; } - if (stream.match(section)) { return "section"; } + if (stream.match(preamble)) { return "header"; } + if (stream.match(section)) { return "atom"; } } if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT' @@ -73,21 +73,29 @@ CodeMirror.defineMode("rpm-spec", function() { if (stream.eol()) { state.controlFlow = false; } } - if (stream.match(arch)) { return "number"; } + if (stream.match(arch)) { + if (stream.eol()) { state.controlFlow = false; } + return "number"; + } // Macros like '%make_install' or '%attr(0775,root,root)' if (stream.match(/^%[\w]+/)) { if (stream.match(/^\(/)) { state.macroParameters = true; } - return "macro"; + return "keyword"; } if (state.macroParameters) { if (stream.match(/^\d+/)) { return "number";} if (stream.match(/^\)/)) { state.macroParameters = false; - return "macro"; + return "keyword"; } } - if (stream.match(/^%\{\??[\w \-]+\}/)) { return "macro"; } // Macros like '%{defined fedora}' + + // Macros like '%{defined fedora}' + if (stream.match(/^%\{\??[\w \-\:\!]+\}/)) { + if (stream.eol()) { state.controlFlow = false; } + return "def"; + } //TODO: Include bash script sub-parser (CodeMirror supports that) stream.next(); diff --git a/www/code/mode/rst/index.html b/www/code/codemirror-5.13.2/mode/rst/index.html similarity index 100% rename from www/code/mode/rst/index.html rename to www/code/codemirror-5.13.2/mode/rst/index.html diff --git a/www/code/mode/rst/rst.js b/www/code/codemirror-5.13.2/mode/rst/rst.js similarity index 100% rename from www/code/mode/rst/rst.js rename to www/code/codemirror-5.13.2/mode/rst/rst.js diff --git a/www/code/mode/ruby/index.html b/www/code/codemirror-5.13.2/mode/ruby/index.html similarity index 100% rename from www/code/mode/ruby/index.html rename to www/code/codemirror-5.13.2/mode/ruby/index.html diff --git a/www/code/mode/ruby/ruby.js b/www/code/codemirror-5.13.2/mode/ruby/ruby.js similarity index 99% rename from www/code/mode/ruby/ruby.js rename to www/code/codemirror-5.13.2/mode/ruby/ruby.js index 089a971d4..10cad8d9f 100644 --- a/www/code/mode/ruby/ruby.js +++ b/www/code/codemirror-5.13.2/mode/ruby/ruby.js @@ -275,7 +275,7 @@ CodeMirror.defineMode("ruby", function(config) { (state.continuedLine ? config.indentUnit : 0); }, - electricChars: "}de", // enD and rescuE + electricInput: /^\s*(?:end|rescue|\})$/, lineComment: "#" }; }); diff --git a/www/code/mode/ruby/test.js b/www/code/codemirror-5.13.2/mode/ruby/test.js similarity index 100% rename from www/code/mode/ruby/test.js rename to www/code/codemirror-5.13.2/mode/ruby/test.js diff --git a/www/code/mode/rust/index.html b/www/code/codemirror-5.13.2/mode/rust/index.html similarity index 100% rename from www/code/mode/rust/index.html rename to www/code/codemirror-5.13.2/mode/rust/index.html diff --git a/www/code/mode/rust/rust.js b/www/code/codemirror-5.13.2/mode/rust/rust.js similarity index 82% rename from www/code/mode/rust/rust.js rename to www/code/codemirror-5.13.2/mode/rust/rust.js index 1ce0c01ee..8558b53fe 100644 --- a/www/code/mode/rust/rust.js +++ b/www/code/codemirror-5.13.2/mode/rust/rust.js @@ -12,11 +12,12 @@ "use strict"; CodeMirror.defineSimpleMode("rust",{ - start:[ + start: [ // string and byte string - {regex: /b?"(?:[^\\]|\\.)*?"/, token: "string"}, + {regex: /b?"/, token: "string", next: "string"}, // raw string and raw byte string - {regex: /(b?r)(#*)(".*?)("\2)/, token: ["string", "string", "string", "string"]}, + {regex: /b?r"/, token: "string", next: "string_raw"}, + {regex: /b?r#+"/, token: "string", next: "string_raw_hash"}, // character {regex: /'(?:[^'\\]|\\(?:[nrt0'"]|x[\da-fA-F]{2}|u\{[\da-fA-F]{6}\}))'/, token: "string-2"}, // byte @@ -39,6 +40,18 @@ CodeMirror.defineSimpleMode("rust",{ {regex: /[\{\[\(]/, indent: true}, {regex: /[\}\]\)]/, dedent: true} ], + string: [ + {regex: /"/, token: "string", next: "start"}, + {regex: /(?:[^\\"]|\\(?:.|$))*/, token: "string"} + ], + string_raw: [ + {regex: /"/, token: "string", next: "start"}, + {regex: /[^"]*/, token: "string"} + ], + string_raw_hash: [ + {regex: /"#+/, token: "string", next: "start"}, + {regex: /(?:[^"]|"(?!#))*/, token: "string"} + ], comment: [ {regex: /.*?\*\//, token: "comment", next: "start"}, {regex: /.*/, token: "comment"} diff --git a/www/code/mode/rust/test.js b/www/code/codemirror-5.13.2/mode/rust/test.js similarity index 96% rename from www/code/mode/rust/test.js rename to www/code/codemirror-5.13.2/mode/rust/test.js index 1a3c6e720..eb256c47e 100644 --- a/www/code/mode/rust/test.js +++ b/www/code/codemirror-5.13.2/mode/rust/test.js @@ -26,7 +26,6 @@ '[string "\\"foo\\""]', '[string r#""foo""#]', '[string "foo #\\"# bar"]', - '[string r##"foo #"# bar"##]', '[string b"foo"]', '[string br"foo"]', diff --git a/www/code/mode/sass/index.html b/www/code/codemirror-5.13.2/mode/sass/index.html similarity index 100% rename from www/code/mode/sass/index.html rename to www/code/codemirror-5.13.2/mode/sass/index.html diff --git a/www/code/mode/sass/sass.js b/www/code/codemirror-5.13.2/mode/sass/sass.js similarity index 100% rename from www/code/mode/sass/sass.js rename to www/code/codemirror-5.13.2/mode/sass/sass.js diff --git a/www/code/mode/scheme/index.html b/www/code/codemirror-5.13.2/mode/scheme/index.html similarity index 100% rename from www/code/mode/scheme/index.html rename to www/code/codemirror-5.13.2/mode/scheme/index.html diff --git a/www/code/mode/scheme/scheme.js b/www/code/codemirror-5.13.2/mode/scheme/scheme.js similarity index 100% rename from www/code/mode/scheme/scheme.js rename to www/code/codemirror-5.13.2/mode/scheme/scheme.js diff --git a/www/code/mode/shell/index.html b/www/code/codemirror-5.13.2/mode/shell/index.html similarity index 100% rename from www/code/mode/shell/index.html rename to www/code/codemirror-5.13.2/mode/shell/index.html diff --git a/www/code/mode/shell/shell.js b/www/code/codemirror-5.13.2/mode/shell/shell.js similarity index 100% rename from www/code/mode/shell/shell.js rename to www/code/codemirror-5.13.2/mode/shell/shell.js diff --git a/www/code/mode/shell/test.js b/www/code/codemirror-5.13.2/mode/shell/test.js similarity index 100% rename from www/code/mode/shell/test.js rename to www/code/codemirror-5.13.2/mode/shell/test.js diff --git a/www/code/mode/sieve/index.html b/www/code/codemirror-5.13.2/mode/sieve/index.html similarity index 100% rename from www/code/mode/sieve/index.html rename to www/code/codemirror-5.13.2/mode/sieve/index.html diff --git a/www/code/mode/sieve/sieve.js b/www/code/codemirror-5.13.2/mode/sieve/sieve.js similarity index 100% rename from www/code/mode/sieve/sieve.js rename to www/code/codemirror-5.13.2/mode/sieve/sieve.js diff --git a/www/code/mode/slim/index.html b/www/code/codemirror-5.13.2/mode/slim/index.html similarity index 100% rename from www/code/mode/slim/index.html rename to www/code/codemirror-5.13.2/mode/slim/index.html diff --git a/www/code/mode/slim/slim.js b/www/code/codemirror-5.13.2/mode/slim/slim.js similarity index 100% rename from www/code/mode/slim/slim.js rename to www/code/codemirror-5.13.2/mode/slim/slim.js diff --git a/www/code/mode/slim/test.js b/www/code/codemirror-5.13.2/mode/slim/test.js similarity index 100% rename from www/code/mode/slim/test.js rename to www/code/codemirror-5.13.2/mode/slim/test.js diff --git a/www/code/mode/smalltalk/index.html b/www/code/codemirror-5.13.2/mode/smalltalk/index.html similarity index 100% rename from www/code/mode/smalltalk/index.html rename to www/code/codemirror-5.13.2/mode/smalltalk/index.html diff --git a/www/code/mode/smalltalk/smalltalk.js b/www/code/codemirror-5.13.2/mode/smalltalk/smalltalk.js similarity index 100% rename from www/code/mode/smalltalk/smalltalk.js rename to www/code/codemirror-5.13.2/mode/smalltalk/smalltalk.js diff --git a/www/code/mode/smarty/index.html b/www/code/codemirror-5.13.2/mode/smarty/index.html similarity index 100% rename from www/code/mode/smarty/index.html rename to www/code/codemirror-5.13.2/mode/smarty/index.html diff --git a/www/code/mode/smarty/smarty.js b/www/code/codemirror-5.13.2/mode/smarty/smarty.js similarity index 100% rename from www/code/mode/smarty/smarty.js rename to www/code/codemirror-5.13.2/mode/smarty/smarty.js diff --git a/www/code/mode/solr/index.html b/www/code/codemirror-5.13.2/mode/solr/index.html similarity index 100% rename from www/code/mode/solr/index.html rename to www/code/codemirror-5.13.2/mode/solr/index.html diff --git a/www/code/mode/solr/solr.js b/www/code/codemirror-5.13.2/mode/solr/solr.js similarity index 100% rename from www/code/mode/solr/solr.js rename to www/code/codemirror-5.13.2/mode/solr/solr.js diff --git a/www/code/mode/soy/index.html b/www/code/codemirror-5.13.2/mode/soy/index.html similarity index 100% rename from www/code/mode/soy/index.html rename to www/code/codemirror-5.13.2/mode/soy/index.html diff --git a/www/code/mode/soy/soy.js b/www/code/codemirror-5.13.2/mode/soy/soy.js similarity index 100% rename from www/code/mode/soy/soy.js rename to www/code/codemirror-5.13.2/mode/soy/soy.js diff --git a/www/code/mode/sparql/index.html b/www/code/codemirror-5.13.2/mode/sparql/index.html similarity index 100% rename from www/code/mode/sparql/index.html rename to www/code/codemirror-5.13.2/mode/sparql/index.html diff --git a/www/code/mode/sparql/sparql.js b/www/code/codemirror-5.13.2/mode/sparql/sparql.js similarity index 96% rename from www/code/mode/sparql/sparql.js rename to www/code/codemirror-5.13.2/mode/sparql/sparql.js index bbf8a76a0..90b08e18f 100644 --- a/www/code/mode/sparql/sparql.js +++ b/www/code/codemirror-5.13.2/mode/sparql/sparql.js @@ -135,7 +135,11 @@ CodeMirror.defineMode("sparql", function(config) { else if (curPunc == "{") pushContext(state, "}", stream.column()); else if (/[\]\}\)]/.test(curPunc)) { while (state.context && state.context.type == "pattern") popContext(state); - if (state.context && curPunc == state.context.type) popContext(state); + if (state.context && curPunc == state.context.type) { + popContext(state); + if (curPunc == "}" && state.context && state.context.type == "pattern") + popContext(state); + } } else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); else if (/atom|string|variable/.test(style) && state.context) { @@ -165,7 +169,9 @@ CodeMirror.defineMode("sparql", function(config) { return context.col + (closing ? 0 : 1); else return context.indent + (closing ? 0 : indentUnit); - } + }, + + lineComment: "#" }; }); diff --git a/www/code/mode/spreadsheet/index.html b/www/code/codemirror-5.13.2/mode/spreadsheet/index.html similarity index 100% rename from www/code/mode/spreadsheet/index.html rename to www/code/codemirror-5.13.2/mode/spreadsheet/index.html diff --git a/www/code/mode/spreadsheet/spreadsheet.js b/www/code/codemirror-5.13.2/mode/spreadsheet/spreadsheet.js similarity index 97% rename from www/code/mode/spreadsheet/spreadsheet.js rename to www/code/codemirror-5.13.2/mode/spreadsheet/spreadsheet.js index 6fab00fdd..222f29766 100644 --- a/www/code/mode/spreadsheet/spreadsheet.js +++ b/www/code/codemirror-5.13.2/mode/spreadsheet/spreadsheet.js @@ -70,7 +70,10 @@ return "operator"; case "\\": if (stream.match(/\\[a-z]+/)) return "string-2"; - else return null; + else { + stream.next(); + return "atom"; + } case ".": case ",": case ";": diff --git a/www/code/mode/sql/index.html b/www/code/codemirror-5.13.2/mode/sql/index.html similarity index 97% rename from www/code/mode/sql/index.html rename to www/code/codemirror-5.13.2/mode/sql/index.html index a0d8d9e1b..7ea4b6934 100644 --- a/www/code/mode/sql/index.html +++ b/www/code/codemirror-5.13.2/mode/sql/index.html @@ -57,6 +57,7 @@ SELECT SQL_NO_CACHE DISTINCT text/x-plsql, text/x-mssql, text/x-hive. + text/x-pgsql.

    -

    The XML mode supports two configuration parameters:

    +

    The XML mode supports these configuration parameters:

    htmlMode (boolean)
    This switches the mode to parse HTML instead of XML. This means attributes do not have to be quoted, and some elements (such as br) do not require a closing tag.
    +
    matchClosing (boolean)
    +
    Controls whether the mode checks that close tags match the + corresponding opening tag, and highlights mismatches as errors. + Defaults to true.
    alignCDATA (boolean)
    Setting this to true will force the opening tag of CDATA blocks to not be indented.
    diff --git a/www/code/mode/xml/test.js b/www/code/codemirror-5.13.2/mode/xml/test.js similarity index 100% rename from www/code/mode/xml/test.js rename to www/code/codemirror-5.13.2/mode/xml/test.js diff --git a/www/code/mode/xml/xml.js b/www/code/codemirror-5.13.2/mode/xml/xml.js similarity index 70% rename from www/code/mode/xml/xml.js rename to www/code/codemirror-5.13.2/mode/xml/xml.js index 5ad21720f..f987a3a3c 100644 --- a/www/code/mode/xml/xml.js +++ b/www/code/codemirror-5.13.2/mode/xml/xml.js @@ -11,54 +11,56 @@ })(function(CodeMirror) { "use strict"; -CodeMirror.defineMode("xml", function(config, parserConfig) { - var indentUnit = config.indentUnit; - var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1; - var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag; - if (multilineTagIndentPastTag == null) multilineTagIndentPastTag = true; +var htmlConfig = { + autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, + 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, + 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, + 'track': true, 'wbr': true, 'menuitem': true}, + implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, + 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, + 'th': true, 'tr': true}, + contextGrabbers: { + 'dd': {'dd': true, 'dt': true}, + 'dt': {'dd': true, 'dt': true}, + 'li': {'li': true}, + 'option': {'option': true, 'optgroup': true}, + 'optgroup': {'optgroup': true}, + 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, + 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, + 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, + 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, + 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, + 'rp': {'rp': true, 'rt': true}, + 'rt': {'rp': true, 'rt': true}, + 'tbody': {'tbody': true, 'tfoot': true}, + 'td': {'td': true, 'th': true}, + 'tfoot': {'tbody': true}, + 'th': {'td': true, 'th': true}, + 'thead': {'tbody': true, 'tfoot': true}, + 'tr': {'tr': true} + }, + doNotIndent: {"pre": true}, + allowUnquoted: true, + allowMissing: true, + caseFold: true +} - var Kludges = parserConfig.htmlMode ? { - autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, - 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, - 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, - 'track': true, 'wbr': true, 'menuitem': true}, - implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, - 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, - 'th': true, 'tr': true}, - contextGrabbers: { - 'dd': {'dd': true, 'dt': true}, - 'dt': {'dd': true, 'dt': true}, - 'li': {'li': true}, - 'option': {'option': true, 'optgroup': true}, - 'optgroup': {'optgroup': true}, - 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, - 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, - 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, - 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, - 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, - 'rp': {'rp': true, 'rt': true}, - 'rt': {'rp': true, 'rt': true}, - 'tbody': {'tbody': true, 'tfoot': true}, - 'td': {'td': true, 'th': true}, - 'tfoot': {'tbody': true}, - 'th': {'td': true, 'th': true}, - 'thead': {'tbody': true, 'tfoot': true}, - 'tr': {'tr': true} - }, - doNotIndent: {"pre": true}, - allowUnquoted: true, - allowMissing: true, - caseFold: true - } : { - autoSelfClosers: {}, - implicitlyClosed: {}, - contextGrabbers: {}, - doNotIndent: {}, - allowUnquoted: false, - allowMissing: false, - caseFold: false - }; - var alignCDATA = parserConfig.alignCDATA; +var xmlConfig = { + autoSelfClosers: {}, + implicitlyClosed: {}, + contextGrabbers: {}, + doNotIndent: {}, + allowUnquoted: false, + allowMissing: false, + caseFold: false +} + +CodeMirror.defineMode("xml", function(editorConf, config_) { + var indentUnit = editorConf.indentUnit + var config = {} + var defaults = config_.htmlMode ? htmlConfig : xmlConfig + for (var prop in defaults) config[prop] = defaults[prop] + for (var prop in config_) config[prop] = config_[prop] // Return variables for tokenizers var type, setStyle; @@ -188,7 +190,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { this.tagName = tagName; this.indent = state.indented; this.startOfLine = startOfLine; - if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) + if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) this.noIndent = true; } function popContext(state) { @@ -201,8 +203,8 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { return; } parentTagName = state.context.tagName; - if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || - !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { + if (!config.contextGrabbers.hasOwnProperty(parentTagName) || + !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { return; } popContext(state); @@ -233,9 +235,9 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { if (type == "word") { var tagName = stream.current(); if (state.context && state.context.tagName != tagName && - Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName)) + config.implicitlyClosed.hasOwnProperty(state.context.tagName)) popContext(state); - if (state.context && state.context.tagName == tagName) { + if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) { setStyle = "tag"; return closeState; } else { @@ -269,7 +271,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { var tagName = state.tagName, tagStart = state.tagStart; state.tagName = state.tagStart = null; if (type == "selfcloseTag" || - Kludges.autoSelfClosers.hasOwnProperty(tagName)) { + config.autoSelfClosers.hasOwnProperty(tagName)) { maybePopContext(state, tagName); } else { maybePopContext(state, tagName); @@ -282,12 +284,12 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { } function attrEqState(type, stream, state) { if (type == "equals") return attrValueState; - if (!Kludges.allowMissing) setStyle = "error"; + if (!config.allowMissing) setStyle = "error"; return attrState(type, stream, state); } function attrValueState(type, stream, state) { if (type == "string") return attrContinuedState; - if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;} + if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;} setStyle = "error"; return attrState(type, stream, state); } @@ -297,12 +299,14 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { } return { - startState: function() { - return {tokenize: inText, - state: baseState, - indented: 0, - tagName: null, tagStart: null, - context: null}; + startState: function(baseIndent) { + var state = {tokenize: inText, + state: baseState, + indented: baseIndent || 0, + tagName: null, tagStart: null, + context: null} + if (baseIndent != null) state.baseIndent = baseIndent + return state }, token: function(stream, state) { @@ -335,19 +339,19 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; // Indent the starts of attribute names. if (state.tagName) { - if (multilineTagIndentPastTag) + if (config.multilineTagIndentPastTag !== false) return state.tagStart + state.tagName.length + 2; else - return state.tagStart + indentUnit * multilineTagIndentFactor; + return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1); } - if (alignCDATA && /$/, blockCommentStart: "", - configuration: parserConfig.htmlMode ? "html" : "xml", - helperType: parserConfig.htmlMode ? "html" : "xml" + configuration: config.htmlMode ? "html" : "xml", + helperType: config.htmlMode ? "html" : "xml", + + skipAttribute: function(state) { + if (state.state == attrValueState) + state.state = attrState + } }; }); diff --git a/www/code/mode/xquery/index.html b/www/code/codemirror-5.13.2/mode/xquery/index.html similarity index 100% rename from www/code/mode/xquery/index.html rename to www/code/codemirror-5.13.2/mode/xquery/index.html diff --git a/www/code/mode/xquery/test.js b/www/code/codemirror-5.13.2/mode/xquery/test.js similarity index 100% rename from www/code/mode/xquery/test.js rename to www/code/codemirror-5.13.2/mode/xquery/test.js diff --git a/www/code/mode/xquery/xquery.js b/www/code/codemirror-5.13.2/mode/xquery/xquery.js similarity index 100% rename from www/code/mode/xquery/xquery.js rename to www/code/codemirror-5.13.2/mode/xquery/xquery.js diff --git a/www/code/codemirror-5.13.2/mode/yaml-frontmatter/index.html b/www/code/codemirror-5.13.2/mode/yaml-frontmatter/index.html new file mode 100644 index 000000000..30bed2f85 --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/yaml-frontmatter/index.html @@ -0,0 +1,121 @@ + + +CodeMirror: YAML front matter mode + + + + + + + + + + + + + +
    +

    YAML front matter mode

    +
    + +

    Defines a mode that parses +a YAML frontmatter +at the start of a file, switching to a base mode at the end of that. +Takes a mode configuration option base to configure the +base mode, which defaults to "gfm".

    + + + +
    diff --git a/www/code/codemirror-5.13.2/mode/yaml-frontmatter/yaml-frontmatter.js b/www/code/codemirror-5.13.2/mode/yaml-frontmatter/yaml-frontmatter.js new file mode 100644 index 000000000..5f4977237 --- /dev/null +++ b/www/code/codemirror-5.13.2/mode/yaml-frontmatter/yaml-frontmatter.js @@ -0,0 +1,68 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function (mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../yaml/yaml")) + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../yaml/yaml"], mod) + else // Plain browser env + mod(CodeMirror) +})(function (CodeMirror) { + + var START = 0, FRONTMATTER = 1, BODY = 2 + + // a mixed mode for Markdown text with an optional YAML front matter + CodeMirror.defineMode("yaml-frontmatter", function (config, parserConfig) { + var yamlMode = CodeMirror.getMode(config, "yaml") + var innerMode = CodeMirror.getMode(config, parserConfig && parserConfig.base || "gfm") + + function curMode(state) { + return state.state == BODY ? innerMode : yamlMode + } + + return { + startState: function () { + return { + state: START, + inner: CodeMirror.startState(yamlMode) + } + }, + copyState: function (state) { + return { + state: state.state, + inner: CodeMirror.copyState(curMode(state), state.inner) + } + }, + token: function (stream, state) { + if (state.state == START) { + if (stream.match(/---/, false)) { + state.state = FRONTMATTER + return yamlMode.token(stream, state.inner) + } else { + state.state = BODY + state.inner = CodeMirror.startState(innerMode) + return innerMode.token(stream, state.inner) + } + } else if (state.state == FRONTMATTER) { + var end = stream.sol() && stream.match(/---/, false) + var style = yamlMode.token(stream, state.inner) + if (end) { + state.state = BODY + state.inner = CodeMirror.startState(innerMode) + } + return style + } else { + return innerMode.token(stream, state.inner) + } + }, + innerMode: function (state) { + return {mode: curMode(state), state: state.inner} + }, + blankLine: function (state) { + var mode = curMode(state) + if (mode.blankLine) return mode.blankLine(state.inner) + } + } + }) +}); diff --git a/www/code/mode/yaml/index.html b/www/code/codemirror-5.13.2/mode/yaml/index.html similarity index 100% rename from www/code/mode/yaml/index.html rename to www/code/codemirror-5.13.2/mode/yaml/index.html diff --git a/www/code/mode/yaml/yaml.js b/www/code/codemirror-5.13.2/mode/yaml/yaml.js similarity index 100% rename from www/code/mode/yaml/yaml.js rename to www/code/codemirror-5.13.2/mode/yaml/yaml.js diff --git a/www/code/mode/z80/index.html b/www/code/codemirror-5.13.2/mode/z80/index.html similarity index 100% rename from www/code/mode/z80/index.html rename to www/code/codemirror-5.13.2/mode/z80/index.html diff --git a/www/code/mode/z80/z80.js b/www/code/codemirror-5.13.2/mode/z80/z80.js similarity index 100% rename from www/code/mode/z80/z80.js rename to www/code/codemirror-5.13.2/mode/z80/z80.js diff --git a/www/code/codemirror-5.13.2/package.json b/www/code/codemirror-5.13.2/package.json new file mode 100644 index 000000000..7440fd5b3 --- /dev/null +++ b/www/code/codemirror-5.13.2/package.json @@ -0,0 +1,28 @@ +{ + "name": "codemirror", + "version":"5.13.2", + "main": "lib/codemirror.js", + "description": "In-browser code editing made bearable", + "license": "MIT", + "directories": {"lib": "./lib"}, + "scripts": { + "test": "node ./test/run.js", + "lint": "bin/lint" + }, + "devDependencies": {"node-static": "0.6.0", + "phantomjs": "1.9.2-5", + "blint": ">=0.1.1"}, + "bugs": "http://github.com/codemirror/CodeMirror/issues", + "keywords": ["JavaScript", "CodeMirror", "Editor"], + "homepage": "http://codemirror.net", + "maintainers":[{"name": "Marijn Haverbeke", + "email": "marijnh@gmail.com", + "web": "http://marijnhaverbeke.nl"}], + "repository": {"type": "git", + "url": "https://github.com/codemirror/CodeMirror.git"}, + "jspm": { + "directories": {}, + "dependencies": {}, + "devDependencies": {} + } +} diff --git a/www/code/codemirror-5.13.2/test/comment_test.js b/www/code/codemirror-5.13.2/test/comment_test.js new file mode 100644 index 000000000..26e474493 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/comment_test.js @@ -0,0 +1,100 @@ +namespace = "comment_"; + +(function() { + function test(name, mode, run, before, after) { + return testCM(name, function(cm) { + run(cm); + eq(cm.getValue(), after); + }, {value: before, mode: mode}); + } + + var simpleProg = "function foo() {\n return bar;\n}"; + var inlineBlock = "foo(/* bar */ true);"; + var inlineBlocks = "foo(/* bar */ true, /* baz */ false);"; + var multiLineInlineBlock = ["above();", "foo(/* bar */ true);", "below();"]; + + test("block", "javascript", function(cm) { + cm.blockComment(Pos(0, 3), Pos(3, 0), {blockCommentLead: " *"}); + }, simpleProg + "\n", "/* function foo() {\n * return bar;\n * }\n */"); + + test("blockToggle", "javascript", function(cm) { + cm.blockComment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"}); + cm.uncomment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"}); + }, simpleProg, simpleProg); + + test("blockToggle2", "javascript", function(cm) { + cm.setCursor({line: 0, ch: 7 /* inside the block comment */}); + cm.execCommand("toggleComment"); + }, inlineBlock, "foo(bar true);"); + + // This test should work but currently fails. + // test("blockToggle3", "javascript", function(cm) { + // cm.setCursor({line: 0, ch: 7 /* inside the first block comment */}); + // cm.execCommand("toggleComment"); + // }, inlineBlocks, "foo(bar true, /* baz */ false);"); + + test("line", "javascript", function(cm) { + cm.lineComment(Pos(1, 1), Pos(1, 1)); + }, simpleProg, "function foo() {\n// return bar;\n}"); + + test("lineToggle", "javascript", function(cm) { + cm.lineComment(Pos(0, 0), Pos(2, 1)); + cm.uncomment(Pos(0, 0), Pos(2, 1)); + }, simpleProg, simpleProg); + + test("fallbackToBlock", "css", function(cm) { + cm.lineComment(Pos(0, 0), Pos(2, 1)); + }, "html {\n border: none;\n}", "/* html {\n border: none;\n} */"); + + test("fallbackToLine", "ruby", function(cm) { + cm.blockComment(Pos(0, 0), Pos(1)); + }, "def blah()\n return hah\n", "# def blah()\n# return hah\n"); + + test("ignoreExternalBlockComments", "javascript", function(cm) { + cm.execCommand("toggleComment"); + }, inlineBlocks, "// " + inlineBlocks); + + test("ignoreExternalBlockComments2", "javascript", function(cm) { + cm.setCursor({line: 0, ch: null /* eol */}); + cm.execCommand("toggleComment"); + }, inlineBlocks, "// " + inlineBlocks); + + test("ignoreExternalBlockCommentsMultiLineAbove", "javascript", function(cm) { + cm.setSelection({line: 0, ch: 0}, {line: 1, ch: 1}); + cm.execCommand("toggleComment"); + }, multiLineInlineBlock.join("\n"), ["// " + multiLineInlineBlock[0], + "// " + multiLineInlineBlock[1], + multiLineInlineBlock[2]].join("\n")); + + test("ignoreExternalBlockCommentsMultiLineBelow", "javascript", function(cm) { + cm.setSelection({line: 1, ch: 13 /* after end of block comment */}, {line: 2, ch: 1}); + cm.execCommand("toggleComment"); + }, multiLineInlineBlock.join("\n"), [multiLineInlineBlock[0], + "// " + multiLineInlineBlock[1], + "// " + multiLineInlineBlock[2]].join("\n")); + + test("commentRange", "javascript", function(cm) { + cm.blockComment(Pos(1, 2), Pos(1, 13), {fullLines: false}); + }, simpleProg, "function foo() {\n /*return bar;*/\n}"); + + test("indented", "javascript", function(cm) { + cm.lineComment(Pos(1, 0), Pos(2), {indent: true}); + }, simpleProg, "function foo() {\n// return bar;\n// }"); + + test("singleEmptyLine", "javascript", function(cm) { + cm.setCursor(1); + cm.execCommand("toggleComment"); + }, "a;\n\nb;", "a;\n// \nb;"); + + test("dontMessWithStrings", "javascript", function(cm) { + cm.execCommand("toggleComment"); + }, "console.log(\"/*string*/\");", "// console.log(\"/*string*/\");"); + + test("dontMessWithStrings2", "javascript", function(cm) { + cm.execCommand("toggleComment"); + }, "console.log(\"// string\");", "// console.log(\"// string\");"); + + test("dontMessWithStrings3", "javascript", function(cm) { + cm.execCommand("toggleComment"); + }, "// console.log(\"// string\");", "console.log(\"// string\");"); +})(); diff --git a/www/code/codemirror-5.13.2/test/doc_test.js b/www/code/codemirror-5.13.2/test/doc_test.js new file mode 100644 index 000000000..5f242f658 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/doc_test.js @@ -0,0 +1,371 @@ +(function() { + // A minilanguage for instantiating linked CodeMirror instances and Docs + function instantiateSpec(spec, place, opts) { + var names = {}, pos = 0, l = spec.length, editors = []; + while (spec) { + var m = spec.match(/^(\w+)(\*?)(?:='([^\']*)'|<(~?)(\w+)(?:\/(\d+)-(\d+))?)\s*/); + var name = m[1], isDoc = m[2], cur; + if (m[3]) { + cur = isDoc ? CodeMirror.Doc(m[3]) : CodeMirror(place, clone(opts, {value: m[3]})); + } else { + var other = m[5]; + if (!names.hasOwnProperty(other)) { + names[other] = editors.length; + editors.push(CodeMirror(place, opts)); + } + var doc = editors[names[other]].linkedDoc({ + sharedHist: !m[4], + from: m[6] ? Number(m[6]) : null, + to: m[7] ? Number(m[7]) : null + }); + cur = isDoc ? doc : CodeMirror(place, clone(opts, {value: doc})); + } + names[name] = editors.length; + editors.push(cur); + spec = spec.slice(m[0].length); + } + return editors; + } + + function clone(obj, props) { + if (!obj) return; + clone.prototype = obj; + var inst = new clone(); + if (props) for (var n in props) if (props.hasOwnProperty(n)) + inst[n] = props[n]; + return inst; + } + + function eqAll(val) { + var end = arguments.length, msg = null; + if (typeof arguments[end-1] == "string") + msg = arguments[--end]; + if (i == end) throw new Error("No editors provided to eqAll"); + for (var i = 1; i < end; ++i) + eq(arguments[i].getValue(), val, msg) + } + + function testDoc(name, spec, run, opts, expectFail) { + if (!opts) opts = {}; + + return test("doc_" + name, function() { + var place = document.getElementById("testground"); + var editors = instantiateSpec(spec, place, opts); + var successful = false; + + try { + run.apply(null, editors); + successful = true; + } finally { + if (!successful || verbose) { + place.style.visibility = "visible"; + } else { + for (var i = 0; i < editors.length; ++i) + if (editors[i] instanceof CodeMirror) + place.removeChild(editors[i].getWrapperElement()); + } + } + }, expectFail); + } + + var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); + + function testBasic(a, b) { + eqAll("x", a, b); + a.setValue("hey"); + eqAll("hey", a, b); + b.setValue("wow"); + eqAll("wow", a, b); + a.replaceRange("u\nv\nw", Pos(0, 3)); + b.replaceRange("i", Pos(0, 4)); + b.replaceRange("j", Pos(2, 1)); + eqAll("wowui\nv\nwj", a, b); + } + + testDoc("basic", "A='x' B 0, "not at left"); + is(pos.top > 0, "not at top"); + }); + + testDoc("copyDoc", "A='u'", function(a) { + var copy = a.getDoc().copy(true); + a.setValue("foo"); + copy.setValue("bar"); + var old = a.swapDoc(copy); + eq(a.getValue(), "bar"); + a.undo(); + eq(a.getValue(), "u"); + a.swapDoc(old); + eq(a.getValue(), "foo"); + eq(old.historySize().undo, 1); + eq(old.copy(false).historySize().undo, 0); + }); + + testDoc("docKeepsMode", "A='1+1'", function(a) { + var other = CodeMirror.Doc("hi", "text/x-markdown"); + a.setOption("mode", "text/javascript"); + var old = a.swapDoc(other); + eq(a.getOption("mode"), "text/x-markdown"); + eq(a.getMode().name, "markdown"); + a.swapDoc(old); + eq(a.getOption("mode"), "text/javascript"); + eq(a.getMode().name, "javascript"); + }); + + testDoc("subview", "A='1\n2\n3\n4\n5' B<~A/1-3", function(a, b) { + eq(b.getValue(), "2\n3"); + eq(b.firstLine(), 1); + b.setCursor(Pos(4)); + eqPos(b.getCursor(), Pos(2, 1)); + a.replaceRange("-1\n0\n", Pos(0, 0)); + eq(b.firstLine(), 3); + eqPos(b.getCursor(), Pos(4, 1)); + a.undo(); + eqPos(b.getCursor(), Pos(2, 1)); + b.replaceRange("oyoy\n", Pos(2, 0)); + eq(a.getValue(), "1\n2\noyoy\n3\n4\n5"); + b.undo(); + eq(a.getValue(), "1\n2\n3\n4\n5"); + }); + + testDoc("subviewEditOnBoundary", "A='11\n22\n33\n44\n55' B<~A/1-4", function(a, b) { + a.replaceRange("x\nyy\nz", Pos(0, 1), Pos(2, 1)); + eq(b.firstLine(), 2); + eq(b.lineCount(), 2); + eq(b.getValue(), "z3\n44"); + a.replaceRange("q\nrr\ns", Pos(3, 1), Pos(4, 1)); + eq(b.firstLine(), 2); + eq(b.getValue(), "z3\n4q"); + eq(a.getValue(), "1x\nyy\nz3\n4q\nrr\ns5"); + a.execCommand("selectAll"); + a.replaceSelection("!"); + eqAll("!", a, b); + }); + + + testDoc("sharedMarker", "A='ab\ncd\nef\ngh' B 500){ + totalTime = 0; + delay = 50; + } + setTimeout(function(){step(i + 1);}, delay); + } else { // Quit tests + running = false; + return null; + } + } + step(0); +} + +function label(str, msg) { + if (msg) return str + " (" + msg + ")"; + return str; +} +function eq(a, b, msg) { + if (a != b) throw new Failure(label(a + " != " + b, msg)); +} +function near(a, b, margin, msg) { + if (Math.abs(a - b) > margin) + throw new Failure(label(a + " is not close to " + b + " (" + margin + ")", msg)); +} +function eqPos(a, b, msg) { + function str(p) { return "{line:" + p.line + ",ch:" + p.ch + "}"; } + if (a == b) return; + if (a == null) throw new Failure(label("comparing null to " + str(b), msg)); + if (b == null) throw new Failure(label("comparing " + str(a) + " to null", msg)); + if (a.line != b.line || a.ch != b.ch) throw new Failure(label(str(a) + " != " + str(b), msg)); +} +function is(a, msg) { + if (!a) throw new Failure(label("assertion failed", msg)); +} + +function countTests() { + if (!filters.length) return tests.length; + var sum = 0; + for (var i = 0; i < tests.length; ++i) { + var name = tests[i].name; + for (var j = 0; j < filters.length; j++) { + if (name.match(filters[j])) { + ++sum; + break; + } + } + } + return sum; +} + +function parseTestFilter(s) { + if (/_\*$/.test(s)) return new RegExp("^" + s.slice(0, s.length - 2), "i"); + else return new RegExp(s, "i"); +} diff --git a/www/code/codemirror-5.13.2/test/emacs_test.js b/www/code/codemirror-5.13.2/test/emacs_test.js new file mode 100644 index 000000000..124575c72 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/emacs_test.js @@ -0,0 +1,147 @@ +(function() { + "use strict"; + + var Pos = CodeMirror.Pos; + namespace = "emacs_"; + + var eventCache = {}; + function fakeEvent(keyName) { + var event = eventCache[key]; + if (event) return event; + + var ctrl, shift, alt; + var key = keyName.replace(/\w+-/g, function(type) { + if (type == "Ctrl-") ctrl = true; + else if (type == "Alt-") alt = true; + else if (type == "Shift-") shift = true; + return ""; + }); + var code; + for (var c in CodeMirror.keyNames) + if (CodeMirror.keyNames[c] == key) { code = c; break; } + if (code == null) throw new Error("Unknown key: " + key); + + return eventCache[keyName] = { + type: "keydown", keyCode: code, ctrlKey: ctrl, shiftKey: shift, altKey: alt, + preventDefault: function(){}, stopPropagation: function(){} + }; + } + + function sim(name, start /*, actions... */) { + var keys = Array.prototype.slice.call(arguments, 2); + testCM(name, function(cm) { + for (var i = 0; i < keys.length; ++i) { + var cur = keys[i]; + if (cur instanceof Pos) cm.setCursor(cur); + else if (cur.call) cur(cm); + else cm.triggerOnKeyDown(fakeEvent(cur)); + } + }, {keyMap: "emacs", value: start, mode: "javascript"}); + } + + function at(line, ch) { return function(cm) { eqPos(cm.getCursor(), Pos(line, ch)); }; } + function txt(str) { return function(cm) { eq(cm.getValue(), str); }; } + + sim("motionHSimple", "abc", "Ctrl-F", "Ctrl-F", "Ctrl-B", at(0, 1)); + sim("motionHMulti", "abcde", + "Ctrl-4", "Ctrl-F", at(0, 4), "Ctrl--", "Ctrl-2", "Ctrl-F", at(0, 2), + "Ctrl-5", "Ctrl-B", at(0, 0)); + + sim("motionHWord", "abc. def ghi", + "Alt-F", at(0, 3), "Alt-F", at(0, 8), + "Ctrl-B", "Alt-B", at(0, 5), "Alt-B", at(0, 0)); + sim("motionHWordMulti", "abc. def ghi ", + "Ctrl-3", "Alt-F", at(0, 12), "Ctrl-2", "Alt-B", at(0, 5), + "Ctrl--", "Alt-B", at(0, 8)); + + sim("motionVSimple", "a\nb\nc\n", "Ctrl-N", "Ctrl-N", "Ctrl-P", at(1, 0)); + sim("motionVMulti", "a\nb\nc\nd\ne\n", + "Ctrl-2", "Ctrl-N", at(2, 0), "Ctrl-F", "Ctrl--", "Ctrl-N", at(1, 1), + "Ctrl--", "Ctrl-3", "Ctrl-P", at(4, 1)); + + sim("killYank", "abc\ndef\nghi", + "Ctrl-F", "Ctrl-Space", "Ctrl-N", "Ctrl-N", "Ctrl-W", "Ctrl-E", "Ctrl-Y", + txt("ahibc\ndef\ng")); + sim("killRing", "abcdef", + "Ctrl-Space", "Ctrl-F", "Ctrl-W", "Ctrl-Space", "Ctrl-F", "Ctrl-W", + "Ctrl-Y", "Alt-Y", + txt("acdef")); + sim("copyYank", "abcd", + "Ctrl-Space", "Ctrl-E", "Alt-W", "Ctrl-Y", + txt("abcdabcd")); + + sim("killLineSimple", "foo\nbar", "Ctrl-F", "Ctrl-K", txt("f\nbar")); + sim("killLineEmptyLine", "foo\n \nbar", "Ctrl-N", "Ctrl-K", txt("foo\nbar")); + sim("killLineMulti", "foo\nbar\nbaz", + "Ctrl-F", "Ctrl-F", "Ctrl-K", "Ctrl-K", "Ctrl-K", "Ctrl-A", "Ctrl-Y", + txt("o\nbarfo\nbaz")); + + sim("moveByParagraph", "abc\ndef\n\n\nhij\nklm\n\n", + "Ctrl-F", "Ctrl-Down", at(2, 0), "Ctrl-Down", at(6, 0), + "Ctrl-N", "Ctrl-Up", at(3, 0), "Ctrl-Up", at(0, 0), + Pos(1, 2), "Ctrl-Down", at(2, 0), Pos(4, 2), "Ctrl-Up", at(3, 0)); + sim("moveByParagraphMulti", "abc\n\ndef\n\nhij\n\nklm", + "Ctrl-U", "2", "Ctrl-Down", at(3, 0), + "Shift-Alt-.", "Ctrl-3", "Ctrl-Up", at(1, 0)); + + sim("moveBySentence", "sentence one! sentence\ntwo\n\nparagraph two", + "Alt-E", at(0, 13), "Alt-E", at(1, 3), "Ctrl-F", "Alt-A", at(0, 13)); + + sim("moveByExpr", "function foo(a, b) {}", + "Ctrl-Alt-F", at(0, 8), "Ctrl-Alt-F", at(0, 12), "Ctrl-Alt-F", at(0, 18), + "Ctrl-Alt-B", at(0, 12), "Ctrl-Alt-B", at(0, 9)); + sim("moveByExprMulti", "foo bar baz bug", + "Ctrl-2", "Ctrl-Alt-F", at(0, 7), + "Ctrl--", "Ctrl-Alt-F", at(0, 4), + "Ctrl--", "Ctrl-2", "Ctrl-Alt-B", at(0, 11)); + sim("delExpr", "var x = [\n a,\n b\n c\n];", + Pos(0, 8), "Ctrl-Alt-K", txt("var x = ;"), "Ctrl-/", + Pos(4, 1), "Ctrl-Alt-Backspace", txt("var x = ;")); + sim("delExprMulti", "foo bar baz", + "Ctrl-2", "Ctrl-Alt-K", txt(" baz"), + "Ctrl-/", "Ctrl-E", "Ctrl-2", "Ctrl-Alt-Backspace", txt("foo ")); + + sim("justOneSpace", "hi bye ", + Pos(0, 4), "Alt-Space", txt("hi bye "), + Pos(0, 4), "Alt-Space", txt("hi b ye "), + "Ctrl-A", "Alt-Space", "Ctrl-E", "Alt-Space", txt(" hi b ye ")); + + sim("openLine", "foo bar", "Alt-F", "Ctrl-O", txt("foo\n bar")) + + sim("transposeChar", "abcd\ne", + "Ctrl-F", "Ctrl-T", "Ctrl-T", txt("bcad\ne"), at(0, 3), + "Ctrl-F", "Ctrl-T", "Ctrl-T", "Ctrl-T", txt("bcda\ne"), at(0, 4), + "Ctrl-F", "Ctrl-T", txt("bcde\na"), at(1, 0)); + + sim("manipWordCase", "foo BAR bAZ", + "Alt-C", "Alt-L", "Alt-U", txt("Foo bar BAZ"), + "Ctrl-A", "Alt-U", "Alt-L", "Alt-C", txt("FOO bar Baz")); + sim("manipWordCaseMulti", "foo Bar bAz", + "Ctrl-2", "Alt-U", txt("FOO BAR bAz"), + "Ctrl-A", "Ctrl-3", "Alt-C", txt("Foo Bar Baz")); + + sim("upExpr", "foo {\n bar[];\n baz(blah);\n}", + Pos(2, 7), "Ctrl-Alt-U", at(2, 5), "Ctrl-Alt-U", at(0, 4)); + sim("transposeExpr", "do foo[bar] dah", + Pos(0, 6), "Ctrl-Alt-T", txt("do [bar]foo dah")); + + sim("clearMark", "abcde", Pos(0, 2), "Ctrl-Space", "Ctrl-F", "Ctrl-F", + "Ctrl-G", "Ctrl-W", txt("abcde")); + + sim("delRegion", "abcde", "Ctrl-Space", "Ctrl-F", "Ctrl-F", "Delete", txt("cde")); + sim("backspaceRegion", "abcde", "Ctrl-Space", "Ctrl-F", "Ctrl-F", "Backspace", txt("cde")); + + testCM("save", function(cm) { + var saved = false; + CodeMirror.commands.save = function(cm) { saved = cm.getValue(); }; + cm.triggerOnKeyDown(fakeEvent("Ctrl-X")); + cm.triggerOnKeyDown(fakeEvent("Ctrl-S")); + is(saved, "hi"); + }, {value: "hi", keyMap: "emacs"}); + + testCM("gotoInvalidLineFloat", function(cm) { + cm.openDialog = function(_, cb) { cb("2.2"); }; + cm.triggerOnKeyDown(fakeEvent("Alt-G")); + cm.triggerOnKeyDown(fakeEvent("G")); + }, {value: "1\n2\n3\n4", keyMap: "emacs"}); +})(); diff --git a/www/code/codemirror-5.13.2/test/index.html b/www/code/codemirror-5.13.2/test/index.html new file mode 100644 index 000000000..5f8ebadc1 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/index.html @@ -0,0 +1,253 @@ + + + +CodeMirror: Test Suite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Test Suite

    + +

    A limited set of programmatic sanity tests for CodeMirror.

    + +
    +
    Ran 0 of 0 tests
    +
    +

    Please enable JavaScript...

    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/www/code/codemirror-5.13.2/test/lint.js b/www/code/codemirror-5.13.2/test/lint.js new file mode 100644 index 000000000..35777562e --- /dev/null +++ b/www/code/codemirror-5.13.2/test/lint.js @@ -0,0 +1,11 @@ +var blint = require("blint"); + +["mode", "lib", "addon", "keymap"].forEach(function(dir) { + blint.checkDir(dir, { + browser: true, + allowedGlobals: ["CodeMirror", "define", "test", "requirejs"], + blob: "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http:\/\/codemirror.net\/LICENSE\n\n" + }); +}); + +module.exports = {ok: blint.success()}; diff --git a/www/code/codemirror-5.13.2/test/mode_test.css b/www/code/codemirror-5.13.2/test/mode_test.css new file mode 100644 index 000000000..f83271b4e --- /dev/null +++ b/www/code/codemirror-5.13.2/test/mode_test.css @@ -0,0 +1,23 @@ +.mt-output .mt-token { + border: 1px solid #ddd; + white-space: pre; + font-family: "Consolas", monospace; + text-align: center; +} + +.mt-output .mt-style { + font-size: x-small; +} + +.mt-output .mt-state { + font-size: x-small; + vertical-align: top; +} + +.mt-output .mt-state-row { + display: none; +} + +.mt-state-unhide .mt-output .mt-state-row { + display: table-row; +} diff --git a/www/code/codemirror-5.13.2/test/mode_test.js b/www/code/codemirror-5.13.2/test/mode_test.js new file mode 100644 index 000000000..0aed50f7d --- /dev/null +++ b/www/code/codemirror-5.13.2/test/mode_test.js @@ -0,0 +1,192 @@ +/** + * Helper to test CodeMirror highlighting modes. It pretty prints output of the + * highlighter and can check against expected styles. + * + * Mode tests are registered by calling test.mode(testName, mode, + * tokens), where mode is a mode object as returned by + * CodeMirror.getMode, and tokens is an array of lines that make up + * the test. + * + * These lines are strings, in which styled stretches of code are + * enclosed in brackets `[]`, and prefixed by their style. For + * example, `[keyword if]`. Brackets in the code itself must be + * duplicated to prevent them from being interpreted as token + * boundaries. For example `a[[i]]` for `a[i]`. If a token has + * multiple styles, the styles must be separated by ampersands, for + * example `[tag&error ]`. + * + * See the test.js files in the css, markdown, gfm, and stex mode + * directories for examples. + */ +(function() { + function findSingle(str, pos, ch) { + for (;;) { + var found = str.indexOf(ch, pos); + if (found == -1) return null; + if (str.charAt(found + 1) != ch) return found; + pos = found + 2; + } + } + + var styleName = /[\w&-_]+/g; + function parseTokens(strs) { + var tokens = [], plain = ""; + for (var i = 0; i < strs.length; ++i) { + if (i) plain += "\n"; + var str = strs[i], pos = 0; + while (pos < str.length) { + var style = null, text; + if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") { + styleName.lastIndex = pos + 1; + var m = styleName.exec(str); + style = m[0].replace(/&/g, " "); + var textStart = pos + style.length + 2; + var end = findSingle(str, textStart, "]"); + if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style); + text = str.slice(textStart, end); + pos = end + 1; + } else { + var end = findSingle(str, pos, "["); + if (end == null) end = str.length; + text = str.slice(pos, end); + pos = end; + } + text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);}); + tokens.push({style: style, text: text}); + plain += text; + } + } + return {tokens: tokens, plain: plain}; + } + + test.mode = function(name, mode, tokens, modeName) { + var data = parseTokens(tokens); + return test((modeName || mode.name) + "_" + name, function() { + return compare(data.plain, data.tokens, mode); + }); + }; + + function esc(str) { + return str.replace('&', '&').replace('<', '<').replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); +; + } + + function compare(text, expected, mode) { + + var expectedOutput = []; + for (var i = 0; i < expected.length; ++i) { + var sty = expected[i].style; + if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' '); + expectedOutput.push({style: sty, text: expected[i].text}); + } + + var observedOutput = highlight(text, mode); + + var s = ""; + var diff = highlightOutputsDifferent(expectedOutput, observedOutput); + if (diff != null) { + s += '
    '; + s += '
    ' + esc(text) + '
    '; + s += '
    '; + s += 'expected:'; + s += prettyPrintOutputTable(expectedOutput, diff); + s += 'observed: [display states]'; + s += prettyPrintOutputTable(observedOutput, diff); + s += '
    '; + s += '
    '; + } + if (observedOutput.indentFailures) { + for (var i = 0; i < observedOutput.indentFailures.length; i++) + s += "
    " + esc(observedOutput.indentFailures[i]) + "
    "; + } + if (s) throw new Failure(s); + } + + function stringify(obj) { + function replacer(key, obj) { + if (typeof obj == "function") { + var m = obj.toString().match(/function\s*[^\s(]*/); + return m ? m[0] : "function"; + } + return obj; + } + if (window.JSON && JSON.stringify) + return JSON.stringify(obj, replacer, 2); + return "[unsupported]"; // Fail safely if no native JSON. + } + + function highlight(string, mode) { + var state = mode.startState(); + + var lines = string.replace(/\r\n/g,'\n').split('\n'); + var st = [], pos = 0; + for (var i = 0; i < lines.length; ++i) { + var line = lines[i], newLine = true; + if (mode.indent) { + var ws = line.match(/^\s*/)[0]; + var indent = mode.indent(state, line.slice(ws.length)); + if (indent != CodeMirror.Pass && indent != ws.length) + (st.indentFailures || (st.indentFailures = [])).push( + "Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")"); + } + var stream = new CodeMirror.StringStream(line); + if (line == "" && mode.blankLine) mode.blankLine(state); + /* Start copied code from CodeMirror.highlight */ + while (!stream.eol()) { + for (var j = 0; j < 10 && stream.start >= stream.pos; j++) + var compare = mode.token(stream, state); + if (j == 10) + throw new Failure("Failed to advance the stream." + stream.string + " " + stream.pos); + var substr = stream.current(); + if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' '); + stream.start = stream.pos; + if (pos && st[pos-1].style == compare && !newLine) { + st[pos-1].text += substr; + } else if (substr) { + st[pos++] = {style: compare, text: substr, state: stringify(state)}; + } + // Give up when line is ridiculously long + if (stream.pos > 5000) { + st[pos++] = {style: null, text: this.text.slice(stream.pos)}; + break; + } + newLine = false; + } + } + + return st; + } + + function highlightOutputsDifferent(o1, o2) { + var minLen = Math.min(o1.length, o2.length); + for (var i = 0; i < minLen; ++i) + if (o1[i].style != o2[i].style || o1[i].text != o2[i].text) return i; + if (o1.length > minLen || o2.length > minLen) return minLen; + } + + function prettyPrintOutputTable(output, diffAt) { + var s = ''; + s += ''; + for (var i = 0; i < output.length; ++i) { + var style = output[i].style, val = output[i].text; + s += + ''; + } + s += ''; + for (var i = 0; i < output.length; ++i) { + s += ''; + } + if(output[0].state) { + s += ''; + for (var i = 0; i < output.length; ++i) { + s += ''; + } + } + s += '
    ' + + '' + + esc(val.replace(/ /g,'\xb7')) + // · MIDDLE DOT + '' + + '
    ' + (output[i].style || null) + '
    ' + esc(output[i].state) + '
    '; + return s; + } +})(); diff --git a/www/code/codemirror-5.13.2/test/multi_test.js b/www/code/codemirror-5.13.2/test/multi_test.js new file mode 100644 index 000000000..a8e760d27 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/multi_test.js @@ -0,0 +1,285 @@ +(function() { + namespace = "multi_"; + + function hasSelections(cm) { + var sels = cm.listSelections(); + var given = (arguments.length - 1) / 4; + if (sels.length != given) + throw new Failure("expected " + given + " selections, found " + sels.length); + for (var i = 0, p = 1; i < given; i++, p += 4) { + var anchor = Pos(arguments[p], arguments[p + 1]); + var head = Pos(arguments[p + 2], arguments[p + 3]); + eqPos(sels[i].anchor, anchor, "anchor of selection " + i); + eqPos(sels[i].head, head, "head of selection " + i); + } + } + function hasCursors(cm) { + var sels = cm.listSelections(); + var given = (arguments.length - 1) / 2; + if (sels.length != given) + throw new Failure("expected " + given + " selections, found " + sels.length); + for (var i = 0, p = 1; i < given; i++, p += 2) { + eqPos(sels[i].anchor, sels[i].head, "something selected for " + i); + var head = Pos(arguments[p], arguments[p + 1]); + eqPos(sels[i].head, head, "selection " + i); + } + } + + testCM("getSelection", function(cm) { + select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)}, {anchor: Pos(2, 2), head: Pos(2, 0)}); + eq(cm.getSelection(), "1234\n56\n90"); + eq(cm.getSelection(false).join("|"), "1234|56|90"); + eq(cm.getSelections().join("|"), "1234\n56|90"); + }, {value: "1234\n5678\n90"}); + + testCM("setSelection", function(cm) { + select(cm, Pos(3, 0), Pos(0, 0), {anchor: Pos(2, 5), head: Pos(1, 0)}); + hasSelections(cm, 0, 0, 0, 0, + 2, 5, 1, 0, + 3, 0, 3, 0); + cm.setSelection(Pos(1, 2), Pos(1, 1)); + hasSelections(cm, 1, 2, 1, 1); + select(cm, {anchor: Pos(1, 1), head: Pos(2, 4)}, + {anchor: Pos(0, 0), head: Pos(1, 3)}, + Pos(3, 0), Pos(2, 2)); + hasSelections(cm, 0, 0, 2, 4, + 3, 0, 3, 0); + cm.setSelections([{anchor: Pos(0, 1), head: Pos(0, 2)}, + {anchor: Pos(1, 1), head: Pos(1, 2)}, + {anchor: Pos(2, 1), head: Pos(2, 2)}], 1); + eqPos(cm.getCursor("head"), Pos(1, 2)); + eqPos(cm.getCursor("anchor"), Pos(1, 1)); + eqPos(cm.getCursor("from"), Pos(1, 1)); + eqPos(cm.getCursor("to"), Pos(1, 2)); + cm.setCursor(Pos(1, 1)); + hasCursors(cm, 1, 1); + }, {value: "abcde\nabcde\nabcde\n"}); + + testCM("somethingSelected", function(cm) { + select(cm, Pos(0, 1), {anchor: Pos(0, 3), head: Pos(0, 5)}); + eq(cm.somethingSelected(), true); + select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5)); + eq(cm.somethingSelected(), false); + }, {value: "123456789"}); + + testCM("extendSelection", function(cm) { + select(cm, Pos(0, 1), Pos(1, 1), Pos(2, 1)); + cm.setExtending(true); + cm.extendSelections([Pos(0, 2), Pos(1, 0), Pos(2, 3)]); + hasSelections(cm, 0, 1, 0, 2, + 1, 1, 1, 0, + 2, 1, 2, 3); + cm.extendSelection(Pos(2, 4), Pos(2, 0)); + hasSelections(cm, 2, 4, 2, 0); + }, {value: "1234\n1234\n1234"}); + + testCM("addSelection", function(cm) { + select(cm, Pos(0, 1), Pos(1, 1)); + cm.addSelection(Pos(0, 0), Pos(0, 4)); + hasSelections(cm, 0, 0, 0, 4, + 1, 1, 1, 1); + cm.addSelection(Pos(2, 2)); + hasSelections(cm, 0, 0, 0, 4, + 1, 1, 1, 1, + 2, 2, 2, 2); + }, {value: "1234\n1234\n1234"}); + + testCM("replaceSelection", function(cm) { + var selections = [{anchor: Pos(0, 0), head: Pos(0, 1)}, + {anchor: Pos(0, 2), head: Pos(0, 3)}, + {anchor: Pos(0, 4), head: Pos(0, 5)}, + {anchor: Pos(2, 1), head: Pos(2, 4)}, + {anchor: Pos(2, 5), head: Pos(2, 6)}]; + var val = "123456\n123456\n123456"; + cm.setValue(val); + cm.setSelections(selections); + cm.replaceSelection("ab", "around"); + eq(cm.getValue(), "ab2ab4ab6\n123456\n1ab5ab"); + hasSelections(cm, 0, 0, 0, 2, + 0, 3, 0, 5, + 0, 6, 0, 8, + 2, 1, 2, 3, + 2, 4, 2, 6); + cm.setValue(val); + cm.setSelections(selections); + cm.replaceSelection("", "around"); + eq(cm.getValue(), "246\n123456\n15"); + hasSelections(cm, 0, 0, 0, 0, + 0, 1, 0, 1, + 0, 2, 0, 2, + 2, 1, 2, 1, + 2, 2, 2, 2); + cm.setValue(val); + cm.setSelections(selections); + cm.replaceSelection("X\nY\nZ", "around"); + hasSelections(cm, 0, 0, 2, 1, + 2, 2, 4, 1, + 4, 2, 6, 1, + 8, 1, 10, 1, + 10, 2, 12, 1); + cm.replaceSelection("a", "around"); + hasSelections(cm, 0, 0, 0, 1, + 0, 2, 0, 3, + 0, 4, 0, 5, + 2, 1, 2, 2, + 2, 3, 2, 4); + cm.replaceSelection("xy", "start"); + hasSelections(cm, 0, 0, 0, 0, + 0, 3, 0, 3, + 0, 6, 0, 6, + 2, 1, 2, 1, + 2, 4, 2, 4); + cm.replaceSelection("z\nf"); + hasSelections(cm, 1, 1, 1, 1, + 2, 1, 2, 1, + 3, 1, 3, 1, + 6, 1, 6, 1, + 7, 1, 7, 1); + eq(cm.getValue(), "z\nfxy2z\nfxy4z\nfxy6\n123456\n1z\nfxy5z\nfxy"); + }); + + function select(cm) { + var sels = []; + for (var i = 1; i < arguments.length; i++) { + var arg = arguments[i]; + if (arg.head) sels.push(arg); + else sels.push({head: arg, anchor: arg}); + } + cm.setSelections(sels, sels.length - 1); + } + + testCM("indentSelection", function(cm) { + select(cm, Pos(0, 1), Pos(1, 1)); + cm.indentSelection(4); + eq(cm.getValue(), " foo\n bar\nbaz"); + + select(cm, Pos(0, 2), Pos(0, 3), Pos(0, 4)); + cm.indentSelection(-2); + eq(cm.getValue(), " foo\n bar\nbaz"); + + select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)}, + {anchor: Pos(1, 3), head: Pos(2, 0)}); + cm.indentSelection(-2); + eq(cm.getValue(), "foo\n bar\nbaz"); + }, {value: "foo\nbar\nbaz"}); + + testCM("killLine", function(cm) { + select(cm, Pos(0, 1), Pos(0, 2), Pos(1, 1)); + cm.execCommand("killLine"); + eq(cm.getValue(), "f\nb\nbaz"); + cm.execCommand("killLine"); + eq(cm.getValue(), "fbbaz"); + cm.setValue("foo\nbar\nbaz"); + select(cm, Pos(0, 1), {anchor: Pos(0, 2), head: Pos(2, 1)}); + cm.execCommand("killLine"); + eq(cm.getValue(), "faz"); + }, {value: "foo\nbar\nbaz"}); + + testCM("deleteLine", function(cm) { + select(cm, Pos(0, 0), + {head: Pos(0, 1), anchor: Pos(2, 0)}, + Pos(4, 0)); + cm.execCommand("deleteLine"); + eq(cm.getValue(), "4\n6\n7"); + select(cm, Pos(2, 1)); + cm.execCommand("deleteLine"); + eq(cm.getValue(), "4\n6\n"); + }, {value: "1\n2\n3\n4\n5\n6\n7"}); + + testCM("deleteH", function(cm) { + select(cm, Pos(0, 4), {anchor: Pos(1, 4), head: Pos(1, 5)}); + cm.execCommand("delWordAfter"); + eq(cm.getValue(), "foo bar baz\nabc ef ghi\n"); + cm.execCommand("delWordAfter"); + eq(cm.getValue(), "foo baz\nabc ghi\n"); + cm.execCommand("delCharBefore"); + cm.execCommand("delCharBefore"); + eq(cm.getValue(), "fo baz\nab ghi\n"); + select(cm, Pos(0, 3), Pos(0, 4), Pos(0, 5)); + cm.execCommand("delWordAfter"); + eq(cm.getValue(), "fo \nab ghi\n"); + }, {value: "foo bar baz\nabc def ghi\n"}); + + testCM("goLineStart", function(cm) { + select(cm, Pos(0, 2), Pos(0, 3), Pos(1, 1)); + cm.execCommand("goLineStart"); + hasCursors(cm, 0, 0, 1, 0); + select(cm, Pos(1, 1), Pos(0, 1)); + cm.setExtending(true); + cm.execCommand("goLineStart"); + hasSelections(cm, 0, 1, 0, 0, + 1, 1, 1, 0); + }, {value: "foo\nbar\nbaz"}); + + testCM("moveV", function(cm) { + select(cm, Pos(0, 2), Pos(1, 2)); + cm.execCommand("goLineDown"); + hasCursors(cm, 1, 2, 2, 2); + cm.execCommand("goLineUp"); + hasCursors(cm, 0, 2, 1, 2); + cm.execCommand("goLineUp"); + hasCursors(cm, 0, 0, 0, 2); + cm.execCommand("goLineUp"); + hasCursors(cm, 0, 0); + select(cm, Pos(0, 2), Pos(1, 2)); + cm.setExtending(true); + cm.execCommand("goLineDown"); + hasSelections(cm, 0, 2, 2, 2); + }, {value: "12345\n12345\n12345"}); + + testCM("moveH", function(cm) { + select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5), Pos(2, 3)); + cm.execCommand("goCharRight"); + hasCursors(cm, 0, 2, 0, 4, 1, 0, 2, 4); + cm.execCommand("goCharLeft"); + hasCursors(cm, 0, 1, 0, 3, 0, 5, 2, 3); + for (var i = 0; i < 15; i++) + cm.execCommand("goCharRight"); + hasCursors(cm, 2, 4, 2, 5); + }, {value: "12345\n12345\n12345"}); + + testCM("newlineAndIndent", function(cm) { + select(cm, Pos(0, 5), Pos(1, 5)); + cm.execCommand("newlineAndIndent"); + hasCursors(cm, 1, 2, 3, 2); + eq(cm.getValue(), "x = [\n 1];\ny = [\n 2];"); + cm.undo(); + eq(cm.getValue(), "x = [1];\ny = [2];"); + hasCursors(cm, 0, 5, 1, 5); + select(cm, Pos(0, 5), Pos(0, 6)); + cm.execCommand("newlineAndIndent"); + hasCursors(cm, 1, 2, 2, 0); + eq(cm.getValue(), "x = [\n 1\n];\ny = [2];"); + }, {value: "x = [1];\ny = [2];", mode: "javascript"}); + + testCM("goDocStartEnd", function(cm) { + select(cm, Pos(0, 1), Pos(1, 1)); + cm.execCommand("goDocStart"); + hasCursors(cm, 0, 0); + select(cm, Pos(0, 1), Pos(1, 1)); + cm.execCommand("goDocEnd"); + hasCursors(cm, 1, 3); + select(cm, Pos(0, 1), Pos(1, 1)); + cm.setExtending(true); + cm.execCommand("goDocEnd"); + hasSelections(cm, 1, 1, 1, 3); + }, {value: "abc\ndef"}); + + testCM("selectionHistory", function(cm) { + for (var i = 0; i < 3; ++i) + cm.addSelection(Pos(0, i * 2), Pos(0, i * 2 + 1)); + cm.execCommand("undoSelection"); + eq(cm.getSelection(), "1\n2"); + cm.execCommand("undoSelection"); + eq(cm.getSelection(), "1"); + cm.execCommand("undoSelection"); + eq(cm.getSelection(), ""); + eqPos(cm.getCursor(), Pos(0, 0)); + cm.execCommand("redoSelection"); + eq(cm.getSelection(), "1"); + cm.execCommand("redoSelection"); + eq(cm.getSelection(), "1\n2"); + cm.execCommand("redoSelection"); + eq(cm.getSelection(), "1\n2\n3"); + }, {value: "1 2 3"}); +})(); diff --git a/www/code/codemirror-5.13.2/test/phantom_driver.js b/www/code/codemirror-5.13.2/test/phantom_driver.js new file mode 100644 index 000000000..dbad08db6 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/phantom_driver.js @@ -0,0 +1,31 @@ +var page = require('webpage').create(); + +page.open("http://localhost:3000/test/index.html", function (status) { + if (status != "success") { + console.log("page couldn't be loaded successfully"); + phantom.exit(1); + } + waitFor(function () { + return page.evaluate(function () { + var output = document.getElementById('status'); + if (!output) { return false; } + return (/^(\d+ failures?|all passed)/i).test(output.innerText); + }); + }, function () { + var failed = page.evaluate(function () { return window.failed; }); + var output = page.evaluate(function () { + return document.getElementById('output').innerText + "\n" + + document.getElementById('status').innerText; + }); + console.log(output); + phantom.exit(failed > 0 ? 1 : 0); + }); +}); + +function waitFor (test, cb) { + if (test()) { + cb(); + } else { + setTimeout(function () { waitFor(test, cb); }, 250); + } +} diff --git a/www/code/codemirror-5.13.2/test/run.js b/www/code/codemirror-5.13.2/test/run.js new file mode 100644 index 000000000..24b835c37 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/run.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node + +var ok = require("./lint").ok; + +var files = new (require('node-static').Server)(); + +var server = require('http').createServer(function (req, res) { + req.addListener('end', function () { + files.serve(req, res, function (err/*, result */) { + if (err) { + console.error(err); + process.exit(1); + } + }); + }).resume(); +}).addListener('error', function (err) { + throw err; +}).listen(3000, function () { + var childProcess = require('child_process'); + var phantomjs = require("phantomjs"); + var childArgs = [ + require("path").join(__dirname, 'phantom_driver.js') + ]; + childProcess.execFile(phantomjs.path, childArgs, function (err, stdout, stderr) { + server.close(); + console.log(stdout); + if (err) console.error(err); + if (stderr) console.error(stderr); + process.exit(err || stderr || !ok ? 1 : 0); + }); +}); diff --git a/www/code/codemirror-5.13.2/test/scroll_test.js b/www/code/codemirror-5.13.2/test/scroll_test.js new file mode 100644 index 000000000..55aac78eb --- /dev/null +++ b/www/code/codemirror-5.13.2/test/scroll_test.js @@ -0,0 +1,115 @@ +(function() { + "use strict"; + + namespace = "scroll_"; + + testCM("bars_hidden", function(cm) { + for (var i = 0;; i++) { + var wrapBox = cm.getWrapperElement().getBoundingClientRect(); + var scrollBox = cm.getScrollerElement().getBoundingClientRect(); + is(wrapBox.bottom < scrollBox.bottom - 10); + is(wrapBox.right < scrollBox.right - 10); + if (i == 1) break; + cm.getWrapperElement().style.height = "auto"; + cm.refresh(); + } + }); + + function barH(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0]; } + function barV(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-vscrollbar")[0]; } + + function displayBottom(cm, scrollbar) { + if (scrollbar && cm.display.scroller.offsetHeight > cm.display.scroller.clientHeight) + return barH(cm).getBoundingClientRect().top; + else + return cm.getWrapperElement().getBoundingClientRect().bottom - 1; + } + + function displayRight(cm, scrollbar) { + if (scrollbar && cm.display.scroller.offsetWidth > cm.display.scroller.clientWidth) + return barV(cm).getBoundingClientRect().left; + else + return cm.getWrapperElement().getBoundingClientRect().right - 1; + } + + function testMovedownFixed(cm, hScroll) { + cm.setSize("100px", "100px"); + if (hScroll) cm.setValue(new Array(100).join("x")); + var bottom = displayBottom(cm, hScroll); + for (var i = 0; i < 30; i++) { + cm.replaceSelection("x\n"); + var cursorBottom = cm.cursorCoords(null, "window").bottom; + is(cursorBottom <= bottom); + } + is(cursorBottom >= bottom - 5); + } + + testCM("movedown_fixed", function(cm) {testMovedownFixed(cm, false);}); + testCM("movedown_hscroll_fixed", function(cm) {testMovedownFixed(cm, true);}); + + function testMovedownResize(cm, hScroll) { + cm.getWrapperElement().style.height = "auto"; + if (hScroll) cm.setValue(new Array(100).join("x")); + cm.refresh(); + for (var i = 0; i < 30; i++) { + cm.replaceSelection("x\n"); + var bottom = displayBottom(cm, hScroll); + var cursorBottom = cm.cursorCoords(null, "window").bottom; + is(cursorBottom <= bottom); + is(cursorBottom >= bottom - 5); + } + } + + testCM("movedown_resize", function(cm) {testMovedownResize(cm, false);}); + testCM("movedown_hscroll_resize", function(cm) {testMovedownResize(cm, true);}); + + function testMoveright(cm, wrap, scroll) { + cm.setSize("100px", "100px"); + if (wrap) cm.setOption("lineWrapping", true); + if (scroll) { + cm.setValue("\n" + new Array(100).join("x\n")); + cm.setCursor(Pos(0, 0)); + } + var right = displayRight(cm, scroll); + for (var i = 0; i < 10; i++) { + cm.replaceSelection("xxxxxxxxxx"); + var cursorRight = cm.cursorCoords(null, "window").right; + is(cursorRight < right); + } + if (!wrap) is(cursorRight > right - 20); + } + + testCM("moveright", function(cm) {testMoveright(cm, false, false);}); + testCM("moveright_wrap", function(cm) {testMoveright(cm, true, false);}); + testCM("moveright_scroll", function(cm) {testMoveright(cm, false, true);}); + testCM("moveright_scroll_wrap", function(cm) {testMoveright(cm, true, true);}); + + testCM("suddenly_wide", function(cm) { + addDoc(cm, 100, 100); + cm.replaceSelection(new Array(600).join("l ") + "\n"); + cm.execCommand("goLineUp"); + cm.execCommand("goLineEnd"); + is(barH(cm).scrollLeft > cm.getScrollerElement().scrollLeft - 1); + }); + + testCM("wrap_changes_height", function(cm) { + var line = new Array(20).join("a ") + "\n"; + cm.setValue(new Array(20).join(line)); + var box = cm.getWrapperElement().getBoundingClientRect(); + cm.setSize(cm.cursorCoords(Pos(0), "window").right - box.left + 2, + cm.cursorCoords(Pos(19, 0), "window").bottom - box.top + 2); + cm.setCursor(Pos(19, 0)); + cm.replaceSelection("\n"); + is(cm.cursorCoords(null, "window").bottom < displayBottom(cm, false)); + }, {lineWrapping: true}); + + testCM("height_auto_with_gutter_expect_no_scroll_after_line_delete", function(cm) { + cm.setSize(null, "auto"); + cm.setValue("x\n"); + cm.execCommand("goDocEnd"); + cm.execCommand("delCharBefore"); + eq(cm.getScrollInfo().top, 0); + cm.scrollTo(null, 10); + is(cm.getScrollInfo().top < 5); + }, {lineNumbers: true}); +})(); diff --git a/www/code/codemirror-5.13.2/test/search_test.js b/www/code/codemirror-5.13.2/test/search_test.js new file mode 100644 index 000000000..04a1e685a --- /dev/null +++ b/www/code/codemirror-5.13.2/test/search_test.js @@ -0,0 +1,62 @@ +(function() { + "use strict"; + + function test(name) { + var text = Array.prototype.slice.call(arguments, 1, arguments.length - 1).join("\n"); + var body = arguments[arguments.length - 1]; + return window.test("search_" + name, function() { + body(new CodeMirror.Doc(text)); + }); + } + + function run(doc, query, insensitive) { + var cursor = doc.getSearchCursor(query, null, insensitive); + for (var i = 3; i < arguments.length; i += 4) { + var found = cursor.findNext(); + is(found, "not enough results (forward)"); + eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, forward, " + (i - 3) / 4); + eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, forward, " + (i - 3) / 4); + } + is(!cursor.findNext(), "too many matches (forward)"); + for (var i = arguments.length - 4; i >= 3; i -= 4) { + var found = cursor.findPrevious(); + is(found, "not enough results (backwards)"); + eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, backwards, " + (i - 3) / 4); + eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, backwards, " + (i - 3) / 4); + } + is(!cursor.findPrevious(), "too many matches (backwards)"); + } + + test("simple", "abcdefg", "abcdefg", function(doc) { + run(doc, "cde", false, 0, 2, 0, 5, 1, 2, 1, 5); + }); + + test("multiline", "hallo", "goodbye", function(doc) { + run(doc, "llo\ngoo", false, 0, 2, 1, 3); + run(doc, "blah\nhall", false); + run(doc, "bye\neye", false); + }); + + test("regexp", "abcde", "abcde", function(doc) { + run(doc, /bcd/, false, 0, 1, 0, 4, 1, 1, 1, 4); + run(doc, /BCD/, false); + run(doc, /BCD/i, false, 0, 1, 0, 4, 1, 1, 1, 4); + }); + + test("insensitive", "hallo", "HALLO", "oink", "hAllO", function(doc) { + run(doc, "All", false, 3, 1, 3, 4); + run(doc, "All", true, 0, 1, 0, 4, 1, 1, 1, 4, 3, 1, 3, 4); + }); + + test("multilineInsensitive", "zie ginds komT", "De Stoomboot", "uit Spanje weer aan", function(doc) { + run(doc, "komt\nde stoomboot\nuit", false); + run(doc, "komt\nde stoomboot\nuit", true, 0, 10, 2, 3); + run(doc, "kOMt\ndE stOOmboot\nuiT", true, 0, 10, 2, 3); + }); + + test("expandingCaseFold", "İİ İİ", "uu uu", function(doc) { + if (phantom) return; // A Phantom bug makes this hang + run(doc, "", true, 0, 8, 0, 12, 1, 8, 1, 12); + run(doc, "İİ", true, 0, 3, 0, 5, 0, 6, 0, 8); + }); +})(); diff --git a/www/code/codemirror-5.13.2/test/sql-hint-test.js b/www/code/codemirror-5.13.2/test/sql-hint-test.js new file mode 100644 index 000000000..39f2172e1 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/sql-hint-test.js @@ -0,0 +1,189 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var Pos = CodeMirror.Pos; + + var simpleTables = { + "users": ["name", "score", "birthDate"], + "xcountries": ["name", "population", "size"] + }; + + var schemaTables = { + "schema.users": ["name", "score", "birthDate"], + "schema.countries": ["name", "population", "size"] + }; + + var displayTextTables = [{ + text: "mytable", + displayText: "mytable | The main table", + columns: [{text: "id", displayText: "id | Unique ID"}, + {text: "name", displayText: "name | The name"}] + }]; + + namespace = "sql-hint_"; + + function test(name, spec) { + testCM(name, function(cm) { + cm.setValue(spec.value); + cm.setCursor(spec.cursor); + var completion = CodeMirror.hint.sql(cm, {tables: spec.tables}); + if (!deepCompare(completion.list, spec.list)) + throw new Failure("Wrong completion results " + JSON.stringify(completion.list) + " vs " + JSON.stringify(spec.list)); + eqPos(completion.from, spec.from); + eqPos(completion.to, spec.to); + }, { + value: spec.value, + mode: "text/x-mysql" + }); + } + + test("keywords", { + value: "SEL", + cursor: Pos(0, 3), + list: ["SELECT"], + from: Pos(0, 0), + to: Pos(0, 3) + }); + + test("from", { + value: "SELECT * fr", + cursor: Pos(0, 11), + list: ["FROM"], + from: Pos(0, 9), + to: Pos(0, 11) + }); + + test("table", { + value: "SELECT xc", + cursor: Pos(0, 9), + tables: simpleTables, + list: ["xcountries"], + from: Pos(0, 7), + to: Pos(0, 9) + }); + + test("columns", { + value: "SELECT users.", + cursor: Pos(0, 13), + tables: simpleTables, + list: ["users.name", "users.score", "users.birthDate"], + from: Pos(0, 7), + to: Pos(0, 13) + }); + + test("singlecolumn", { + value: "SELECT users.na", + cursor: Pos(0, 15), + tables: simpleTables, + list: ["users.name"], + from: Pos(0, 7), + to: Pos(0, 15) + }); + + test("quoted", { + value: "SELECT `users`.`na", + cursor: Pos(0, 18), + tables: simpleTables, + list: ["`users`.`name`"], + from: Pos(0, 7), + to: Pos(0, 18) + }); + + test("quotedcolumn", { + value: "SELECT users.`na", + cursor: Pos(0, 16), + tables: simpleTables, + list: ["`users`.`name`"], + from: Pos(0, 7), + to: Pos(0, 16) + }); + + test("schema", { + value: "SELECT schem", + cursor: Pos(0, 12), + tables: schemaTables, + list: ["schema.users", "schema.countries", + "SCHEMA", "SCHEMA_NAME", "SCHEMAS"], + from: Pos(0, 7), + to: Pos(0, 12) + }); + + test("schemaquoted", { + value: "SELECT `sch", + cursor: Pos(0, 11), + tables: schemaTables, + list: ["`schema`.`users`", "`schema`.`countries`"], + from: Pos(0, 7), + to: Pos(0, 11) + }); + + test("schemacolumn", { + value: "SELECT schema.users.", + cursor: Pos(0, 20), + tables: schemaTables, + list: ["schema.users.name", + "schema.users.score", + "schema.users.birthDate"], + from: Pos(0, 7), + to: Pos(0, 20) + }); + + test("schemacolumnquoted", { + value: "SELECT `schema`.`users`.", + cursor: Pos(0, 24), + tables: schemaTables, + list: ["`schema`.`users`.`name`", + "`schema`.`users`.`score`", + "`schema`.`users`.`birthDate`"], + from: Pos(0, 7), + to: Pos(0, 24) + }); + + test("displayText_table", { + value: "SELECT myt", + cursor: Pos(0, 10), + tables: displayTextTables, + list: displayTextTables, + from: Pos(0, 7), + to: Pos(0, 10) + }); + + test("displayText_column", { + value: "SELECT mytable.", + cursor: Pos(0, 15), + tables: displayTextTables, + list: [{text: "mytable.id", displayText: "id | Unique ID"}, + {text: "mytable.name", displayText: "name | The name"}], + from: Pos(0, 7), + to: Pos(0, 15) + }); + + test("alias_complete", { + value: "SELECT t. FROM users t", + cursor: Pos(0, 9), + tables: simpleTables, + list: ["t.name", "t.score", "t.birthDate"], + from: Pos(0, 7), + to: Pos(0, 9) + }); + + test("alias_complete_with_displayText", { + value: "SELECT t. FROM mytable t", + cursor: Pos(0, 9), + tables: displayTextTables, + list: [{text: "t.id", displayText: "id | Unique ID"}, + {text: "t.name", displayText: "name | The name"}], + from: Pos(0, 7), + to: Pos(0, 9) + }) + + function deepCompare(a, b) { + if (!a || typeof a != "object") + return a === b; + if (!b || typeof b != "object") + return false; + for (var prop in a) if (!deepCompare(a[prop], b[prop])) return false; + return true; + } +})(); diff --git a/www/code/codemirror-5.13.2/test/sublime_test.js b/www/code/codemirror-5.13.2/test/sublime_test.js new file mode 100644 index 000000000..c93e041b8 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/sublime_test.js @@ -0,0 +1,303 @@ +(function() { + "use strict"; + + var Pos = CodeMirror.Pos; + namespace = "sublime_"; + + function stTest(name) { + var actions = Array.prototype.slice.call(arguments, 1); + testCM(name, function(cm) { + for (var i = 0; i < actions.length; i++) { + var action = actions[i]; + if (typeof action == "string" && i == 0) + cm.setValue(action); + else if (typeof action == "string") + cm.execCommand(action); + else if (action instanceof Pos) + cm.setCursor(action); + else + action(cm); + } + }); + } + + function at(line, ch, msg) { + return function(cm) { + eq(cm.listSelections().length, 1); + eqPos(cm.getCursor("head"), Pos(line, ch), msg); + eqPos(cm.getCursor("anchor"), Pos(line, ch), msg); + }; + } + + function val(content, msg) { + return function(cm) { eq(cm.getValue(), content, msg); }; + } + + function argsToRanges(args) { + if (args.length % 4) throw new Error("Wrong number of arguments for ranges."); + var ranges = []; + for (var i = 0; i < args.length; i += 4) + ranges.push({anchor: Pos(args[i], args[i + 1]), + head: Pos(args[i + 2], args[i + 3])}); + return ranges; + } + + function setSel() { + var ranges = argsToRanges(arguments); + return function(cm) { cm.setSelections(ranges, 0); }; + } + + function hasSel() { + var ranges = argsToRanges(arguments); + return function(cm) { + var sels = cm.listSelections(); + if (sels.length != ranges.length) + throw new Failure("Expected " + ranges.length + " selections, but found " + sels.length); + for (var i = 0; i < sels.length; i++) { + eqPos(sels[i].anchor, ranges[i].anchor, "anchor " + i); + eqPos(sels[i].head, ranges[i].head, "head " + i); + } + }; + } + + stTest("bySubword", "the foo_bar DooDahBah \n a", + "goSubwordLeft", at(0, 0), + "goSubwordRight", at(0, 3), + "goSubwordRight", at(0, 7), + "goSubwordRight", at(0, 11), + "goSubwordRight", at(0, 15), + "goSubwordRight", at(0, 18), + "goSubwordRight", at(0, 21), + "goSubwordRight", at(0, 22), + "goSubwordRight", at(1, 0), + "goSubwordRight", at(1, 2), + "goSubwordRight", at(1, 2), + "goSubwordLeft", at(1, 1), + "goSubwordLeft", at(1, 0), + "goSubwordLeft", at(0, 22), + "goSubwordLeft", at(0, 18), + "goSubwordLeft", at(0, 15), + "goSubwordLeft", at(0, 12), + "goSubwordLeft", at(0, 8), + "goSubwordLeft", at(0, 4), + "goSubwordLeft", at(0, 0)); + + stTest("splitSelectionByLine", "abc\ndef\nghi", + setSel(0, 1, 2, 2), + "splitSelectionByLine", + hasSel(0, 1, 0, 3, + 1, 0, 1, 3, + 2, 0, 2, 2)); + + stTest("splitSelectionByLineMulti", "abc\ndef\nghi\njkl", + setSel(0, 1, 1, 1, + 1, 2, 3, 2, + 3, 3, 3, 3), + "splitSelectionByLine", + hasSel(0, 1, 0, 3, + 1, 0, 1, 1, + 1, 2, 1, 3, + 2, 0, 2, 3, + 3, 0, 3, 2, + 3, 3, 3, 3)); + + stTest("selectLine", "abc\ndef\nghi", + setSel(0, 1, 0, 1, + 2, 0, 2, 1), + "selectLine", + hasSel(0, 0, 1, 0, + 2, 0, 2, 3), + setSel(0, 1, 1, 0), + "selectLine", + hasSel(0, 0, 2, 0)); + + stTest("insertLineAfter", "abcde\nfghijkl\nmn", + setSel(0, 1, 0, 1, + 0, 3, 0, 3, + 1, 2, 1, 2, + 1, 3, 1, 5), "insertLineAfter", + hasSel(1, 0, 1, 0, + 3, 0, 3, 0), val("abcde\n\nfghijkl\n\nmn")); + + stTest("insertLineBefore", "abcde\nfghijkl\nmn", + setSel(0, 1, 0, 1, + 0, 3, 0, 3, + 1, 2, 1, 2, + 1, 3, 1, 5), "insertLineBefore", + hasSel(0, 0, 0, 0, + 2, 0, 2, 0), val("\nabcde\n\nfghijkl\nmn")); + + stTest("selectNextOccurrence", "a foo bar\nfoobar foo", + setSel(0, 2, 0, 5), + "selectNextOccurrence", hasSel(0, 2, 0, 5, + 1, 0, 1, 3), + "selectNextOccurrence", hasSel(0, 2, 0, 5, + 1, 0, 1, 3, + 1, 7, 1, 10), + "selectNextOccurrence", hasSel(0, 2, 0, 5, + 1, 0, 1, 3, + 1, 7, 1, 10), + Pos(0, 3), "selectNextOccurrence", hasSel(0, 2, 0, 5), + "selectNextOccurrence", hasSel(0, 2, 0, 5, + 1, 7, 1, 10), + setSel(0, 6, 0, 9), + "selectNextOccurrence", hasSel(0, 6, 0, 9, + 1, 3, 1, 6)); + + stTest("selectScope", "foo(a) {\n bar[1, 2];\n}", + "selectScope", hasSel(0, 0, 2, 1), + Pos(0, 4), "selectScope", hasSel(0, 4, 0, 5), + Pos(0, 5), "selectScope", hasSel(0, 4, 0, 5), + Pos(0, 6), "selectScope", hasSel(0, 0, 2, 1), + Pos(0, 8), "selectScope", hasSel(0, 8, 2, 0), + Pos(1, 2), "selectScope", hasSel(0, 8, 2, 0), + Pos(1, 6), "selectScope", hasSel(1, 6, 1, 10), + Pos(1, 9), "selectScope", hasSel(1, 6, 1, 10)); + + stTest("goToBracket", "foo(a) {\n bar[1, 2];\n}", + Pos(0, 0), "goToBracket", at(0, 0), + Pos(0, 4), "goToBracket", at(0, 5), "goToBracket", at(0, 4), + Pos(0, 8), "goToBracket", at(2, 0), "goToBracket", at(0, 8), + Pos(1, 2), "goToBracket", at(2, 0), + Pos(1, 7), "goToBracket", at(1, 10), "goToBracket", at(1, 6)); + + stTest("swapLine", "1\n2\n3---\n4\n5", + "swapLineDown", val("2\n1\n3---\n4\n5"), + "swapLineUp", val("1\n2\n3---\n4\n5"), + "swapLineUp", val("1\n2\n3---\n4\n5"), + Pos(4, 1), "swapLineDown", val("1\n2\n3---\n4\n5"), + setSel(0, 1, 0, 1, + 1, 0, 2, 0, + 2, 2, 2, 2), + "swapLineDown", val("4\n1\n2\n3---\n5"), + hasSel(1, 1, 1, 1, + 2, 0, 3, 0, + 3, 2, 3, 2), + "swapLineUp", val("1\n2\n3---\n4\n5"), + hasSel(0, 1, 0, 1, + 1, 0, 2, 0, + 2, 2, 2, 2)); + + stTest("swapLineEmptyBottomSel", "1\n2\n3", + setSel(0, 1, 1, 0), + "swapLineDown", val("2\n1\n3"), hasSel(1, 1, 2, 0), + "swapLineUp", val("1\n2\n3"), hasSel(0, 1, 1, 0), + "swapLineUp", val("1\n2\n3"), hasSel(0, 0, 0, 0)); + + stTest("swapLineUpFromEnd", "a\nb\nc", + Pos(2, 1), "swapLineUp", + hasSel(1, 1, 1, 1), val("a\nc\nb")); + + stTest("joinLines", "abc\ndef\nghi\njkl", + "joinLines", val("abc def\nghi\njkl"), at(0, 4), + "undo", + setSel(0, 2, 1, 1), "joinLines", + val("abc def ghi\njkl"), hasSel(0, 2, 0, 8), + "undo", + setSel(0, 1, 0, 1, + 1, 1, 1, 1, + 3, 1, 3, 1), "joinLines", + val("abc def ghi\njkl"), hasSel(0, 4, 0, 4, + 0, 8, 0, 8, + 1, 3, 1, 3)); + + stTest("duplicateLine", "abc\ndef\nghi", + Pos(1, 0), "duplicateLine", val("abc\ndef\ndef\nghi"), at(2, 0), + "undo", + setSel(0, 1, 0, 1, + 1, 1, 1, 1, + 2, 1, 2, 1), "duplicateLine", + val("abc\nabc\ndef\ndef\nghi\nghi"), hasSel(1, 1, 1, 1, + 3, 1, 3, 1, + 5, 1, 5, 1)); + stTest("duplicateLineSelection", "abcdef", + setSel(0, 1, 0, 1, + 0, 2, 0, 4, + 0, 5, 0, 5), + "duplicateLine", + val("abcdef\nabcdcdef\nabcdcdef"), hasSel(2, 1, 2, 1, + 2, 4, 2, 6, + 2, 7, 2, 7)); + + stTest("selectLinesUpward", "123\n345\n789\n012", + setSel(0, 1, 0, 1, + 1, 1, 1, 3, + 2, 0, 2, 0, + 3, 0, 3, 0), + "selectLinesUpward", + hasSel(0, 1, 0, 1, + 0, 3, 0, 3, + 1, 0, 1, 0, + 1, 1, 1, 3, + 2, 0, 2, 0, + 3, 0, 3, 0)); + + stTest("selectLinesDownward", "123\n345\n789\n012", + setSel(0, 1, 0, 1, + 1, 1, 1, 3, + 2, 0, 2, 0, + 3, 0, 3, 0), + "selectLinesDownward", + hasSel(0, 1, 0, 1, + 1, 1, 1, 3, + 2, 0, 2, 0, + 2, 3, 2, 3, + 3, 0, 3, 0)); + + stTest("sortLines", "c\nb\na\nC\nB\nA", + "sortLines", val("A\nB\nC\na\nb\nc"), + "undo", + setSel(0, 0, 2, 0, + 3, 0, 5, 0), + "sortLines", val("a\nb\nc\nA\nB\nC"), + hasSel(0, 0, 2, 1, + 3, 0, 5, 1), + "undo", + setSel(1, 0, 4, 0), "sortLinesInsensitive", val("c\na\nB\nb\nC\nA")); + + stTest("bookmarks", "abc\ndef\nghi\njkl", + Pos(0, 1), "toggleBookmark", + setSel(1, 1, 1, 2), "toggleBookmark", + setSel(2, 1, 2, 2), "toggleBookmark", + "nextBookmark", hasSel(0, 1, 0, 1), + "nextBookmark", hasSel(1, 1, 1, 2), + "nextBookmark", hasSel(2, 1, 2, 2), + "prevBookmark", hasSel(1, 1, 1, 2), + "prevBookmark", hasSel(0, 1, 0, 1), + "prevBookmark", hasSel(2, 1, 2, 2), + "prevBookmark", hasSel(1, 1, 1, 2), + "toggleBookmark", + "prevBookmark", hasSel(2, 1, 2, 2), + "prevBookmark", hasSel(0, 1, 0, 1), + "selectBookmarks", hasSel(0, 1, 0, 1, + 2, 1, 2, 2), + "clearBookmarks", + Pos(0, 0), "selectBookmarks", at(0, 0)); + + stTest("upAndDowncaseAtCursor", "abc\ndef x\nghI", + setSel(0, 1, 0, 3, + 1, 1, 1, 1, + 1, 4, 1, 4), "upcaseAtCursor", + val("aBC\nDEF x\nghI"), hasSel(0, 1, 0, 3, + 1, 3, 1, 3, + 1, 4, 1, 4), + "downcaseAtCursor", + val("abc\ndef x\nghI"), hasSel(0, 1, 0, 3, + 1, 3, 1, 3, + 1, 4, 1, 4)); + + stTest("mark", "abc\ndef\nghi", + Pos(1, 1), "setSublimeMark", + Pos(2, 1), "selectToSublimeMark", hasSel(2, 1, 1, 1), + Pos(0, 1), "swapWithSublimeMark", at(1, 1), "swapWithSublimeMark", at(0, 1), + "deleteToSublimeMark", val("aef\nghi"), + "sublimeYank", val("abc\ndef\nghi"), at(1, 1)); + + stTest("findUnder", "foo foobar a", + "findUnder", hasSel(0, 4, 0, 7), + "findUnder", hasSel(0, 0, 0, 3), + "findUnderPrevious", hasSel(0, 4, 0, 7), + "findUnderPrevious", hasSel(0, 0, 0, 3), + Pos(0, 4), "findUnder", hasSel(0, 4, 0, 10), + Pos(0, 11), "findUnder", hasSel(0, 11, 0, 11)); +})(); diff --git a/www/code/codemirror-5.13.2/test/test.js b/www/code/codemirror-5.13.2/test/test.js new file mode 100644 index 000000000..82ee231e3 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/test.js @@ -0,0 +1,2151 @@ +var Pos = CodeMirror.Pos; + +CodeMirror.defaults.rtlMoveVisually = true; + +function forEach(arr, f) { + for (var i = 0, e = arr.length; i < e; ++i) f(arr[i], i); +} + +function addDoc(cm, width, height) { + var content = [], line = ""; + for (var i = 0; i < width; ++i) line += "x"; + for (var i = 0; i < height; ++i) content.push(line); + cm.setValue(content.join("\n")); +} + +function byClassName(elt, cls) { + if (elt.getElementsByClassName) return elt.getElementsByClassName(cls); + var found = [], re = new RegExp("\\b" + cls + "\\b"); + function search(elt) { + if (elt.nodeType == 3) return; + if (re.test(elt.className)) found.push(elt); + for (var i = 0, e = elt.childNodes.length; i < e; ++i) + search(elt.childNodes[i]); + } + search(elt); + return found; +} + +var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); +var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); +var mac = /Mac/.test(navigator.platform); +var phantom = /PhantomJS/.test(navigator.userAgent); +var opera = /Opera\/\./.test(navigator.userAgent); +var opera_version = opera && navigator.userAgent.match(/Version\/(\d+\.\d+)/); +if (opera_version) opera_version = Number(opera_version); +var opera_lt10 = opera && (!opera_version || opera_version < 10); + +namespace = "core_"; + +test("core_fromTextArea", function() { + var te = document.getElementById("code"); + te.value = "CONTENT"; + var cm = CodeMirror.fromTextArea(te); + is(!te.offsetHeight); + eq(cm.getValue(), "CONTENT"); + cm.setValue("foo\nbar"); + eq(cm.getValue(), "foo\nbar"); + cm.save(); + is(/^foo\r?\nbar$/.test(te.value)); + cm.setValue("xxx"); + cm.toTextArea(); + is(te.offsetHeight); + eq(te.value, "xxx"); +}); + +testCM("getRange", function(cm) { + eq(cm.getLine(0), "1234"); + eq(cm.getLine(1), "5678"); + eq(cm.getLine(2), null); + eq(cm.getLine(-1), null); + eq(cm.getRange(Pos(0, 0), Pos(0, 3)), "123"); + eq(cm.getRange(Pos(0, -1), Pos(0, 200)), "1234"); + eq(cm.getRange(Pos(0, 2), Pos(1, 2)), "34\n56"); + eq(cm.getRange(Pos(1, 2), Pos(100, 0)), "78"); +}, {value: "1234\n5678"}); + +testCM("replaceRange", function(cm) { + eq(cm.getValue(), ""); + cm.replaceRange("foo\n", Pos(0, 0)); + eq(cm.getValue(), "foo\n"); + cm.replaceRange("a\nb", Pos(0, 1)); + eq(cm.getValue(), "fa\nboo\n"); + eq(cm.lineCount(), 3); + cm.replaceRange("xyzzy", Pos(0, 0), Pos(1, 1)); + eq(cm.getValue(), "xyzzyoo\n"); + cm.replaceRange("abc", Pos(0, 0), Pos(10, 0)); + eq(cm.getValue(), "abc"); + eq(cm.lineCount(), 1); +}); + +testCM("selection", function(cm) { + cm.setSelection(Pos(0, 4), Pos(2, 2)); + is(cm.somethingSelected()); + eq(cm.getSelection(), "11\n222222\n33"); + eqPos(cm.getCursor(false), Pos(2, 2)); + eqPos(cm.getCursor(true), Pos(0, 4)); + cm.setSelection(Pos(1, 0)); + is(!cm.somethingSelected()); + eq(cm.getSelection(), ""); + eqPos(cm.getCursor(true), Pos(1, 0)); + cm.replaceSelection("abc", "around"); + eq(cm.getSelection(), "abc"); + eq(cm.getValue(), "111111\nabc222222\n333333"); + cm.replaceSelection("def", "end"); + eq(cm.getSelection(), ""); + eqPos(cm.getCursor(true), Pos(1, 3)); + cm.setCursor(Pos(2, 1)); + eqPos(cm.getCursor(true), Pos(2, 1)); + cm.setCursor(1, 2); + eqPos(cm.getCursor(true), Pos(1, 2)); +}, {value: "111111\n222222\n333333"}); + +testCM("extendSelection", function(cm) { + cm.setExtending(true); + addDoc(cm, 10, 10); + cm.setSelection(Pos(3, 5)); + eqPos(cm.getCursor("head"), Pos(3, 5)); + eqPos(cm.getCursor("anchor"), Pos(3, 5)); + cm.setSelection(Pos(2, 5), Pos(5, 5)); + eqPos(cm.getCursor("head"), Pos(5, 5)); + eqPos(cm.getCursor("anchor"), Pos(2, 5)); + eqPos(cm.getCursor("start"), Pos(2, 5)); + eqPos(cm.getCursor("end"), Pos(5, 5)); + cm.setSelection(Pos(5, 5), Pos(2, 5)); + eqPos(cm.getCursor("head"), Pos(2, 5)); + eqPos(cm.getCursor("anchor"), Pos(5, 5)); + eqPos(cm.getCursor("start"), Pos(2, 5)); + eqPos(cm.getCursor("end"), Pos(5, 5)); + cm.extendSelection(Pos(3, 2)); + eqPos(cm.getCursor("head"), Pos(3, 2)); + eqPos(cm.getCursor("anchor"), Pos(5, 5)); + cm.extendSelection(Pos(6, 2)); + eqPos(cm.getCursor("head"), Pos(6, 2)); + eqPos(cm.getCursor("anchor"), Pos(5, 5)); + cm.extendSelection(Pos(6, 3), Pos(6, 4)); + eqPos(cm.getCursor("head"), Pos(6, 4)); + eqPos(cm.getCursor("anchor"), Pos(5, 5)); + cm.extendSelection(Pos(0, 3), Pos(0, 4)); + eqPos(cm.getCursor("head"), Pos(0, 3)); + eqPos(cm.getCursor("anchor"), Pos(5, 5)); + cm.extendSelection(Pos(4, 5), Pos(6, 5)); + eqPos(cm.getCursor("head"), Pos(6, 5)); + eqPos(cm.getCursor("anchor"), Pos(4, 5)); + cm.setExtending(false); + cm.extendSelection(Pos(0, 3), Pos(0, 4)); + eqPos(cm.getCursor("head"), Pos(0, 3)); + eqPos(cm.getCursor("anchor"), Pos(0, 4)); +}); + +testCM("lines", function(cm) { + eq(cm.getLine(0), "111111"); + eq(cm.getLine(1), "222222"); + eq(cm.getLine(-1), null); + cm.replaceRange("", Pos(1, 0), Pos(2, 0)) + cm.replaceRange("abc", Pos(1, 0), Pos(1)); + eq(cm.getValue(), "111111\nabc"); +}, {value: "111111\n222222\n333333"}); + +testCM("indent", function(cm) { + cm.indentLine(1); + eq(cm.getLine(1), " blah();"); + cm.setOption("indentUnit", 8); + cm.indentLine(1); + eq(cm.getLine(1), "\tblah();"); + cm.setOption("indentUnit", 10); + cm.setOption("tabSize", 4); + cm.indentLine(1); + eq(cm.getLine(1), "\t\t blah();"); +}, {value: "if (x) {\nblah();\n}", indentUnit: 3, indentWithTabs: true, tabSize: 8}); + +testCM("indentByNumber", function(cm) { + cm.indentLine(0, 2); + eq(cm.getLine(0), " foo"); + cm.indentLine(0, -200); + eq(cm.getLine(0), "foo"); + cm.setSelection(Pos(0, 0), Pos(1, 2)); + cm.indentSelection(3); + eq(cm.getValue(), " foo\n bar\nbaz"); +}, {value: "foo\nbar\nbaz"}); + +test("core_defaults", function() { + var defsCopy = {}, defs = CodeMirror.defaults; + for (var opt in defs) defsCopy[opt] = defs[opt]; + defs.indentUnit = 5; + defs.value = "uu"; + defs.indentWithTabs = true; + defs.tabindex = 55; + var place = document.getElementById("testground"), cm = CodeMirror(place); + try { + eq(cm.getOption("indentUnit"), 5); + cm.setOption("indentUnit", 10); + eq(defs.indentUnit, 5); + eq(cm.getValue(), "uu"); + eq(cm.getOption("indentWithTabs"), true); + eq(cm.getInputField().tabIndex, 55); + } + finally { + for (var opt in defsCopy) defs[opt] = defsCopy[opt]; + place.removeChild(cm.getWrapperElement()); + } +}); + +testCM("lineInfo", function(cm) { + eq(cm.lineInfo(-1), null); + var mark = document.createElement("span"); + var lh = cm.setGutterMarker(1, "FOO", mark); + var info = cm.lineInfo(1); + eq(info.text, "222222"); + eq(info.gutterMarkers.FOO, mark); + eq(info.line, 1); + eq(cm.lineInfo(2).gutterMarkers, null); + cm.setGutterMarker(lh, "FOO", null); + eq(cm.lineInfo(1).gutterMarkers, null); + cm.setGutterMarker(1, "FOO", mark); + cm.setGutterMarker(0, "FOO", mark); + cm.clearGutter("FOO"); + eq(cm.lineInfo(0).gutterMarkers, null); + eq(cm.lineInfo(1).gutterMarkers, null); +}, {value: "111111\n222222\n333333"}); + +testCM("coords", function(cm) { + cm.setSize(null, 100); + addDoc(cm, 32, 200); + var top = cm.charCoords(Pos(0, 0)); + var bot = cm.charCoords(Pos(200, 30)); + is(top.left < bot.left); + is(top.top < bot.top); + is(top.top < top.bottom); + cm.scrollTo(null, 100); + var top2 = cm.charCoords(Pos(0, 0)); + is(top.top > top2.top); + eq(top.left, top2.left); +}); + +testCM("coordsChar", function(cm) { + addDoc(cm, 35, 70); + for (var i = 0; i < 2; ++i) { + var sys = i ? "local" : "page"; + for (var ch = 0; ch <= 35; ch += 5) { + for (var line = 0; line < 70; line += 5) { + cm.setCursor(line, ch); + var coords = cm.charCoords(Pos(line, ch), sys); + var pos = cm.coordsChar({left: coords.left + 1, top: coords.top + 1}, sys); + eqPos(pos, Pos(line, ch)); + } + } + } +}, {lineNumbers: true}); + +testCM("posFromIndex", function(cm) { + cm.setValue( + "This function should\n" + + "convert a zero based index\n" + + "to line and ch." + ); + + var examples = [ + { index: -1, line: 0, ch: 0 }, // <- Tests clipping + { index: 0, line: 0, ch: 0 }, + { index: 10, line: 0, ch: 10 }, + { index: 39, line: 1, ch: 18 }, + { index: 55, line: 2, ch: 7 }, + { index: 63, line: 2, ch: 15 }, + { index: 64, line: 2, ch: 15 } // <- Tests clipping + ]; + + for (var i = 0; i < examples.length; i++) { + var example = examples[i]; + var pos = cm.posFromIndex(example.index); + eq(pos.line, example.line); + eq(pos.ch, example.ch); + if (example.index >= 0 && example.index < 64) + eq(cm.indexFromPos(pos), example.index); + } +}); + +testCM("undo", function(cm) { + cm.replaceRange("def", Pos(0, 0), Pos(0)); + eq(cm.historySize().undo, 1); + cm.undo(); + eq(cm.getValue(), "abc"); + eq(cm.historySize().undo, 0); + eq(cm.historySize().redo, 1); + cm.redo(); + eq(cm.getValue(), "def"); + eq(cm.historySize().undo, 1); + eq(cm.historySize().redo, 0); + cm.setValue("1\n\n\n2"); + cm.clearHistory(); + eq(cm.historySize().undo, 0); + for (var i = 0; i < 20; ++i) { + cm.replaceRange("a", Pos(0, 0)); + cm.replaceRange("b", Pos(3, 0)); + } + eq(cm.historySize().undo, 40); + for (var i = 0; i < 40; ++i) + cm.undo(); + eq(cm.historySize().redo, 40); + eq(cm.getValue(), "1\n\n\n2"); +}, {value: "abc"}); + +testCM("undoDepth", function(cm) { + cm.replaceRange("d", Pos(0)); + cm.replaceRange("e", Pos(0)); + cm.replaceRange("f", Pos(0)); + cm.undo(); cm.undo(); cm.undo(); + eq(cm.getValue(), "abcd"); +}, {value: "abc", undoDepth: 4}); + +testCM("undoDoesntClearValue", function(cm) { + cm.undo(); + eq(cm.getValue(), "x"); +}, {value: "x"}); + +testCM("undoMultiLine", function(cm) { + cm.operation(function() { + cm.replaceRange("x", Pos(0, 0)); + cm.replaceRange("y", Pos(1, 0)); + }); + cm.undo(); + eq(cm.getValue(), "abc\ndef\nghi"); + cm.operation(function() { + cm.replaceRange("y", Pos(1, 0)); + cm.replaceRange("x", Pos(0, 0)); + }); + cm.undo(); + eq(cm.getValue(), "abc\ndef\nghi"); + cm.operation(function() { + cm.replaceRange("y", Pos(2, 0)); + cm.replaceRange("x", Pos(1, 0)); + cm.replaceRange("z", Pos(2, 0)); + }); + cm.undo(); + eq(cm.getValue(), "abc\ndef\nghi", 3); +}, {value: "abc\ndef\nghi"}); + +testCM("undoComposite", function(cm) { + cm.replaceRange("y", Pos(1)); + cm.operation(function() { + cm.replaceRange("x", Pos(0)); + cm.replaceRange("z", Pos(2)); + }); + eq(cm.getValue(), "ax\nby\ncz\n"); + cm.undo(); + eq(cm.getValue(), "a\nby\nc\n"); + cm.undo(); + eq(cm.getValue(), "a\nb\nc\n"); + cm.redo(); cm.redo(); + eq(cm.getValue(), "ax\nby\ncz\n"); +}, {value: "a\nb\nc\n"}); + +testCM("undoSelection", function(cm) { + cm.setSelection(Pos(0, 2), Pos(0, 4)); + cm.replaceSelection(""); + cm.setCursor(Pos(1, 0)); + cm.undo(); + eqPos(cm.getCursor(true), Pos(0, 2)); + eqPos(cm.getCursor(false), Pos(0, 4)); + cm.setCursor(Pos(1, 0)); + cm.redo(); + eqPos(cm.getCursor(true), Pos(0, 2)); + eqPos(cm.getCursor(false), Pos(0, 2)); +}, {value: "abcdefgh\n"}); + +testCM("undoSelectionAsBefore", function(cm) { + cm.replaceSelection("abc", "around"); + cm.undo(); + cm.redo(); + eq(cm.getSelection(), "abc"); +}); + +testCM("selectionChangeConfusesHistory", function(cm) { + cm.replaceSelection("abc", null, "dontmerge"); + cm.operation(function() { + cm.setCursor(Pos(0, 0)); + cm.replaceSelection("abc", null, "dontmerge"); + }); + eq(cm.historySize().undo, 2); +}); + +testCM("markTextSingleLine", function(cm) { + forEach([{a: 0, b: 1, c: "", f: 2, t: 5}, + {a: 0, b: 4, c: "", f: 0, t: 2}, + {a: 1, b: 2, c: "x", f: 3, t: 6}, + {a: 4, b: 5, c: "", f: 3, t: 5}, + {a: 4, b: 5, c: "xx", f: 3, t: 7}, + {a: 2, b: 5, c: "", f: 2, t: 3}, + {a: 2, b: 5, c: "abcd", f: 6, t: 7}, + {a: 2, b: 6, c: "x", f: null, t: null}, + {a: 3, b: 6, c: "", f: null, t: null}, + {a: 0, b: 9, c: "hallo", f: null, t: null}, + {a: 4, b: 6, c: "x", f: 3, t: 4}, + {a: 4, b: 8, c: "", f: 3, t: 4}, + {a: 6, b: 6, c: "a", f: 3, t: 6}, + {a: 8, b: 9, c: "", f: 3, t: 6}], function(test) { + cm.setValue("1234567890"); + var r = cm.markText(Pos(0, 3), Pos(0, 6), {className: "foo"}); + cm.replaceRange(test.c, Pos(0, test.a), Pos(0, test.b)); + var f = r.find(); + eq(f && f.from.ch, test.f); eq(f && f.to.ch, test.t); + }); +}); + +testCM("markTextMultiLine", function(cm) { + function p(v) { return v && Pos(v[0], v[1]); } + forEach([{a: [0, 0], b: [0, 5], c: "", f: [0, 0], t: [2, 5]}, + {a: [0, 0], b: [0, 5], c: "foo\n", f: [1, 0], t: [3, 5]}, + {a: [0, 1], b: [0, 10], c: "", f: [0, 1], t: [2, 5]}, + {a: [0, 5], b: [0, 6], c: "x", f: [0, 6], t: [2, 5]}, + {a: [0, 0], b: [1, 0], c: "", f: [0, 0], t: [1, 5]}, + {a: [0, 6], b: [2, 4], c: "", f: [0, 5], t: [0, 7]}, + {a: [0, 6], b: [2, 4], c: "aa", f: [0, 5], t: [0, 9]}, + {a: [1, 2], b: [1, 8], c: "", f: [0, 5], t: [2, 5]}, + {a: [0, 5], b: [2, 5], c: "xx", f: null, t: null}, + {a: [0, 0], b: [2, 10], c: "x", f: null, t: null}, + {a: [1, 5], b: [2, 5], c: "", f: [0, 5], t: [1, 5]}, + {a: [2, 0], b: [2, 3], c: "", f: [0, 5], t: [2, 2]}, + {a: [2, 5], b: [3, 0], c: "a\nb", f: [0, 5], t: [2, 5]}, + {a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 3]}, + {a: [1, 1], b: [1, 9], c: "1\n2\n3", f: [0, 5], t: [4, 5]}], function(test) { + cm.setValue("aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\ndddddddd\n"); + var r = cm.markText(Pos(0, 5), Pos(2, 5), + {className: "CodeMirror-matchingbracket"}); + cm.replaceRange(test.c, p(test.a), p(test.b)); + var f = r.find(); + eqPos(f && f.from, p(test.f)); eqPos(f && f.to, p(test.t)); + }); +}); + +testCM("markTextUndo", function(cm) { + var marker1, marker2, bookmark; + marker1 = cm.markText(Pos(0, 1), Pos(0, 3), + {className: "CodeMirror-matchingbracket"}); + marker2 = cm.markText(Pos(0, 0), Pos(2, 1), + {className: "CodeMirror-matchingbracket"}); + bookmark = cm.setBookmark(Pos(1, 5)); + cm.operation(function(){ + cm.replaceRange("foo", Pos(0, 2)); + cm.replaceRange("bar\nbaz\nbug\n", Pos(2, 0), Pos(3, 0)); + }); + var v1 = cm.getValue(); + cm.setValue(""); + eq(marker1.find(), null); eq(marker2.find(), null); eq(bookmark.find(), null); + cm.undo(); + eqPos(bookmark.find(), Pos(1, 5), "still there"); + cm.undo(); + var m1Pos = marker1.find(), m2Pos = marker2.find(); + eqPos(m1Pos.from, Pos(0, 1)); eqPos(m1Pos.to, Pos(0, 3)); + eqPos(m2Pos.from, Pos(0, 0)); eqPos(m2Pos.to, Pos(2, 1)); + eqPos(bookmark.find(), Pos(1, 5)); + cm.redo(); cm.redo(); + eq(bookmark.find(), null); + cm.undo(); + eqPos(bookmark.find(), Pos(1, 5)); + eq(cm.getValue(), v1); +}, {value: "1234\n56789\n00\n"}); + +testCM("markTextStayGone", function(cm) { + var m1 = cm.markText(Pos(0, 0), Pos(0, 1)); + cm.replaceRange("hi", Pos(0, 2)); + m1.clear(); + cm.undo(); + eq(m1.find(), null); +}, {value: "hello"}); + +testCM("markTextAllowEmpty", function(cm) { + var m1 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false}); + is(m1.find()); + cm.replaceRange("x", Pos(0, 0)); + is(m1.find()); + cm.replaceRange("y", Pos(0, 2)); + is(m1.find()); + cm.replaceRange("z", Pos(0, 3), Pos(0, 4)); + is(!m1.find()); + var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false, + inclusiveLeft: true, + inclusiveRight: true}); + cm.replaceRange("q", Pos(0, 1), Pos(0, 2)); + is(m2.find()); + cm.replaceRange("", Pos(0, 0), Pos(0, 3)); + is(!m2.find()); + var m3 = cm.markText(Pos(0, 1), Pos(0, 1), {clearWhenEmpty: false}); + cm.replaceRange("a", Pos(0, 3)); + is(m3.find()); + cm.replaceRange("b", Pos(0, 1)); + is(!m3.find()); +}, {value: "abcde"}); + +testCM("markTextStacked", function(cm) { + var m1 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false}); + var m2 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false}); + cm.replaceRange("B", Pos(0, 1)); + is(m1.find() && m2.find()); +}, {value: "A"}); + +testCM("undoPreservesNewMarks", function(cm) { + cm.markText(Pos(0, 3), Pos(0, 4)); + cm.markText(Pos(1, 1), Pos(1, 3)); + cm.replaceRange("", Pos(0, 3), Pos(3, 1)); + var mBefore = cm.markText(Pos(0, 0), Pos(0, 1)); + var mAfter = cm.markText(Pos(0, 5), Pos(0, 6)); + var mAround = cm.markText(Pos(0, 2), Pos(0, 4)); + cm.undo(); + eqPos(mBefore.find().from, Pos(0, 0)); + eqPos(mBefore.find().to, Pos(0, 1)); + eqPos(mAfter.find().from, Pos(3, 3)); + eqPos(mAfter.find().to, Pos(3, 4)); + eqPos(mAround.find().from, Pos(0, 2)); + eqPos(mAround.find().to, Pos(3, 2)); + var found = cm.findMarksAt(Pos(2, 2)); + eq(found.length, 1); + eq(found[0], mAround); +}, {value: "aaaa\nbbbb\ncccc\ndddd"}); + +testCM("markClearBetween", function(cm) { + cm.setValue("aaa\nbbb\nccc\nddd\n"); + cm.markText(Pos(0, 0), Pos(2)); + cm.replaceRange("aaa\nbbb\nccc", Pos(0, 0), Pos(2)); + eq(cm.findMarksAt(Pos(1, 1)).length, 0); +}); + +testCM("findMarksMiddle", function(cm) { + var mark = cm.markText(Pos(1, 1), Pos(3, 1)); + var found = cm.findMarks(Pos(2, 1), Pos(2, 2)); + eq(found.length, 1); + eq(found[0], mark); +}, {value: "line 0\nline 1\nline 2\nline 3"}); + +testCM("deleteSpanCollapsedInclusiveLeft", function(cm) { + var from = Pos(1, 0), to = Pos(1, 1); + var m = cm.markText(from, to, {collapsed: true, inclusiveLeft: true}); + // Delete collapsed span. + cm.replaceRange("", from, to); +}, {value: "abc\nX\ndef"}); + +testCM("markTextCSS", function(cm) { + function present() { + var spans = cm.display.lineDiv.getElementsByTagName("span"); + for (var i = 0; i < spans.length; i++) + if (spans[i].style.color == "cyan" && span[i].textContent == "cdefg") return true; + } + var m = cm.markText(Pos(0, 2), Pos(0, 6), {css: "color: cyan"}); + m.clear(); + is(!present()); +}, {value: "abcdefgh"}); + +testCM("bookmark", function(cm) { + function p(v) { return v && Pos(v[0], v[1]); } + forEach([{a: [1, 0], b: [1, 1], c: "", d: [1, 4]}, + {a: [1, 1], b: [1, 1], c: "xx", d: [1, 7]}, + {a: [1, 4], b: [1, 5], c: "ab", d: [1, 6]}, + {a: [1, 4], b: [1, 6], c: "", d: null}, + {a: [1, 5], b: [1, 6], c: "abc", d: [1, 5]}, + {a: [1, 6], b: [1, 8], c: "", d: [1, 5]}, + {a: [1, 4], b: [1, 4], c: "\n\n", d: [3, 1]}, + {bm: [1, 9], a: [1, 1], b: [1, 1], c: "\n", d: [2, 8]}], function(test) { + cm.setValue("1234567890\n1234567890\n1234567890"); + var b = cm.setBookmark(p(test.bm) || Pos(1, 5)); + cm.replaceRange(test.c, p(test.a), p(test.b)); + eqPos(b.find(), p(test.d)); + }); +}); + +testCM("bookmarkInsertLeft", function(cm) { + var br = cm.setBookmark(Pos(0, 2), {insertLeft: false}); + var bl = cm.setBookmark(Pos(0, 2), {insertLeft: true}); + cm.setCursor(Pos(0, 2)); + cm.replaceSelection("hi"); + eqPos(br.find(), Pos(0, 2)); + eqPos(bl.find(), Pos(0, 4)); + cm.replaceRange("", Pos(0, 4), Pos(0, 5)); + cm.replaceRange("", Pos(0, 2), Pos(0, 4)); + cm.replaceRange("", Pos(0, 1), Pos(0, 2)); + // Verify that deleting next to bookmarks doesn't kill them + eqPos(br.find(), Pos(0, 1)); + eqPos(bl.find(), Pos(0, 1)); +}, {value: "abcdef"}); + +testCM("bookmarkCursor", function(cm) { + var pos01 = cm.cursorCoords(Pos(0, 1)), pos11 = cm.cursorCoords(Pos(1, 1)), + pos20 = cm.cursorCoords(Pos(2, 0)), pos30 = cm.cursorCoords(Pos(3, 0)), + pos41 = cm.cursorCoords(Pos(4, 1)); + cm.setBookmark(Pos(0, 1), {widget: document.createTextNode("←"), insertLeft: true}); + cm.setBookmark(Pos(2, 0), {widget: document.createTextNode("←"), insertLeft: true}); + cm.setBookmark(Pos(1, 1), {widget: document.createTextNode("→")}); + cm.setBookmark(Pos(3, 0), {widget: document.createTextNode("→")}); + var new01 = cm.cursorCoords(Pos(0, 1)), new11 = cm.cursorCoords(Pos(1, 1)), + new20 = cm.cursorCoords(Pos(2, 0)), new30 = cm.cursorCoords(Pos(3, 0)); + near(new01.left, pos01.left, 1); + near(new01.top, pos01.top, 1); + is(new11.left > pos11.left, "at right, middle of line"); + near(new11.top == pos11.top, 1); + near(new20.left, pos20.left, 1); + near(new20.top, pos20.top, 1); + is(new30.left > pos30.left, "at right, empty line"); + near(new30.top, pos30, 1); + cm.setBookmark(Pos(4, 0), {widget: document.createTextNode("→")}); + is(cm.cursorCoords(Pos(4, 1)).left > pos41.left, "single-char bug"); +}, {value: "foo\nbar\n\n\nx\ny"}); + +testCM("multiBookmarkCursor", function(cm) { + if (phantom) return; + var ms = [], m; + function add(insertLeft) { + for (var i = 0; i < 3; ++i) { + var node = document.createElement("span"); + node.innerHTML = "X"; + ms.push(cm.setBookmark(Pos(0, 1), {widget: node, insertLeft: insertLeft})); + } + } + var base1 = cm.cursorCoords(Pos(0, 1)).left, base4 = cm.cursorCoords(Pos(0, 4)).left; + add(true); + near(base1, cm.cursorCoords(Pos(0, 1)).left, 1); + while (m = ms.pop()) m.clear(); + add(false); + near(base4, cm.cursorCoords(Pos(0, 1)).left, 1); +}, {value: "abcdefg"}); + +testCM("getAllMarks", function(cm) { + addDoc(cm, 10, 10); + var m1 = cm.setBookmark(Pos(0, 2)); + var m2 = cm.markText(Pos(0, 2), Pos(3, 2)); + var m3 = cm.markText(Pos(1, 2), Pos(1, 8)); + var m4 = cm.markText(Pos(8, 0), Pos(9, 0)); + eq(cm.getAllMarks().length, 4); + m1.clear(); + m3.clear(); + eq(cm.getAllMarks().length, 2); +}); + +testCM("setValueClears", function(cm) { + cm.addLineClass(0, "wrap", "foo"); + var mark = cm.markText(Pos(0, 0), Pos(1, 1), {inclusiveLeft: true, inclusiveRight: true}); + cm.setValue("foo"); + is(!cm.lineInfo(0).wrapClass); + is(!mark.find()); +}, {value: "a\nb"}); + +testCM("bug577", function(cm) { + cm.setValue("a\nb"); + cm.clearHistory(); + cm.setValue("fooooo"); + cm.undo(); +}); + +testCM("scrollSnap", function(cm) { + cm.setSize(100, 100); + addDoc(cm, 200, 200); + cm.setCursor(Pos(100, 180)); + var info = cm.getScrollInfo(); + is(info.left > 0 && info.top > 0); + cm.setCursor(Pos(0, 0)); + info = cm.getScrollInfo(); + is(info.left == 0 && info.top == 0, "scrolled clean to top"); + cm.setCursor(Pos(100, 180)); + cm.setCursor(Pos(199, 0)); + info = cm.getScrollInfo(); + is(info.left == 0 && info.top + 2 > info.height - cm.getScrollerElement().clientHeight, "scrolled clean to bottom"); +}); + +testCM("scrollIntoView", function(cm) { + if (phantom) return; + var outer = cm.getWrapperElement().getBoundingClientRect(); + function test(line, ch, msg) { + var pos = Pos(line, ch); + cm.scrollIntoView(pos); + var box = cm.charCoords(pos, "window"); + is(box.left >= outer.left, msg + " (left)"); + is(box.right <= outer.right, msg + " (right)"); + is(box.top >= outer.top, msg + " (top)"); + is(box.bottom <= outer.bottom, msg + " (bottom)"); + } + addDoc(cm, 200, 200); + test(199, 199, "bottom right"); + test(0, 0, "top left"); + test(100, 100, "center"); + test(199, 0, "bottom left"); + test(0, 199, "top right"); + test(100, 100, "center again"); +}); + +testCM("scrollBackAndForth", function(cm) { + addDoc(cm, 1, 200); + cm.operation(function() { + cm.scrollIntoView(Pos(199, 0)); + cm.scrollIntoView(Pos(4, 0)); + }); + is(cm.getScrollInfo().top > 0); +}); + +testCM("selectAllNoScroll", function(cm) { + addDoc(cm, 1, 200); + cm.execCommand("selectAll"); + eq(cm.getScrollInfo().top, 0); + cm.setCursor(199); + cm.execCommand("selectAll"); + is(cm.getScrollInfo().top > 0); +}); + +testCM("selectionPos", function(cm) { + if (phantom || cm.getOption("inputStyle") != "textarea") return; + cm.setSize(100, 100); + addDoc(cm, 200, 100); + cm.setSelection(Pos(1, 100), Pos(98, 100)); + var lineWidth = cm.charCoords(Pos(0, 200), "local").left; + var lineHeight = (cm.charCoords(Pos(99)).top - cm.charCoords(Pos(0)).top) / 100; + cm.scrollTo(0, 0); + var selElt = byClassName(cm.getWrapperElement(), "CodeMirror-selected"); + var outer = cm.getWrapperElement().getBoundingClientRect(); + var sawMiddle, sawTop, sawBottom; + for (var i = 0, e = selElt.length; i < e; ++i) { + var box = selElt[i].getBoundingClientRect(); + var atLeft = box.left - outer.left < 30; + var width = box.right - box.left; + var atRight = box.right - outer.left > .8 * lineWidth; + if (atLeft && atRight) { + sawMiddle = true; + is(box.bottom - box.top > 90 * lineHeight, "middle high"); + is(width > .9 * lineWidth, "middle wide"); + } else { + is(width > .4 * lineWidth, "top/bot wide enough"); + is(width < .6 * lineWidth, "top/bot slim enough"); + if (atLeft) { + sawBottom = true; + is(box.top - outer.top > 96 * lineHeight, "bot below"); + } else if (atRight) { + sawTop = true; + is(box.top - outer.top < 2.1 * lineHeight, "top above"); + } + } + } + is(sawTop && sawBottom && sawMiddle, "all parts"); +}, null); + +testCM("restoreHistory", function(cm) { + cm.setValue("abc\ndef"); + cm.replaceRange("hello", Pos(1, 0), Pos(1)); + cm.replaceRange("goop", Pos(0, 0), Pos(0)); + cm.undo(); + var storedVal = cm.getValue(), storedHist = cm.getHistory(); + if (window.JSON) storedHist = JSON.parse(JSON.stringify(storedHist)); + eq(storedVal, "abc\nhello"); + cm.setValue(""); + cm.clearHistory(); + eq(cm.historySize().undo, 0); + cm.setValue(storedVal); + cm.setHistory(storedHist); + cm.redo(); + eq(cm.getValue(), "goop\nhello"); + cm.undo(); cm.undo(); + eq(cm.getValue(), "abc\ndef"); +}); + +testCM("doubleScrollbar", function(cm) { + var dummy = document.body.appendChild(document.createElement("p")); + dummy.style.cssText = "height: 50px; overflow: scroll; width: 50px"; + var scrollbarWidth = dummy.offsetWidth + 1 - dummy.clientWidth; + document.body.removeChild(dummy); + if (scrollbarWidth < 2) return; + cm.setSize(null, 100); + addDoc(cm, 1, 300); + var wrap = cm.getWrapperElement(); + is(wrap.offsetWidth - byClassName(wrap, "CodeMirror-lines")[0].offsetWidth <= scrollbarWidth * 1.5); +}); + +testCM("weirdLinebreaks", function(cm) { + cm.setValue("foo\nbar\rbaz\r\nquux\n\rplop"); + is(cm.getValue(), "foo\nbar\nbaz\nquux\n\nplop"); + is(cm.lineCount(), 6); + cm.setValue("\n\n"); + is(cm.lineCount(), 3); +}); + +testCM("setSize", function(cm) { + cm.setSize(100, 100); + var wrap = cm.getWrapperElement(); + is(wrap.offsetWidth, 100); + is(wrap.offsetHeight, 100); + cm.setSize("100%", "3em"); + is(wrap.style.width, "100%"); + is(wrap.style.height, "3em"); + cm.setSize(null, 40); + is(wrap.style.width, "100%"); + is(wrap.style.height, "40px"); +}); + +function foldLines(cm, start, end, autoClear) { + return cm.markText(Pos(start, 0), Pos(end - 1), { + inclusiveLeft: true, + inclusiveRight: true, + collapsed: true, + clearOnEnter: autoClear + }); +} + +testCM("collapsedLines", function(cm) { + addDoc(cm, 4, 10); + var range = foldLines(cm, 4, 5), cleared = 0; + CodeMirror.on(range, "clear", function() {cleared++;}); + cm.setCursor(Pos(3, 0)); + CodeMirror.commands.goLineDown(cm); + eqPos(cm.getCursor(), Pos(5, 0)); + cm.replaceRange("abcdefg", Pos(3, 0), Pos(3)); + cm.setCursor(Pos(3, 6)); + CodeMirror.commands.goLineDown(cm); + eqPos(cm.getCursor(), Pos(5, 4)); + cm.replaceRange("ab", Pos(3, 0), Pos(3)); + cm.setCursor(Pos(3, 2)); + CodeMirror.commands.goLineDown(cm); + eqPos(cm.getCursor(), Pos(5, 2)); + cm.operation(function() {range.clear(); range.clear();}); + eq(cleared, 1); +}); + +testCM("collapsedRangeCoordsChar", function(cm) { + var pos_1_3 = cm.charCoords(Pos(1, 3)); + pos_1_3.left += 2; pos_1_3.top += 2; + var opts = {collapsed: true, inclusiveLeft: true, inclusiveRight: true}; + var m1 = cm.markText(Pos(0, 0), Pos(2, 0), opts); + eqPos(cm.coordsChar(pos_1_3), Pos(3, 3)); + m1.clear(); + var m1 = cm.markText(Pos(0, 0), Pos(1, 1), {collapsed: true, inclusiveLeft: true}); + var m2 = cm.markText(Pos(1, 1), Pos(2, 0), {collapsed: true, inclusiveRight: true}); + eqPos(cm.coordsChar(pos_1_3), Pos(3, 3)); + m1.clear(); m2.clear(); + var m1 = cm.markText(Pos(0, 0), Pos(1, 6), opts); + eqPos(cm.coordsChar(pos_1_3), Pos(3, 3)); +}, {value: "123456\nabcdef\nghijkl\nmnopqr\n"}); + +testCM("collapsedRangeBetweenLinesSelected", function(cm) { + if (cm.getOption("inputStyle") != "textarea") return; + var widget = document.createElement("span"); + widget.textContent = "\u2194"; + cm.markText(Pos(0, 3), Pos(1, 0), {replacedWith: widget}); + cm.setSelection(Pos(0, 3), Pos(1, 0)); + var selElts = byClassName(cm.getWrapperElement(), "CodeMirror-selected"); + for (var i = 0, w = 0; i < selElts.length; i++) + w += selElts[i].offsetWidth; + is(w > 0); +}, {value: "one\ntwo"}); + +testCM("randomCollapsedRanges", function(cm) { + addDoc(cm, 20, 500); + cm.operation(function() { + for (var i = 0; i < 200; i++) { + var start = Pos(Math.floor(Math.random() * 500), Math.floor(Math.random() * 20)); + if (i % 4) + try { cm.markText(start, Pos(start.line + 2, 1), {collapsed: true}); } + catch(e) { if (!/overlapping/.test(String(e))) throw e; } + else + cm.markText(start, Pos(start.line, start.ch + 4), {"className": "foo"}); + } + }); +}); + +testCM("hiddenLinesAutoUnfold", function(cm) { + var range = foldLines(cm, 1, 3, true), cleared = 0; + CodeMirror.on(range, "clear", function() {cleared++;}); + cm.setCursor(Pos(3, 0)); + eq(cleared, 0); + cm.execCommand("goCharLeft"); + eq(cleared, 1); + range = foldLines(cm, 1, 3, true); + CodeMirror.on(range, "clear", function() {cleared++;}); + eqPos(cm.getCursor(), Pos(3, 0)); + cm.setCursor(Pos(0, 3)); + cm.execCommand("goCharRight"); + eq(cleared, 2); +}, {value: "abc\ndef\nghi\njkl"}); + +testCM("hiddenLinesSelectAll", function(cm) { // Issue #484 + addDoc(cm, 4, 20); + foldLines(cm, 0, 10); + foldLines(cm, 11, 20); + CodeMirror.commands.selectAll(cm); + eqPos(cm.getCursor(true), Pos(10, 0)); + eqPos(cm.getCursor(false), Pos(10, 4)); +}); + + +testCM("everythingFolded", function(cm) { + addDoc(cm, 2, 2); + function enterPress() { + cm.triggerOnKeyDown({type: "keydown", keyCode: 13, preventDefault: function(){}, stopPropagation: function(){}}); + } + var fold = foldLines(cm, 0, 2); + enterPress(); + eq(cm.getValue(), "xx\nxx"); + fold.clear(); + fold = foldLines(cm, 0, 2, true); + eq(fold.find(), null); + enterPress(); + eq(cm.getValue(), "\nxx\nxx"); +}); + +testCM("structuredFold", function(cm) { + if (phantom) return; + addDoc(cm, 4, 8); + var range = cm.markText(Pos(1, 2), Pos(6, 2), { + replacedWith: document.createTextNode("Q") + }); + cm.setCursor(0, 3); + CodeMirror.commands.goLineDown(cm); + eqPos(cm.getCursor(), Pos(6, 2)); + CodeMirror.commands.goCharLeft(cm); + eqPos(cm.getCursor(), Pos(1, 2)); + CodeMirror.commands.delCharAfter(cm); + eq(cm.getValue(), "xxxx\nxxxx\nxxxx"); + addDoc(cm, 4, 8); + range = cm.markText(Pos(1, 2), Pos(6, 2), { + replacedWith: document.createTextNode("M"), + clearOnEnter: true + }); + var cleared = 0; + CodeMirror.on(range, "clear", function(){++cleared;}); + cm.setCursor(0, 3); + CodeMirror.commands.goLineDown(cm); + eqPos(cm.getCursor(), Pos(6, 2)); + CodeMirror.commands.goCharLeft(cm); + eqPos(cm.getCursor(), Pos(6, 1)); + eq(cleared, 1); + range.clear(); + eq(cleared, 1); + range = cm.markText(Pos(1, 2), Pos(6, 2), { + replacedWith: document.createTextNode("Q"), + clearOnEnter: true + }); + range.clear(); + cm.setCursor(1, 2); + CodeMirror.commands.goCharRight(cm); + eqPos(cm.getCursor(), Pos(1, 3)); + range = cm.markText(Pos(2, 0), Pos(4, 4), { + replacedWith: document.createTextNode("M") + }); + cm.setCursor(1, 0); + CodeMirror.commands.goLineDown(cm); + eqPos(cm.getCursor(), Pos(2, 0)); +}, null); + +testCM("nestedFold", function(cm) { + addDoc(cm, 10, 3); + function fold(ll, cl, lr, cr) { + return cm.markText(Pos(ll, cl), Pos(lr, cr), {collapsed: true}); + } + var inner1 = fold(0, 6, 1, 3), inner2 = fold(0, 2, 1, 8), outer = fold(0, 1, 2, 3), inner0 = fold(0, 5, 0, 6); + cm.setCursor(0, 1); + CodeMirror.commands.goCharRight(cm); + eqPos(cm.getCursor(), Pos(2, 3)); + inner0.clear(); + CodeMirror.commands.goCharLeft(cm); + eqPos(cm.getCursor(), Pos(0, 1)); + outer.clear(); + CodeMirror.commands.goCharRight(cm); + eqPos(cm.getCursor(), Pos(0, 2)); + CodeMirror.commands.goCharRight(cm); + eqPos(cm.getCursor(), Pos(1, 8)); + inner2.clear(); + CodeMirror.commands.goCharLeft(cm); + eqPos(cm.getCursor(), Pos(1, 7)); + cm.setCursor(0, 5); + CodeMirror.commands.goCharRight(cm); + eqPos(cm.getCursor(), Pos(0, 6)); + CodeMirror.commands.goCharRight(cm); + eqPos(cm.getCursor(), Pos(1, 3)); +}); + +testCM("badNestedFold", function(cm) { + addDoc(cm, 4, 4); + cm.markText(Pos(0, 2), Pos(3, 2), {collapsed: true}); + var caught; + try {cm.markText(Pos(0, 1), Pos(0, 3), {collapsed: true});} + catch(e) {caught = e;} + is(caught instanceof Error, "no error"); + is(/overlap/i.test(caught.message), "wrong error"); +}); + +testCM("nestedFoldOnSide", function(cm) { + var m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true, inclusiveRight: true}); + var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true}); + cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true}).clear(); + try { cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true, inclusiveLeft: true}); } + catch(e) { var caught = e; } + is(caught && /overlap/i.test(caught.message)); + var m3 = cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true}); + var m4 = cm.markText(Pos(2, 0), Pos(2, 1), {collapse: true, inclusiveRight: true}); + m1.clear(); m4.clear(); + m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true}); + cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true}).clear(); + try { cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true, inclusiveRight: true}); } + catch(e) { var caught = e; } + is(caught && /overlap/i.test(caught.message)); +}, {value: "ab\ncd\ef"}); + +testCM("editInFold", function(cm) { + addDoc(cm, 4, 6); + var m = cm.markText(Pos(1, 2), Pos(3, 2), {collapsed: true}); + cm.replaceRange("", Pos(0, 0), Pos(1, 3)); + cm.replaceRange("", Pos(2, 1), Pos(3, 3)); + cm.replaceRange("a\nb\nc\nd", Pos(0, 1), Pos(1, 0)); + cm.cursorCoords(Pos(0, 0)); +}); + +testCM("wrappingInlineWidget", function(cm) { + cm.setSize("11em"); + var w = document.createElement("span"); + w.style.color = "red"; + w.innerHTML = "one two three four"; + cm.markText(Pos(0, 6), Pos(0, 9), {replacedWith: w}); + var cur0 = cm.cursorCoords(Pos(0, 0)), cur1 = cm.cursorCoords(Pos(0, 10)); + is(cur0.top < cur1.top); + is(cur0.bottom < cur1.bottom); + var curL = cm.cursorCoords(Pos(0, 6)), curR = cm.cursorCoords(Pos(0, 9)); + eq(curL.top, cur0.top); + eq(curL.bottom, cur0.bottom); + eq(curR.top, cur1.top); + eq(curR.bottom, cur1.bottom); + cm.replaceRange("", Pos(0, 9), Pos(0)); + curR = cm.cursorCoords(Pos(0, 9)); + if (phantom) return; + eq(curR.top, cur1.top); + eq(curR.bottom, cur1.bottom); +}, {value: "1 2 3 xxx 4", lineWrapping: true}); + +testCM("showEmptyWidgetSpan", function(cm) { + var marker = cm.markText(Pos(0, 2), Pos(0, 2), { + clearWhenEmpty: false, + replacedWith: document.createTextNode("X") + }); + eq(cm.display.view[0].text.textContent, "abXc"); +}, {value: "abc"}); + +testCM("changedInlineWidget", function(cm) { + cm.setSize("10em"); + var w = document.createElement("span"); + w.innerHTML = "x"; + var m = cm.markText(Pos(0, 4), Pos(0, 5), {replacedWith: w}); + w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed"; + m.changed(); + var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0]; + is(hScroll.scrollWidth > hScroll.clientWidth); +}, {value: "hello there"}); + +testCM("changedBookmark", function(cm) { + cm.setSize("10em"); + var w = document.createElement("span"); + w.innerHTML = "x"; + var m = cm.setBookmark(Pos(0, 4), {widget: w}); + w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed"; + m.changed(); + var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0]; + is(hScroll.scrollWidth > hScroll.clientWidth); +}, {value: "abcdefg"}); + +testCM("inlineWidget", function(cm) { + var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")}); + cm.setCursor(0, 2); + CodeMirror.commands.goLineDown(cm); + eqPos(cm.getCursor(), Pos(1, 4)); + cm.setCursor(0, 2); + cm.replaceSelection("hi"); + eqPos(w.find(), Pos(0, 2)); + cm.setCursor(0, 1); + cm.replaceSelection("ay"); + eqPos(w.find(), Pos(0, 4)); + eq(cm.getLine(0), "uayuhiuu"); +}, {value: "uuuu\nuuuuuu"}); + +testCM("wrappingAndResizing", function(cm) { + cm.setSize(null, "auto"); + cm.setOption("lineWrapping", true); + var wrap = cm.getWrapperElement(), h0 = wrap.offsetHeight; + var doc = "xxx xxx xxx xxx xxx"; + cm.setValue(doc); + for (var step = 10, w = cm.charCoords(Pos(0, 18), "div").right;; w += step) { + cm.setSize(w); + if (wrap.offsetHeight <= h0 * (opera_lt10 ? 1.2 : 1.5)) { + if (step == 10) { w -= 10; step = 1; } + else break; + } + } + // Ensure that putting the cursor at the end of the maximally long + // line doesn't cause wrapping to happen. + cm.setCursor(Pos(0, doc.length)); + eq(wrap.offsetHeight, h0); + cm.replaceSelection("x"); + is(wrap.offsetHeight > h0, "wrapping happens"); + // Now add a max-height and, in a document consisting of + // almost-wrapped lines, go over it so that a scrollbar appears. + cm.setValue(doc + "\n" + doc + "\n"); + cm.getScrollerElement().style.maxHeight = "100px"; + cm.replaceRange("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n!\n", Pos(2, 0)); + forEach([Pos(0, doc.length), Pos(0, doc.length - 1), + Pos(0, 0), Pos(1, doc.length), Pos(1, doc.length - 1)], + function(pos) { + var coords = cm.charCoords(pos); + eqPos(pos, cm.coordsChar({left: coords.left + 2, top: coords.top + 5})); + }); +}, null, ie_lt8); + +testCM("measureEndOfLine", function(cm) { + cm.setSize(null, "auto"); + var inner = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild; + var lh = inner.offsetHeight; + for (var step = 10, w = cm.charCoords(Pos(0, 7), "div").right;; w += step) { + cm.setSize(w); + if (inner.offsetHeight < 2.5 * lh) { + if (step == 10) { w -= 10; step = 1; } + else break; + } + } + cm.setValue(cm.getValue() + "\n\n"); + var endPos = cm.charCoords(Pos(0, 18), "local"); + is(endPos.top > lh * .8, "not at top"); + is(endPos.left > w - 20, "not at right"); + endPos = cm.charCoords(Pos(0, 18)); + eqPos(cm.coordsChar({left: endPos.left, top: endPos.top + 5}), Pos(0, 18)); +}, {mode: "text/html", value: "", lineWrapping: true}, ie_lt8 || opera_lt10); + +testCM("scrollVerticallyAndHorizontally", function(cm) { + if (cm.getOption("inputStyle") != "textarea") return; + cm.setSize(100, 100); + addDoc(cm, 40, 40); + cm.setCursor(39); + var wrap = cm.getWrapperElement(), bar = byClassName(wrap, "CodeMirror-vscrollbar")[0]; + is(bar.offsetHeight < wrap.offsetHeight, "vertical scrollbar limited by horizontal one"); + var cursorBox = byClassName(wrap, "CodeMirror-cursor")[0].getBoundingClientRect(); + var editorBox = wrap.getBoundingClientRect(); + is(cursorBox.bottom < editorBox.top + cm.getScrollerElement().clientHeight, + "bottom line visible"); +}, {lineNumbers: true}); + +testCM("moveVstuck", function(cm) { + var lines = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild, h0 = lines.offsetHeight; + var val = "fooooooooooooooooooooooooo baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar\n"; + cm.setValue(val); + for (var w = cm.charCoords(Pos(0, 26), "div").right * 2.8;; w += 5) { + cm.setSize(w); + if (lines.offsetHeight <= 3.5 * h0) break; + } + cm.setCursor(Pos(0, val.length - 1)); + cm.moveV(-1, "line"); + eqPos(cm.getCursor(), Pos(0, 26)); +}, {lineWrapping: true}, ie_lt8 || opera_lt10); + +testCM("collapseOnMove", function(cm) { + cm.setSelection(Pos(0, 1), Pos(2, 4)); + cm.execCommand("goLineUp"); + is(!cm.somethingSelected()); + eqPos(cm.getCursor(), Pos(0, 1)); + cm.setSelection(Pos(0, 1), Pos(2, 4)); + cm.execCommand("goPageDown"); + is(!cm.somethingSelected()); + eqPos(cm.getCursor(), Pos(2, 4)); + cm.execCommand("goLineUp"); + cm.execCommand("goLineUp"); + eqPos(cm.getCursor(), Pos(0, 4)); + cm.setSelection(Pos(0, 1), Pos(2, 4)); + cm.execCommand("goCharLeft"); + is(!cm.somethingSelected()); + eqPos(cm.getCursor(), Pos(0, 1)); +}, {value: "aaaaa\nb\nccccc"}); + +testCM("clickTab", function(cm) { + var p0 = cm.charCoords(Pos(0, 0)); + eqPos(cm.coordsChar({left: p0.left + 5, top: p0.top + 5}), Pos(0, 0)); + eqPos(cm.coordsChar({left: p0.right - 5, top: p0.top + 5}), Pos(0, 1)); +}, {value: "\t\n\n", lineWrapping: true, tabSize: 8}); + +testCM("verticalScroll", function(cm) { + cm.setSize(100, 200); + cm.setValue("foo\nbar\nbaz\n"); + var sc = cm.getScrollerElement(), baseWidth = sc.scrollWidth; + cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0)); + is(sc.scrollWidth > baseWidth, "scrollbar present"); + cm.replaceRange("foo", Pos(0, 0), Pos(0)); + if (!phantom) eq(sc.scrollWidth, baseWidth, "scrollbar gone"); + cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0)); + cm.replaceRange("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbh", Pos(1, 0), Pos(1)); + is(sc.scrollWidth > baseWidth, "present again"); + var curWidth = sc.scrollWidth; + cm.replaceRange("foo", Pos(0, 0), Pos(0)); + is(sc.scrollWidth < curWidth, "scrollbar smaller"); + is(sc.scrollWidth > baseWidth, "but still present"); +}); + +testCM("extraKeys", function(cm) { + var outcome; + function fakeKey(expected, code, props) { + if (typeof code == "string") code = code.charCodeAt(0); + var e = {type: "keydown", keyCode: code, preventDefault: function(){}, stopPropagation: function(){}}; + if (props) for (var n in props) e[n] = props[n]; + outcome = null; + cm.triggerOnKeyDown(e); + eq(outcome, expected); + } + CodeMirror.commands.testCommand = function() {outcome = "tc";}; + CodeMirror.commands.goTestCommand = function() {outcome = "gtc";}; + cm.setOption("extraKeys", {"Shift-X": function() {outcome = "sx";}, + "X": function() {outcome = "x";}, + "Ctrl-Alt-U": function() {outcome = "cau";}, + "End": "testCommand", + "Home": "goTestCommand", + "Tab": false}); + fakeKey(null, "U"); + fakeKey("cau", "U", {ctrlKey: true, altKey: true}); + fakeKey(null, "U", {shiftKey: true, ctrlKey: true, altKey: true}); + fakeKey("x", "X"); + fakeKey("sx", "X", {shiftKey: true}); + fakeKey("tc", 35); + fakeKey(null, 35, {shiftKey: true}); + fakeKey("gtc", 36); + fakeKey("gtc", 36, {shiftKey: true}); + fakeKey(null, 9); +}, null, window.opera && mac); + +testCM("wordMovementCommands", function(cm) { + cm.execCommand("goWordLeft"); + eqPos(cm.getCursor(), Pos(0, 0)); + cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); + eqPos(cm.getCursor(), Pos(0, 7)); + cm.execCommand("goWordLeft"); + eqPos(cm.getCursor(), Pos(0, 5)); + cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); + eqPos(cm.getCursor(), Pos(0, 12)); + cm.execCommand("goWordLeft"); + eqPos(cm.getCursor(), Pos(0, 9)); + cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); + eqPos(cm.getCursor(), Pos(0, 24)); + cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); + eqPos(cm.getCursor(), Pos(1, 9)); + cm.execCommand("goWordRight"); + eqPos(cm.getCursor(), Pos(1, 13)); + cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); + eqPos(cm.getCursor(), Pos(2, 0)); +}, {value: "this is (the) firstline.\na foo12\u00e9\u00f8\u00d7bar\n"}); + +testCM("groupMovementCommands", function(cm) { + cm.execCommand("goGroupLeft"); + eqPos(cm.getCursor(), Pos(0, 0)); + cm.execCommand("goGroupRight"); + eqPos(cm.getCursor(), Pos(0, 4)); + cm.execCommand("goGroupRight"); + eqPos(cm.getCursor(), Pos(0, 7)); + cm.execCommand("goGroupRight"); + eqPos(cm.getCursor(), Pos(0, 10)); + cm.execCommand("goGroupLeft"); + eqPos(cm.getCursor(), Pos(0, 7)); + cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight"); + eqPos(cm.getCursor(), Pos(0, 15)); + cm.setCursor(Pos(0, 17)); + cm.execCommand("goGroupLeft"); + eqPos(cm.getCursor(), Pos(0, 16)); + cm.execCommand("goGroupLeft"); + eqPos(cm.getCursor(), Pos(0, 14)); + cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight"); + eqPos(cm.getCursor(), Pos(0, 20)); + cm.execCommand("goGroupRight"); + eqPos(cm.getCursor(), Pos(1, 0)); + cm.execCommand("goGroupRight"); + eqPos(cm.getCursor(), Pos(1, 2)); + cm.execCommand("goGroupRight"); + eqPos(cm.getCursor(), Pos(1, 5)); + cm.execCommand("goGroupLeft"); cm.execCommand("goGroupLeft"); + eqPos(cm.getCursor(), Pos(1, 0)); + cm.execCommand("goGroupLeft"); + eqPos(cm.getCursor(), Pos(0, 20)); + cm.execCommand("goGroupLeft"); + eqPos(cm.getCursor(), Pos(0, 16)); +}, {value: "booo ba---quux. ffff\n abc d"}); + +testCM("groupsAndWhitespace", function(cm) { + var positions = [Pos(0, 0), Pos(0, 2), Pos(0, 5), Pos(0, 9), Pos(0, 11), + Pos(1, 0), Pos(1, 2), Pos(1, 5)]; + for (var i = 1; i < positions.length; i++) { + cm.execCommand("goGroupRight"); + eqPos(cm.getCursor(), positions[i]); + } + for (var i = positions.length - 2; i >= 0; i--) { + cm.execCommand("goGroupLeft"); + eqPos(cm.getCursor(), i == 2 ? Pos(0, 6) : positions[i]); + } +}, {value: " foo +++ \n bar"}); + +testCM("charMovementCommands", function(cm) { + cm.execCommand("goCharLeft"); cm.execCommand("goColumnLeft"); + eqPos(cm.getCursor(), Pos(0, 0)); + cm.execCommand("goCharRight"); cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(0, 2)); + cm.setCursor(Pos(1, 0)); + cm.execCommand("goColumnLeft"); + eqPos(cm.getCursor(), Pos(1, 0)); + cm.execCommand("goCharLeft"); + eqPos(cm.getCursor(), Pos(0, 5)); + cm.execCommand("goColumnRight"); + eqPos(cm.getCursor(), Pos(0, 5)); + cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(1, 0)); + cm.execCommand("goLineEnd"); + eqPos(cm.getCursor(), Pos(1, 5)); + cm.execCommand("goLineStartSmart"); + eqPos(cm.getCursor(), Pos(1, 1)); + cm.execCommand("goLineStartSmart"); + eqPos(cm.getCursor(), Pos(1, 0)); + cm.setCursor(Pos(2, 0)); + cm.execCommand("goCharRight"); cm.execCommand("goColumnRight"); + eqPos(cm.getCursor(), Pos(2, 0)); +}, {value: "line1\n ine2\n"}); + +testCM("verticalMovementCommands", function(cm) { + cm.execCommand("goLineUp"); + eqPos(cm.getCursor(), Pos(0, 0)); + cm.execCommand("goLineDown"); + if (!phantom) // This fails in PhantomJS, though not in a real Webkit + eqPos(cm.getCursor(), Pos(1, 0)); + cm.setCursor(Pos(1, 12)); + cm.execCommand("goLineDown"); + eqPos(cm.getCursor(), Pos(2, 5)); + cm.execCommand("goLineDown"); + eqPos(cm.getCursor(), Pos(3, 0)); + cm.execCommand("goLineUp"); + eqPos(cm.getCursor(), Pos(2, 5)); + cm.execCommand("goLineUp"); + eqPos(cm.getCursor(), Pos(1, 12)); + cm.execCommand("goPageDown"); + eqPos(cm.getCursor(), Pos(5, 0)); + cm.execCommand("goPageDown"); cm.execCommand("goLineDown"); + eqPos(cm.getCursor(), Pos(5, 0)); + cm.execCommand("goPageUp"); + eqPos(cm.getCursor(), Pos(0, 0)); +}, {value: "line1\nlong long line2\nline3\n\nline5\n"}); + +testCM("verticalMovementCommandsWrapping", function(cm) { + cm.setSize(120); + cm.setCursor(Pos(0, 5)); + cm.execCommand("goLineDown"); + eq(cm.getCursor().line, 0); + is(cm.getCursor().ch > 5, "moved beyond wrap"); + for (var i = 0; ; ++i) { + is(i < 20, "no endless loop"); + cm.execCommand("goLineDown"); + var cur = cm.getCursor(); + if (cur.line == 1) eq(cur.ch, 5); + if (cur.line == 2) { eq(cur.ch, 1); break; } + } +}, {value: "a very long line that wraps around somehow so that we can test cursor movement\nshortone\nk", + lineWrapping: true}); + +testCM("rtlMovement", function(cm) { + if (cm.getOption("inputStyle") != "textarea") return; + forEach(["خحج", "خحabcخحج", "abخحخحجcd", "abخde", "abخح2342خ1حج", "خ1ح2خح3حxج", + "خحcd", "1خحcd", "abcdeح1ج", "خمرحبها مها!", "foobarر", "خ ة ق", + "", "يتم السحب في 05 فبراير 2014"], function(line) { + var inv = line.charCodeAt(0) > 128; + cm.setValue(line + "\n"); cm.execCommand(inv ? "goLineEnd" : "goLineStart"); + var cursors = byClassName(cm.getWrapperElement(), "CodeMirror-cursors")[0]; + var cursor = cursors.firstChild; + var prevX = cursor.offsetLeft, prevY = cursor.offsetTop; + for (var i = 0; i <= line.length; ++i) { + cm.execCommand("goCharRight"); + cursor = cursors.firstChild; + if (i == line.length) is(cursor.offsetTop > prevY, "next line"); + else is(cursor.offsetLeft > prevX, "moved right"); + prevX = cursor.offsetLeft; prevY = cursor.offsetTop; + } + cm.setCursor(0, 0); cm.execCommand(inv ? "goLineStart" : "goLineEnd"); + prevX = cursors.firstChild.offsetLeft; + for (var i = 0; i < line.length; ++i) { + cm.execCommand("goCharLeft"); + cursor = cursors.firstChild; + is(cursor.offsetLeft < prevX, "moved left"); + prevX = cursor.offsetLeft; + } + }); +}, null, ie_lt9); + +// Verify that updating a line clears its bidi ordering +testCM("bidiUpdate", function(cm) { + cm.setCursor(Pos(0, 2)); + cm.replaceSelection("خحج", "start"); + cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(0, 4)); +}, {value: "abcd\n"}); + +testCM("movebyTextUnit", function(cm) { + cm.setValue("בְּרֵאשִ\nééé́\n"); + cm.execCommand("goLineEnd"); + for (var i = 0; i < 4; ++i) cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(0, 0)); + cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(1, 0)); + cm.execCommand("goCharRight"); + cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(1, 4)); + cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(1, 7)); +}); + +testCM("lineChangeEvents", function(cm) { + addDoc(cm, 3, 5); + var log = [], want = ["ch 0", "ch 1", "del 2", "ch 0", "ch 0", "del 1", "del 3", "del 4"]; + for (var i = 0; i < 5; ++i) { + CodeMirror.on(cm.getLineHandle(i), "delete", function(i) { + return function() {log.push("del " + i);}; + }(i)); + CodeMirror.on(cm.getLineHandle(i), "change", function(i) { + return function() {log.push("ch " + i);}; + }(i)); + } + cm.replaceRange("x", Pos(0, 1)); + cm.replaceRange("xy", Pos(1, 1), Pos(2)); + cm.replaceRange("foo\nbar", Pos(0, 1)); + cm.replaceRange("", Pos(0, 0), Pos(cm.lineCount())); + eq(log.length, want.length, "same length"); + for (var i = 0; i < log.length; ++i) + eq(log[i], want[i]); +}); + +testCM("scrollEntirelyToRight", function(cm) { + if (phantom || cm.getOption("inputStyle") != "textarea") return; + addDoc(cm, 500, 2); + cm.setCursor(Pos(0, 500)); + var wrap = cm.getWrapperElement(), cur = byClassName(wrap, "CodeMirror-cursor")[0]; + is(wrap.getBoundingClientRect().right > cur.getBoundingClientRect().left); +}); + +testCM("lineWidgets", function(cm) { + addDoc(cm, 500, 3); + var last = cm.charCoords(Pos(2, 0)); + var node = document.createElement("div"); + node.innerHTML = "hi"; + var widget = cm.addLineWidget(1, node); + is(last.top < cm.charCoords(Pos(2, 0)).top, "took up space"); + cm.setCursor(Pos(1, 1)); + cm.execCommand("goLineDown"); + eqPos(cm.getCursor(), Pos(2, 1)); + cm.execCommand("goLineUp"); + eqPos(cm.getCursor(), Pos(1, 1)); +}); + +testCM("lineWidgetFocus", function(cm) { + var place = document.getElementById("testground"); + place.className = "offscreen"; + try { + addDoc(cm, 500, 10); + var node = document.createElement("input"); + var widget = cm.addLineWidget(1, node); + node.focus(); + eq(document.activeElement, node); + cm.replaceRange("new stuff", Pos(1, 0)); + eq(document.activeElement, node); + } finally { + place.className = ""; + } +}); + +testCM("lineWidgetCautiousRedraw", function(cm) { + var node = document.createElement("div"); + node.innerHTML = "hahah"; + var w = cm.addLineWidget(0, node); + var redrawn = false; + w.on("redraw", function() { redrawn = true; }); + cm.replaceSelection("0"); + is(!redrawn); +}, {value: "123\n456"}); + + +var knownScrollbarWidth; +function scrollbarWidth(measure) { + if (knownScrollbarWidth != null) return knownScrollbarWidth; + var div = document.createElement('div'); + div.style.cssText = "width: 50px; height: 50px; overflow-x: scroll"; + document.body.appendChild(div); + knownScrollbarWidth = div.offsetHeight - div.clientHeight; + document.body.removeChild(div); + return knownScrollbarWidth || 0; +} + +testCM("lineWidgetChanged", function(cm) { + addDoc(cm, 2, 300); + var halfScrollbarWidth = scrollbarWidth(cm.display.measure)/2; + cm.setOption('lineNumbers', true); + cm.setSize(600, cm.defaultTextHeight() * 50); + cm.scrollTo(null, cm.heightAtLine(125, "local")); + + var expectedWidgetHeight = 60; + var expectedLinesInWidget = 3; + function w() { + var node = document.createElement("div"); + // we use these children with just under half width of the line to check measurements are made with correct width + // when placed in the measure div. + // If the widget is measured at a width much narrower than it is displayed at, the underHalf children will span two lines and break the test. + // If the widget is measured at a width much wider than it is displayed at, the overHalf children will combine and break the test. + // Note that this test only checks widgets where coverGutter is true, because these require extra styling to get the width right. + // It may also be worthwhile to check this for non-coverGutter widgets. + // Visually: + // Good: + // | ------------- display width ------------- | + // | ------- widget-width when measured ------ | + // | | -- under-half -- | | -- under-half -- | | + // | | --- over-half --- | | + // | | --- over-half --- | | + // Height: measured as 3 lines, same as it will be when actually displayed + + // Bad (too narrow): + // | ------------- display width ------------- | + // | ------ widget-width when measured ----- | < -- uh oh + // | | -- under-half -- | | + // | | -- under-half -- | | < -- when measured, shoved to next line + // | | --- over-half --- | | + // | | --- over-half --- | | + // Height: measured as 4 lines, more than expected . Will be displayed as 3 lines! + + // Bad (too wide): + // | ------------- display width ------------- | + // | -------- widget-width when measured ------- | < -- uh oh + // | | -- under-half -- | | -- under-half -- | | + // | | --- over-half --- | | --- over-half --- | | < -- when measured, combined on one line + // Height: measured as 2 lines, less than expected. Will be displayed as 3 lines! + + var barelyUnderHalfWidthHtml = '
    '; + var barelyOverHalfWidthHtml = '
    '; + node.innerHTML = new Array(3).join(barelyUnderHalfWidthHtml) + new Array(3).join(barelyOverHalfWidthHtml); + node.style.cssText = "background: yellow;font-size:0;line-height: " + (expectedWidgetHeight/expectedLinesInWidget) + "px;"; + return node; + } + var info0 = cm.getScrollInfo(); + var w0 = cm.addLineWidget(0, w(), { coverGutter: true }); + var w150 = cm.addLineWidget(150, w(), { coverGutter: true }); + var w300 = cm.addLineWidget(300, w(), { coverGutter: true }); + var info1 = cm.getScrollInfo(); + eq(info0.height + (3 * expectedWidgetHeight), info1.height); + eq(info0.top + expectedWidgetHeight, info1.top); + expectedWidgetHeight = 12; + w0.node.style.lineHeight = w150.node.style.lineHeight = w300.node.style.lineHeight = (expectedWidgetHeight/expectedLinesInWidget) + "px"; + w0.changed(); w150.changed(); w300.changed(); + var info2 = cm.getScrollInfo(); + eq(info0.height + (3 * expectedWidgetHeight), info2.height); + eq(info0.top + expectedWidgetHeight, info2.top); +}); + +testCM("getLineNumber", function(cm) { + addDoc(cm, 2, 20); + var h1 = cm.getLineHandle(1); + eq(cm.getLineNumber(h1), 1); + cm.replaceRange("hi\nbye\n", Pos(0, 0)); + eq(cm.getLineNumber(h1), 3); + cm.setValue(""); + eq(cm.getLineNumber(h1), null); +}); + +testCM("jumpTheGap", function(cm) { + if (phantom) return; + var longLine = "abcdef ghiklmnop qrstuvw xyz "; + longLine += longLine; longLine += longLine; longLine += longLine; + cm.replaceRange(longLine, Pos(2, 0), Pos(2)); + cm.setSize("200px", null); + cm.getWrapperElement().style.lineHeight = 2; + cm.refresh(); + cm.setCursor(Pos(0, 1)); + cm.execCommand("goLineDown"); + eqPos(cm.getCursor(), Pos(1, 1)); + cm.execCommand("goLineDown"); + eqPos(cm.getCursor(), Pos(2, 1)); + cm.execCommand("goLineDown"); + eq(cm.getCursor().line, 2); + is(cm.getCursor().ch > 1); + cm.execCommand("goLineUp"); + eqPos(cm.getCursor(), Pos(2, 1)); + cm.execCommand("goLineUp"); + eqPos(cm.getCursor(), Pos(1, 1)); + var node = document.createElement("div"); + node.innerHTML = "hi"; node.style.height = "30px"; + cm.addLineWidget(0, node); + cm.addLineWidget(1, node.cloneNode(true), {above: true}); + cm.setCursor(Pos(0, 2)); + cm.execCommand("goLineDown"); + eqPos(cm.getCursor(), Pos(1, 2)); + cm.execCommand("goLineUp"); + eqPos(cm.getCursor(), Pos(0, 2)); +}, {lineWrapping: true, value: "abc\ndef\nghi\njkl\n"}); + +testCM("addLineClass", function(cm) { + function cls(line, text, bg, wrap, gutter) { + var i = cm.lineInfo(line); + eq(i.textClass, text); + eq(i.bgClass, bg); + eq(i.wrapClass, wrap); + if (typeof i.handle.gutterClass !== 'undefined') { + eq(i.handle.gutterClass, gutter); + } + } + cm.addLineClass(0, "text", "foo"); + cm.addLineClass(0, "text", "bar"); + cm.addLineClass(1, "background", "baz"); + cm.addLineClass(1, "wrap", "foo"); + cm.addLineClass(1, "gutter", "gutter-class"); + cls(0, "foo bar", null, null, null); + cls(1, null, "baz", "foo", "gutter-class"); + var lines = cm.display.lineDiv; + eq(byClassName(lines, "foo").length, 2); + eq(byClassName(lines, "bar").length, 1); + eq(byClassName(lines, "baz").length, 1); + eq(byClassName(lines, "gutter-class").length, 2); // Gutter classes are reflected in 2 nodes + cm.removeLineClass(0, "text", "foo"); + cls(0, "bar", null, null, null); + cm.removeLineClass(0, "text", "foo"); + cls(0, "bar", null, null, null); + cm.removeLineClass(0, "text", "bar"); + cls(0, null, null, null); + + cm.addLineClass(1, "wrap", "quux"); + cls(1, null, "baz", "foo quux", "gutter-class"); + cm.removeLineClass(1, "wrap"); + cls(1, null, "baz", null, "gutter-class"); + cm.removeLineClass(1, "gutter", "gutter-class"); + eq(byClassName(lines, "gutter-class").length, 0); + cls(1, null, "baz", null, null); + + cm.addLineClass(1, "gutter", "gutter-class"); + cls(1, null, "baz", null, "gutter-class"); + cm.removeLineClass(1, "gutter", "gutter-class"); + cls(1, null, "baz", null, null); + +}, {value: "hohoho\n", lineNumbers: true}); + +testCM("atomicMarker", function(cm) { + addDoc(cm, 10, 10); + function atom(ll, cl, lr, cr, li, ri) { + return cm.markText(Pos(ll, cl), Pos(lr, cr), + {atomic: true, inclusiveLeft: li, inclusiveRight: ri}); + } + var m = atom(0, 1, 0, 5); + cm.setCursor(Pos(0, 1)); + cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(0, 5)); + cm.execCommand("goCharLeft"); + eqPos(cm.getCursor(), Pos(0, 1)); + m.clear(); + m = atom(0, 0, 0, 5, true); + eqPos(cm.getCursor(), Pos(0, 5), "pushed out"); + cm.execCommand("goCharLeft"); + eqPos(cm.getCursor(), Pos(0, 5)); + m.clear(); + m = atom(8, 4, 9, 10, false, true); + cm.setCursor(Pos(9, 8)); + eqPos(cm.getCursor(), Pos(8, 4), "set"); + cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(8, 4), "char right"); + cm.execCommand("goLineDown"); + eqPos(cm.getCursor(), Pos(8, 4), "line down"); + cm.execCommand("goCharLeft"); + eqPos(cm.getCursor(), Pos(8, 3)); + m.clear(); + m = atom(1, 1, 3, 8); + cm.setCursor(Pos(0, 0)); + cm.setCursor(Pos(2, 0)); + eqPos(cm.getCursor(), Pos(3, 8)); + cm.execCommand("goCharLeft"); + eqPos(cm.getCursor(), Pos(1, 1)); + cm.execCommand("goCharRight"); + eqPos(cm.getCursor(), Pos(3, 8)); + cm.execCommand("goLineUp"); + eqPos(cm.getCursor(), Pos(1, 1)); + cm.execCommand("goLineDown"); + eqPos(cm.getCursor(), Pos(3, 8)); + cm.execCommand("delCharBefore"); + eq(cm.getValue().length, 80, "del chunk"); + m = atom(3, 0, 5, 5); + cm.setCursor(Pos(3, 0)); + cm.execCommand("delWordAfter"); + eq(cm.getValue().length, 53, "del chunk"); +}); + +testCM("selectionBias", function(cm) { + cm.markText(Pos(0, 1), Pos(0, 3), {atomic: true}); + cm.setCursor(Pos(0, 2)); + eqPos(cm.getCursor(), Pos(0, 1)); + cm.setCursor(Pos(0, 2)); + eqPos(cm.getCursor(), Pos(0, 3)); + cm.setCursor(Pos(0, 2)); + eqPos(cm.getCursor(), Pos(0, 1)); + cm.setCursor(Pos(0, 2), null, {bias: -1}); + eqPos(cm.getCursor(), Pos(0, 1)); + cm.setCursor(Pos(0, 4)); + cm.setCursor(Pos(0, 2), null, {bias: 1}); + eqPos(cm.getCursor(), Pos(0, 3)); +}, {value: "12345"}); + +testCM("selectionHomeEnd", function(cm) { + cm.markText(Pos(1, 0), Pos(1, 1), {atomic: true, inclusiveLeft: true}); + cm.markText(Pos(1, 3), Pos(1, 4), {atomic: true, inclusiveRight: true}); + cm.setCursor(Pos(1, 2)); + cm.execCommand("goLineStart"); + eqPos(cm.getCursor(), Pos(1, 1)); + cm.execCommand("goLineEnd"); + eqPos(cm.getCursor(), Pos(1, 3)); +}, {value: "ab\ncdef\ngh"}); + +testCM("readOnlyMarker", function(cm) { + function mark(ll, cl, lr, cr, at) { + return cm.markText(Pos(ll, cl), Pos(lr, cr), + {readOnly: true, atomic: at}); + } + var m = mark(0, 1, 0, 4); + cm.setCursor(Pos(0, 2)); + cm.replaceSelection("hi", "end"); + eqPos(cm.getCursor(), Pos(0, 2)); + eq(cm.getLine(0), "abcde"); + cm.execCommand("selectAll"); + cm.replaceSelection("oops", "around"); + eq(cm.getValue(), "oopsbcd"); + cm.undo(); + eqPos(m.find().from, Pos(0, 1)); + eqPos(m.find().to, Pos(0, 4)); + m.clear(); + cm.setCursor(Pos(0, 2)); + cm.replaceSelection("hi", "around"); + eq(cm.getLine(0), "abhicde"); + eqPos(cm.getCursor(), Pos(0, 4)); + m = mark(0, 2, 2, 2, true); + cm.setSelection(Pos(1, 1), Pos(2, 4)); + cm.replaceSelection("t", "end"); + eqPos(cm.getCursor(), Pos(2, 3)); + eq(cm.getLine(2), "klto"); + cm.execCommand("goCharLeft"); + cm.execCommand("goCharLeft"); + eqPos(cm.getCursor(), Pos(0, 2)); + cm.setSelection(Pos(0, 1), Pos(0, 3)); + cm.replaceSelection("xx", "around"); + eqPos(cm.getCursor(), Pos(0, 3)); + eq(cm.getLine(0), "axxhicde"); +}, {value: "abcde\nfghij\nklmno\n"}); + +testCM("dirtyBit", function(cm) { + eq(cm.isClean(), true); + cm.replaceSelection("boo", null, "test"); + eq(cm.isClean(), false); + cm.undo(); + eq(cm.isClean(), true); + cm.replaceSelection("boo", null, "test"); + cm.replaceSelection("baz", null, "test"); + cm.undo(); + eq(cm.isClean(), false); + cm.markClean(); + eq(cm.isClean(), true); + cm.undo(); + eq(cm.isClean(), false); + cm.redo(); + eq(cm.isClean(), true); +}); + +testCM("changeGeneration", function(cm) { + cm.replaceSelection("x"); + var softGen = cm.changeGeneration(); + cm.replaceSelection("x"); + cm.undo(); + eq(cm.getValue(), ""); + is(!cm.isClean(softGen)); + cm.replaceSelection("x"); + var hardGen = cm.changeGeneration(true); + cm.replaceSelection("x"); + cm.undo(); + eq(cm.getValue(), "x"); + is(cm.isClean(hardGen)); +}); + +testCM("addKeyMap", function(cm) { + function sendKey(code) { + cm.triggerOnKeyDown({type: "keydown", keyCode: code, + preventDefault: function(){}, stopPropagation: function(){}}); + } + + sendKey(39); + eqPos(cm.getCursor(), Pos(0, 1)); + var test = 0; + var map1 = {Right: function() { ++test; }}, map2 = {Right: function() { test += 10; }} + cm.addKeyMap(map1); + sendKey(39); + eqPos(cm.getCursor(), Pos(0, 1)); + eq(test, 1); + cm.addKeyMap(map2, true); + sendKey(39); + eq(test, 2); + cm.removeKeyMap(map1); + sendKey(39); + eq(test, 12); + cm.removeKeyMap(map2); + sendKey(39); + eq(test, 12); + eqPos(cm.getCursor(), Pos(0, 2)); + cm.addKeyMap({Right: function() { test = 55; }, name: "mymap"}); + sendKey(39); + eq(test, 55); + cm.removeKeyMap("mymap"); + sendKey(39); + eqPos(cm.getCursor(), Pos(0, 3)); +}, {value: "abc"}); + +testCM("findPosH", function(cm) { + forEach([{from: Pos(0, 0), to: Pos(0, 1), by: 1}, + {from: Pos(0, 0), to: Pos(0, 0), by: -1, hitSide: true}, + {from: Pos(0, 0), to: Pos(0, 4), by: 1, unit: "word"}, + {from: Pos(0, 0), to: Pos(0, 8), by: 2, unit: "word"}, + {from: Pos(0, 0), to: Pos(2, 0), by: 20, unit: "word", hitSide: true}, + {from: Pos(0, 7), to: Pos(0, 5), by: -1, unit: "word"}, + {from: Pos(0, 4), to: Pos(0, 8), by: 1, unit: "word"}, + {from: Pos(1, 0), to: Pos(1, 18), by: 3, unit: "word"}, + {from: Pos(1, 22), to: Pos(1, 5), by: -3, unit: "word"}, + {from: Pos(1, 15), to: Pos(1, 10), by: -5}, + {from: Pos(1, 15), to: Pos(1, 10), by: -5, unit: "column"}, + {from: Pos(1, 15), to: Pos(1, 0), by: -50, unit: "column", hitSide: true}, + {from: Pos(1, 15), to: Pos(1, 24), by: 50, unit: "column", hitSide: true}, + {from: Pos(1, 15), to: Pos(2, 0), by: 50, hitSide: true}], function(t) { + var r = cm.findPosH(t.from, t.by, t.unit || "char"); + eqPos(r, t.to); + eq(!!r.hitSide, !!t.hitSide); + }); +}, {value: "line one\nline two.something.other\n"}); + +testCM("beforeChange", function(cm) { + cm.on("beforeChange", function(cm, change) { + var text = []; + for (var i = 0; i < change.text.length; ++i) + text.push(change.text[i].replace(/\s/g, "_")); + change.update(null, null, text); + }); + cm.setValue("hello, i am a\nnew document\n"); + eq(cm.getValue(), "hello,_i_am_a\nnew_document\n"); + CodeMirror.on(cm.getDoc(), "beforeChange", function(doc, change) { + if (change.from.line == 0) change.cancel(); + }); + cm.setValue("oops"); // Canceled + eq(cm.getValue(), "hello,_i_am_a\nnew_document\n"); + cm.replaceRange("hey hey hey", Pos(1, 0), Pos(2, 0)); + eq(cm.getValue(), "hello,_i_am_a\nhey_hey_hey"); +}, {value: "abcdefghijk"}); + +testCM("beforeChangeUndo", function(cm) { + cm.replaceRange("hi", Pos(0, 0), Pos(0)); + cm.replaceRange("bye", Pos(0, 0), Pos(0)); + eq(cm.historySize().undo, 2); + cm.on("beforeChange", function(cm, change) { + is(!change.update); + change.cancel(); + }); + cm.undo(); + eq(cm.historySize().undo, 0); + eq(cm.getValue(), "bye\ntwo"); +}, {value: "one\ntwo"}); + +testCM("beforeSelectionChange", function(cm) { + function notAtEnd(cm, pos) { + var len = cm.getLine(pos.line).length; + if (!len || pos.ch == len) return Pos(pos.line, pos.ch - 1); + return pos; + } + cm.on("beforeSelectionChange", function(cm, obj) { + obj.update([{anchor: notAtEnd(cm, obj.ranges[0].anchor), + head: notAtEnd(cm, obj.ranges[0].head)}]); + }); + + addDoc(cm, 10, 10); + cm.execCommand("goLineEnd"); + eqPos(cm.getCursor(), Pos(0, 9)); + cm.execCommand("selectAll"); + eqPos(cm.getCursor("start"), Pos(0, 0)); + eqPos(cm.getCursor("end"), Pos(9, 9)); +}); + +testCM("change_removedText", function(cm) { + cm.setValue("abc\ndef"); + + var removedText = []; + cm.on("change", function(cm, change) { + removedText.push(change.removed); + }); + + cm.operation(function() { + cm.replaceRange("xyz", Pos(0, 0), Pos(1,1)); + cm.replaceRange("123", Pos(0,0)); + }); + + eq(removedText.length, 2); + eq(removedText[0].join("\n"), "abc\nd"); + eq(removedText[1].join("\n"), ""); + + var removedText = []; + cm.undo(); + eq(removedText.length, 2); + eq(removedText[0].join("\n"), "123"); + eq(removedText[1].join("\n"), "xyz"); + + var removedText = []; + cm.redo(); + eq(removedText.length, 2); + eq(removedText[0].join("\n"), "abc\nd"); + eq(removedText[1].join("\n"), ""); +}); + +testCM("lineStyleFromMode", function(cm) { + CodeMirror.defineMode("test_mode", function() { + return {token: function(stream) { + if (stream.match(/^\[[^\]]*\]/)) return " line-brackets "; + if (stream.match(/^\([^\)]*\)/)) return " line-background-parens "; + if (stream.match(/^<[^>]*>/)) return " span line-line line-background-bg "; + stream.match(/^\s+|^\S+/); + }}; + }); + cm.setOption("mode", "test_mode"); + var bracketElts = byClassName(cm.getWrapperElement(), "brackets"); + eq(bracketElts.length, 1, "brackets count"); + eq(bracketElts[0].nodeName, "PRE"); + is(!/brackets.*brackets/.test(bracketElts[0].className)); + var parenElts = byClassName(cm.getWrapperElement(), "parens"); + eq(parenElts.length, 1, "parens count"); + eq(parenElts[0].nodeName, "DIV"); + is(!/parens.*parens/.test(parenElts[0].className)); + eq(parenElts[0].parentElement.nodeName, "DIV"); + + eq(byClassName(cm.getWrapperElement(), "bg").length, 1); + eq(byClassName(cm.getWrapperElement(), "line").length, 1); + var spanElts = byClassName(cm.getWrapperElement(), "cm-span"); + eq(spanElts.length, 2); + is(/^\s*cm-span\s*$/.test(spanElts[0].className)); +}, {value: "line1: [br] [br]\nline2: (par) (par)\nline3: "}); + +testCM("lineStyleFromBlankLine", function(cm) { + CodeMirror.defineMode("lineStyleFromBlankLine_mode", function() { + return {token: function(stream) { stream.skipToEnd(); return "comment"; }, + blankLine: function() { return "line-blank"; }}; + }); + cm.setOption("mode", "lineStyleFromBlankLine_mode"); + var blankElts = byClassName(cm.getWrapperElement(), "blank"); + eq(blankElts.length, 1); + eq(blankElts[0].nodeName, "PRE"); + cm.replaceRange("x", Pos(1, 0)); + blankElts = byClassName(cm.getWrapperElement(), "blank"); + eq(blankElts.length, 0); +}, {value: "foo\n\nbar"}); + +CodeMirror.registerHelper("xxx", "a", "A"); +CodeMirror.registerHelper("xxx", "b", "B"); +CodeMirror.defineMode("yyy", function() { + return { + token: function(stream) { stream.skipToEnd(); }, + xxx: ["a", "b", "q"] + }; +}); +CodeMirror.registerGlobalHelper("xxx", "c", function(m) { return m.enableC; }, "C"); + +testCM("helpers", function(cm) { + cm.setOption("mode", "yyy"); + eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "A/B"); + cm.setOption("mode", {name: "yyy", modeProps: {xxx: "b", enableC: true}}); + eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "B/C"); + cm.setOption("mode", "javascript"); + eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), ""); +}); + +testCM("selectionHistory", function(cm) { + for (var i = 0; i < 3; i++) { + cm.setExtending(true); + cm.execCommand("goCharRight"); + cm.setExtending(false); + cm.execCommand("goCharRight"); + cm.execCommand("goCharRight"); + } + cm.execCommand("undoSelection"); + eq(cm.getSelection(), "c"); + cm.execCommand("undoSelection"); + eq(cm.getSelection(), ""); + eqPos(cm.getCursor(), Pos(0, 4)); + cm.execCommand("undoSelection"); + eq(cm.getSelection(), "b"); + cm.execCommand("redoSelection"); + eq(cm.getSelection(), ""); + eqPos(cm.getCursor(), Pos(0, 4)); + cm.execCommand("redoSelection"); + eq(cm.getSelection(), "c"); + cm.execCommand("redoSelection"); + eq(cm.getSelection(), ""); + eqPos(cm.getCursor(), Pos(0, 6)); +}, {value: "a b c d"}); + +testCM("selectionChangeReducesRedo", function(cm) { + cm.replaceSelection("X"); + cm.execCommand("goCharRight"); + cm.undoSelection(); + cm.execCommand("selectAll"); + cm.undoSelection(); + eq(cm.getValue(), "Xabc"); + eqPos(cm.getCursor(), Pos(0, 1)); + cm.undoSelection(); + eq(cm.getValue(), "abc"); +}, {value: "abc"}); + +testCM("selectionHistoryNonOverlapping", function(cm) { + cm.setSelection(Pos(0, 0), Pos(0, 1)); + cm.setSelection(Pos(0, 2), Pos(0, 3)); + cm.execCommand("undoSelection"); + eqPos(cm.getCursor("anchor"), Pos(0, 0)); + eqPos(cm.getCursor("head"), Pos(0, 1)); +}, {value: "1234"}); + +testCM("cursorMotionSplitsHistory", function(cm) { + cm.replaceSelection("a"); + cm.execCommand("goCharRight"); + cm.replaceSelection("b"); + cm.replaceSelection("c"); + cm.undo(); + eq(cm.getValue(), "a1234"); + eqPos(cm.getCursor(), Pos(0, 2)); + cm.undo(); + eq(cm.getValue(), "1234"); + eqPos(cm.getCursor(), Pos(0, 0)); +}, {value: "1234"}); + +testCM("selChangeInOperationDoesNotSplit", function(cm) { + for (var i = 0; i < 4; i++) { + cm.operation(function() { + cm.replaceSelection("x"); + cm.setCursor(Pos(0, cm.getCursor().ch - 1)); + }); + } + eqPos(cm.getCursor(), Pos(0, 0)); + eq(cm.getValue(), "xxxxa"); + cm.undo(); + eq(cm.getValue(), "a"); +}, {value: "a"}); + +testCM("alwaysMergeSelEventWithChangeOrigin", function(cm) { + cm.replaceSelection("U", null, "foo"); + cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "foo"}); + cm.undoSelection(); + eq(cm.getValue(), "a"); + cm.replaceSelection("V", null, "foo"); + cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "bar"}); + cm.undoSelection(); + eq(cm.getValue(), "Va"); +}, {value: "a"}); + +testCM("getTokenAt", function(cm) { + var tokPlus = cm.getTokenAt(Pos(0, 2)); + eq(tokPlus.type, "operator"); + eq(tokPlus.string, "+"); + var toks = cm.getLineTokens(0); + eq(toks.length, 3); + forEach([["number", "1"], ["operator", "+"], ["number", "2"]], function(expect, i) { + eq(toks[i].type, expect[0]); + eq(toks[i].string, expect[1]); + }); +}, {value: "1+2", mode: "javascript"}); + +testCM("getTokenTypeAt", function(cm) { + eq(cm.getTokenTypeAt(Pos(0, 0)), "number"); + eq(cm.getTokenTypeAt(Pos(0, 6)), "string"); + cm.addOverlay({ + token: function(stream) { + if (stream.match("foo")) return "foo"; + else stream.next(); + } + }); + eq(byClassName(cm.getWrapperElement(), "cm-foo").length, 1); + eq(cm.getTokenTypeAt(Pos(0, 6)), "string"); +}, {value: "1 + 'foo'", mode: "javascript"}); + +testCM("resizeLineWidget", function(cm) { + addDoc(cm, 200, 3); + var widget = document.createElement("pre"); + widget.innerHTML = "imwidget"; + widget.style.background = "yellow"; + cm.addLineWidget(1, widget, {noHScroll: true}); + cm.setSize(40); + is(widget.parentNode.offsetWidth < 42); +}); + +testCM("combinedOperations", function(cm) { + var place = document.getElementById("testground"); + var other = CodeMirror(place, {value: "123"}); + try { + cm.operation(function() { + cm.addLineClass(0, "wrap", "foo"); + other.addLineClass(0, "wrap", "foo"); + }); + eq(byClassName(cm.getWrapperElement(), "foo").length, 1); + eq(byClassName(other.getWrapperElement(), "foo").length, 1); + cm.operation(function() { + cm.removeLineClass(0, "wrap", "foo"); + other.removeLineClass(0, "wrap", "foo"); + }); + eq(byClassName(cm.getWrapperElement(), "foo").length, 0); + eq(byClassName(other.getWrapperElement(), "foo").length, 0); + } finally { + place.removeChild(other.getWrapperElement()); + } +}, {value: "abc"}); + +testCM("eventOrder", function(cm) { + var seen = []; + cm.on("change", function() { + if (!seen.length) cm.replaceSelection("."); + seen.push("change"); + }); + cm.on("cursorActivity", function() { + cm.replaceSelection("!"); + seen.push("activity"); + }); + cm.replaceSelection("/"); + eq(seen.join(","), "change,change,activity,change"); +}); + +testCM("splitSpaces_nonspecial", function(cm) { + eq(byClassName(cm.getWrapperElement(), "cm-invalidchar").length, 0); +}, { + specialChars: /[\u00a0]/, + value: "spaces -> <- between" +}); + +test("core_rmClass", function() { + var node = document.createElement("div"); + node.className = "foo-bar baz-quux yadda"; + CodeMirror.rmClass(node, "quux"); + eq(node.className, "foo-bar baz-quux yadda"); + CodeMirror.rmClass(node, "baz-quux"); + eq(node.className, "foo-bar yadda"); + CodeMirror.rmClass(node, "yadda"); + eq(node.className, "foo-bar"); + CodeMirror.rmClass(node, "foo-bar"); + eq(node.className, ""); + node.className = " foo "; + CodeMirror.rmClass(node, "foo"); + eq(node.className, ""); +}); + +test("core_addClass", function() { + var node = document.createElement("div"); + CodeMirror.addClass(node, "a"); + eq(node.className, "a"); + CodeMirror.addClass(node, "a"); + eq(node.className, "a"); + CodeMirror.addClass(node, "b"); + eq(node.className, "a b"); + CodeMirror.addClass(node, "a"); + CodeMirror.addClass(node, "b"); + eq(node.className, "a b"); +}); + +testCM("lineSeparator", function(cm) { + eq(cm.lineCount(), 3); + eq(cm.getLine(1), "bar\r"); + eq(cm.getLine(2), "baz\rquux"); + cm.setOption("lineSeparator", "\r"); + eq(cm.lineCount(), 5); + eq(cm.getLine(4), "quux"); + eq(cm.getValue(), "foo\rbar\r\rbaz\rquux"); + eq(cm.getValue("\n"), "foo\nbar\n\nbaz\nquux"); + cm.setOption("lineSeparator", null); + cm.setValue("foo\nbar\r\nbaz\rquux"); + eq(cm.lineCount(), 4); +}, {value: "foo\nbar\r\nbaz\rquux", + lineSeparator: "\n"}); diff --git a/www/code/codemirror-5.13.2/test/vim_test.js b/www/code/codemirror-5.13.2/test/vim_test.js new file mode 100644 index 000000000..7080c3062 --- /dev/null +++ b/www/code/codemirror-5.13.2/test/vim_test.js @@ -0,0 +1,4003 @@ +CodeMirror.Vim.suppressErrorLogging = true; + +var code = '' + +' wOrd1 (#%\n' + +' word3] \n' + +'aopop pop 0 1 2 3 4\n' + +' (a) [b] {c} \n' + +'int getchar(void) {\n' + +' static char buf[BUFSIZ];\n' + +' static char *bufp = buf;\n' + +' if (n == 0) { /* buffer is empty */\n' + +' n = read(0, buf, sizeof buf);\n' + +' bufp = buf;\n' + +' }\n' + +'\n' + +' return (--n >= 0) ? (unsigned char) *bufp++ : EOF;\n' + +' \n' + +'}\n'; + +var lines = (function() { + lineText = code.split('\n'); + var ret = []; + for (var i = 0; i < lineText.length; i++) { + ret[i] = { + line: i, + length: lineText[i].length, + lineText: lineText[i], + textStart: /^\s*/.exec(lineText[i])[0].length + }; + } + return ret; +})(); +var endOfDocument = makeCursor(lines.length - 1, + lines[lines.length - 1].length); +var wordLine = lines[0]; +var bigWordLine = lines[1]; +var charLine = lines[2]; +var bracesLine = lines[3]; +var seekBraceLine = lines[4]; + +var word1 = { + start: { line: wordLine.line, ch: 1 }, + end: { line: wordLine.line, ch: 5 } +}; +var word2 = { + start: { line: wordLine.line, ch: word1.end.ch + 2 }, + end: { line: wordLine.line, ch: word1.end.ch + 4 } +}; +var word3 = { + start: { line: bigWordLine.line, ch: 1 }, + end: { line: bigWordLine.line, ch: 5 } +}; +var bigWord1 = word1; +var bigWord2 = word2; +var bigWord3 = { + start: { line: bigWordLine.line, ch: 1 }, + end: { line: bigWordLine.line, ch: 7 } +}; +var bigWord4 = { + start: { line: bigWordLine.line, ch: bigWord1.end.ch + 3 }, + end: { line: bigWordLine.line, ch: bigWord1.end.ch + 7 } +}; + +var oChars = [ { line: charLine.line, ch: 1 }, + { line: charLine.line, ch: 3 }, + { line: charLine.line, ch: 7 } ]; +var pChars = [ { line: charLine.line, ch: 2 }, + { line: charLine.line, ch: 4 }, + { line: charLine.line, ch: 6 }, + { line: charLine.line, ch: 8 } ]; +var numChars = [ { line: charLine.line, ch: 10 }, + { line: charLine.line, ch: 12 }, + { line: charLine.line, ch: 14 }, + { line: charLine.line, ch: 16 }, + { line: charLine.line, ch: 18 }]; +var parens1 = { + start: { line: bracesLine.line, ch: 1 }, + end: { line: bracesLine.line, ch: 3 } +}; +var squares1 = { + start: { line: bracesLine.line, ch: 5 }, + end: { line: bracesLine.line, ch: 7 } +}; +var curlys1 = { + start: { line: bracesLine.line, ch: 9 }, + end: { line: bracesLine.line, ch: 11 } +}; +var seekOutside = { + start: { line: seekBraceLine.line, ch: 1 }, + end: { line: seekBraceLine.line, ch: 16 } +}; +var seekInside = { + start: { line: seekBraceLine.line, ch: 14 }, + end: { line: seekBraceLine.line, ch: 11 } +}; + +function copyCursor(cur) { + return { ch: cur.ch, line: cur.line }; +} + +function forEach(arr, func) { + for (var i = 0; i < arr.length; i++) { + func(arr[i], i, arr); + } +} + +function testVim(name, run, opts, expectedFail) { + var vimOpts = { + lineNumbers: true, + vimMode: true, + showCursorWhenSelecting: true, + value: code + }; + for (var prop in opts) { + if (opts.hasOwnProperty(prop)) { + vimOpts[prop] = opts[prop]; + } + } + return test('vim_' + name, function() { + var place = document.getElementById("testground"); + var cm = CodeMirror(place, vimOpts); + var vim = CodeMirror.Vim.maybeInitVimState_(cm); + + function doKeysFn(cm) { + return function(args) { + if (args instanceof Array) { + arguments = args; + } + for (var i = 0; i < arguments.length; i++) { + CodeMirror.Vim.handleKey(cm, arguments[i]); + } + } + } + function doInsertModeKeysFn(cm) { + return function(args) { + if (args instanceof Array) { arguments = args; } + function executeHandler(handler) { + if (typeof handler == 'string') { + CodeMirror.commands[handler](cm); + } else { + handler(cm); + } + return true; + } + for (var i = 0; i < arguments.length; i++) { + var key = arguments[i]; + // Find key in keymap and handle. + var handled = CodeMirror.lookupKey(key, 'vim-insert', executeHandler); + // Record for insert mode. + if (handled == "handled" && cm.state.vim.insertMode && arguments[i] != 'Esc') { + var lastChange = CodeMirror.Vim.getVimGlobalState_().macroModeState.lastInsertModeChanges; + if (lastChange) { + lastChange.changes.push(new CodeMirror.Vim.InsertModeKey(key)); + } + } + } + } + } + function doExFn(cm) { + return function(command) { + cm.openDialog = helpers.fakeOpenDialog(command); + helpers.doKeys(':'); + } + } + function assertCursorAtFn(cm) { + return function(line, ch) { + var pos; + if (ch == null && typeof line.line == 'number') { + pos = line; + } else { + pos = makeCursor(line, ch); + } + eqPos(pos, cm.getCursor()); + } + } + function fakeOpenDialog(result) { + return function(text, callback) { + return callback(result); + } + } + function fakeOpenNotification(matcher) { + return function(text) { + matcher(text); + } + } + var helpers = { + doKeys: doKeysFn(cm), + // Warning: Only emulates keymap events, not character insertions. Use + // replaceRange to simulate character insertions. + // Keys are in CodeMirror format, NOT vim format. + doInsertModeKeys: doInsertModeKeysFn(cm), + doEx: doExFn(cm), + assertCursorAt: assertCursorAtFn(cm), + fakeOpenDialog: fakeOpenDialog, + fakeOpenNotification: fakeOpenNotification, + getRegisterController: function() { + return CodeMirror.Vim.getRegisterController(); + } + } + CodeMirror.Vim.resetVimGlobalState_(); + var successful = false; + var savedOpenNotification = cm.openNotification; + var savedOpenDialog = cm.openDialog; + try { + run(cm, vim, helpers); + successful = true; + } finally { + cm.openNotification = savedOpenNotification; + cm.openDialog = savedOpenDialog; + if (!successful || verbose) { + place.style.visibility = "visible"; + } else { + place.removeChild(cm.getWrapperElement()); + } + } + }, expectedFail); +}; +testVim('qq@q', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'q', 'l', 'l', 'q'); + helpers.assertCursorAt(0,2); + helpers.doKeys('@', 'q'); + helpers.assertCursorAt(0,4); +}, { value: ' '}); +testVim('@@', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'q', 'l', 'l', 'q'); + helpers.assertCursorAt(0,2); + helpers.doKeys('@', 'q'); + helpers.assertCursorAt(0,4); + helpers.doKeys('@', '@'); + helpers.assertCursorAt(0,6); +}, { value: ' '}); +var jumplistScene = ''+ + 'word\n'+ + '(word)\n'+ + '{word\n'+ + 'word.\n'+ + '\n'+ + 'word search\n'+ + '}word\n'+ + 'word\n'+ + 'word\n'; +function testJumplist(name, keys, endPos, startPos, dialog) { + endPos = makeCursor(endPos[0], endPos[1]); + startPos = makeCursor(startPos[0], startPos[1]); + testVim(name, function(cm, vim, helpers) { + CodeMirror.Vim.resetVimGlobalState_(); + if(dialog)cm.openDialog = helpers.fakeOpenDialog('word'); + cm.setCursor(startPos); + helpers.doKeys.apply(null, keys); + helpers.assertCursorAt(endPos); + }, {value: jumplistScene}); +}; +testJumplist('jumplist_H', ['H', ''], [5,2], [5,2]); +testJumplist('jumplist_M', ['M', ''], [2,2], [2,2]); +testJumplist('jumplist_L', ['L', ''], [2,2], [2,2]); +testJumplist('jumplist_[[', ['[', '[', ''], [5,2], [5,2]); +testJumplist('jumplist_]]', [']', ']', ''], [2,2], [2,2]); +testJumplist('jumplist_G', ['G', ''], [5,2], [5,2]); +testJumplist('jumplist_gg', ['g', 'g', ''], [5,2], [5,2]); +testJumplist('jumplist_%', ['%', ''], [1,5], [1,5]); +testJumplist('jumplist_{', ['{', ''], [1,5], [1,5]); +testJumplist('jumplist_}', ['}', ''], [1,5], [1,5]); +testJumplist('jumplist_\'', ['m', 'a', 'h', '\'', 'a', 'h', ''], [1,0], [1,5]); +testJumplist('jumplist_`', ['m', 'a', 'h', '`', 'a', 'h', ''], [1,5], [1,5]); +testJumplist('jumplist_*_cachedCursor', ['*', ''], [1,3], [1,3]); +testJumplist('jumplist_#_cachedCursor', ['#', ''], [1,3], [1,3]); +testJumplist('jumplist_n', ['#', 'n', ''], [1,1], [2,3]); +testJumplist('jumplist_N', ['#', 'N', ''], [1,1], [2,3]); +testJumplist('jumplist_repeat_', ['*', '*', '*', '3', ''], [2,3], [2,3]); +testJumplist('jumplist_repeat_', ['*', '*', '*', '3', '', '2', ''], [5,0], [2,3]); +testJumplist('jumplist_repeated_motion', ['3', '*', ''], [2,3], [2,3]); +testJumplist('jumplist_/', ['/', ''], [2,3], [2,3], 'dialog'); +testJumplist('jumplist_?', ['?', ''], [2,3], [2,3], 'dialog'); +testJumplist('jumplist_skip_delted_mark', + ['*', 'n', 'n', 'k', 'd', 'k', '', '', ''], + [0,2], [0,2]); +testJumplist('jumplist_skip_delted_mark', + ['*', 'n', 'n', 'k', 'd', 'k', '', '', ''], + [1,0], [0,2]); + +/** + * @param name Name of the test + * @param keys An array of keys or a string with a single key to simulate. + * @param endPos The expected end position of the cursor. + * @param startPos The position the cursor should start at, defaults to 0, 0. + */ +function testMotion(name, keys, endPos, startPos) { + testVim(name, function(cm, vim, helpers) { + if (!startPos) { + startPos = { line: 0, ch: 0 }; + } + cm.setCursor(startPos); + helpers.doKeys(keys); + helpers.assertCursorAt(endPos); + }); +}; + +function makeCursor(line, ch) { + return { line: line, ch: ch }; +}; + +function offsetCursor(cur, offsetLine, offsetCh) { + return { line: cur.line + offsetLine, ch: cur.ch + offsetCh }; +}; + +// Motion tests +testMotion('|', '|', makeCursor(0, 0), makeCursor(0,4)); +testMotion('|_repeat', ['3', '|'], makeCursor(0, 2), makeCursor(0,4)); +testMotion('h', 'h', makeCursor(0, 0), word1.start); +testMotion('h_repeat', ['3', 'h'], offsetCursor(word1.end, 0, -3), word1.end); +testMotion('l', 'l', makeCursor(0, 1)); +testMotion('l_repeat', ['2', 'l'], makeCursor(0, 2)); +testMotion('j', 'j', offsetCursor(word1.end, 1, 0), word1.end); +testMotion('j_repeat', ['2', 'j'], offsetCursor(word1.end, 2, 0), word1.end); +testMotion('j_repeat_clip', ['1000', 'j'], endOfDocument); +testMotion('k', 'k', offsetCursor(word3.end, -1, 0), word3.end); +testMotion('k_repeat', ['2', 'k'], makeCursor(0, 4), makeCursor(2, 4)); +testMotion('k_repeat_clip', ['1000', 'k'], makeCursor(0, 4), makeCursor(2, 4)); +testMotion('w', 'w', word1.start); +testMotion('w_multiple_newlines_no_space', 'w', makeCursor(12, 2), makeCursor(11, 2)); +testMotion('w_multiple_newlines_with_space', 'w', makeCursor(14, 0), makeCursor(12, 51)); +testMotion('w_repeat', ['2', 'w'], word2.start); +testMotion('w_wrap', ['w'], word3.start, word2.start); +testMotion('w_endOfDocument', 'w', endOfDocument, endOfDocument); +testMotion('w_start_to_end', ['1000', 'w'], endOfDocument, makeCursor(0, 0)); +testMotion('W', 'W', bigWord1.start); +testMotion('W_repeat', ['2', 'W'], bigWord3.start, bigWord1.start); +testMotion('e', 'e', word1.end); +testMotion('e_repeat', ['2', 'e'], word2.end); +testMotion('e_wrap', 'e', word3.end, word2.end); +testMotion('e_endOfDocument', 'e', endOfDocument, endOfDocument); +testMotion('e_start_to_end', ['1000', 'e'], endOfDocument, makeCursor(0, 0)); +testMotion('b', 'b', word3.start, word3.end); +testMotion('b_repeat', ['2', 'b'], word2.start, word3.end); +testMotion('b_wrap', 'b', word2.start, word3.start); +testMotion('b_startOfDocument', 'b', makeCursor(0, 0), makeCursor(0, 0)); +testMotion('b_end_to_start', ['1000', 'b'], makeCursor(0, 0), endOfDocument); +testMotion('ge', ['g', 'e'], word2.end, word3.end); +testMotion('ge_repeat', ['2', 'g', 'e'], word1.end, word3.start); +testMotion('ge_wrap', ['g', 'e'], word2.end, word3.start); +testMotion('ge_startOfDocument', ['g', 'e'], makeCursor(0, 0), + makeCursor(0, 0)); +testMotion('ge_end_to_start', ['1000', 'g', 'e'], makeCursor(0, 0), endOfDocument); +testMotion('gg', ['g', 'g'], makeCursor(lines[0].line, lines[0].textStart), + makeCursor(3, 1)); +testMotion('gg_repeat', ['3', 'g', 'g'], + makeCursor(lines[2].line, lines[2].textStart)); +testMotion('G', 'G', + makeCursor(lines[lines.length - 1].line, lines[lines.length - 1].textStart), + makeCursor(3, 1)); +testMotion('G_repeat', ['3', 'G'], makeCursor(lines[2].line, + lines[2].textStart)); +// TODO: Make the test code long enough to test Ctrl-F and Ctrl-B. +testMotion('0', '0', makeCursor(0, 0), makeCursor(0, 8)); +testMotion('^', '^', makeCursor(0, lines[0].textStart), makeCursor(0, 8)); +testMotion('+', '+', makeCursor(1, lines[1].textStart), makeCursor(0, 8)); +testMotion('-', '-', makeCursor(0, lines[0].textStart), makeCursor(1, 4)); +testMotion('_', ['6','_'], makeCursor(5, lines[5].textStart), makeCursor(0, 8)); +testMotion('$', '$', makeCursor(0, lines[0].length - 1), makeCursor(0, 1)); +testMotion('$_repeat', ['2', '$'], makeCursor(1, lines[1].length - 1), + makeCursor(0, 3)); +testMotion('f', ['f', 'p'], pChars[0], makeCursor(charLine.line, 0)); +testMotion('f_repeat', ['2', 'f', 'p'], pChars[2], pChars[0]); +testMotion('f_num', ['f', '2'], numChars[2], makeCursor(charLine.line, 0)); +testMotion('t', ['t','p'], offsetCursor(pChars[0], 0, -1), + makeCursor(charLine.line, 0)); +testMotion('t_repeat', ['2', 't', 'p'], offsetCursor(pChars[2], 0, -1), + pChars[0]); +testMotion('F', ['F', 'p'], pChars[0], pChars[1]); +testMotion('F_repeat', ['2', 'F', 'p'], pChars[0], pChars[2]); +testMotion('T', ['T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[1]); +testMotion('T_repeat', ['2', 'T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[2]); +testMotion('%_parens', ['%'], parens1.end, parens1.start); +testMotion('%_squares', ['%'], squares1.end, squares1.start); +testMotion('%_braces', ['%'], curlys1.end, curlys1.start); +testMotion('%_seek_outside', ['%'], seekOutside.end, seekOutside.start); +testMotion('%_seek_inside', ['%'], seekInside.end, seekInside.start); +testVim('%_seek_skip', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,9); +}, {value:'01234"("()'}); +testVim('%_skip_string', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,4); + cm.setCursor(0,2); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,0); +}, {value:'(")")'}); +testVim('%_skip_comment', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,6); + cm.setCursor(0,3); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,0); +}, {value:'(/*)*/)'}); +// Make sure that moving down after going to the end of a line always leaves you +// at the end of a line, but preserves the offset in other cases +testVim('Changing lines after Eol operation', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys(['$']); + helpers.doKeys(['j']); + // After moving to Eol and then down, we should be at Eol of line 2 + helpers.assertCursorAt({ line: 1, ch: lines[1].length - 1 }); + helpers.doKeys(['j']); + // After moving down, we should be at Eol of line 3 + helpers.assertCursorAt({ line: 2, ch: lines[2].length - 1 }); + helpers.doKeys(['h']); + helpers.doKeys(['j']); + // After moving back one space and then down, since line 4 is shorter than line 2, we should + // be at Eol of line 2 - 1 + helpers.assertCursorAt({ line: 3, ch: lines[3].length - 1 }); + helpers.doKeys(['j']); + helpers.doKeys(['j']); + // After moving down again, since line 3 has enough characters, we should be back to the + // same place we were at on line 1 + helpers.assertCursorAt({ line: 5, ch: lines[2].length - 2 }); +}); +//making sure gj and gk recover from clipping +testVim('gj_gk_clipping', function(cm,vim,helpers){ + cm.setCursor(0, 1); + helpers.doKeys('g','j','g','j'); + helpers.assertCursorAt(2, 1); + helpers.doKeys('g','k','g','k'); + helpers.assertCursorAt(0, 1); +},{value: 'line 1\n\nline 2'}); +//testing a mix of j/k and gj/gk +testVim('j_k_and_gj_gk', function(cm,vim,helpers){ + cm.setSize(120); + cm.setCursor(0, 0); + //go to the last character on the first line + helpers.doKeys('$'); + //move up/down on the column within the wrapped line + //side-effect: cursor is not locked to eol anymore + helpers.doKeys('g','k'); + var cur=cm.getCursor(); + eq(cur.line,0); + is((cur.ch<176),'gk didn\'t move cursor back (1)'); + helpers.doKeys('g','j'); + helpers.assertCursorAt(0, 176); + //should move to character 177 on line 2 (j/k preserve character index within line) + helpers.doKeys('j'); + //due to different line wrapping, the cursor can be on a different screen-x now + //gj and gk preserve screen-x on movement, much like moveV + helpers.doKeys('3','g','k'); + cur=cm.getCursor(); + eq(cur.line,1); + is((cur.ch<176),'gk didn\'t move cursor back (2)'); + helpers.doKeys('g','j','2','g','j'); + //should return to the same character-index + helpers.doKeys('k'); + helpers.assertCursorAt(0, 176); +},{ lineWrapping:true, value: 'This line is intentially long to test movement of gj and gk over wrapped lines. I will start on the end of this line, then make a step up and back to set the origin for j and k.\nThis line is supposed to be even longer than the previous. I will jump here and make another wiggle with gj and gk, before I jump back to the line above. Both wiggles should not change my cursor\'s target character but both j/k and gj/gk change each other\'s reference position.'}); +testVim('gj_gk', function(cm, vim, helpers) { + if (phantom) return; + cm.setSize(120); + // Test top of document edge case. + cm.setCursor(0, 4); + helpers.doKeys('g', 'j'); + helpers.doKeys('10', 'g', 'k'); + helpers.assertCursorAt(0, 4); + + // Test moving down preserves column position. + helpers.doKeys('g', 'j'); + var pos1 = cm.getCursor(); + var expectedPos2 = { line: 0, ch: (pos1.ch - 4) * 2 + 4}; + helpers.doKeys('g', 'j'); + helpers.assertCursorAt(expectedPos2); + + // Move to the last character + cm.setCursor(0, 0); + // Move left to reset HSPos + helpers.doKeys('h'); + // Test bottom of document edge case. + helpers.doKeys('100', 'g', 'j'); + var endingPos = cm.getCursor(); + is(endingPos != 0, 'gj should not be on wrapped line 0'); + var topLeftCharCoords = cm.charCoords(makeCursor(0, 0)); + var endingCharCoords = cm.charCoords(endingPos); + is(topLeftCharCoords.left == endingCharCoords.left, 'gj should end up on column 0'); +},{ lineNumbers: false, lineWrapping:true, value: 'Thislineisintentiallylongtotestmovementofgjandgkoverwrappedlines.' }); +testVim('}', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('}'); + helpers.assertCursorAt(1, 0); + cm.setCursor(0, 0); + helpers.doKeys('2', '}'); + helpers.assertCursorAt(4, 0); + cm.setCursor(0, 0); + helpers.doKeys('6', '}'); + helpers.assertCursorAt(5, 0); +}, { value: 'a\n\nb\nc\n\nd' }); +testVim('{', function(cm, vim, helpers) { + cm.setCursor(5, 0); + helpers.doKeys('{'); + helpers.assertCursorAt(4, 0); + cm.setCursor(5, 0); + helpers.doKeys('2', '{'); + helpers.assertCursorAt(1, 0); + cm.setCursor(5, 0); + helpers.doKeys('6', '{'); + helpers.assertCursorAt(0, 0); +}, { value: 'a\n\nb\nc\n\nd' }); +testVim('paragraph_motions', function(cm, vim, helpers) { + cm.setCursor(10, 0); + helpers.doKeys('{'); + helpers.assertCursorAt(4, 0); + helpers.doKeys('{'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('2', '}'); + helpers.assertCursorAt(7, 0); + helpers.doKeys('2', '}'); + helpers.assertCursorAt(16, 0); + + cm.setCursor(9, 0); + helpers.doKeys('}'); + helpers.assertCursorAt(14, 0); + + cm.setCursor(6, 0); + helpers.doKeys('}'); + helpers.assertCursorAt(7, 0); + + // ip inside empty space + cm.setCursor(10, 0); + helpers.doKeys('v', 'i', 'p'); + eqPos(Pos(7, 0), cm.getCursor('anchor')); + eqPos(Pos(12, 0), cm.getCursor('head')); + helpers.doKeys('i', 'p'); + eqPos(Pos(7, 0), cm.getCursor('anchor')); + eqPos(Pos(13, 1), cm.getCursor('head')); + helpers.doKeys('2', 'i', 'p'); + eqPos(Pos(7, 0), cm.getCursor('anchor')); + eqPos(Pos(16, 1), cm.getCursor('head')); + + // should switch to visualLine mode + cm.setCursor(14, 0); + helpers.doKeys('', 'v', 'i', 'p'); + helpers.assertCursorAt(14, 0); + + cm.setCursor(14, 0); + helpers.doKeys('', 'V', 'i', 'p'); + eqPos(Pos(16, 1), cm.getCursor('head')); + + // ap inside empty space + cm.setCursor(10, 0); + helpers.doKeys('', 'v', 'a', 'p'); + eqPos(Pos(7, 0), cm.getCursor('anchor')); + eqPos(Pos(13, 1), cm.getCursor('head')); + helpers.doKeys('a', 'p'); + eqPos(Pos(7, 0), cm.getCursor('anchor')); + eqPos(Pos(16, 1), cm.getCursor('head')); + + cm.setCursor(13, 0); + helpers.doKeys('v', 'a', 'p'); + eqPos(Pos(13, 0), cm.getCursor('anchor')); + eqPos(Pos(14, 0), cm.getCursor('head')); + + cm.setCursor(16, 0); + helpers.doKeys('v', 'a', 'p'); + eqPos(Pos(14, 0), cm.getCursor('anchor')); + eqPos(Pos(16, 1), cm.getCursor('head')); + + cm.setCursor(0, 0); + helpers.doKeys('v', 'a', 'p'); + eqPos(Pos(0, 0), cm.getCursor('anchor')); + eqPos(Pos(4, 0), cm.getCursor('head')); + + cm.setCursor(0, 0); + helpers.doKeys('d', 'i', 'p'); + var register = helpers.getRegisterController().getRegister(); + eq('a\na\n', register.toString()); + is(register.linewise); + helpers.doKeys('3', 'j', 'p'); + helpers.doKeys('y', 'i', 'p'); + is(register.linewise); + eq('b\na\na\nc\n', register.toString()); +}, { value: 'a\na\n\n\n\nb\nc\n\n\n\n\n\n\nd\n\ne\nf' }); + +// Operator tests +testVim('dl', function(cm, vim, helpers) { + var curStart = makeCursor(0, 0); + cm.setCursor(curStart); + helpers.doKeys('d', 'l'); + eq('word1 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' ', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dl_eol', function(cm, vim, helpers) { + cm.setCursor(0, 6); + helpers.doKeys('d', 'l'); + eq(' word1', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' ', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 5); +}, { value: ' word1 ' }); +testVim('dl_repeat', function(cm, vim, helpers) { + var curStart = makeCursor(0, 0); + cm.setCursor(curStart); + helpers.doKeys('2', 'd', 'l'); + eq('ord1 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' w', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dh', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'h'); + eq(' wrd1 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('o', register.toString()); + is(!register.linewise); + eqPos(offsetCursor(curStart, 0 , -1), cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dj', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'j'); + eq(' word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' word1\nword2\n', register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\nword2\n word3' }); +testVim('dj_end_of_document', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'j'); + eq('', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' word1 \n', register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1 ' }); +testVim('dk', function(cm, vim, helpers) { + var curStart = makeCursor(1, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'k'); + eq(' word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' word1\nword2\n', register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\nword2\n word3' }); +testVim('dk_start_of_document', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'k'); + eq('', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' word1 \n', register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1 ' }); +testVim('dw_space', function(cm, vim, helpers) { + var curStart = makeCursor(0, 0); + cm.setCursor(curStart); + helpers.doKeys('d', 'w'); + eq('word1 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' ', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dw_word', function(cm, vim, helpers) { + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('d', 'w'); + eq(' word2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1 ', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1 word2' }); +testVim('dw_unicode_word', function(cm, vim, helpers) { + helpers.doKeys('d', 'w'); + eq(cm.getValue().length, 10); + helpers.doKeys('d', 'w'); + eq(cm.getValue().length, 6); + helpers.doKeys('d', 'w'); + eq(cm.getValue().length, 5); + helpers.doKeys('d', 'e'); + eq(cm.getValue().length, 2); +}, { value: ' \u0562\u0561\u0580\u0587\xbbe\xb5g ' }); +testVim('dw_only_word', function(cm, vim, helpers) { + // Test that if there is only 1 word left, dw deletes till the end of the + // line. + cm.setCursor(0, 1); + helpers.doKeys('d', 'w'); + eq(' ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1 ', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1 ' }); +testVim('dw_eol', function(cm, vim, helpers) { + // Assert that dw does not delete the newline if last word to delete is at end + // of line. + cm.setCursor(0, 1); + helpers.doKeys('d', 'w'); + eq(' \nword2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1\nword2' }); +testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) { + // Assert that dw does not delete the newline if last word to delete is at end + // of line and it is followed by multiple newlines. + cm.setCursor(0, 1); + helpers.doKeys('d', 'w'); + eq(' \n\nword2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1\n\nword2' }); +testVim('dw_empty_line_followed_by_whitespace', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq(' \nword', cm.getValue()); +}, { value: '\n \nword' }); +testVim('dw_empty_line_followed_by_word', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('word', cm.getValue()); +}, { value: '\nword' }); +testVim('dw_empty_line_followed_by_empty_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('\n', cm.getValue()); +}, { value: '\n\n' }); +testVim('dw_whitespace_followed_by_whitespace', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('\n \n', cm.getValue()); +}, { value: ' \n \n' }); +testVim('dw_whitespace_followed_by_empty_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('\n\n', cm.getValue()); +}, { value: ' \n\n' }); +testVim('dw_word_whitespace_word', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('\n \nword2', cm.getValue()); +}, { value: 'word1\n \nword2'}) +testVim('dw_end_of_document', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('d', 'w'); + eq('\nab', cm.getValue()); +}, { value: '\nabc' }); +testVim('dw_repeat', function(cm, vim, helpers) { + // Assert that dw does delete newline if it should go to the next line, and + // that repeat works properly. + cm.setCursor(0, 1); + helpers.doKeys('d', '2', 'w'); + eq(' ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1\nword2', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1\nword2' }); +testVim('de_word_start_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'e'); + eq('\n\n', cm.getValue()); +}, { value: 'word\n\n' }); +testVim('de_word_end_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(0, 3); + helpers.doKeys('d', 'e'); + eq('wor', cm.getValue()); +}, { value: 'word\n\n\n' }); +testVim('de_whitespace_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'e'); + eq('', cm.getValue()); +}, { value: ' \n\n\n' }); +testVim('de_end_of_document', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('d', 'e'); + eq('\nab', cm.getValue()); +}, { value: '\nabc' }); +testVim('db_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('d', 'b'); + eq('\n\n', cm.getValue()); +}, { value: '\n\n\n' }); +testVim('db_word_start_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('d', 'b'); + eq('\nword', cm.getValue()); +}, { value: '\n\nword' }); +testVim('db_word_end_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 3); + helpers.doKeys('d', 'b'); + eq('\n\nd', cm.getValue()); +}, { value: '\n\nword' }); +testVim('db_whitespace_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('d', 'b'); + eq('', cm.getValue()); +}, { value: '\n \n' }); +testVim('db_start_of_document', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'b'); + eq('abc\n', cm.getValue()); +}, { value: 'abc\n' }); +testVim('dge_empty_lines', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doKeys('d', 'g', 'e'); + // Note: In real VIM the result should be '', but it's not quite consistent, + // since 2 newlines are deleted. But in the similar case of word\n\n, only + // 1 newline is deleted. We'll diverge from VIM's behavior since it's much + // easier this way. + eq('\n', cm.getValue()); +}, { value: '\n\n' }); +testVim('dge_word_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doKeys('d', 'g', 'e'); + eq('wor\n', cm.getValue()); +}, { value: 'word\n\n'}); +testVim('dge_whitespace_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('d', 'g', 'e'); + eq('', cm.getValue()); +}, { value: '\n \n' }); +testVim('dge_start_of_document', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'g', 'e'); + eq('bc\n', cm.getValue()); +}, { value: 'abc\n' }); +testVim('d_inclusive', function(cm, vim, helpers) { + // Assert that when inclusive is set, the character the cursor is on gets + // deleted too. + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('d', 'e'); + eq(' ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('d_reverse', function(cm, vim, helpers) { + // Test that deleting in reverse works. + cm.setCursor(1, 0); + helpers.doKeys('d', 'b'); + eq(' word2 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1\n', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\nword2 ' }); +testVim('dd', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange({ line: 0, ch: 0 }, + { line: 1, ch: 0 }); + var expectedLineCount = cm.lineCount() - 1; + helpers.doKeys('d', 'd'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, lines[1].textStart); +}); +testVim('dd_prefix_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange({ line: 0, ch: 0 }, + { line: 2, ch: 0 }); + var expectedLineCount = cm.lineCount() - 2; + helpers.doKeys('2', 'd', 'd'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, lines[2].textStart); +}); +testVim('dd_motion_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange({ line: 0, ch: 0 }, + { line: 2, ch: 0 }); + var expectedLineCount = cm.lineCount() - 2; + helpers.doKeys('d', '2', 'd'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, lines[2].textStart); +}); +testVim('dd_multiply_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange({ line: 0, ch: 0 }, + { line: 6, ch: 0 }); + var expectedLineCount = cm.lineCount() - 6; + helpers.doKeys('2', 'd', '3', 'd'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, lines[6].textStart); +}); +testVim('dd_lastline', function(cm, vim, helpers) { + cm.setCursor(cm.lineCount(), 0); + var expectedLineCount = cm.lineCount() - 1; + helpers.doKeys('d', 'd'); + eq(expectedLineCount, cm.lineCount()); + helpers.assertCursorAt(cm.lineCount() - 1, 0); +}); +testVim('dd_only_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + var expectedRegister = cm.getValue() + "\n"; + helpers.doKeys('d','d'); + eq(1, cm.lineCount()); + eq('', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedRegister, register.toString()); +}, { value: "thisistheonlyline" }); +// Yank commands should behave the exact same as d commands, expect that nothing +// gets deleted. +testVim('yw_repeat', function(cm, vim, helpers) { + // Assert that yw does yank newline if it should go to the next line, and + // that repeat works properly. + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('y', '2', 'w'); + eq(' word1\nword2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1\nword2', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1\nword2' }); +testVim('yy_multiply_repeat', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + var expectedBuffer = cm.getRange({ line: 0, ch: 0 }, + { line: 6, ch: 0 }); + var expectedLineCount = cm.lineCount(); + helpers.doKeys('2', 'y', '3', 'y'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + eqPos(curStart, cm.getCursor()); +}); +// Change commands behave like d commands except that it also enters insert +// mode. In addition, when the change is linewise, an additional newline is +// inserted so that insert mode starts on that line. +testVim('cw', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('c', '2', 'w'); + eq(' word3', cm.getValue()); + helpers.assertCursorAt(0, 0); +}, { value: 'word1 word2 word3'}); +testVim('cw_repeat', function(cm, vim, helpers) { + // Assert that cw does delete newline if it should go to the next line, and + // that repeat works properly. + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('c', '2', 'w'); + eq(' ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1\nword2', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: ' word1\nword2' }); +testVim('cc_multiply_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange({ line: 0, ch: 0 }, + { line: 6, ch: 0 }); + var expectedLineCount = cm.lineCount() - 5; + helpers.doKeys('2', 'c', '3', 'c'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('ct', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('c', 't', 'w'); + eq(' word1 word3', cm.getValue()); + helpers.doKeys('', 'c', '|'); + eq(' word3', cm.getValue()); + helpers.assertCursorAt(0, 0); + helpers.doKeys('', '2', 'u', 'w', 'h'); + helpers.doKeys('c', '2', 'g', 'e'); + eq(' wordword3', cm.getValue()); +}, { value: ' word1 word2 word3'}); +testVim('cc_should_not_append_to_document', function(cm, vim, helpers) { + var expectedLineCount = cm.lineCount(); + cm.setCursor(cm.lastLine(), 0); + helpers.doKeys('c', 'c'); + eq(expectedLineCount, cm.lineCount()); +}); +function fillArray(val, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(val); + } + return arr; +} +testVim('c_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', '2', 'j', 'l', 'l', 'l', 'c'); + var replacement = fillArray('hello', 3); + cm.replaceSelections(replacement); + eq('1hello\n5hello\nahellofg', cm.getValue()); + helpers.doKeys(''); + cm.setCursor(2, 3); + helpers.doKeys('', '2', 'k', 'h', 'C'); + replacement = fillArray('world', 3); + cm.replaceSelections(replacement); + eq('1hworld\n5hworld\nahworld', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); +testVim('c_visual_block_replay', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', '2', 'j', 'l', 'c'); + var replacement = fillArray('fo', 3); + cm.replaceSelections(replacement); + eq('1fo4\n5fo8\nafodefg', cm.getValue()); + helpers.doKeys(''); + cm.setCursor(0, 0); + helpers.doKeys('.'); + eq('foo4\nfoo8\nfoodefg', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); + +testVim('d_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', '2', 'j', 'l', 'l', 'l', 'd'); + eq('1\n5\nafg', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); +testVim('D_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', '2', 'j', 'l', 'D'); + eq('1\n5\na', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); + +testVim('s_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', '2', 'j', 'l', 'l', 'l', 's'); + var replacement = fillArray('hello{', 3); + cm.replaceSelections(replacement); + eq('1hello{\n5hello{\nahello{fg\n', cm.getValue()); + helpers.doKeys(''); + cm.setCursor(2, 3); + helpers.doKeys('', '1', 'k', 'h', 'S'); + replacement = fillArray('world', 1); + cm.replaceSelections(replacement); + eq('1hello{\n world\n', cm.getValue()); +}, {value: '1234\n5678\nabcdefg\n'}); + +// Swapcase commands edit in place and do not modify registers. +testVim('g~w_repeat', function(cm, vim, helpers) { + // Assert that dw does delete newline if it should go to the next line, and + // that repeat works properly. + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('g', '~', '2', 'w'); + eq(' WORD1\nWORD2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1\nword2' }); +testVim('g~g~', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + var expectedLineCount = cm.lineCount(); + var expectedValue = cm.getValue().toUpperCase(); + helpers.doKeys('2', 'g', '~', '3', 'g', '~'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1\nword2\nword3\nword4\nword5\nword6' }); +testVim('gu_and_gU', function(cm, vim, helpers) { + var curStart = makeCursor(0, 7); + var value = cm.getValue(); + cm.setCursor(curStart); + helpers.doKeys('2', 'g', 'U', 'w'); + eq(cm.getValue(), 'wa wb xX WC wd'); + eqPos(curStart, cm.getCursor()); + helpers.doKeys('2', 'g', 'u', 'w'); + eq(cm.getValue(), value); + + helpers.doKeys('2', 'g', 'U', 'B'); + eq(cm.getValue(), 'wa WB Xx wc wd'); + eqPos(makeCursor(0, 3), cm.getCursor()); + + cm.setCursor(makeCursor(0, 4)); + helpers.doKeys('g', 'u', 'i', 'w'); + eq(cm.getValue(), 'wa wb Xx wc wd'); + eqPos(makeCursor(0, 3), cm.getCursor()); + + // TODO: support gUgU guu + // eqPos(makeCursor(0, 0), cm.getCursor()); + + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); +}, { value: 'wa wb xx wc wd' }); +testVim('visual_block_~', function(cm, vim, helpers) { + cm.setCursor(1, 1); + helpers.doKeys('', 'l', 'l', 'j', '~'); + helpers.assertCursorAt(1, 1); + eq('hello\nwoRLd\naBCDe', cm.getValue()); + cm.setCursor(2, 0); + helpers.doKeys('v', 'l', 'l', '~'); + helpers.assertCursorAt(2, 0); + eq('hello\nwoRLd\nAbcDe', cm.getValue()); +},{value: 'hello\nwOrld\nabcde' }); +testVim('._swapCase_visualBlock', function(cm, vim, helpers) { + helpers.doKeys('', 'j', 'j', 'l', '~'); + cm.setCursor(0, 3); + helpers.doKeys('.'); + eq('HelLO\nWorLd\nAbcdE', cm.getValue()); +},{value: 'hEllo\nwOrlD\naBcDe' }); +testVim('._delete_visualBlock', function(cm, vim, helpers) { + helpers.doKeys('', 'j', 'x'); + eq('ive\ne\nsome\nsugar', cm.getValue()); + helpers.doKeys('.'); + eq('ve\n\nsome\nsugar', cm.getValue()); + helpers.doKeys('j', 'j', '.'); + eq('ve\n\nome\nugar', cm.getValue()); + helpers.doKeys('u', '', '.'); + eq('ve\n\nme\ngar', cm.getValue()); +},{value: 'give\nme\nsome\nsugar' }); +testVim('>{motion}', function(cm, vim, helpers) { + cm.setCursor(1, 3); + var expectedLineCount = cm.lineCount(); + var expectedValue = ' word1\n word2\nword3 '; + helpers.doKeys('>', 'k'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 3); +}, { value: ' word1\nword2\nword3 ', indentUnit: 2 }); +testVim('>>', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedLineCount = cm.lineCount(); + var expectedValue = ' word1\n word2\nword3 '; + helpers.doKeys('2', '>', '>'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 3); +}, { value: ' word1\nword2\nword3 ', indentUnit: 2 }); +testVim('<{motion}', function(cm, vim, helpers) { + cm.setCursor(1, 3); + var expectedLineCount = cm.lineCount(); + var expectedValue = ' word1\nword2\nword3 '; + helpers.doKeys('<', 'k'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\n word2\nword3 ', indentUnit: 2 }); +testVim('<<', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedLineCount = cm.lineCount(); + var expectedValue = ' word1\nword2\nword3 '; + helpers.doKeys('2', '<', '<'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\n word2\nword3 ', indentUnit: 2 }); + +// Edit tests +function testEdit(name, before, pos, edit, after) { + return testVim(name, function(cm, vim, helpers) { + var ch = before.search(pos) + var line = before.substring(0, ch).split('\n').length - 1; + if (line) { + ch = before.substring(0, ch).split('\n').pop().length; + } + cm.setCursor(line, ch); + helpers.doKeys.apply(this, edit.split('')); + eq(after, cm.getValue()); + }, {value: before}); +} + +// These Delete tests effectively cover word-wise Change, Visual & Yank. +// Tabs are used as differentiated whitespace to catch edge cases. +// Normal word: +testEdit('diw_mid_spc', 'foo \tbAr\t baz', /A/, 'diw', 'foo \t\t baz'); +testEdit('daw_mid_spc', 'foo \tbAr\t baz', /A/, 'daw', 'foo \tbaz'); +testEdit('diw_mid_punct', 'foo \tbAr.\t baz', /A/, 'diw', 'foo \t.\t baz'); +testEdit('daw_mid_punct', 'foo \tbAr.\t baz', /A/, 'daw', 'foo.\t baz'); +testEdit('diw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diw', 'foo \t,.\t baz'); +testEdit('daw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daw', 'foo \t,.\t baz'); +testEdit('diw_start_spc', 'bAr \tbaz', /A/, 'diw', ' \tbaz'); +testEdit('daw_start_spc', 'bAr \tbaz', /A/, 'daw', 'baz'); +testEdit('diw_start_punct', 'bAr. \tbaz', /A/, 'diw', '. \tbaz'); +testEdit('daw_start_punct', 'bAr. \tbaz', /A/, 'daw', '. \tbaz'); +testEdit('diw_end_spc', 'foo \tbAr', /A/, 'diw', 'foo \t'); +testEdit('daw_end_spc', 'foo \tbAr', /A/, 'daw', 'foo'); +testEdit('diw_end_punct', 'foo \tbAr.', /A/, 'diw', 'foo \t.'); +testEdit('daw_end_punct', 'foo \tbAr.', /A/, 'daw', 'foo.'); +// Big word: +testEdit('diW_mid_spc', 'foo \tbAr\t baz', /A/, 'diW', 'foo \t\t baz'); +testEdit('daW_mid_spc', 'foo \tbAr\t baz', /A/, 'daW', 'foo \tbaz'); +testEdit('diW_mid_punct', 'foo \tbAr.\t baz', /A/, 'diW', 'foo \t\t baz'); +testEdit('daW_mid_punct', 'foo \tbAr.\t baz', /A/, 'daW', 'foo \tbaz'); +testEdit('diW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diW', 'foo \t\t baz'); +testEdit('daW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daW', 'foo \tbaz'); +testEdit('diW_start_spc', 'bAr\t baz', /A/, 'diW', '\t baz'); +testEdit('daW_start_spc', 'bAr\t baz', /A/, 'daW', 'baz'); +testEdit('diW_start_punct', 'bAr.\t baz', /A/, 'diW', '\t baz'); +testEdit('daW_start_punct', 'bAr.\t baz', /A/, 'daW', 'baz'); +testEdit('diW_end_spc', 'foo \tbAr', /A/, 'diW', 'foo \t'); +testEdit('daW_end_spc', 'foo \tbAr', /A/, 'daW', 'foo'); +testEdit('diW_end_punct', 'foo \tbAr.', /A/, 'diW', 'foo \t'); +testEdit('daW_end_punct', 'foo \tbAr.', /A/, 'daW', 'foo'); +// Deleting text objects +// Open and close on same line +testEdit('di(_open_spc', 'foo (bAr) baz', /\(/, 'di(', 'foo () baz'); +testEdit('di)_open_spc', 'foo (bAr) baz', /\(/, 'di)', 'foo () baz'); +testEdit('dib_open_spc', 'foo (bAr) baz', /\(/, 'dib', 'foo () baz'); +testEdit('da(_open_spc', 'foo (bAr) baz', /\(/, 'da(', 'foo baz'); +testEdit('da)_open_spc', 'foo (bAr) baz', /\(/, 'da)', 'foo baz'); + +testEdit('di(_middle_spc', 'foo (bAr) baz', /A/, 'di(', 'foo () baz'); +testEdit('di)_middle_spc', 'foo (bAr) baz', /A/, 'di)', 'foo () baz'); +testEdit('da(_middle_spc', 'foo (bAr) baz', /A/, 'da(', 'foo baz'); +testEdit('da)_middle_spc', 'foo (bAr) baz', /A/, 'da)', 'foo baz'); + +testEdit('di(_close_spc', 'foo (bAr) baz', /\)/, 'di(', 'foo () baz'); +testEdit('di)_close_spc', 'foo (bAr) baz', /\)/, 'di)', 'foo () baz'); +testEdit('da(_close_spc', 'foo (bAr) baz', /\)/, 'da(', 'foo baz'); +testEdit('da)_close_spc', 'foo (bAr) baz', /\)/, 'da)', 'foo baz'); + +// delete around and inner b. +testEdit('dab_on_(_should_delete_around_()block', 'o( in(abc) )', /\(a/, 'dab', 'o( in )'); + +// delete around and inner B. +testEdit('daB_on_{_should_delete_around_{}block', 'o{ in{abc} }', /{a/, 'daB', 'o{ in }'); +testEdit('diB_on_{_should_delete_inner_{}block', 'o{ in{abc} }', /{a/, 'diB', 'o{ in{} }'); + +testEdit('da{_on_{_should_delete_inner_block', 'o{ in{abc} }', /{a/, 'da{', 'o{ in }'); +testEdit('di[_on_(_should_not_delete', 'foo (bAr) baz', /\(/, 'di[', 'foo (bAr) baz'); +testEdit('di[_on_)_should_not_delete', 'foo (bAr) baz', /\)/, 'di[', 'foo (bAr) baz'); +testEdit('da[_on_(_should_not_delete', 'foo (bAr) baz', /\(/, 'da[', 'foo (bAr) baz'); +testEdit('da[_on_)_should_not_delete', 'foo (bAr) baz', /\)/, 'da[', 'foo (bAr) baz'); +testMotion('di(_outside_should_stay', ['d', 'i', '('], { line: 0, ch: 0}, { line: 0, ch: 0}); + +// Open and close on different lines, equally indented +testEdit('di{_middle_spc', 'a{\n\tbar\n}b', /r/, 'di{', 'a{}b'); +testEdit('di}_middle_spc', 'a{\n\tbar\n}b', /r/, 'di}', 'a{}b'); +testEdit('da{_middle_spc', 'a{\n\tbar\n}b', /r/, 'da{', 'ab'); +testEdit('da}_middle_spc', 'a{\n\tbar\n}b', /r/, 'da}', 'ab'); +testEdit('daB_middle_spc', 'a{\n\tbar\n}b', /r/, 'daB', 'ab'); + +// open and close on diff lines, open indented less than close +testEdit('di{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di{', 'a{}b'); +testEdit('di}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di}', 'a{}b'); +testEdit('da{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da{', 'ab'); +testEdit('da}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da}', 'ab'); + +// open and close on diff lines, open indented more than close +testEdit('di[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di[', 'a\t[]b'); +testEdit('di]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di]', 'a\t[]b'); +testEdit('da[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da[', 'a\tb'); +testEdit('da]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da]', 'a\tb'); + +function testSelection(name, before, pos, keys, sel) { + return testVim(name, function(cm, vim, helpers) { + var ch = before.search(pos) + var line = before.substring(0, ch).split('\n').length - 1; + if (line) { + ch = before.substring(0, ch).split('\n').pop().length; + } + cm.setCursor(line, ch); + helpers.doKeys.apply(this, keys.split('')); + eq(sel, cm.getSelection()); + }, {value: before}); +} +testSelection('viw_middle_spc', 'foo \tbAr\t baz', /A/, 'viw', 'bAr'); +testSelection('vaw_middle_spc', 'foo \tbAr\t baz', /A/, 'vaw', 'bAr\t '); +testSelection('viw_middle_punct', 'foo \tbAr,\t baz', /A/, 'viw', 'bAr'); +testSelection('vaW_middle_punct', 'foo \tbAr,\t baz', /A/, 'vaW', 'bAr,\t '); +testSelection('viw_start_spc', 'foo \tbAr\t baz', /b/, 'viw', 'bAr'); +testSelection('viw_end_spc', 'foo \tbAr\t baz', /r/, 'viw', 'bAr'); +testSelection('viw_eol', 'foo \tbAr', /r/, 'viw', 'bAr'); +testSelection('vi{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'vi{', '\n\tbar\n\t'); +testSelection('va{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'va{', '{\n\tbar\n\t}'); + +testVim('mouse_select', function(cm, vim, helpers) { + cm.setSelection(Pos(0, 2), Pos(0, 4), {origin: '*mouse'}); + is(cm.state.vim.visualMode); + is(!cm.state.vim.visualLine); + is(!cm.state.vim.visualBlock); + helpers.doKeys(''); + is(!cm.somethingSelected()); + helpers.doKeys('g', 'v'); + eq('cd', cm.getSelection()); +}, {value: 'abcdef'}); + +// Operator-motion tests +testVim('D', function(cm, vim, helpers) { + cm.setCursor(0, 3); + helpers.doKeys('D'); + eq(' wo\nword2\n word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('rd1', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 2); +}, { value: ' word1\nword2\n word3' }); +testVim('C', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('C'); + eq(' wo\nword2\n word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('rd1', register.toString()); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: ' word1\nword2\n word3' }); +testVim('Y', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('Y'); + eq(' word1\nword2\n word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('rd1', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 3); +}, { value: ' word1\nword2\n word3' }); +testVim('~', function(cm, vim, helpers) { + helpers.doKeys('3', '~'); + eq('ABCdefg', cm.getValue()); + helpers.assertCursorAt(0, 3); +}, { value: 'abcdefg' }); + +// Action tests +testVim('ctrl-a', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys(''); + eq('-9', cm.getValue()); + helpers.assertCursorAt(0, 1); + helpers.doKeys('2',''); + eq('-7', cm.getValue()); +}, {value: '-10'}); +testVim('ctrl-x', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys(''); + eq('-1', cm.getValue()); + helpers.assertCursorAt(0, 1); + helpers.doKeys('2',''); + eq('-3', cm.getValue()); +}, {value: '0'}); +testVim('/ search forward', function(cm, vim, helpers) { + forEach(['', ''], function(key) { + cm.setCursor(0, 0); + helpers.doKeys(key); + helpers.assertCursorAt(0, 5); + helpers.doKeys('l'); + helpers.doKeys(key); + helpers.assertCursorAt(0, 10); + cm.setCursor(0, 11); + helpers.doKeys(key); + helpers.assertCursorAt(0, 11); + }); +}, {value: '__jmp1 jmp2 jmp'}); +testVim('a', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('a'); + helpers.assertCursorAt(0, 2); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('a_eol', function(cm, vim, helpers) { + cm.setCursor(0, lines[0].length - 1); + helpers.doKeys('a'); + helpers.assertCursorAt(0, lines[0].length); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('A_endOfSelectedArea', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('v', 'j', 'l'); + helpers.doKeys('A'); + helpers.assertCursorAt(1, 2); + eq('vim-insert', cm.getOption('keyMap')); +}, {value: 'foo\nbar'}); +testVim('i', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('i'); + helpers.assertCursorAt(0, 1); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('i_repeat', function(cm, vim, helpers) { + helpers.doKeys('3', 'i'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys(''); + eq('testtesttest', cm.getValue()); + helpers.assertCursorAt(0, 11); +}, { value: '' }); +testVim('i_repeat_delete', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('2', 'i'); + cm.replaceRange('z', cm.getCursor()); + helpers.doInsertModeKeys('Backspace', 'Backspace'); + helpers.doKeys(''); + eq('abe', cm.getValue()); + helpers.assertCursorAt(0, 1); +}, { value: 'abcde' }); +testVim('A', function(cm, vim, helpers) { + helpers.doKeys('A'); + helpers.assertCursorAt(0, lines[0].length); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('A_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', '2', 'j', 'l', 'l', 'A'); + var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' '); + replacement.pop(); + cm.replaceSelections(replacement); + eq('testhello\nmehello\npleahellose', cm.getValue()); + helpers.doKeys(''); + cm.setCursor(0, 0); + helpers.doKeys('.'); + // TODO this doesn't work yet + // eq('teshellothello\nme hello hello\nplehelloahellose', cm.getValue()); +}, {value: 'test\nme\nplease'}); +testVim('I', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('I'); + helpers.assertCursorAt(0, lines[0].textStart); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('I_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('3', 'I'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys(''); + eq('testtesttestblah', cm.getValue()); + helpers.assertCursorAt(0, 11); +}, { value: 'blah' }); +testVim('I_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '2', 'j', 'l', 'l', 'I'); + var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' '); + replacement.pop(); + cm.replaceSelections(replacement); + eq('hellotest\nhellome\nhelloplease', cm.getValue()); +}, {value: 'test\nme\nplease'}); +testVim('o', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('o'); + eq('word1\n\nword2', cm.getValue()); + helpers.assertCursorAt(1, 0); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'word1\nword2' }); +testVim('o_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('3', 'o'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys(''); + eq('\ntest\ntest\ntest', cm.getValue()); + helpers.assertCursorAt(3, 3); +}, { value: '' }); +testVim('O', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('O'); + eq('\nword1\nword2', cm.getValue()); + helpers.assertCursorAt(0, 0); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'word1\nword2' }); +testVim('J', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('J'); + var expectedValue = 'word1 word2\nword3\n word4'; + eq(expectedValue, cm.getValue()); + helpers.assertCursorAt(0, expectedValue.indexOf('word2') - 1); +}, { value: 'word1 \n word2\nword3\n word4' }); +testVim('J_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('3', 'J'); + var expectedValue = 'word1 word2 word3\n word4'; + eq(expectedValue, cm.getValue()); + helpers.assertCursorAt(0, expectedValue.indexOf('word3') - 1); +}, { value: 'word1 \n word2\nword3\n word4' }); +testVim('p', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false); + helpers.doKeys('p'); + eq('__abc\ndef_', cm.getValue()); + helpers.assertCursorAt(1, 2); +}, { value: '___' }); +testVim('p_register', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().getRegister('a').setText('abc\ndef', false); + helpers.doKeys('"', 'a', 'p'); + eq('__abc\ndef_', cm.getValue()); + helpers.assertCursorAt(1, 2); +}, { value: '___' }); +testVim('p_wrong_register', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().getRegister('a').setText('abc\ndef', false); + helpers.doKeys('p'); + eq('___', cm.getValue()); + helpers.assertCursorAt(0, 1); +}, { value: '___' }); +testVim('p_line', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true); + helpers.doKeys('2', 'p'); + eq('___\n a\nd\n a\nd', cm.getValue()); + helpers.assertCursorAt(1, 2); +}, { value: '___' }); +testVim('p_lastline', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', ' a\nd', true); + helpers.doKeys('2', 'p'); + eq('___\n a\nd\n a\nd', cm.getValue()); + helpers.assertCursorAt(1, 2); +}, { value: '___' }); +testVim(']p_first_indent_is_smaller', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true); + helpers.doKeys(']', 'p'); + eq(' ___\n abc\n def', cm.getValue()); +}, { value: ' ___' }); +testVim(']p_first_indent_is_larger', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true); + helpers.doKeys(']', 'p'); + eq(' ___\n abc\ndef', cm.getValue()); +}, { value: ' ___' }); +testVim(']p_with_tab_indents', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', '\t\tabc\n\t\t\tdef\n', true); + helpers.doKeys(']', 'p'); + eq('\t___\n\tabc\n\t\tdef', cm.getValue()); +}, { value: '\t___', indentWithTabs: true}); +testVim(']p_with_spaces_translated_to_tabs', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true); + helpers.doKeys(']', 'p'); + eq('\t___\n\tabc\n\t\tdef', cm.getValue()); +}, { value: '\t___', indentWithTabs: true, tabSize: 2 }); +testVim('[p', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true); + helpers.doKeys('[', 'p'); + eq(' abc\n def\n ___', cm.getValue()); +}, { value: ' ___' }); +testVim('P', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false); + helpers.doKeys('P'); + eq('_abc\ndef__', cm.getValue()); + helpers.assertCursorAt(1, 3); +}, { value: '___' }); +testVim('P_line', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true); + helpers.doKeys('2', 'P'); + eq(' a\nd\n a\nd\n___', cm.getValue()); + helpers.assertCursorAt(0, 2); +}, { value: '___' }); +testVim('r', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('3', 'r', 'u'); + eq('wuuuet\nanother', cm.getValue(),'3r failed'); + helpers.assertCursorAt(0, 3); + cm.setCursor(0, 4); + helpers.doKeys('v', 'j', 'h', 'r', ''); + eq('wuuu \n her', cm.getValue(),'Replacing selection by space-characters failed'); +}, { value: 'wordet\nanother' }); +testVim('r_visual_block', function(cm, vim, helpers) { + cm.setCursor(2, 3); + helpers.doKeys('', 'k', 'k', 'h', 'h', 'r', 'l'); + eq('1lll\n5lll\nalllefg', cm.getValue()); + helpers.doKeys('', 'l', 'j', 'r', ''); + eq('1 l\n5 l\nalllefg', cm.getValue()); + cm.setCursor(2, 0); + helpers.doKeys('o'); + helpers.doKeys(''); + cm.replaceRange('\t\t', cm.getCursor()); + helpers.doKeys('', 'h', 'h', 'r', 'r'); + eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); +testVim('R', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('R'); + helpers.assertCursorAt(0, 1); + eq('vim-replace', cm.getOption('keyMap')); + is(cm.state.overwrite, 'Setting overwrite state failed'); +}); +testVim('mark', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(0, 0); + helpers.doKeys('`', 't'); + helpers.assertCursorAt(2, 2); + cm.setCursor(2, 0); + cm.replaceRange(' h', cm.getCursor()); + cm.setCursor(0, 0); + helpers.doKeys('\'', 't'); + helpers.assertCursorAt(2, 3); +}); +testVim('jumpToMark_next', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(0, 0); + helpers.doKeys(']', '`'); + helpers.assertCursorAt(2, 2); + cm.setCursor(0, 0); + helpers.doKeys(']', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_next_repeat', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(0, 0); + helpers.doKeys('2', ']', '`'); + helpers.assertCursorAt(3, 2); + cm.setCursor(0, 0); + helpers.doKeys('2', ']', '\''); + helpers.assertCursorAt(3, 1); +}); +testVim('jumpToMark_next_sameline', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 4); + helpers.doKeys('m', 'b'); + cm.setCursor(2, 2); + helpers.doKeys(']', '`'); + helpers.assertCursorAt(2, 4); +}); +testVim('jumpToMark_next_onlyprev', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('m', 'a'); + cm.setCursor(4, 0); + helpers.doKeys(']', '`'); + helpers.assertCursorAt(4, 0); +}); +testVim('jumpToMark_next_nomark', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys(']', '`'); + helpers.assertCursorAt(2, 2); + helpers.doKeys(']', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_next_linewise_over', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(3, 4); + helpers.doKeys('m', 'b'); + cm.setCursor(2, 1); + helpers.doKeys(']', '\''); + helpers.assertCursorAt(3, 1); +}); +testVim('jumpToMark_next_action', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(0, 0); + helpers.doKeys('d', ']', '`'); + helpers.assertCursorAt(0, 0); + var actual = cm.getLine(0); + var expected = 'pop pop 0 1 2 3 4'; + eq(actual, expected, "Deleting while jumping to the next mark failed."); +}); +testVim('jumpToMark_next_line_action', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(0, 0); + helpers.doKeys('d', ']', '\''); + helpers.assertCursorAt(0, 1); + var actual = cm.getLine(0); + var expected = ' (a) [b] {c} ' + eq(actual, expected, "Deleting while jumping to the next mark line failed."); +}); +testVim('jumpToMark_prev', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(4, 0); + helpers.doKeys('[', '`'); + helpers.assertCursorAt(2, 2); + cm.setCursor(4, 0); + helpers.doKeys('[', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_prev_repeat', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(5, 0); + helpers.doKeys('2', '[', '`'); + helpers.assertCursorAt(3, 2); + cm.setCursor(5, 0); + helpers.doKeys('2', '[', '\''); + helpers.assertCursorAt(3, 1); +}); +testVim('jumpToMark_prev_sameline', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 4); + helpers.doKeys('m', 'b'); + cm.setCursor(2, 2); + helpers.doKeys('[', '`'); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_prev_onlynext', function(cm, vim, helpers) { + cm.setCursor(4, 4); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 0); + helpers.doKeys('[', '`'); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_prev_nomark', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('[', '`'); + helpers.assertCursorAt(2, 2); + helpers.doKeys('[', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_prev_linewise_over', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(3, 4); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 6); + helpers.doKeys('[', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('delmark_single', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 't'); + helpers.doEx('delmarks t'); + cm.setCursor(0, 0); + helpers.doKeys('`', 't'); + helpers.assertCursorAt(0, 0); +}); +testVim('delmark_range', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'd'); + cm.setCursor(5, 2); + helpers.doKeys('m', 'e'); + helpers.doEx('delmarks b-d'); + cm.setCursor(0, 0); + helpers.doKeys('`', 'a'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'b'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'c'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'd'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'e'); + helpers.assertCursorAt(5, 2); +}); +testVim('delmark_multi', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'd'); + cm.setCursor(5, 2); + helpers.doKeys('m', 'e'); + helpers.doEx('delmarks bcd'); + cm.setCursor(0, 0); + helpers.doKeys('`', 'a'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'b'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'c'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'd'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'e'); + helpers.assertCursorAt(5, 2); +}); +testVim('delmark_multi_space', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'd'); + cm.setCursor(5, 2); + helpers.doKeys('m', 'e'); + helpers.doEx('delmarks b c d'); + cm.setCursor(0, 0); + helpers.doKeys('`', 'a'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'b'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'c'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'd'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'e'); + helpers.assertCursorAt(5, 2); +}); +testVim('delmark_all', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'd'); + cm.setCursor(5, 2); + helpers.doKeys('m', 'e'); + helpers.doEx('delmarks a b-de'); + cm.setCursor(0, 0); + helpers.doKeys('`', 'a'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('`', 'b'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('`', 'c'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('`', 'd'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('`', 'e'); + helpers.assertCursorAt(0, 0); +}); +testVim('visual', function(cm, vim, helpers) { + helpers.doKeys('l', 'v', 'l', 'l'); + helpers.assertCursorAt(0, 4); + eqPos(makeCursor(0, 1), cm.getCursor('anchor')); + helpers.doKeys('d'); + eq('15', cm.getValue()); +}, { value: '12345' }); +testVim('visual_yank', function(cm, vim, helpers) { + helpers.doKeys('v', '3', 'l', 'y'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('p'); + eq('aa te test for yank', cm.getValue()); +}, { value: 'a test for yank' }) +testVim('visual_w', function(cm, vim, helpers) { + helpers.doKeys('v', 'w'); + eq(cm.getSelection(), 'motion t'); +}, { value: 'motion test'}); +testVim('visual_initial_selection', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('v'); + cm.getSelection('n'); +}, { value: 'init'}); +testVim('visual_crossover_left', function(cm, vim, helpers) { + cm.setCursor(0, 2); + helpers.doKeys('v', 'l', 'h', 'h'); + cm.getSelection('ro'); +}, { value: 'cross'}); +testVim('visual_crossover_left', function(cm, vim, helpers) { + cm.setCursor(0, 2); + helpers.doKeys('v', 'h', 'l', 'l'); + cm.getSelection('os'); +}, { value: 'cross'}); +testVim('visual_crossover_up', function(cm, vim, helpers) { + cm.setCursor(3, 2); + helpers.doKeys('v', 'j', 'k', 'k'); + eqPos(Pos(2, 2), cm.getCursor('head')); + eqPos(Pos(3, 3), cm.getCursor('anchor')); + helpers.doKeys('k'); + eqPos(Pos(1, 2), cm.getCursor('head')); + eqPos(Pos(3, 3), cm.getCursor('anchor')); +}, { value: 'cross\ncross\ncross\ncross\ncross\n'}); +testVim('visual_crossover_down', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('v', 'k', 'j', 'j'); + eqPos(Pos(2, 3), cm.getCursor('head')); + eqPos(Pos(1, 2), cm.getCursor('anchor')); + helpers.doKeys('j'); + eqPos(Pos(3, 3), cm.getCursor('head')); + eqPos(Pos(1, 2), cm.getCursor('anchor')); +}, { value: 'cross\ncross\ncross\ncross\ncross\n'}); +testVim('visual_exit', function(cm, vim, helpers) { + helpers.doKeys('', 'l', 'j', 'j', ''); + eqPos(cm.getCursor('anchor'), cm.getCursor('head')); + eq(vim.visualMode, false); +}, { value: 'hello\nworld\nfoo' }); +testVim('visual_line', function(cm, vim, helpers) { + helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd'); + eq(' 4\n 5', cm.getValue()); +}, { value: ' 1\n 2\n 3\n 4\n 5' }); +testVim('visual_block_move_to_eol', function(cm, vim, helpers) { + // moveToEol should move all block cursors to end of line + cm.setCursor(0, 0); + helpers.doKeys('', 'G', '$'); + var selections = cm.getSelections().join(); + eq('123,45,6', selections); + // Checks that with cursor at Infinity, finding words backwards still works. + helpers.doKeys('2', 'k', 'b'); + selections = cm.getSelections().join(); + eq('1', selections); +}, {value: '123\n45\n6'}); +testVim('visual_block_different_line_lengths', function(cm, vim, helpers) { + // test the block selection with lines of different length + // i.e. extending the selection + // till the end of the longest line. + helpers.doKeys('', 'l', 'j', 'j', '6', 'l', 'd'); + helpers.doKeys('d', 'd', 'd', 'd'); + eq('', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); +testVim('visual_block_truncate_on_short_line', function(cm, vim, helpers) { + // check for left side selection in case + // of moving up to a shorter line. + cm.replaceRange('', cm.getCursor()); + cm.setCursor(3, 4); + helpers.doKeys('', 'l', 'k', 'k', 'd'); + eq('hello world\n{\ntis\nsa!', cm.getValue()); +}, {value: 'hello world\n{\nthis is\nsparta!'}); +testVim('visual_block_corners', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('', '2', 'l', 'k'); + // circle around the anchor + // and check the selections + var selections = cm.getSelections(); + eq('345891', selections.join('')); + helpers.doKeys('4', 'h'); + selections = cm.getSelections(); + eq('123678', selections.join('')); + helpers.doKeys('j', 'j'); + selections = cm.getSelections(); + eq('678abc', selections.join('')); + helpers.doKeys('4', 'l'); + selections = cm.getSelections(); + eq('891cde', selections.join('')); +}, {value: '12345\n67891\nabcde'}); +testVim('visual_block_mode_switch', function(cm, vim, helpers) { + // switch between visual modes + cm.setCursor(1, 1); + // blockwise to characterwise visual + helpers.doKeys('', 'j', 'l', 'v'); + selections = cm.getSelections(); + eq('7891\nabc', selections.join('')); + // characterwise to blockwise + helpers.doKeys(''); + selections = cm.getSelections(); + eq('78bc', selections.join('')); + // blockwise to linewise visual + helpers.doKeys('V'); + selections = cm.getSelections(); + eq('67891\nabcde', selections.join('')); +}, {value: '12345\n67891\nabcde'}); +testVim('visual_block_crossing_short_line', function(cm, vim, helpers) { + // visual block with long and short lines + cm.setCursor(0, 3); + helpers.doKeys('', 'j', 'j', 'j'); + var selections = cm.getSelections().join(); + eq('4,,d,b', selections); + helpers.doKeys('3', 'k'); + selections = cm.getSelections().join(); + eq('4', selections); + helpers.doKeys('5', 'j', 'k'); + selections = cm.getSelections().join(""); + eq(10, selections.length); +}, {value: '123456\n78\nabcdefg\nfoobar\n}\n'}); +testVim('visual_block_curPos_on_exit', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '3' , 'l', ''); + eqPos(makeCursor(0, 3), cm.getCursor()); + helpers.doKeys('h', '', '2' , 'j' ,'3' , 'l'); + eq(cm.getSelections().join(), "3456,,cdef"); + helpers.doKeys('4' , 'h'); + eq(cm.getSelections().join(), "23,8,bc"); + helpers.doKeys('2' , 'l'); + eq(cm.getSelections().join(), "34,,cd"); +}, {value: '123456\n78\nabcdefg\nfoobar'}); + +testVim('visual_marks', function(cm, vim, helpers) { + helpers.doKeys('l', 'v', 'l', 'l', 'j', 'j', 'v'); + // Test visual mode marks + cm.setCursor(2, 1); + helpers.doKeys('\'', '<'); + helpers.assertCursorAt(0, 1); + helpers.doKeys('\'', '>'); + helpers.assertCursorAt(2, 0); +}); +testVim('visual_join', function(cm, vim, helpers) { + helpers.doKeys('l', 'V', 'l', 'j', 'j', 'J'); + eq(' 1 2 3\n 4\n 5', cm.getValue()); + is(!vim.visualMode); +}, { value: ' 1\n 2\n 3\n 4\n 5' }); +testVim('visual_join_2', function(cm, vim, helpers) { + helpers.doKeys('G', 'V', 'g', 'g', 'J'); + eq('1 2 3 4 5 6 ', cm.getValue()); + is(!vim.visualMode); +}, { value: '1\n2\n3\n4\n5\n6\n'}); +testVim('visual_blank', function(cm, vim, helpers) { + helpers.doKeys('v', 'k'); + eq(vim.visualMode, true); +}, { value: '\n' }); +testVim('reselect_visual', function(cm, vim, helpers) { + helpers.doKeys('l', 'v', 'l', 'l', 'l', 'y', 'g', 'v'); + helpers.assertCursorAt(0, 5); + eqPos(makeCursor(0, 1), cm.getCursor('anchor')); + helpers.doKeys('v'); + cm.setCursor(1, 0); + helpers.doKeys('v', 'l', 'l', 'p'); + eq('123456\n2345\nbar', cm.getValue()); + cm.setCursor(0, 0); + helpers.doKeys('g', 'v'); + // here the fake cursor is at (1, 3) + helpers.assertCursorAt(1, 4); + eqPos(makeCursor(1, 0), cm.getCursor('anchor')); + helpers.doKeys('v'); + cm.setCursor(2, 0); + helpers.doKeys('v', 'l', 'l', 'g', 'v'); + helpers.assertCursorAt(1, 4); + eqPos(makeCursor(1, 0), cm.getCursor('anchor')); + helpers.doKeys('g', 'v'); + helpers.assertCursorAt(2, 3); + eqPos(makeCursor(2, 0), cm.getCursor('anchor')); + eq('123456\n2345\nbar', cm.getValue()); +}, { value: '123456\nfoo\nbar' }); +testVim('reselect_visual_line', function(cm, vim, helpers) { + helpers.doKeys('l', 'V', 'j', 'j', 'V', 'g', 'v', 'd'); + eq('foo\nand\nbar', cm.getValue()); + cm.setCursor(1, 0); + helpers.doKeys('V', 'y', 'j'); + helpers.doKeys('V', 'p' , 'g', 'v', 'd'); + eq('foo\nand', cm.getValue()); +}, { value: 'hello\nthis\nis\nfoo\nand\nbar' }); +testVim('reselect_visual_block', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('', 'k', 'h', ''); + cm.setCursor(2, 1); + helpers.doKeys('v', 'l', 'g', 'v'); + eqPos(Pos(1, 2), vim.sel.anchor); + eqPos(Pos(0, 1), vim.sel.head); + // Ensure selection is done with visual block mode rather than one + // continuous range. + eq(cm.getSelections().join(''), '23oo') + helpers.doKeys('g', 'v'); + eqPos(Pos(2, 1), vim.sel.anchor); + eqPos(Pos(2, 2), vim.sel.head); + helpers.doKeys(''); + // Ensure selection of deleted range + cm.setCursor(1, 1); + helpers.doKeys('v', '', 'j', 'd', 'g', 'v'); + eq(cm.getSelections().join(''), 'or'); +}, { value: '123456\nfoo\nbar' }); +testVim('s_normal', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('s'); + helpers.doKeys(''); + eq('ac', cm.getValue()); +}, { value: 'abc'}); +testVim('s_visual', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('v', 's'); + helpers.doKeys(''); + helpers.assertCursorAt(0, 0); + eq('ac', cm.getValue()); +}, { value: 'abc'}); +testVim('o_visual', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys('v','l','l','l','o'); + helpers.assertCursorAt(0,0); + helpers.doKeys('v','v','j','j','j','o'); + helpers.assertCursorAt(0,0); + helpers.doKeys('O'); + helpers.doKeys('l','l') + helpers.assertCursorAt(3, 3); + helpers.doKeys('d'); + eq('p',cm.getValue()); +}, { value: 'abcd\nefgh\nijkl\nmnop'}); +testVim('o_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('','3','j','l','l', 'o'); + eqPos(Pos(3, 3), vim.sel.anchor); + eqPos(Pos(0, 1), vim.sel.head); + helpers.doKeys('O'); + eqPos(Pos(3, 1), vim.sel.anchor); + eqPos(Pos(0, 3), vim.sel.head); + helpers.doKeys('o'); + eqPos(Pos(0, 3), vim.sel.anchor); + eqPos(Pos(3, 1), vim.sel.head); +}, { value: 'abcd\nefgh\nijkl\nmnop'}); +testVim('changeCase_visual', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('v', 'l', 'l'); + helpers.doKeys('U'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('v', 'l', 'l'); + helpers.doKeys('u'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('l', 'l', 'l', '.'); + helpers.assertCursorAt(0, 3); + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', 'v', 'j', 'U', 'q'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('j', '@', 'a'); + helpers.assertCursorAt(1, 0); + cm.setCursor(3, 0); + helpers.doKeys('V', 'U', 'j', '.'); + eq('ABCDEF\nGHIJKL\nMnopq\nSHORT LINE\nLONG LINE OF TEXT', cm.getValue()); +}, { value: 'abcdef\nghijkl\nmnopq\nshort line\nlong line of text'}); +testVim('changeCase_visual_block', function(cm, vim, helpers) { + cm.setCursor(2, 1); + helpers.doKeys('', 'k', 'k', 'h', 'U'); + eq('ABcdef\nGHijkl\nMNopq\nfoo', cm.getValue()); + cm.setCursor(0, 2); + helpers.doKeys('.'); + eq('ABCDef\nGHIJkl\nMNOPq\nfoo', cm.getValue()); + // check when last line is shorter. + cm.setCursor(2, 2); + helpers.doKeys('.'); + eq('ABCDef\nGHIJkl\nMNOPq\nfoO', cm.getValue()); +}, { value: 'abcdef\nghijkl\nmnopq\nfoo'}); +testVim('visual_paste', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('v', 'l', 'l', 'y'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('3', 'l', 'j', 'v', 'l', 'p'); + helpers.assertCursorAt(1, 5); + eq('this is a\nunithitest for visual paste', cm.getValue()); + cm.setCursor(0, 0); + // in case of pasting whole line + helpers.doKeys('y', 'y'); + cm.setCursor(1, 6); + helpers.doKeys('v', 'l', 'l', 'l', 'p'); + helpers.assertCursorAt(2, 0); + eq('this is a\nunithi\nthis is a\n for visual paste', cm.getValue()); +}, { value: 'this is a\nunit test for visual paste'}); + +// This checks the contents of the register used to paste the text +testVim('v_paste_from_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'w'); + cm.setCursor(1, 0); + helpers.doKeys('v', 'p'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+register/.test(text)); + }); +}, { value: 'register contents\nare not erased'}); +testVim('S_normal', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('j', 'S'); + helpers.doKeys(''); + helpers.assertCursorAt(1, 1); + eq('aa{\n \ncc', cm.getValue()); + helpers.doKeys('j', 'S'); + eq('aa{\n \n ', cm.getValue()); + helpers.assertCursorAt(2, 2); + helpers.doKeys(''); + helpers.doKeys('d', 'd', 'd', 'd'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('S'); + is(vim.insertMode); + eq('', cm.getValue()); +}, { value: 'aa{\nbb\ncc'}); +testVim('blockwise_paste', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '3', 'j', 'l', 'y'); + cm.setCursor(0, 2); + // paste one char after the current cursor position + helpers.doKeys('p'); + eq('helhelo\nworwold\nfoofo\nbarba', cm.getValue()); + cm.setCursor(0, 0); + helpers.doKeys('v', '4', 'l', 'y'); + cm.setCursor(0, 0); + helpers.doKeys('', '3', 'j', 'p'); + eq('helheelhelo\norwold\noofo\narba', cm.getValue()); +}, { value: 'hello\nworld\nfoo\nbar'}); +testVim('blockwise_paste_long/short_line', function(cm, vim, helpers) { + // extend short lines in case of different line lengths. + cm.setCursor(0, 0); + helpers.doKeys('', 'j', 'j', 'y'); + cm.setCursor(0, 3); + helpers.doKeys('p'); + eq('hellho\nfoo f\nbar b', cm.getValue()); +}, { value: 'hello\nfoo\nbar'}); +testVim('blockwise_paste_cut_paste', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '2', 'j', 'x'); + cm.setCursor(0, 0); + helpers.doKeys('P'); + eq('cut\nand\npaste\nme', cm.getValue()); +}, { value: 'cut\nand\npaste\nme'}); +testVim('blockwise_paste_from_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '2', 'j', '"', 'a', 'y'); + cm.setCursor(0, 3); + helpers.doKeys('"', 'a', 'p'); + eq('foobfar\nhellho\nworlwd', cm.getValue()); +}, { value: 'foobar\nhello\nworld'}); +testVim('blockwise_paste_last_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '2', 'j', 'l', 'y'); + cm.setCursor(3, 0); + helpers.doKeys('p'); + eq('cut\nand\npaste\nmcue\n an\n pa', cm.getValue()); +}, { value: 'cut\nand\npaste\nme'}); + +testVim('S_visual', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('v', 'j', 'S'); + helpers.doKeys(''); + helpers.assertCursorAt(0, 0); + eq('\ncc', cm.getValue()); +}, { value: 'aa\nbb\ncc'}); + +testVim('d_/', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('2', 'd', '/'); + helpers.assertCursorAt(0, 0); + eq('match \n next', cm.getValue()); + cm.openDialog = helpers.fakeOpenDialog('2'); + helpers.doKeys('d', ':'); + // TODO eq(' next', cm.getValue()); +}, { value: 'text match match \n next' }); +testVim('/ and n/N', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('/'); + helpers.assertCursorAt(0, 11); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 6); + helpers.doKeys('N'); + helpers.assertCursorAt(0, 11); + + cm.setCursor(0, 0); + helpers.doKeys('2', '/'); + helpers.assertCursorAt(1, 6); +}, { value: 'match nope match \n nope Match' }); +testVim('/_case', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('Match'); + helpers.doKeys('/'); + helpers.assertCursorAt(1, 6); +}, { value: 'match nope match \n nope Match' }); +testVim('/_2_pcre', function(cm, vim, helpers) { + CodeMirror.Vim.setOption('pcre', true); + cm.openDialog = helpers.fakeOpenDialog('(word){2}'); + helpers.doKeys('/'); + helpers.assertCursorAt(1, 9); + helpers.doKeys('n'); + helpers.assertCursorAt(2, 1); +}, { value: 'word\n another wordword\n wordwordword\n' }); +testVim('/_2_nopcre', function(cm, vim, helpers) { + CodeMirror.Vim.setOption('pcre', false); + cm.openDialog = helpers.fakeOpenDialog('\\(word\\)\\{2}'); + helpers.doKeys('/'); + helpers.assertCursorAt(1, 9); + helpers.doKeys('n'); + helpers.assertCursorAt(2, 1); +}, { value: 'word\n another wordword\n wordwordword\n' }); +testVim('/_nongreedy', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('aa'); + helpers.doKeys('/'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa \n a aa'}); +testVim('?_nongreedy', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('aa'); + helpers.doKeys('?'); + helpers.assertCursorAt(1, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa \n a aa'}); +testVim('/_greedy', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('a+'); + helpers.doKeys('/'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 1); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa \n a aa'}); +testVim('?_greedy', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('a+'); + helpers.doKeys('?'); + helpers.assertCursorAt(1, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 1); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa \n a aa'}); +testVim('/_greedy_0_or_more', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('a*'); + helpers.doKeys('/'); + helpers.assertCursorAt(0, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 5); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 0); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 1); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa\n aa'}); +testVim('?_greedy_0_or_more', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('a*'); + helpers.doKeys('?'); + helpers.assertCursorAt(1, 1); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 0); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 5); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa\n aa'}); +testVim('? and n/N', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('?'); + helpers.assertCursorAt(1, 6); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 11); + helpers.doKeys('N'); + helpers.assertCursorAt(1, 6); + + cm.setCursor(0, 0); + helpers.doKeys('2', '?'); + helpers.assertCursorAt(0, 11); +}, { value: 'match nope match \n nope Match' }); +testVim('*', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('*'); + helpers.assertCursorAt(0, 22); + + cm.setCursor(0, 9); + helpers.doKeys('2', '*'); + helpers.assertCursorAt(1, 8); +}, { value: 'nomatch match nomatch match \nnomatch Match' }); +testVim('*_no_word', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('*'); + helpers.assertCursorAt(0, 0); +}, { value: ' \n match \n' }); +testVim('*_symbol', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('*'); + helpers.assertCursorAt(1, 0); +}, { value: ' /}\n/} match \n' }); +testVim('#', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('#'); + helpers.assertCursorAt(1, 8); + + cm.setCursor(0, 9); + helpers.doKeys('2', '#'); + helpers.assertCursorAt(0, 22); +}, { value: 'nomatch match nomatch match \nnomatch Match' }); +testVim('*_seek', function(cm, vim, helpers) { + // Should skip over space and symbols. + cm.setCursor(0, 3); + helpers.doKeys('*'); + helpers.assertCursorAt(0, 22); +}, { value: ' := match nomatch match \nnomatch Match' }); +testVim('#', function(cm, vim, helpers) { + // Should skip over space and symbols. + cm.setCursor(0, 3); + helpers.doKeys('#'); + helpers.assertCursorAt(1, 8); +}, { value: ' := match nomatch match \nnomatch Match' }); +testVim('g*', function(cm, vim, helpers) { + cm.setCursor(0, 8); + helpers.doKeys('g', '*'); + helpers.assertCursorAt(0, 18); + cm.setCursor(0, 8); + helpers.doKeys('3', 'g', '*'); + helpers.assertCursorAt(1, 8); +}, { value: 'matches match alsoMatch\nmatchme matching' }); +testVim('g#', function(cm, vim, helpers) { + cm.setCursor(0, 8); + helpers.doKeys('g', '#'); + helpers.assertCursorAt(0, 0); + cm.setCursor(0, 8); + helpers.doKeys('3', 'g', '#'); + helpers.assertCursorAt(1, 0); +}, { value: 'matches match alsoMatch\nmatchme matching' }); +testVim('macro_insert', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', '0', 'i'); + cm.replaceRange('foo', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('q', '@', 'a'); + eq('foofoo', cm.getValue()); +}, { value: ''}); +testVim('macro_insert_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', '$', 'a'); + cm.replaceRange('larry.', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('a'); + cm.replaceRange('curly.', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('q'); + helpers.doKeys('a'); + cm.replaceRange('moe.', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('@', 'a'); + // At this point, the most recent edit should be the 2nd insert change + // inside the macro, i.e. "curly.". + helpers.doKeys('.'); + eq('larry.curly.moe.larry.curly.curly.', cm.getValue()); +}, { value: ''}); +testVim('macro_space', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', ''); + helpers.assertCursorAt(0, 2); + helpers.doKeys('q', 'a', '', '', 'q'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('@', 'a'); + helpers.assertCursorAt(0, 6); + helpers.doKeys('@', 'a'); + helpers.assertCursorAt(0, 8); +}, { value: 'one line of text.'}); +testVim('macro_t_search', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', 't', 'e', 'q'); + helpers.assertCursorAt(0, 1); + helpers.doKeys('l', '@', 'a'); + helpers.assertCursorAt(0, 6); + helpers.doKeys('l', ';'); + helpers.assertCursorAt(0, 12); +}, { value: 'one line of text.'}); +testVim('macro_f_search', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'b', 'f', 'e', 'q'); + helpers.assertCursorAt(0, 2); + helpers.doKeys('@', 'b'); + helpers.assertCursorAt(0, 7); + helpers.doKeys(';'); + helpers.assertCursorAt(0, 13); +}, { value: 'one line of text.'}); +testVim('macro_slash_search', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'c'); + cm.openDialog = helpers.fakeOpenDialog('e'); + helpers.doKeys('/', 'q'); + helpers.assertCursorAt(0, 2); + helpers.doKeys('@', 'c'); + helpers.assertCursorAt(0, 7); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 13); +}, { value: 'one line of text.'}); +testVim('macro_multislash_search', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'd'); + cm.openDialog = helpers.fakeOpenDialog('e'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('t'); + helpers.doKeys('/', 'q'); + helpers.assertCursorAt(0, 12); + helpers.doKeys('@', 'd'); + helpers.assertCursorAt(0, 15); +}, { value: 'one line of text to rule them all.'}); +testVim('macro_last_ex_command_register', function (cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doEx('s/a/b'); + helpers.doKeys('2', '@', ':'); + eq('bbbaa', cm.getValue()); + helpers.assertCursorAt(0, 2); +}, { value: 'aaaaa'}); +testVim('macro_parens', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'z', 'i'); + cm.replaceRange('(', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('e', 'a'); + cm.replaceRange(')', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('q'); + helpers.doKeys('w', '@', 'z'); + helpers.doKeys('w', '@', 'z'); + eq('(see) (spot) (run)', cm.getValue()); +}, { value: 'see spot run'}); +testVim('macro_overwrite', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'z', '0', 'i'); + cm.replaceRange('I ', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('q'); + helpers.doKeys('e'); + // Now replace the macro with something else. + helpers.doKeys('q', 'z', 'a'); + cm.replaceRange('.', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('q'); + helpers.doKeys('e', '@', 'z'); + helpers.doKeys('e', '@', 'z'); + eq('I see. spot. run.', cm.getValue()); +}, { value: 'see spot run'}); +testVim('macro_search_f', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', 'f', ' '); + helpers.assertCursorAt(0,3); + helpers.doKeys('q', '0'); + helpers.assertCursorAt(0,0); + helpers.doKeys('@', 'a'); + helpers.assertCursorAt(0,3); +}, { value: 'The quick brown fox jumped over the lazy dog.'}); +testVim('macro_search_2f', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', '2', 'f', ' '); + helpers.assertCursorAt(0,9); + helpers.doKeys('q', '0'); + helpers.assertCursorAt(0,0); + helpers.doKeys('@', 'a'); + helpers.assertCursorAt(0,9); +}, { value: 'The quick brown fox jumped over the lazy dog.'}); +testVim('yank_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'y'); + helpers.doKeys('j', '"', 'b', 'y', 'y'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foo/.test(text)); + is(/b\s+bar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', 'l', 'j', '"', 'a', 'y'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+oo\nar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_append_line_to_line_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'y'); + helpers.doKeys('j', '"', 'A', 'y', 'y'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foo\nbar/.test(text)); + is(/"\s+foo\nbar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_append_word_to_word_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'w'); + helpers.doKeys('j', '"', 'A', 'y', 'w'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foobar/.test(text)); + is(/"\s+foobar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_append_line_to_word_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'w'); + helpers.doKeys('j', '"', 'A', 'y', 'y'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foo\nbar/.test(text)); + is(/"\s+foo\nbar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_append_word_to_line_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'y'); + helpers.doKeys('j', '"', 'A', 'y', 'w'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foo\nbar/.test(text)); + is(/"\s+foo\nbar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('macro_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', 'i'); + cm.replaceRange('gangnam', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('q'); + helpers.doKeys('q', 'b', 'o'); + cm.replaceRange('style', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('q'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+i/.test(text)); + is(/b\s+o/.test(text)); + }); + helpers.doKeys(':'); +}, { value: ''}); +testVim('._register', function(cm,vim,helpers) { + cm.setCursor(0,0); + helpers.doKeys('i'); + cm.replaceRange('foo',cm.getCursor()); + helpers.doKeys(''); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/\.\s+foo/.test(text)); + }); + helpers.doKeys(':'); +}, {value: ''}); +testVim(':_register', function(cm,vim,helpers) { + helpers.doEx('bar'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/:\s+bar/.test(text)); + }); + helpers.doKeys(':'); +}, {value: ''}); +testVim('search_register_escape', function(cm, vim, helpers) { + // Check that the register is restored if the user escapes rather than confirms. + cm.openDialog = helpers.fakeOpenDialog('waldo'); + helpers.doKeys('/'); + var onKeyDown; + var onKeyUp; + var KEYCODES = { + f: 70, + o: 79, + Esc: 27 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + onKeyUp = options.onKeyUp; + }; + var close = function() {}; + helpers.doKeys('/'); + // Fake some keyboard events coming in. + onKeyDown({keyCode: KEYCODES.f}, '', close); + onKeyUp({keyCode: KEYCODES.f}, '', close); + onKeyDown({keyCode: KEYCODES.o}, 'f', close); + onKeyUp({keyCode: KEYCODES.o}, 'f', close); + onKeyDown({keyCode: KEYCODES.o}, 'fo', close); + onKeyUp({keyCode: KEYCODES.o}, 'fo', close); + onKeyDown({keyCode: KEYCODES.Esc}, 'foo', close); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/waldo/.test(text)); + is(!/foo/.test(text)); + }); + helpers.doKeys(':'); +}, {value: ''}); +testVim('search_register', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('foo'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/\/\s+foo/.test(text)); + }); + helpers.doKeys(':'); +}, {value: ''}); +testVim('search_history', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('this'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('checks'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('search'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('history'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('checks'); + helpers.doKeys('/'); + var onKeyDown; + var onKeyUp; + var query = ''; + var keyCodes = { + Up: 38, + Down: 40 + }; + cm.openDialog = function(template, callback, options) { + onKeyUp = options.onKeyUp; + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') query = newVal; + } + helpers.doKeys('/'); + onKeyDown({keyCode: keyCodes.Up}, query, close); + onKeyUp({keyCode: keyCodes.Up}, query, close); + eq(query, 'checks'); + onKeyDown({keyCode: keyCodes.Up}, query, close); + onKeyUp({keyCode: keyCodes.Up}, query, close); + eq(query, 'history'); + onKeyDown({keyCode: keyCodes.Up}, query, close); + onKeyUp({keyCode: keyCodes.Up}, query, close); + eq(query, 'search'); + onKeyDown({keyCode: keyCodes.Up}, query, close); + onKeyUp({keyCode: keyCodes.Up}, query, close); + eq(query, 'this'); + onKeyDown({keyCode: keyCodes.Down}, query, close); + onKeyUp({keyCode: keyCodes.Down}, query, close); + eq(query, 'search'); +}, {value: ''}); +testVim('exCommand_history', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('registers'); + helpers.doKeys(':'); + cm.openDialog = helpers.fakeOpenDialog('sort'); + helpers.doKeys(':'); + cm.openDialog = helpers.fakeOpenDialog('map'); + helpers.doKeys(':'); + cm.openDialog = helpers.fakeOpenDialog('invalid'); + helpers.doKeys(':'); + var onKeyDown; + var onKeyUp; + var input = ''; + var keyCodes = { + Up: 38, + Down: 40, + s: 115 + }; + cm.openDialog = function(template, callback, options) { + onKeyUp = options.onKeyUp; + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys(':'); + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'invalid'); + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'map'); + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'sort'); + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'registers'); + onKeyDown({keyCode: keyCodes.s}, '', close); + input = 's'; + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'sort'); +}, {value: ''}); +testVim('search_clear', function(cm, vim, helpers) { + var onKeyDown; + var input = ''; + var keyCodes = { + Ctrl: 17, + u: 85 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys('/'); + input = 'foo'; + onKeyDown({keyCode: keyCodes.Ctrl}, input, close); + onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close); + eq(input, ''); +}); +testVim('exCommand_clear', function(cm, vim, helpers) { + var onKeyDown; + var input = ''; + var keyCodes = { + Ctrl: 17, + u: 85 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys(':'); + input = 'foo'; + onKeyDown({keyCode: keyCodes.Ctrl}, input, close); + onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close); + eq(input, ''); +}); +testVim('.', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('2', 'd', 'w'); + helpers.doKeys('.'); + eq('5 6', cm.getValue()); +}, { value: '1 2 3 4 5 6'}); +testVim('._repeat', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('2', 'd', 'w'); + helpers.doKeys('3', '.'); + eq('6', cm.getValue()); +}, { value: '1 2 3 4 5 6'}); +testVim('._insert', function(cm, vim, helpers) { + helpers.doKeys('i'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys(''); + helpers.doKeys('.'); + eq('testestt', cm.getValue()); + helpers.assertCursorAt(0, 6); +}, { value: ''}); +testVim('._insert_repeat', function(cm, vim, helpers) { + helpers.doKeys('i'); + cm.replaceRange('test', cm.getCursor()); + cm.setCursor(0, 4); + helpers.doKeys(''); + helpers.doKeys('2', '.'); + eq('testesttestt', cm.getValue()); + helpers.assertCursorAt(0, 10); +}, { value: ''}); +testVim('._repeat_insert', function(cm, vim, helpers) { + helpers.doKeys('3', 'i'); + cm.replaceRange('te', cm.getCursor()); + cm.setCursor(0, 2); + helpers.doKeys(''); + helpers.doKeys('.'); + eq('tetettetetee', cm.getValue()); + helpers.assertCursorAt(0, 10); +}, { value: ''}); +testVim('._insert_o', function(cm, vim, helpers) { + helpers.doKeys('o'); + cm.replaceRange('z', cm.getCursor()); + cm.setCursor(1, 1); + helpers.doKeys(''); + helpers.doKeys('.'); + eq('\nz\nz', cm.getValue()); + helpers.assertCursorAt(2, 0); +}, { value: ''}); +testVim('._insert_o_repeat', function(cm, vim, helpers) { + helpers.doKeys('o'); + cm.replaceRange('z', cm.getCursor()); + helpers.doKeys(''); + cm.setCursor(1, 0); + helpers.doKeys('2', '.'); + eq('\nz\nz\nz', cm.getValue()); + helpers.assertCursorAt(3, 0); +}, { value: ''}); +testVim('._insert_o_indent', function(cm, vim, helpers) { + helpers.doKeys('o'); + cm.replaceRange('z', cm.getCursor()); + helpers.doKeys(''); + cm.setCursor(1, 2); + helpers.doKeys('.'); + eq('{\n z\n z', cm.getValue()); + helpers.assertCursorAt(2, 2); +}, { value: '{'}); +testVim('._insert_cw', function(cm, vim, helpers) { + helpers.doKeys('c', 'w'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys(''); + cm.setCursor(0, 3); + helpers.doKeys('2', 'l'); + helpers.doKeys('.'); + eq('test test word3', cm.getValue()); + helpers.assertCursorAt(0, 8); +}, { value: 'word1 word2 word3' }); +testVim('._insert_cw_repeat', function(cm, vim, helpers) { + // For some reason, repeat cw in desktop VIM will does not repeat insert mode + // changes. Will conform to that behavior. + helpers.doKeys('c', 'w'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys(''); + cm.setCursor(0, 4); + helpers.doKeys('l'); + helpers.doKeys('2', '.'); + eq('test test', cm.getValue()); + helpers.assertCursorAt(0, 8); +}, { value: 'word1 word2 word3' }); +testVim('._delete', function(cm, vim, helpers) { + cm.setCursor(0, 5); + helpers.doKeys('i'); + helpers.doInsertModeKeys('Backspace'); + helpers.doKeys(''); + helpers.doKeys('.'); + eq('zace', cm.getValue()); + helpers.assertCursorAt(0, 1); +}, { value: 'zabcde'}); +testVim('._delete_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 6); + helpers.doKeys('i'); + helpers.doInsertModeKeys('Backspace'); + helpers.doKeys(''); + helpers.doKeys('2', '.'); + eq('zzce', cm.getValue()); + helpers.assertCursorAt(0, 1); +}, { value: 'zzabcde'}); +testVim('._visual_>', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('V', 'j', '>'); + cm.setCursor(2, 0) + helpers.doKeys('.'); + eq(' 1\n 2\n 3\n 4', cm.getValue()); + helpers.assertCursorAt(2, 2); +}, { value: '1\n2\n3\n4'}); +testVim('f;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('f', 'x'); + helpers.doKeys(';'); + helpers.doKeys('2', ';'); + eq(9, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('F;', function(cm, vim, helpers) { + cm.setCursor(0, 8); + helpers.doKeys('F', 'x'); + helpers.doKeys(';'); + helpers.doKeys('2', ';'); + eq(2, cm.getCursor().ch); +}, { value: '01x3xx6x8x'}); +testVim('t;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('t', 'x'); + helpers.doKeys(';'); + helpers.doKeys('2', ';'); + eq(8, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('T;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('T', 'x'); + helpers.doKeys(';'); + helpers.doKeys('2', ';'); + eq(2, cm.getCursor().ch); +}, { value: '0xx3xx678x'}); +testVim('f,', function(cm, vim, helpers) { + cm.setCursor(0, 6); + helpers.doKeys('f', 'x'); + helpers.doKeys(','); + helpers.doKeys('2', ','); + eq(2, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('F,', function(cm, vim, helpers) { + cm.setCursor(0, 3); + helpers.doKeys('F', 'x'); + helpers.doKeys(','); + helpers.doKeys('2', ','); + eq(9, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('t,', function(cm, vim, helpers) { + cm.setCursor(0, 6); + helpers.doKeys('t', 'x'); + helpers.doKeys(','); + helpers.doKeys('2', ','); + eq(3, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('T,', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('T', 'x'); + helpers.doKeys(','); + helpers.doKeys('2', ','); + eq(8, cm.getCursor().ch); +}, { value: '01x3xx67xx'}); +testVim('fd,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('f', '4'); + cm.setCursor(0, 0); + helpers.doKeys('d', ';'); + eq('56789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('d', ','); + eq('01239', cm.getValue()); +}, { value: '0123456789'}); +testVim('Fd,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('F', '4'); + cm.setCursor(0, 9); + helpers.doKeys('d', ';'); + eq('01239', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('d', ','); + eq('56789', cm.getValue()); +}, { value: '0123456789'}); +testVim('td,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('t', '4'); + cm.setCursor(0, 0); + helpers.doKeys('d', ';'); + eq('456789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('d', ','); + eq('012349', cm.getValue()); +}, { value: '0123456789'}); +testVim('Td,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('T', '4'); + cm.setCursor(0, 9); + helpers.doKeys('d', ';'); + eq('012349', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('d', ','); + eq('456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('fc,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('f', '4'); + cm.setCursor(0, 0); + helpers.doKeys('c', ';', ''); + eq('56789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('c', ','); + eq('01239', cm.getValue()); +}, { value: '0123456789'}); +testVim('Fc,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('F', '4'); + cm.setCursor(0, 9); + helpers.doKeys('c', ';', ''); + eq('01239', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('c', ','); + eq('56789', cm.getValue()); +}, { value: '0123456789'}); +testVim('tc,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('t', '4'); + cm.setCursor(0, 0); + helpers.doKeys('c', ';', ''); + eq('456789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('c', ','); + eq('012349', cm.getValue()); +}, { value: '0123456789'}); +testVim('Tc,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('T', '4'); + cm.setCursor(0, 9); + helpers.doKeys('c', ';', ''); + eq('012349', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('c', ','); + eq('456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('fy,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('f', '4'); + cm.setCursor(0, 0); + helpers.doKeys('y', ';', 'P'); + eq('012340123456789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('y', ',', 'P'); + eq('012345678456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('Fy,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('F', '4'); + cm.setCursor(0, 9); + helpers.doKeys('y', ';', 'p'); + eq('012345678945678', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('y', ',', 'P'); + eq('012340123456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('ty,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('t', '4'); + cm.setCursor(0, 0); + helpers.doKeys('y', ';', 'P'); + eq('01230123456789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('y', ',', 'p'); + eq('01234567895678', cm.getValue()); +}, { value: '0123456789'}); +testVim('Ty,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('T', '4'); + cm.setCursor(0, 9); + helpers.doKeys('y', ';', 'p'); + eq('01234567895678', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('y', ',', 'P'); + eq('01230123456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('HML', function(cm, vim, helpers) { + var lines = 35; + var textHeight = cm.defaultTextHeight(); + cm.setSize(600, lines*textHeight); + cm.setCursor(120, 0); + helpers.doKeys('H'); + helpers.assertCursorAt(86, 2); + helpers.doKeys('L'); + helpers.assertCursorAt(120, 4); + helpers.doKeys('M'); + helpers.assertCursorAt(103,4); +}, { value: (function(){ + var lines = new Array(100); + var upper = ' xx\n'; + var lower = ' xx\n'; + upper = lines.join(upper); + lower = lines.join(lower); + return upper + lower; +})()}); + +var zVals = []; +forEach(['zb','zz','zt','z-','z.','z'], function(e, idx){ + var lineNum = 250; + var lines = 35; + testVim(e, function(cm, vim, helpers) { + var k1 = e[0]; + var k2 = e.substring(1); + var textHeight = cm.defaultTextHeight(); + cm.setSize(600, lines*textHeight); + cm.setCursor(lineNum, 0); + helpers.doKeys(k1, k2); + zVals[idx] = cm.getScrollInfo().top; + }, { value: (function(){ + return new Array(500).join('\n'); + })()}); +}); +testVim('zb_to_bottom', function(cm, vim, helpers){ + var lineNum = 250; + cm.setSize(600, 35*cm.defaultTextHeight()); + cm.setCursor(lineNum, 0); + helpers.doKeys('z', 'b'); + var scrollInfo = cm.getScrollInfo(); + eq(scrollInfo.top + scrollInfo.clientHeight, cm.charCoords(Pos(lineNum, 0), 'local').bottom); +}, { value: (function(){ + return new Array(500).join('\n'); +})()}); +testVim('zt_to_top', function(cm, vim, helpers){ + var lineNum = 250; + cm.setSize(600, 35*cm.defaultTextHeight()); + cm.setCursor(lineNum, 0); + helpers.doKeys('z', 't'); + eq(cm.getScrollInfo().top, cm.charCoords(Pos(lineNum, 0), 'local').top); +}, { value: (function(){ + return new Array(500).join('\n'); +})()}); +testVim('zb', function(cm, vim, helpers){ + eq(zVals[2], zVals[5]); +}); + +var moveTillCharacterSandbox = + 'The quick brown fox \n'; +testVim('moveTillCharacter', function(cm, vim, helpers){ + cm.setCursor(0, 0); + // Search for the 'q'. + cm.openDialog = helpers.fakeOpenDialog('q'); + helpers.doKeys('/'); + eq(4, cm.getCursor().ch); + // Jump to just before the first o in the list. + helpers.doKeys('t'); + helpers.doKeys('o'); + eq('The quick brown fox \n', cm.getValue()); + // Delete that one character. + helpers.doKeys('d'); + helpers.doKeys('t'); + helpers.doKeys('o'); + eq('The quick bown fox \n', cm.getValue()); + // Delete everything until the next 'o'. + helpers.doKeys('.'); + eq('The quick box \n', cm.getValue()); + // An unmatched character should have no effect. + helpers.doKeys('d'); + helpers.doKeys('t'); + helpers.doKeys('q'); + eq('The quick box \n', cm.getValue()); + // Matches should only be possible on single lines. + helpers.doKeys('d'); + helpers.doKeys('t'); + helpers.doKeys('z'); + eq('The quick box \n', cm.getValue()); + // After all that, the search for 'q' should still be active, so the 'N' command + // can run it again in reverse. Use that to delete everything back to the 'q'. + helpers.doKeys('d'); + helpers.doKeys('N'); + eq('The ox \n', cm.getValue()); + eq(4, cm.getCursor().ch); +}, { value: moveTillCharacterSandbox}); +testVim('searchForPipe', function(cm, vim, helpers){ + CodeMirror.Vim.setOption('pcre', false); + cm.setCursor(0, 0); + // Search for the '|'. + cm.openDialog = helpers.fakeOpenDialog('|'); + helpers.doKeys('/'); + eq(4, cm.getCursor().ch); +}, { value: 'this|that'}); + + +var scrollMotionSandbox = + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'; +testVim('scrollMotion', function(cm, vim, helpers){ + var prevCursor, prevScrollInfo; + cm.setCursor(0, 0); + // ctrl-y at the top of the file should have no effect. + helpers.doKeys(''); + eq(0, cm.getCursor().line); + prevScrollInfo = cm.getScrollInfo(); + helpers.doKeys(''); + eq(1, cm.getCursor().line); + is(prevScrollInfo.top < cm.getScrollInfo().top); + // Jump to the end of the sandbox. + cm.setCursor(1000, 0); + prevCursor = cm.getCursor(); + // ctrl-e at the bottom of the file should have no effect. + helpers.doKeys(''); + eq(prevCursor.line, cm.getCursor().line); + prevScrollInfo = cm.getScrollInfo(); + helpers.doKeys(''); + eq(prevCursor.line - 1, cm.getCursor().line, "Y"); + is(prevScrollInfo.top > cm.getScrollInfo().top); +}, { value: scrollMotionSandbox}); + +var squareBracketMotionSandbox = ''+ + '({\n'+//0 + ' ({\n'+//11 + ' /*comment {\n'+//2 + ' */(\n'+//3 + '#else \n'+//4 + ' /* )\n'+//5 + '#if }\n'+//6 + ' )}*/\n'+//7 + ')}\n'+//8 + '{}\n'+//9 + '#else {{\n'+//10 + '{}\n'+//11 + '}\n'+//12 + '{\n'+//13 + '#endif\n'+//14 + '}\n'+//15 + '}\n'+//16 + '#else';//17 +testVim('[[, ]]', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys(']', ']'); + helpers.assertCursorAt(9,0); + helpers.doKeys('2', ']', ']'); + helpers.assertCursorAt(13,0); + helpers.doKeys(']', ']'); + helpers.assertCursorAt(17,0); + helpers.doKeys('[', '['); + helpers.assertCursorAt(13,0); + helpers.doKeys('2', '[', '['); + helpers.assertCursorAt(9,0); + helpers.doKeys('[', '['); + helpers.assertCursorAt(0,0); +}, { value: squareBracketMotionSandbox}); +testVim('[], ][', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys(']', '['); + helpers.assertCursorAt(12,0); + helpers.doKeys('2', ']', '['); + helpers.assertCursorAt(16,0); + helpers.doKeys(']', '['); + helpers.assertCursorAt(17,0); + helpers.doKeys('[', ']'); + helpers.assertCursorAt(16,0); + helpers.doKeys('2', '[', ']'); + helpers.assertCursorAt(12,0); + helpers.doKeys('[', ']'); + helpers.assertCursorAt(0,0); +}, { value: squareBracketMotionSandbox}); +testVim('[{, ]}', function(cm, vim, helpers) { + cm.setCursor(4, 10); + helpers.doKeys('[', '{'); + helpers.assertCursorAt(2,12); + helpers.doKeys('2', '[', '{'); + helpers.assertCursorAt(0,1); + cm.setCursor(4, 10); + helpers.doKeys(']', '}'); + helpers.assertCursorAt(6,11); + helpers.doKeys('2', ']', '}'); + helpers.assertCursorAt(8,1); + cm.setCursor(0,1); + helpers.doKeys(']', '}'); + helpers.assertCursorAt(8,1); + helpers.doKeys('[', '{'); + helpers.assertCursorAt(0,1); +}, { value: squareBracketMotionSandbox}); +testVim('[(, ])', function(cm, vim, helpers) { + cm.setCursor(4, 10); + helpers.doKeys('[', '('); + helpers.assertCursorAt(3,14); + helpers.doKeys('2', '[', '('); + helpers.assertCursorAt(0,0); + cm.setCursor(4, 10); + helpers.doKeys(']', ')'); + helpers.assertCursorAt(5,11); + helpers.doKeys('2', ']', ')'); + helpers.assertCursorAt(8,0); + helpers.doKeys('[', '('); + helpers.assertCursorAt(0,0); + helpers.doKeys(']', ')'); + helpers.assertCursorAt(8,0); +}, { value: squareBracketMotionSandbox}); +testVim('[*, ]*, [/, ]/', function(cm, vim, helpers) { + forEach(['*', '/'], function(key){ + cm.setCursor(7, 0); + helpers.doKeys('2', '[', key); + helpers.assertCursorAt(2,2); + helpers.doKeys('2', ']', key); + helpers.assertCursorAt(7,5); + }); +}, { value: squareBracketMotionSandbox}); +testVim('[#, ]#', function(cm, vim, helpers) { + cm.setCursor(10, 3); + helpers.doKeys('2', '[', '#'); + helpers.assertCursorAt(4,0); + helpers.doKeys('5', ']', '#'); + helpers.assertCursorAt(17,0); + cm.setCursor(10, 3); + helpers.doKeys(']', '#'); + helpers.assertCursorAt(14,0); +}, { value: squareBracketMotionSandbox}); +testVim('[m, ]m, [M, ]M', function(cm, vim, helpers) { + cm.setCursor(11, 0); + helpers.doKeys('[', 'm'); + helpers.assertCursorAt(10,7); + helpers.doKeys('4', '[', 'm'); + helpers.assertCursorAt(1,3); + helpers.doKeys('5', ']', 'm'); + helpers.assertCursorAt(11,0); + helpers.doKeys('[', 'M'); + helpers.assertCursorAt(9,1); + helpers.doKeys('3', ']', 'M'); + helpers.assertCursorAt(15,0); + helpers.doKeys('5', '[', 'M'); + helpers.assertCursorAt(7,3); +}, { value: squareBracketMotionSandbox}); + +// Ex mode tests +testVim('ex_go_to_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doEx('4'); + helpers.assertCursorAt(3, 0); +}, { value: 'a\nb\nc\nd\ne\n'}); +testVim('ex_write', function(cm, vim, helpers) { + var tmp = CodeMirror.commands.save; + var written; + var actualCm; + CodeMirror.commands.save = function(cm) { + written = true; + actualCm = cm; + }; + // Test that w, wr, wri ... write all trigger :write. + var command = 'write'; + for (var i = 1; i < command.length; i++) { + written = false; + actualCm = null; + helpers.doEx(command.substring(0, i)); + eq(written, true); + eq(actualCm, cm); + } + CodeMirror.commands.save = tmp; +}); +testVim('ex_sort', function(cm, vim, helpers) { + helpers.doEx('sort'); + eq('Z\na\nb\nc\nd', cm.getValue()); +}, { value: 'b\nZ\nd\nc\na'}); +testVim('ex_sort_reverse', function(cm, vim, helpers) { + helpers.doEx('sort!'); + eq('d\nc\nb\na', cm.getValue()); +}, { value: 'b\nd\nc\na'}); +testVim('ex_sort_range', function(cm, vim, helpers) { + helpers.doEx('2,3sort'); + eq('b\nc\nd\na', cm.getValue()); +}, { value: 'b\nd\nc\na'}); +testVim('ex_sort_oneline', function(cm, vim, helpers) { + helpers.doEx('2sort'); + // Expect no change. + eq('b\nd\nc\na', cm.getValue()); +}, { value: 'b\nd\nc\na'}); +testVim('ex_sort_ignoreCase', function(cm, vim, helpers) { + helpers.doEx('sort i'); + eq('a\nb\nc\nd\nZ', cm.getValue()); +}, { value: 'b\nZ\nd\nc\na'}); +testVim('ex_sort_unique', function(cm, vim, helpers) { + helpers.doEx('sort u'); + eq('Z\na\nb\nc\nd', cm.getValue()); +}, { value: 'b\nZ\na\na\nd\na\nc\na'}); +testVim('ex_sort_decimal', function(cm, vim, helpers) { + helpers.doEx('sort d'); + eq('d3\n s5\n6\n.9', cm.getValue()); +}, { value: '6\nd3\n s5\n.9'}); +testVim('ex_sort_decimal_negative', function(cm, vim, helpers) { + helpers.doEx('sort d'); + eq('z-9\nd3\n s5\n6\n.9', cm.getValue()); +}, { value: '6\nd3\n s5\n.9\nz-9'}); +testVim('ex_sort_decimal_reverse', function(cm, vim, helpers) { + helpers.doEx('sort! d'); + eq('.9\n6\n s5\nd3', cm.getValue()); +}, { value: '6\nd3\n s5\n.9'}); +testVim('ex_sort_hex', function(cm, vim, helpers) { + helpers.doEx('sort x'); + eq(' s5\n6\n.9\n&0xB\nd3', cm.getValue()); +}, { value: '6\nd3\n s5\n&0xB\n.9'}); +testVim('ex_sort_octal', function(cm, vim, helpers) { + helpers.doEx('sort o'); + eq('.8\n.9\nd3\n s5\n6', cm.getValue()); +}, { value: '6\nd3\n s5\n.9\n.8'}); +testVim('ex_sort_decimal_mixed', function(cm, vim, helpers) { + helpers.doEx('sort d'); + eq('y\nz\nc1\nb2\na3', cm.getValue()); +}, { value: 'a3\nz\nc1\ny\nb2'}); +testVim('ex_sort_decimal_mixed_reverse', function(cm, vim, helpers) { + helpers.doEx('sort! d'); + eq('a3\nb2\nc1\nz\ny', cm.getValue()); +}, { value: 'a3\nz\nc1\ny\nb2'}); +testVim('ex_sort_patterns_not_supported', function(cm, vim, helpers) { + var notified = false; + cm.openNotification = helpers.fakeOpenNotification(function(text) { + notified = /patterns not supported/.test(text); + }); + helpers.doEx('sort /abc/'); + is(notified, 'No notification.'); +}); +// test for :global command +testVim('ex_global', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doEx('g/one/s//two'); + eq('two two\n two two\n two two', cm.getValue()); + helpers.doEx('1,2g/two/s//one'); + eq('one one\n one one\n two two', cm.getValue()); +}, {value: 'one one\n one one\n one one'}); +testVim('ex_global_confirm', function(cm, vim, helpers) { + cm.setCursor(0, 0); + var onKeyDown; + var openDialogSave = cm.openDialog; + var KEYCODES = { + a: 65, + n: 78, + q: 81, + y: 89 + }; + // Intercept the ex command, 'global' + cm.openDialog = function(template, callback, options) { + // Intercept the prompt for the embedded ex command, 'substitute' + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + callback('g/one/s//two/gc'); + }; + helpers.doKeys(':'); + var close = function() {}; + onKeyDown({keyCode: KEYCODES.n}, '', close); + onKeyDown({keyCode: KEYCODES.y}, '', close); + onKeyDown({keyCode: KEYCODES.a}, '', close); + onKeyDown({keyCode: KEYCODES.q}, '', close); + onKeyDown({keyCode: KEYCODES.y}, '', close); + eq('one two\n two two\n one one\n two one\n one one', cm.getValue()); +}, {value: 'one one\n one one\n one one\n one one\n one one'}); +// Basic substitute tests. +testVim('ex_substitute_same_line', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doEx('s/one/two/g'); + eq('one one\n two two', cm.getValue()); +}, { value: 'one one\n one one'}); +testVim('ex_substitute_full_file', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doEx('%s/one/two/g'); + eq('two two\n two two', cm.getValue()); +}, { value: 'one one\n one one'}); +testVim('ex_substitute_input_range', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doEx('1,3s/\\d/0/g'); + eq('0\n0\n0\n4', cm.getValue()); +}, { value: '1\n2\n3\n4' }); +testVim('ex_substitute_visual_range', function(cm, vim, helpers) { + cm.setCursor(1, 0); + // Set last visual mode selection marks '< and '> at lines 2 and 4 + helpers.doKeys('V', '2', 'j', 'v'); + helpers.doEx('\'<,\'>s/\\d/0/g'); + eq('1\n0\n0\n0\n5', cm.getValue()); +}, { value: '1\n2\n3\n4\n5' }); +testVim('ex_substitute_empty_query', function(cm, vim, helpers) { + // If the query is empty, use last query. + cm.setCursor(1, 0); + cm.openDialog = helpers.fakeOpenDialog('1'); + helpers.doKeys('/'); + helpers.doEx('s//b/g'); + eq('abb ab2 ab3', cm.getValue()); +}, { value: 'a11 a12 a13' }); +testVim('ex_substitute_javascript', function(cm, vim, helpers) { + CodeMirror.Vim.setOption('pcre', false); + cm.setCursor(1, 0); + // Throw all the things that javascript likes to treat as special values + // into the replace part. All should be literal (this is VIM). + helpers.doEx('s/\\(\\d+\\)/$$ $\' $` $& \\1/g') + eq('a $$ $\' $` $& 0 b', cm.getValue()); +}, { value: 'a 0 b' }); +testVim('ex_substitute_empty_arguments', function(cm,vim,helpers) { + cm.setCursor(0, 0); + helpers.doEx('s/a/b/g'); + cm.setCursor(1, 0); + helpers.doEx('s'); + eq('b b\nb a', cm.getValue()); +}, {value: 'a a\na a'}); + +// More complex substitute tests that test both pcre and nopcre options. +function testSubstitute(name, options) { + testVim(name + '_pcre', function(cm, vim, helpers) { + cm.setCursor(1, 0); + CodeMirror.Vim.setOption('pcre', true); + helpers.doEx(options.expr); + eq(options.expectedValue, cm.getValue()); + }, options); + // If no noPcreExpr is defined, assume that it's the same as the expr. + var noPcreExpr = options.noPcreExpr ? options.noPcreExpr : options.expr; + testVim(name + '_nopcre', function(cm, vim, helpers) { + cm.setCursor(1, 0); + CodeMirror.Vim.setOption('pcre', false); + helpers.doEx(noPcreExpr); + eq(options.expectedValue, cm.getValue()); + }, options); +} +testSubstitute('ex_substitute_capture', { + value: 'a11 a12 a13', + expectedValue: 'a1111 a1212 a1313', + // $n is a backreference + expr: 's/(\\d+)/$1$1/g', + // \n is a backreference. + noPcreExpr: 's/\\(\\d+\\)/\\1\\1/g'}); +testSubstitute('ex_substitute_capture2', { + value: 'a 0 b', + expectedValue: 'a $00 b', + expr: 's/(\\d+)/$$$1$1/g', + noPcreExpr: 's/\\(\\d+\\)/$\\1\\1/g'}); +testSubstitute('ex_substitute_nocapture', { + value: 'a11 a12 a13', + expectedValue: 'a$1$1 a$1$1 a$1$1', + expr: 's/(\\d+)/$$1$$1/g', + noPcreExpr: 's/\\(\\d+\\)/$1$1/g'}); +testSubstitute('ex_substitute_nocapture2', { + value: 'a 0 b', + expectedValue: 'a $10 b', + expr: 's/(\\d+)/$$1$1/g', + noPcreExpr: 's/\\(\\d+\\)/\\$1\\1/g'}); +testSubstitute('ex_substitute_nocapture', { + value: 'a b c', + expectedValue: 'a $ c', + expr: 's/b/$$/', + noPcreExpr: 's/b/$/'}); +testSubstitute('ex_substitute_slash_regex', { + value: 'one/two \n three/four', + expectedValue: 'one|two \n three|four', + expr: '%s/\\//|'}); +testSubstitute('ex_substitute_pipe_regex', { + value: 'one|two \n three|four', + expectedValue: 'one,two \n three,four', + expr: '%s/\\|/,/', + noPcreExpr: '%s/|/,/'}); +testSubstitute('ex_substitute_or_regex', { + value: 'one|two \n three|four', + expectedValue: 'ana|twa \n thraa|faar', + expr: '%s/o|e|u/a/g', + noPcreExpr: '%s/o\\|e\\|u/a/g'}); +testSubstitute('ex_substitute_or_word_regex', { + value: 'one|two \n three|four', + expectedValue: 'five|five \n three|four', + expr: '%s/(one|two)/five/g', + noPcreExpr: '%s/\\(one\\|two\\)/five/g'}); +testSubstitute('ex_substitute_backslashslash_regex', { + value: 'one\\two \n three\\four', + expectedValue: 'one,two \n three,four', + expr: '%s/\\\\/,'}); +testSubstitute('ex_substitute_slash_replacement', { + value: 'one,two \n three,four', + expectedValue: 'one/two \n three/four', + expr: '%s/,/\\/'}); +testSubstitute('ex_substitute_backslash_replacement', { + value: 'one,two \n three,four', + expectedValue: 'one\\two \n three\\four', + expr: '%s/,/\\\\/g'}); +testSubstitute('ex_substitute_multibackslash_replacement', { + value: 'one,two \n three,four', + expectedValue: 'one\\\\\\\\two \n three\\\\\\\\four', // 2*8 backslashes. + expr: '%s/,/\\\\\\\\\\\\\\\\/g'}); // 16 backslashes. +testSubstitute('ex_substitute_newline_replacement', { + value: 'one,two \n three,four', + expectedValue: 'one\ntwo \n three\nfour', + expr: '%s/,/\\n/g'}); +testSubstitute('ex_substitute_braces_word', { + value: 'ababab abb ab{2}', + expectedValue: 'ab abb ab{2}', + expr: '%s/(ab){2}//g', + noPcreExpr: '%s/\\(ab\\)\\{2\\}//g'}); +testSubstitute('ex_substitute_braces_range', { + value: 'a aa aaa aaaa', + expectedValue: 'a a', + expr: '%s/a{2,3}//g', + noPcreExpr: '%s/a\\{2,3\\}//g'}); +testSubstitute('ex_substitute_braces_literal', { + value: 'ababab abb ab{2}', + expectedValue: 'ababab abb ', + expr: '%s/ab\\{2\\}//g', + noPcreExpr: '%s/ab{2}//g'}); +testSubstitute('ex_substitute_braces_char', { + value: 'ababab abb ab{2}', + expectedValue: 'ababab ab{2}', + expr: '%s/ab{2}//g', + noPcreExpr: '%s/ab\\{2\\}//g'}); +testSubstitute('ex_substitute_braces_no_escape', { + value: 'ababab abb ab{2}', + expectedValue: 'ababab ab{2}', + expr: '%s/ab{2}//g', + noPcreExpr: '%s/ab\\{2}//g'}); +testSubstitute('ex_substitute_count', { + value: '1\n2\n3\n4', + expectedValue: '1\n0\n0\n4', + expr: 's/\\d/0/i 2'}); +testSubstitute('ex_substitute_count_with_range', { + value: '1\n2\n3\n4', + expectedValue: '1\n2\n0\n0', + expr: '1,3s/\\d/0/ 3'}); +testSubstitute('ex_substitute_not_global', { + value: 'aaa\nbaa\ncaa', + expectedValue: 'xaa\nbxa\ncxa', + expr: '%s/a/x/'}); +function testSubstituteConfirm(name, command, initialValue, expectedValue, keys, finalPos) { + testVim(name, function(cm, vim, helpers) { + var savedOpenDialog = cm.openDialog; + var savedKeyName = CodeMirror.keyName; + var onKeyDown; + var recordedCallback; + var closed = true; // Start out closed, set false on second openDialog. + function close() { + closed = true; + } + // First openDialog should save callback. + cm.openDialog = function(template, callback, options) { + recordedCallback = callback; + } + // Do first openDialog. + helpers.doKeys(':'); + // Second openDialog should save keyDown handler. + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + closed = false; + }; + // Return the command to Vim and trigger second openDialog. + recordedCallback(command); + // The event should really use keyCode, but here just mock it out and use + // key and replace keyName to just return key. + CodeMirror.keyName = function (e) { return e.key; } + keys = keys.toUpperCase(); + for (var i = 0; i < keys.length; i++) { + is(!closed); + onKeyDown({ key: keys.charAt(i) }, '', close); + } + try { + eq(expectedValue, cm.getValue()); + helpers.assertCursorAt(finalPos); + is(closed); + } catch(e) { + throw e + } finally { + // Restore overriden functions. + CodeMirror.keyName = savedKeyName; + cm.openDialog = savedOpenDialog; + } + }, { value: initialValue }); +}; +testSubstituteConfirm('ex_substitute_confirm_emptydoc', + '%s/x/b/c', '', '', '', makeCursor(0, 0)); +testSubstituteConfirm('ex_substitute_confirm_nomatch', + '%s/x/b/c', 'ba a\nbab', 'ba a\nbab', '', makeCursor(0, 0)); +testSubstituteConfirm('ex_substitute_confirm_accept', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'yyy', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_random_keys', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'ysdkywerty', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_some', + '%s/a/b/cg', 'ba a\nbab', 'bb a\nbbb', 'yny', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_all', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'a', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_accept_then_all', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'ya', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_quit', + '%s/a/b/cg', 'ba a\nbab', 'bb a\nbab', 'yq', makeCursor(0, 3)); +testSubstituteConfirm('ex_substitute_confirm_last', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3)); +testSubstituteConfirm('ex_substitute_confirm_oneline', + '1s/a/b/cg', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3)); +testSubstituteConfirm('ex_substitute_confirm_range_accept', + '1,2s/a/b/cg', 'aa\na \na\na', 'bb\nb \na\na', 'yyy', makeCursor(1, 0)); +testSubstituteConfirm('ex_substitute_confirm_range_some', + '1,3s/a/b/cg', 'aa\na \na\na', 'ba\nb \nb\na', 'ynyy', makeCursor(2, 0)); +testSubstituteConfirm('ex_substitute_confirm_range_all', + '1,3s/a/b/cg', 'aa\na \na\na', 'bb\nb \nb\na', 'a', makeCursor(2, 0)); +testSubstituteConfirm('ex_substitute_confirm_range_last', + '1,3s/a/b/cg', 'aa\na \na\na', 'bb\nb \na\na', 'yyl', makeCursor(1, 0)); +//:noh should clear highlighting of search-results but allow to resume search through n +testVim('ex_noh_clearSearchHighlight', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('?'); + helpers.doEx('noh'); + eq(vim.searchState_.getOverlay(),null,'match-highlighting wasn\'t cleared'); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 11,'can\'t resume search after clearing highlighting'); +}, { value: 'match nope match \n nope Match' }); +testVim('set_boolean', function(cm, vim, helpers) { + CodeMirror.Vim.defineOption('testoption', true, 'boolean'); + // Test default value is set. + is(CodeMirror.Vim.getOption('testoption')); + try { + // Test fail to set to non-boolean + CodeMirror.Vim.setOption('testoption', '5'); + fail(); + } catch (expected) {}; + // Test setOption + CodeMirror.Vim.setOption('testoption', false); + is(!CodeMirror.Vim.getOption('testoption')); +}); +testVim('ex_set_boolean', function(cm, vim, helpers) { + CodeMirror.Vim.defineOption('testoption', true, 'boolean'); + // Test default value is set. + is(CodeMirror.Vim.getOption('testoption')); + try { + // Test fail to set to non-boolean + helpers.doEx('set testoption=22'); + fail(); + } catch (expected) {}; + // Test setOption + helpers.doEx('set notestoption'); + is(!CodeMirror.Vim.getOption('testoption')); +}); +testVim('set_string', function(cm, vim, helpers) { + CodeMirror.Vim.defineOption('testoption', 'a', 'string'); + // Test default value is set. + eq('a', CodeMirror.Vim.getOption('testoption')); + try { + // Test fail to set non-string. + CodeMirror.Vim.setOption('testoption', true); + fail(); + } catch (expected) {}; + try { + // Test fail to set 'notestoption' + CodeMirror.Vim.setOption('notestoption', 'b'); + fail(); + } catch (expected) {}; + // Test setOption + CodeMirror.Vim.setOption('testoption', 'c'); + eq('c', CodeMirror.Vim.getOption('testoption')); +}); +testVim('ex_set_string', function(cm, vim, helpers) { + CodeMirror.Vim.defineOption('testopt', 'a', 'string'); + // Test default value is set. + eq('a', CodeMirror.Vim.getOption('testopt')); + try { + // Test fail to set 'notestopt' + helpers.doEx('set notestopt=b'); + fail(); + } catch (expected) {}; + // Test setOption + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt')); + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global + eq('c', CodeMirror.Vim.getOption('testopt')); // global + // Test setOption global + helpers.doEx('setg testopt=d') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); + // Test setOption local + helpers.doEx('setl testopt=e') + eq('e', CodeMirror.Vim.getOption('testopt', cm)); + eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); +}); +testVim('ex_set_callback', function(cm, vim, helpers) { + var global; + + function cb(val, cm, cfg) { + if (val === undefined) { + // Getter + if (cm) { + return cm._local; + } else { + return global; + } + } else { + // Setter + if (cm) { + cm._local = val; + } else { + global = val; + } + } + } + + CodeMirror.Vim.defineOption('testopt', 'a', 'string', cb); + // Test default value is set. + eq('a', CodeMirror.Vim.getOption('testopt')); + try { + // Test fail to set 'notestopt' + helpers.doEx('set notestopt=b'); + fail(); + } catch (expected) {}; + // Test setOption (Identical to the string tests, but via callback instead) + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global + eq('c', CodeMirror.Vim.getOption('testopt')); // global + // Test setOption global + helpers.doEx('setg testopt=d') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); + // Test setOption local + helpers.doEx('setl testopt=e') + eq('e', CodeMirror.Vim.getOption('testopt', cm)); + eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); +}) +testVim('ex_set_filetype', function(cm, vim, helpers) { + CodeMirror.defineMode('test_mode', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + CodeMirror.defineMode('test_mode_2', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + // Test mode is set. + helpers.doEx('set filetype=test_mode'); + eq('test_mode', cm.getMode().name); + // Test 'ft' alias also sets mode. + helpers.doEx('set ft=test_mode_2'); + eq('test_mode_2', cm.getMode().name); +}); +testVim('ex_set_filetype_null', function(cm, vim, helpers) { + CodeMirror.defineMode('test_mode', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + cm.setOption('mode', 'test_mode'); + // Test mode is set to null. + helpers.doEx('set filetype='); + eq('null', cm.getMode().name); +}); +// TODO: Reset key maps after each test. +testVim('ex_map_key2key', function(cm, vim, helpers) { + helpers.doEx('map a x'); + helpers.doKeys('a'); + helpers.assertCursorAt(0, 0); + eq('bc', cm.getValue()); +}, { value: 'abc' }); +testVim('ex_unmap_key2key', function(cm, vim, helpers) { + helpers.doEx('unmap a'); + helpers.doKeys('a'); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'abc' }); +testVim('ex_unmap_key2key_does_not_remove_default', function(cm, vim, helpers) { + try { + helpers.doEx('unmap a'); + fail(); + } catch (expected) {} + helpers.doKeys('a'); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'abc' }); +testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) { + helpers.doEx('map ; :'); + var dialogOpened = false; + cm.openDialog = function() { + dialogOpened = true; + } + helpers.doKeys(';'); + eq(dialogOpened, true); +}); +testVim('ex_map_ex2key:', function(cm, vim, helpers) { + helpers.doEx('map :del x'); + helpers.doEx('del'); + helpers.assertCursorAt(0, 0); + eq('bc', cm.getValue()); +}, { value: 'abc' }); +testVim('ex_map_ex2ex', function(cm, vim, helpers) { + helpers.doEx('map :del :w'); + var tmp = CodeMirror.commands.save; + var written = false; + var actualCm; + CodeMirror.commands.save = function(cm) { + written = true; + actualCm = cm; + }; + helpers.doEx('del'); + CodeMirror.commands.save = tmp; + eq(written, true); + eq(actualCm, cm); +}); +testVim('ex_map_key2ex', function(cm, vim, helpers) { + helpers.doEx('map a :w'); + var tmp = CodeMirror.commands.save; + var written = false; + var actualCm; + CodeMirror.commands.save = function(cm) { + written = true; + actualCm = cm; + }; + helpers.doKeys('a'); + CodeMirror.commands.save = tmp; + eq(written, true); + eq(actualCm, cm); +}); +testVim('ex_map_key2key_visual_api', function(cm, vim, helpers) { + CodeMirror.Vim.map('b', ':w', 'visual'); + var tmp = CodeMirror.commands.save; + var written = false; + var actualCm; + CodeMirror.commands.save = function(cm) { + written = true; + actualCm = cm; + }; + // Mapping should not work in normal mode. + helpers.doKeys('b'); + eq(written, false); + // Mapping should work in visual mode. + helpers.doKeys('v', 'b'); + eq(written, true); + eq(actualCm, cm); + + CodeMirror.commands.save = tmp; +}); +testVim('ex_imap', function(cm, vim, helpers) { + CodeMirror.Vim.map('jk', '', 'insert'); + helpers.doKeys('i'); + is(vim.insertMode); + helpers.doKeys('j', 'k'); + is(!vim.insertMode); +}); +testVim('ex_unmap_api', function(cm, vim, helpers) { + CodeMirror.Vim.map('', 'gg', 'normal'); + is(CodeMirror.Vim.handleKey(cm, "", "normal"), "Alt-X key is mapped"); + CodeMirror.Vim.unmap("", "normal"); + is(!CodeMirror.Vim.handleKey(cm, "", "normal"), "Alt-X key is unmapped"); +}); + +// Testing registration of functions as ex-commands and mapping to -keys +testVim('ex_api_test', function(cm, vim, helpers) { + var res=false; + var val='from'; + CodeMirror.Vim.defineEx('extest','ext',function(cm,params){ + if(params.args)val=params.args[0]; + else res=true; + }); + helpers.doEx(':ext to'); + eq(val,'to','Defining ex-command failed'); + CodeMirror.Vim.map('',':ext'); + helpers.doKeys('',''); + is(res,'Mapping to key failed'); +}); +// For now, this test needs to be last because it messes up : for future tests. +testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) { + helpers.doEx('map : x'); + helpers.doKeys(':'); + helpers.assertCursorAt(0, 0); + eq('bc', cm.getValue()); +}, { value: 'abc' }); + +// Test event handlers +testVim('beforeSelectionChange', function(cm, vim, helpers) { + cm.setCursor(0, 100); + eqPos(cm.getCursor('head'), cm.getCursor('anchor')); +}, { value: 'abc' }); + + diff --git a/www/code/codemirror-5.13.2/theme/3024-day.css b/www/code/codemirror-5.13.2/theme/3024-day.css new file mode 100644 index 000000000..713265530 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/3024-day.css @@ -0,0 +1,41 @@ +/* + + Name: 3024 day + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-3024-day.CodeMirror { background: #f7f7f7; color: #3a3432; } +.cm-s-3024-day div.CodeMirror-selected { background: #d6d5d4; } + +.cm-s-3024-day .CodeMirror-line::selection, .cm-s-3024-day .CodeMirror-line > span::selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d6d5d4; } +.cm-s-3024-day .CodeMirror-line::-moz-selection, .cm-s-3024-day .CodeMirror-line > span::-moz-selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d9d9d9; } + +.cm-s-3024-day .CodeMirror-gutters { background: #f7f7f7; border-right: 0px; } +.cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; } +.cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; } +.cm-s-3024-day .CodeMirror-linenumber { color: #807d7c; } + +.cm-s-3024-day .CodeMirror-cursor { border-left: 1px solid #5c5855; } + +.cm-s-3024-day span.cm-comment { color: #cdab53; } +.cm-s-3024-day span.cm-atom { color: #a16a94; } +.cm-s-3024-day span.cm-number { color: #a16a94; } + +.cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute { color: #01a252; } +.cm-s-3024-day span.cm-keyword { color: #db2d20; } +.cm-s-3024-day span.cm-string { color: #fded02; } + +.cm-s-3024-day span.cm-variable { color: #01a252; } +.cm-s-3024-day span.cm-variable-2 { color: #01a0e4; } +.cm-s-3024-day span.cm-def { color: #e8bbd0; } +.cm-s-3024-day span.cm-bracket { color: #3a3432; } +.cm-s-3024-day span.cm-tag { color: #db2d20; } +.cm-s-3024-day span.cm-link { color: #a16a94; } +.cm-s-3024-day span.cm-error { background: #db2d20; color: #5c5855; } + +.cm-s-3024-day .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important; } diff --git a/www/code/codemirror-5.13.2/theme/3024-night.css b/www/code/codemirror-5.13.2/theme/3024-night.css new file mode 100644 index 000000000..adc5900ad --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/3024-night.css @@ -0,0 +1,39 @@ +/* + + Name: 3024 night + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-3024-night.CodeMirror { background: #090300; color: #d6d5d4; } +.cm-s-3024-night div.CodeMirror-selected { background: #3a3432; } +.cm-s-3024-night .CodeMirror-line::selection, .cm-s-3024-night .CodeMirror-line > span::selection, .cm-s-3024-night .CodeMirror-line > span > span::selection { background: rgba(58, 52, 50, .99); } +.cm-s-3024-night .CodeMirror-line::-moz-selection, .cm-s-3024-night .CodeMirror-line > span::-moz-selection, .cm-s-3024-night .CodeMirror-line > span > span::-moz-selection { background: rgba(58, 52, 50, .99); } +.cm-s-3024-night .CodeMirror-gutters { background: #090300; border-right: 0px; } +.cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; } +.cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; } +.cm-s-3024-night .CodeMirror-linenumber { color: #5c5855; } + +.cm-s-3024-night .CodeMirror-cursor { border-left: 1px solid #807d7c; } + +.cm-s-3024-night span.cm-comment { color: #cdab53; } +.cm-s-3024-night span.cm-atom { color: #a16a94; } +.cm-s-3024-night span.cm-number { color: #a16a94; } + +.cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute { color: #01a252; } +.cm-s-3024-night span.cm-keyword { color: #db2d20; } +.cm-s-3024-night span.cm-string { color: #fded02; } + +.cm-s-3024-night span.cm-variable { color: #01a252; } +.cm-s-3024-night span.cm-variable-2 { color: #01a0e4; } +.cm-s-3024-night span.cm-def { color: #e8bbd0; } +.cm-s-3024-night span.cm-bracket { color: #d6d5d4; } +.cm-s-3024-night span.cm-tag { color: #db2d20; } +.cm-s-3024-night span.cm-link { color: #a16a94; } +.cm-s-3024-night span.cm-error { background: #db2d20; color: #807d7c; } + +.cm-s-3024-night .CodeMirror-activeline-background { background: #2F2F2F; } +.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/www/code/codemirror-5.13.2/theme/abcdef.css b/www/code/codemirror-5.13.2/theme/abcdef.css new file mode 100644 index 000000000..7f9d78870 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/abcdef.css @@ -0,0 +1,32 @@ +.cm-s-abcdef.CodeMirror { background: #0f0f0f; color: #defdef; } +.cm-s-abcdef div.CodeMirror-selected { background: #515151; } +.cm-s-abcdef .CodeMirror-line::selection, .cm-s-abcdef .CodeMirror-line > span::selection, .cm-s-abcdef .CodeMirror-line > span > span::selection { background: rgba(56, 56, 56, 0.99); } +.cm-s-abcdef .CodeMirror-line::-moz-selection, .cm-s-abcdef .CodeMirror-line > span::-moz-selection, .cm-s-abcdef .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 56, 56, 0.99); } +.cm-s-abcdef .CodeMirror-gutters { background: #555; border-right: 2px solid #314151; } +.cm-s-abcdef .CodeMirror-guttermarker { color: #222; } +.cm-s-abcdef .CodeMirror-guttermarker-subtle { color: azure; } +.cm-s-abcdef .CodeMirror-linenumber { color: #FFFFFF; } +.cm-s-abcdef .CodeMirror-cursor { border-left: 1px solid #00FF00; } + +.cm-s-abcdef span.cm-keyword { color: darkgoldenrod; font-weight: bold; } +.cm-s-abcdef span.cm-atom { color: #77F; } +.cm-s-abcdef span.cm-number { color: violet; } +.cm-s-abcdef span.cm-def { color: #fffabc; } +.cm-s-abcdef span.cm-variable { color: #abcdef; } +.cm-s-abcdef span.cm-variable-2 { color: #cacbcc; } +.cm-s-abcdef span.cm-variable-3 { color: #def; } +.cm-s-abcdef span.cm-property { color: #fedcba; } +.cm-s-abcdef span.cm-operator { color: #ff0; } +.cm-s-abcdef span.cm-comment { color: #7a7b7c; font-style: italic;} +.cm-s-abcdef span.cm-string { color: #2b4; } +.cm-s-abcdef span.cm-meta { color: #C9F; } +.cm-s-abcdef span.cm-qualifier { color: #FFF700; } +.cm-s-abcdef span.cm-builtin { color: #30aabc; } +.cm-s-abcdef span.cm-bracket { color: #8a8a8a; } +.cm-s-abcdef span.cm-tag { color: #FFDD44; } +.cm-s-abcdef span.cm-attribute { color: #DDFF00; } +.cm-s-abcdef span.cm-error { color: #FF0000; } +.cm-s-abcdef span.cm-header { color: aquamarine; font-weight: bold; } +.cm-s-abcdef span.cm-link { color: blueviolet; } + +.cm-s-abcdef .CodeMirror-activeline-background { background: #314151; } diff --git a/www/code/codemirror-5.13.2/theme/ambiance-mobile.css b/www/code/codemirror-5.13.2/theme/ambiance-mobile.css new file mode 100644 index 000000000..88d332e1a --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/ambiance-mobile.css @@ -0,0 +1,5 @@ +.cm-s-ambiance.CodeMirror { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} diff --git a/www/code/codemirror-5.13.2/theme/ambiance.css b/www/code/codemirror-5.13.2/theme/ambiance.css new file mode 100644 index 000000000..bce344649 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/ambiance.css @@ -0,0 +1,74 @@ +/* ambiance theme for codemirror */ + +/* Color scheme */ + +.cm-s-ambiance .cm-header { color: blue; } +.cm-s-ambiance .cm-quote { color: #24C2C7; } + +.cm-s-ambiance .cm-keyword { color: #cda869; } +.cm-s-ambiance .cm-atom { color: #CF7EA9; } +.cm-s-ambiance .cm-number { color: #78CF8A; } +.cm-s-ambiance .cm-def { color: #aac6e3; } +.cm-s-ambiance .cm-variable { color: #ffb795; } +.cm-s-ambiance .cm-variable-2 { color: #eed1b3; } +.cm-s-ambiance .cm-variable-3 { color: #faded3; } +.cm-s-ambiance .cm-property { color: #eed1b3; } +.cm-s-ambiance .cm-operator { color: #fa8d6a; } +.cm-s-ambiance .cm-comment { color: #555; font-style:italic; } +.cm-s-ambiance .cm-string { color: #8f9d6a; } +.cm-s-ambiance .cm-string-2 { color: #9d937c; } +.cm-s-ambiance .cm-meta { color: #D2A8A1; } +.cm-s-ambiance .cm-qualifier { color: yellow; } +.cm-s-ambiance .cm-builtin { color: #9999cc; } +.cm-s-ambiance .cm-bracket { color: #24C2C7; } +.cm-s-ambiance .cm-tag { color: #fee4ff; } +.cm-s-ambiance .cm-attribute { color: #9B859D; } +.cm-s-ambiance .cm-hr { color: pink; } +.cm-s-ambiance .cm-link { color: #F4C20B; } +.cm-s-ambiance .cm-special { color: #FF9D00; } +.cm-s-ambiance .cm-error { color: #AF2018; } + +.cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0; } +.cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22; } + +.cm-s-ambiance div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); } +.cm-s-ambiance.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } +.cm-s-ambiance .CodeMirror-line::selection, .cm-s-ambiance .CodeMirror-line > span::selection, .cm-s-ambiance .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-ambiance .CodeMirror-line::-moz-selection, .cm-s-ambiance .CodeMirror-line > span::-moz-selection, .cm-s-ambiance .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } + +/* Editor styling */ + +.cm-s-ambiance.CodeMirror { + line-height: 1.40em; + color: #E6E1DC; + background-color: #202020; + -webkit-box-shadow: inset 0 0 10px black; + -moz-box-shadow: inset 0 0 10px black; + box-shadow: inset 0 0 10px black; +} + +.cm-s-ambiance .CodeMirror-gutters { + background: #3D3D3D; + border-right: 1px solid #4D4D4D; + box-shadow: 0 10px 20px black; +} + +.cm-s-ambiance .CodeMirror-linenumber { + text-shadow: 0px 1px 1px #4d4d4d; + color: #111; + padding: 0 5px; +} + +.cm-s-ambiance .CodeMirror-guttermarker { color: #aaa; } +.cm-s-ambiance .CodeMirror-guttermarker-subtle { color: #111; } + +.cm-s-ambiance .CodeMirror-cursor { border-left: 1px solid #7991E8; } + +.cm-s-ambiance .CodeMirror-activeline-background { + background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031); +} + +.cm-s-ambiance.CodeMirror, +.cm-s-ambiance .CodeMirror-gutters { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC"); +} diff --git a/www/code/codemirror-5.13.2/theme/base16-dark.css b/www/code/codemirror-5.13.2/theme/base16-dark.css new file mode 100644 index 000000000..026a81689 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/base16-dark.css @@ -0,0 +1,38 @@ +/* + + Name: Base16 Default Dark + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-base16-dark.CodeMirror { background: #151515; color: #e0e0e0; } +.cm-s-base16-dark div.CodeMirror-selected { background: #303030; } +.cm-s-base16-dark .CodeMirror-line::selection, .cm-s-base16-dark .CodeMirror-line > span::selection, .cm-s-base16-dark .CodeMirror-line > span > span::selection { background: rgba(48, 48, 48, .99); } +.cm-s-base16-dark .CodeMirror-line::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(48, 48, 48, .99); } +.cm-s-base16-dark .CodeMirror-gutters { background: #151515; border-right: 0px; } +.cm-s-base16-dark .CodeMirror-guttermarker { color: #ac4142; } +.cm-s-base16-dark .CodeMirror-guttermarker-subtle { color: #505050; } +.cm-s-base16-dark .CodeMirror-linenumber { color: #505050; } +.cm-s-base16-dark .CodeMirror-cursor { border-left: 1px solid #b0b0b0; } + +.cm-s-base16-dark span.cm-comment { color: #8f5536; } +.cm-s-base16-dark span.cm-atom { color: #aa759f; } +.cm-s-base16-dark span.cm-number { color: #aa759f; } + +.cm-s-base16-dark span.cm-property, .cm-s-base16-dark span.cm-attribute { color: #90a959; } +.cm-s-base16-dark span.cm-keyword { color: #ac4142; } +.cm-s-base16-dark span.cm-string { color: #f4bf75; } + +.cm-s-base16-dark span.cm-variable { color: #90a959; } +.cm-s-base16-dark span.cm-variable-2 { color: #6a9fb5; } +.cm-s-base16-dark span.cm-def { color: #d28445; } +.cm-s-base16-dark span.cm-bracket { color: #e0e0e0; } +.cm-s-base16-dark span.cm-tag { color: #ac4142; } +.cm-s-base16-dark span.cm-link { color: #aa759f; } +.cm-s-base16-dark span.cm-error { background: #ac4142; color: #b0b0b0; } + +.cm-s-base16-dark .CodeMirror-activeline-background { background: #202020; } +.cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/www/code/codemirror-5.13.2/theme/base16-light.css b/www/code/codemirror-5.13.2/theme/base16-light.css new file mode 100644 index 000000000..474e0ca9d --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/base16-light.css @@ -0,0 +1,38 @@ +/* + + Name: Base16 Default Light + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-base16-light.CodeMirror { background: #f5f5f5; color: #202020; } +.cm-s-base16-light div.CodeMirror-selected { background: #e0e0e0; } +.cm-s-base16-light .CodeMirror-line::selection, .cm-s-base16-light .CodeMirror-line > span::selection, .cm-s-base16-light .CodeMirror-line > span > span::selection { background: #e0e0e0; } +.cm-s-base16-light .CodeMirror-line::-moz-selection, .cm-s-base16-light .CodeMirror-line > span::-moz-selection, .cm-s-base16-light .CodeMirror-line > span > span::-moz-selection { background: #e0e0e0; } +.cm-s-base16-light .CodeMirror-gutters { background: #f5f5f5; border-right: 0px; } +.cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; } +.cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; } +.cm-s-base16-light .CodeMirror-linenumber { color: #b0b0b0; } +.cm-s-base16-light .CodeMirror-cursor { border-left: 1px solid #505050; } + +.cm-s-base16-light span.cm-comment { color: #8f5536; } +.cm-s-base16-light span.cm-atom { color: #aa759f; } +.cm-s-base16-light span.cm-number { color: #aa759f; } + +.cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute { color: #90a959; } +.cm-s-base16-light span.cm-keyword { color: #ac4142; } +.cm-s-base16-light span.cm-string { color: #f4bf75; } + +.cm-s-base16-light span.cm-variable { color: #90a959; } +.cm-s-base16-light span.cm-variable-2 { color: #6a9fb5; } +.cm-s-base16-light span.cm-def { color: #d28445; } +.cm-s-base16-light span.cm-bracket { color: #202020; } +.cm-s-base16-light span.cm-tag { color: #ac4142; } +.cm-s-base16-light span.cm-link { color: #aa759f; } +.cm-s-base16-light span.cm-error { background: #ac4142; color: #505050; } + +.cm-s-base16-light .CodeMirror-activeline-background { background: #DDDCDC; } +.cm-s-base16-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/www/code/codemirror-5.13.2/theme/bespin.css b/www/code/codemirror-5.13.2/theme/bespin.css new file mode 100644 index 000000000..60913ba93 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/bespin.css @@ -0,0 +1,34 @@ +/* + + Name: Bespin + Author: Mozilla / Jan T. Sott + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-bespin.CodeMirror {background: #28211c; color: #9d9b97;} +.cm-s-bespin div.CodeMirror-selected {background: #36312e !important;} +.cm-s-bespin .CodeMirror-gutters {background: #28211c; border-right: 0px;} +.cm-s-bespin .CodeMirror-linenumber {color: #666666;} +.cm-s-bespin .CodeMirror-cursor {border-left: 1px solid #797977 !important;} + +.cm-s-bespin span.cm-comment {color: #937121;} +.cm-s-bespin span.cm-atom {color: #9b859d;} +.cm-s-bespin span.cm-number {color: #9b859d;} + +.cm-s-bespin span.cm-property, .cm-s-bespin span.cm-attribute {color: #54be0d;} +.cm-s-bespin span.cm-keyword {color: #cf6a4c;} +.cm-s-bespin span.cm-string {color: #f9ee98;} + +.cm-s-bespin span.cm-variable {color: #54be0d;} +.cm-s-bespin span.cm-variable-2 {color: #5ea6ea;} +.cm-s-bespin span.cm-def {color: #cf7d34;} +.cm-s-bespin span.cm-error {background: #cf6a4c; color: #797977;} +.cm-s-bespin span.cm-bracket {color: #9d9b97;} +.cm-s-bespin span.cm-tag {color: #cf6a4c;} +.cm-s-bespin span.cm-link {color: #9b859d;} + +.cm-s-bespin .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} +.cm-s-bespin .CodeMirror-activeline-background { background: #404040; } diff --git a/www/code/codemirror-5.13.2/theme/blackboard.css b/www/code/codemirror-5.13.2/theme/blackboard.css new file mode 100644 index 000000000..b6eaedb18 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/blackboard.css @@ -0,0 +1,32 @@ +/* Port of TextMate's Blackboard theme */ + +.cm-s-blackboard.CodeMirror { background: #0C1021; color: #F8F8F8; } +.cm-s-blackboard div.CodeMirror-selected { background: #253B76; } +.cm-s-blackboard .CodeMirror-line::selection, .cm-s-blackboard .CodeMirror-line > span::selection, .cm-s-blackboard .CodeMirror-line > span > span::selection { background: rgba(37, 59, 118, .99); } +.cm-s-blackboard .CodeMirror-line::-moz-selection, .cm-s-blackboard .CodeMirror-line > span::-moz-selection, .cm-s-blackboard .CodeMirror-line > span > span::-moz-selection { background: rgba(37, 59, 118, .99); } +.cm-s-blackboard .CodeMirror-gutters { background: #0C1021; border-right: 0; } +.cm-s-blackboard .CodeMirror-guttermarker { color: #FBDE2D; } +.cm-s-blackboard .CodeMirror-guttermarker-subtle { color: #888; } +.cm-s-blackboard .CodeMirror-linenumber { color: #888; } +.cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7; } + +.cm-s-blackboard .cm-keyword { color: #FBDE2D; } +.cm-s-blackboard .cm-atom { color: #D8FA3C; } +.cm-s-blackboard .cm-number { color: #D8FA3C; } +.cm-s-blackboard .cm-def { color: #8DA6CE; } +.cm-s-blackboard .cm-variable { color: #FF6400; } +.cm-s-blackboard .cm-operator { color: #FBDE2D; } +.cm-s-blackboard .cm-comment { color: #AEAEAE; } +.cm-s-blackboard .cm-string { color: #61CE3C; } +.cm-s-blackboard .cm-string-2 { color: #61CE3C; } +.cm-s-blackboard .cm-meta { color: #D8FA3C; } +.cm-s-blackboard .cm-builtin { color: #8DA6CE; } +.cm-s-blackboard .cm-tag { color: #8DA6CE; } +.cm-s-blackboard .cm-attribute { color: #8DA6CE; } +.cm-s-blackboard .cm-header { color: #FF6400; } +.cm-s-blackboard .cm-hr { color: #AEAEAE; } +.cm-s-blackboard .cm-link { color: #8DA6CE; } +.cm-s-blackboard .cm-error { background: #9D1E15; color: #F8F8F8; } + +.cm-s-blackboard .CodeMirror-activeline-background { background: #3C3636; } +.cm-s-blackboard .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; } diff --git a/www/code/codemirror-5.13.2/theme/cobalt.css b/www/code/codemirror-5.13.2/theme/cobalt.css new file mode 100644 index 000000000..d88223ed8 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/cobalt.css @@ -0,0 +1,25 @@ +.cm-s-cobalt.CodeMirror { background: #002240; color: white; } +.cm-s-cobalt div.CodeMirror-selected { background: #b36539; } +.cm-s-cobalt .CodeMirror-line::selection, .cm-s-cobalt .CodeMirror-line > span::selection, .cm-s-cobalt .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); } +.cm-s-cobalt .CodeMirror-line::-moz-selection, .cm-s-cobalt .CodeMirror-line > span::-moz-selection, .cm-s-cobalt .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); } +.cm-s-cobalt .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-cobalt .CodeMirror-guttermarker { color: #ffee80; } +.cm-s-cobalt .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-cobalt span.cm-comment { color: #08f; } +.cm-s-cobalt span.cm-atom { color: #845dc4; } +.cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; } +.cm-s-cobalt span.cm-keyword { color: #ffee80; } +.cm-s-cobalt span.cm-string { color: #3ad900; } +.cm-s-cobalt span.cm-meta { color: #ff9d00; } +.cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; } +.cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def { color: white; } +.cm-s-cobalt span.cm-bracket { color: #d8d8d8; } +.cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; } +.cm-s-cobalt span.cm-link { color: #845dc4; } +.cm-s-cobalt span.cm-error { color: #9d1e15; } + +.cm-s-cobalt .CodeMirror-activeline-background { background: #002D57; } +.cm-s-cobalt .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; } diff --git a/www/code/codemirror-5.13.2/theme/colorforth.css b/www/code/codemirror-5.13.2/theme/colorforth.css new file mode 100644 index 000000000..606899f30 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/colorforth.css @@ -0,0 +1,33 @@ +.cm-s-colorforth.CodeMirror { background: #000000; color: #f8f8f8; } +.cm-s-colorforth .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } +.cm-s-colorforth .CodeMirror-guttermarker { color: #FFBD40; } +.cm-s-colorforth .CodeMirror-guttermarker-subtle { color: #78846f; } +.cm-s-colorforth .CodeMirror-linenumber { color: #bababa; } +.cm-s-colorforth .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-colorforth span.cm-comment { color: #ededed; } +.cm-s-colorforth span.cm-def { color: #ff1c1c; font-weight:bold; } +.cm-s-colorforth span.cm-keyword { color: #ffd900; } +.cm-s-colorforth span.cm-builtin { color: #00d95a; } +.cm-s-colorforth span.cm-variable { color: #73ff00; } +.cm-s-colorforth span.cm-string { color: #007bff; } +.cm-s-colorforth span.cm-number { color: #00c4ff; } +.cm-s-colorforth span.cm-atom { color: #606060; } + +.cm-s-colorforth span.cm-variable-2 { color: #EEE; } +.cm-s-colorforth span.cm-variable-3 { color: #DDD; } +.cm-s-colorforth span.cm-property {} +.cm-s-colorforth span.cm-operator {} + +.cm-s-colorforth span.cm-meta { color: yellow; } +.cm-s-colorforth span.cm-qualifier { color: #FFF700; } +.cm-s-colorforth span.cm-bracket { color: #cc7; } +.cm-s-colorforth span.cm-tag { color: #FFBD40; } +.cm-s-colorforth span.cm-attribute { color: #FFF700; } +.cm-s-colorforth span.cm-error { color: #f00; } + +.cm-s-colorforth div.CodeMirror-selected { background: #333d53; } + +.cm-s-colorforth span.cm-compilation { background: rgba(255, 255, 255, 0.12); } + +.cm-s-colorforth .CodeMirror-activeline-background { background: #253540; } diff --git a/www/code/codemirror-5.13.2/theme/dracula.css b/www/code/codemirror-5.13.2/theme/dracula.css new file mode 100644 index 000000000..57f979ae6 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/dracula.css @@ -0,0 +1,41 @@ +/* + + Name: dracula + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) + +*/ + + +.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters { + background-color: #282a36 !important; + color: #f8f8f2 !important; + border: none; +} +.cm-s-dracula .CodeMirror-gutters { color: #282a36; } +.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; } +.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; } +.cm-s-dracula.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } +.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-dracula span.cm-comment { color: #6272a4; } +.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; } +.cm-s-dracula span.cm-number { color: #bd93f9; } +.cm-s-dracula span.cm-variable { color: #50fa7b; } +.cm-s-dracula span.cm-variable-2 { color: white; } +.cm-s-dracula span.cm-def { color: #ffb86c; } +.cm-s-dracula span.cm-keyword { color: #ff79c6; } +.cm-s-dracula span.cm-operator { color: #ff79c6; } +.cm-s-dracula span.cm-keyword { color: #ff79c6; } +.cm-s-dracula span.cm-atom { color: #bd93f9; } +.cm-s-dracula span.cm-meta { color: #f8f8f2; } +.cm-s-dracula span.cm-tag { color: #ff79c6; } +.cm-s-dracula span.cm-attribute { color: #50fa7b; } +.cm-s-dracula span.cm-qualifier { color: #50fa7b; } +.cm-s-dracula span.cm-property { color: #66d9ef; } +.cm-s-dracula span.cm-builtin { color: #50fa7b; } +.cm-s-dracula span.cm-variable-3 { color: #50fa7b; } + +.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); } +.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/www/code/codemirror-5.13.2/theme/eclipse.css b/www/code/codemirror-5.13.2/theme/eclipse.css new file mode 100644 index 000000000..1bde460e9 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/eclipse.css @@ -0,0 +1,23 @@ +.cm-s-eclipse span.cm-meta { color: #FF1717; } +.cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; } +.cm-s-eclipse span.cm-atom { color: #219; } +.cm-s-eclipse span.cm-number { color: #164; } +.cm-s-eclipse span.cm-def { color: #00f; } +.cm-s-eclipse span.cm-variable { color: black; } +.cm-s-eclipse span.cm-variable-2 { color: #0000C0; } +.cm-s-eclipse span.cm-variable-3 { color: #0000C0; } +.cm-s-eclipse span.cm-property { color: black; } +.cm-s-eclipse span.cm-operator { color: black; } +.cm-s-eclipse span.cm-comment { color: #3F7F5F; } +.cm-s-eclipse span.cm-string { color: #2A00FF; } +.cm-s-eclipse span.cm-string-2 { color: #f50; } +.cm-s-eclipse span.cm-qualifier { color: #555; } +.cm-s-eclipse span.cm-builtin { color: #30a; } +.cm-s-eclipse span.cm-bracket { color: #cc7; } +.cm-s-eclipse span.cm-tag { color: #170; } +.cm-s-eclipse span.cm-attribute { color: #00c; } +.cm-s-eclipse span.cm-link { color: #219; } +.cm-s-eclipse span.cm-error { color: #f00; } + +.cm-s-eclipse .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-eclipse .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } diff --git a/www/code/codemirror-5.13.2/theme/elegant.css b/www/code/codemirror-5.13.2/theme/elegant.css new file mode 100644 index 000000000..45b3ea655 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/elegant.css @@ -0,0 +1,13 @@ +.cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom { color: #762; } +.cm-s-elegant span.cm-comment { color: #262; font-style: italic; line-height: 1em; } +.cm-s-elegant span.cm-meta { color: #555; font-style: italic; line-height: 1em; } +.cm-s-elegant span.cm-variable { color: black; } +.cm-s-elegant span.cm-variable-2 { color: #b11; } +.cm-s-elegant span.cm-qualifier { color: #555; } +.cm-s-elegant span.cm-keyword { color: #730; } +.cm-s-elegant span.cm-builtin { color: #30a; } +.cm-s-elegant span.cm-link { color: #762; } +.cm-s-elegant span.cm-error { background-color: #fdd; } + +.cm-s-elegant .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-elegant .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } diff --git a/www/code/codemirror-5.13.2/theme/erlang-dark.css b/www/code/codemirror-5.13.2/theme/erlang-dark.css new file mode 100644 index 000000000..65fe4814c --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/erlang-dark.css @@ -0,0 +1,34 @@ +.cm-s-erlang-dark.CodeMirror { background: #002240; color: white; } +.cm-s-erlang-dark div.CodeMirror-selected { background: #b36539; } +.cm-s-erlang-dark .CodeMirror-line::selection, .cm-s-erlang-dark .CodeMirror-line > span::selection, .cm-s-erlang-dark .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); } +.cm-s-erlang-dark .CodeMirror-line::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); } +.cm-s-erlang-dark .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-erlang-dark .CodeMirror-guttermarker { color: white; } +.cm-s-erlang-dark .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-erlang-dark .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-erlang-dark .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-erlang-dark span.cm-quote { color: #ccc; } +.cm-s-erlang-dark span.cm-atom { color: #f133f1; } +.cm-s-erlang-dark span.cm-attribute { color: #ff80e1; } +.cm-s-erlang-dark span.cm-bracket { color: #ff9d00; } +.cm-s-erlang-dark span.cm-builtin { color: #eaa; } +.cm-s-erlang-dark span.cm-comment { color: #77f; } +.cm-s-erlang-dark span.cm-def { color: #e7a; } +.cm-s-erlang-dark span.cm-keyword { color: #ffee80; } +.cm-s-erlang-dark span.cm-meta { color: #50fefe; } +.cm-s-erlang-dark span.cm-number { color: #ffd0d0; } +.cm-s-erlang-dark span.cm-operator { color: #d55; } +.cm-s-erlang-dark span.cm-property { color: #ccc; } +.cm-s-erlang-dark span.cm-qualifier { color: #ccc; } +.cm-s-erlang-dark span.cm-special { color: #ffbbbb; } +.cm-s-erlang-dark span.cm-string { color: #3ad900; } +.cm-s-erlang-dark span.cm-string-2 { color: #ccc; } +.cm-s-erlang-dark span.cm-tag { color: #9effff; } +.cm-s-erlang-dark span.cm-variable { color: #50fe50; } +.cm-s-erlang-dark span.cm-variable-2 { color: #e0e; } +.cm-s-erlang-dark span.cm-variable-3 { color: #ccc; } +.cm-s-erlang-dark span.cm-error { color: #9d1e15; } + +.cm-s-erlang-dark .CodeMirror-activeline-background { background: #013461; } +.cm-s-erlang-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/www/code/codemirror-5.13.2/theme/hopscotch.css b/www/code/codemirror-5.13.2/theme/hopscotch.css new file mode 100644 index 000000000..7d05431bd --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/hopscotch.css @@ -0,0 +1,34 @@ +/* + + Name: Hopscotch + Author: Jan T. Sott + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-hopscotch.CodeMirror {background: #322931; color: #d5d3d5;} +.cm-s-hopscotch div.CodeMirror-selected {background: #433b42 !important;} +.cm-s-hopscotch .CodeMirror-gutters {background: #322931; border-right: 0px;} +.cm-s-hopscotch .CodeMirror-linenumber {color: #797379;} +.cm-s-hopscotch .CodeMirror-cursor {border-left: 1px solid #989498 !important;} + +.cm-s-hopscotch span.cm-comment {color: #b33508;} +.cm-s-hopscotch span.cm-atom {color: #c85e7c;} +.cm-s-hopscotch span.cm-number {color: #c85e7c;} + +.cm-s-hopscotch span.cm-property, .cm-s-hopscotch span.cm-attribute {color: #8fc13e;} +.cm-s-hopscotch span.cm-keyword {color: #dd464c;} +.cm-s-hopscotch span.cm-string {color: #fdcc59;} + +.cm-s-hopscotch span.cm-variable {color: #8fc13e;} +.cm-s-hopscotch span.cm-variable-2 {color: #1290bf;} +.cm-s-hopscotch span.cm-def {color: #fd8b19;} +.cm-s-hopscotch span.cm-error {background: #dd464c; color: #989498;} +.cm-s-hopscotch span.cm-bracket {color: #d5d3d5;} +.cm-s-hopscotch span.cm-tag {color: #dd464c;} +.cm-s-hopscotch span.cm-link {color: #c85e7c;} + +.cm-s-hopscotch .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} +.cm-s-hopscotch .CodeMirror-activeline-background { background: #302020; } diff --git a/www/code/codemirror-5.13.2/theme/icecoder.css b/www/code/codemirror-5.13.2/theme/icecoder.css new file mode 100644 index 000000000..d70d26e82 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/icecoder.css @@ -0,0 +1,43 @@ +/* +ICEcoder default theme by Matt Pass, used in code editor available at https://icecoder.net +*/ + +.cm-s-icecoder { color: #666; background: #141612; } + +.cm-s-icecoder span.cm-keyword { color: #eee; font-weight:bold; } /* off-white 1 */ +.cm-s-icecoder span.cm-atom { color: #e1c76e; } /* yellow */ +.cm-s-icecoder span.cm-number { color: #6cb5d9; } /* blue */ +.cm-s-icecoder span.cm-def { color: #b9ca4a; } /* green */ + +.cm-s-icecoder span.cm-variable { color: #6cb5d9; } /* blue */ +.cm-s-icecoder span.cm-variable-2 { color: #cc1e5c; } /* pink */ +.cm-s-icecoder span.cm-variable-3 { color: #f9602c; } /* orange */ + +.cm-s-icecoder span.cm-property { color: #eee; } /* off-white 1 */ +.cm-s-icecoder span.cm-operator { color: #9179bb; } /* purple */ +.cm-s-icecoder span.cm-comment { color: #97a3aa; } /* grey-blue */ + +.cm-s-icecoder span.cm-string { color: #b9ca4a; } /* green */ +.cm-s-icecoder span.cm-string-2 { color: #6cb5d9; } /* blue */ + +.cm-s-icecoder span.cm-meta { color: #555; } /* grey */ + +.cm-s-icecoder span.cm-qualifier { color: #555; } /* grey */ +.cm-s-icecoder span.cm-builtin { color: #214e7b; } /* bright blue */ +.cm-s-icecoder span.cm-bracket { color: #cc7; } /* grey-yellow */ + +.cm-s-icecoder span.cm-tag { color: #e8e8e8; } /* off-white 2 */ +.cm-s-icecoder span.cm-attribute { color: #099; } /* teal */ + +.cm-s-icecoder span.cm-header { color: #6a0d6a; } /* purple-pink */ +.cm-s-icecoder span.cm-quote { color: #186718; } /* dark green */ +.cm-s-icecoder span.cm-hr { color: #888; } /* mid-grey */ +.cm-s-icecoder span.cm-link { color: #e1c76e; } /* yellow */ +.cm-s-icecoder span.cm-error { color: #d00; } /* red */ + +.cm-s-icecoder .CodeMirror-cursor { border-left: 1px solid white; } +.cm-s-icecoder div.CodeMirror-selected { color: #fff; background: #037; } +.cm-s-icecoder .CodeMirror-gutters { background: #141612; min-width: 41px; border-right: 0; } +.cm-s-icecoder .CodeMirror-linenumber { color: #555; cursor: default; } +.cm-s-icecoder .CodeMirror-matchingbracket { border: 1px solid grey; color: black !important; } +.cm-s-icecoder .CodeMirror-activeline-background { background: #000; } \ No newline at end of file diff --git a/www/code/codemirror-5.13.2/theme/isotope.css b/www/code/codemirror-5.13.2/theme/isotope.css new file mode 100644 index 000000000..d0d6263cf --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/isotope.css @@ -0,0 +1,34 @@ +/* + + Name: Isotope + Author: David Desandro / Jan T. Sott + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-isotope.CodeMirror {background: #000000; color: #e0e0e0;} +.cm-s-isotope div.CodeMirror-selected {background: #404040 !important;} +.cm-s-isotope .CodeMirror-gutters {background: #000000; border-right: 0px;} +.cm-s-isotope .CodeMirror-linenumber {color: #808080;} +.cm-s-isotope .CodeMirror-cursor {border-left: 1px solid #c0c0c0 !important;} + +.cm-s-isotope span.cm-comment {color: #3300ff;} +.cm-s-isotope span.cm-atom {color: #cc00ff;} +.cm-s-isotope span.cm-number {color: #cc00ff;} + +.cm-s-isotope span.cm-property, .cm-s-isotope span.cm-attribute {color: #33ff00;} +.cm-s-isotope span.cm-keyword {color: #ff0000;} +.cm-s-isotope span.cm-string {color: #ff0099;} + +.cm-s-isotope span.cm-variable {color: #33ff00;} +.cm-s-isotope span.cm-variable-2 {color: #0066ff;} +.cm-s-isotope span.cm-def {color: #ff9900;} +.cm-s-isotope span.cm-error {background: #ff0000; color: #c0c0c0;} +.cm-s-isotope span.cm-bracket {color: #e0e0e0;} +.cm-s-isotope span.cm-tag {color: #ff0000;} +.cm-s-isotope span.cm-link {color: #cc00ff;} + +.cm-s-isotope .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} +.cm-s-isotope .CodeMirror-activeline-background { background: #202020; } diff --git a/www/code/codemirror-5.13.2/theme/lesser-dark.css b/www/code/codemirror-5.13.2/theme/lesser-dark.css new file mode 100644 index 000000000..690c183d7 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/lesser-dark.css @@ -0,0 +1,47 @@ +/* +http://lesscss.org/ dark theme +Ported to CodeMirror by Peter Kroon +*/ +.cm-s-lesser-dark { + line-height: 1.3em; +} +.cm-s-lesser-dark.CodeMirror { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; } +.cm-s-lesser-dark div.CodeMirror-selected { background: #45443B; } /* 33322B*/ +.cm-s-lesser-dark .CodeMirror-line::selection, .cm-s-lesser-dark .CodeMirror-line > span::selection, .cm-s-lesser-dark .CodeMirror-line > span > span::selection { background: rgba(69, 68, 59, .99); } +.cm-s-lesser-dark .CodeMirror-line::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(69, 68, 59, .99); } +.cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white; } +.cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/ + +.cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/ + +.cm-s-lesser-dark .CodeMirror-gutters { background: #262626; border-right:1px solid #aaa; } +.cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff; } +.cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777; } +.cm-s-lesser-dark .CodeMirror-linenumber { color: #777; } + +.cm-s-lesser-dark span.cm-header { color: #a0a; } +.cm-s-lesser-dark span.cm-quote { color: #090; } +.cm-s-lesser-dark span.cm-keyword { color: #599eff; } +.cm-s-lesser-dark span.cm-atom { color: #C2B470; } +.cm-s-lesser-dark span.cm-number { color: #B35E4D; } +.cm-s-lesser-dark span.cm-def { color: white; } +.cm-s-lesser-dark span.cm-variable { color:#D9BF8C; } +.cm-s-lesser-dark span.cm-variable-2 { color: #669199; } +.cm-s-lesser-dark span.cm-variable-3 { color: white; } +.cm-s-lesser-dark span.cm-property { color: #92A75C; } +.cm-s-lesser-dark span.cm-operator { color: #92A75C; } +.cm-s-lesser-dark span.cm-comment { color: #666; } +.cm-s-lesser-dark span.cm-string { color: #BCD279; } +.cm-s-lesser-dark span.cm-string-2 { color: #f50; } +.cm-s-lesser-dark span.cm-meta { color: #738C73; } +.cm-s-lesser-dark span.cm-qualifier { color: #555; } +.cm-s-lesser-dark span.cm-builtin { color: #ff9e59; } +.cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; } +.cm-s-lesser-dark span.cm-tag { color: #669199; } +.cm-s-lesser-dark span.cm-attribute { color: #00c; } +.cm-s-lesser-dark span.cm-hr { color: #999; } +.cm-s-lesser-dark span.cm-link { color: #00c; } +.cm-s-lesser-dark span.cm-error { color: #9d1e15; } + +.cm-s-lesser-dark .CodeMirror-activeline-background { background: #3C3A3A; } +.cm-s-lesser-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/www/code/codemirror-5.13.2/theme/liquibyte.css b/www/code/codemirror-5.13.2/theme/liquibyte.css new file mode 100644 index 000000000..9db8bde73 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/liquibyte.css @@ -0,0 +1,95 @@ +.cm-s-liquibyte.CodeMirror { + background-color: #000; + color: #fff; + line-height: 1.2em; + font-size: 1em; +} +.cm-s-liquibyte .CodeMirror-focused .cm-matchhighlight { + text-decoration: underline; + text-decoration-color: #0f0; + text-decoration-style: wavy; +} +.cm-s-liquibyte .cm-trailingspace { + text-decoration: line-through; + text-decoration-color: #f00; + text-decoration-style: dotted; +} +.cm-s-liquibyte .cm-tab { + text-decoration: line-through; + text-decoration-color: #404040; + text-decoration-style: dotted; +} +.cm-s-liquibyte .CodeMirror-gutters { background-color: #262626; border-right: 1px solid #505050; padding-right: 0.8em; } +.cm-s-liquibyte .CodeMirror-gutter-elt div { font-size: 1.2em; } +.cm-s-liquibyte .CodeMirror-guttermarker { } +.cm-s-liquibyte .CodeMirror-guttermarker-subtle { } +.cm-s-liquibyte .CodeMirror-linenumber { color: #606060; padding-left: 0; } +.cm-s-liquibyte .CodeMirror-cursor { border-left: 1px solid #eee; } + +.cm-s-liquibyte span.cm-comment { color: #008000; } +.cm-s-liquibyte span.cm-def { color: #ffaf40; font-weight: bold; } +.cm-s-liquibyte span.cm-keyword { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-builtin { color: #ffaf40; font-weight: bold; } +.cm-s-liquibyte span.cm-variable { color: #5967ff; font-weight: bold; } +.cm-s-liquibyte span.cm-string { color: #ff8000; } +.cm-s-liquibyte span.cm-number { color: #0f0; font-weight: bold; } +.cm-s-liquibyte span.cm-atom { color: #bf3030; font-weight: bold; } + +.cm-s-liquibyte span.cm-variable-2 { color: #007f7f; font-weight: bold; } +.cm-s-liquibyte span.cm-variable-3 { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-property { color: #999; font-weight: bold; } +.cm-s-liquibyte span.cm-operator { color: #fff; } + +.cm-s-liquibyte span.cm-meta { color: #0f0; } +.cm-s-liquibyte span.cm-qualifier { color: #fff700; font-weight: bold; } +.cm-s-liquibyte span.cm-bracket { color: #cc7; } +.cm-s-liquibyte span.cm-tag { color: #ff0; font-weight: bold; } +.cm-s-liquibyte span.cm-attribute { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-error { color: #f00; } + +.cm-s-liquibyte div.CodeMirror-selected { background-color: rgba(255, 0, 0, 0.25); } + +.cm-s-liquibyte span.cm-compilation { background-color: rgba(255, 255, 255, 0.12); } + +.cm-s-liquibyte .CodeMirror-activeline-background { background-color: rgba(0, 255, 0, 0.15); } + +/* Default styles for common addons */ +.cm-s-liquibyte .CodeMirror span.CodeMirror-matchingbracket { color: #0f0; font-weight: bold; } +.cm-s-liquibyte .CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00; font-weight: bold; } +.CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3); } +/* Scrollbars */ +/* Simple */ +.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div:hover, div.CodeMirror-simplescroll-vertical div:hover { + background-color: rgba(80, 80, 80, .7); +} +.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div, div.CodeMirror-simplescroll-vertical div { + background-color: rgba(80, 80, 80, .3); + border: 1px solid #404040; + border-radius: 5px; +} +.cm-s-liquibyte div.CodeMirror-simplescroll-vertical div { + border-top: 1px solid #404040; + border-bottom: 1px solid #404040; +} +.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div { + border-left: 1px solid #404040; + border-right: 1px solid #404040; +} +.cm-s-liquibyte div.CodeMirror-simplescroll-vertical { + background-color: #262626; +} +.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal { + background-color: #262626; + border-top: 1px solid #404040; +} +/* Overlay */ +.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div, div.CodeMirror-overlayscroll-vertical div { + background-color: #404040; + border-radius: 5px; +} +.cm-s-liquibyte div.CodeMirror-overlayscroll-vertical div { + border: 1px solid #404040; +} +.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div { + border: 1px solid #404040; +} diff --git a/www/code/codemirror-5.13.2/theme/material.css b/www/code/codemirror-5.13.2/theme/material.css new file mode 100644 index 000000000..91ed6cef2 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/material.css @@ -0,0 +1,53 @@ +/* + + Name: material + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original material color scheme by Mattia Astorino (https://github.com/equinusocio/material-theme) + +*/ + +.cm-s-material { + background-color: #263238; + color: rgba(233, 237, 237, 1); +} +.cm-s-material .CodeMirror-gutters { + background: #263238; + color: rgb(83,127,126); + border: none; +} +.cm-s-material .CodeMirror-guttermarker, .cm-s-material .CodeMirror-guttermarker-subtle, .cm-s-material .CodeMirror-linenumber { color: rgb(83,127,126); } +.cm-s-material .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } +.cm-s-material div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); } +.cm-s-material.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } +.cm-s-material .CodeMirror-line::selection, .cm-s-material .CodeMirror-line > span::selection, .cm-s-material .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-material .CodeMirror-line::-moz-selection, .cm-s-material .CodeMirror-line > span::-moz-selection, .cm-s-material .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } + +.cm-s-material .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0); } +.cm-s-material .cm-keyword { color: rgba(199, 146, 234, 1); } +.cm-s-material .cm-operator { color: rgba(233, 237, 237, 1); } +.cm-s-material .cm-variable-2 { color: #80CBC4; } +.cm-s-material .cm-variable-3 { color: #82B1FF; } +.cm-s-material .cm-builtin { color: #DECB6B; } +.cm-s-material .cm-atom { color: #F77669; } +.cm-s-material .cm-number { color: #F77669; } +.cm-s-material .cm-def { color: rgba(233, 237, 237, 1); } +.cm-s-material .cm-string { color: #C3E88D; } +.cm-s-material .cm-string-2 { color: #80CBC4; } +.cm-s-material .cm-comment { color: #546E7A; } +.cm-s-material .cm-variable { color: #82B1FF; } +.cm-s-material .cm-tag { color: #80CBC4; } +.cm-s-material .cm-meta { color: #80CBC4; } +.cm-s-material .cm-attribute { color: #FFCB6B; } +.cm-s-material .cm-property { color: #80CBAE; } +.cm-s-material .cm-qualifier { color: #DECB6B; } +.cm-s-material .cm-variable-3 { color: #DECB6B; } +.cm-s-material .cm-tag { color: rgba(255, 83, 112, 1); } +.cm-s-material .cm-error { + color: rgba(255, 255, 255, 1.0); + background-color: #EC5F67; +} +.cm-s-material .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/www/code/codemirror-5.13.2/theme/mbo.css b/www/code/codemirror-5.13.2/theme/mbo.css new file mode 100644 index 000000000..e164fcf42 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/mbo.css @@ -0,0 +1,37 @@ +/****************************************************************/ +/* Based on mbonaci's Brackets mbo theme */ +/* https://github.com/mbonaci/global/blob/master/Mbo.tmTheme */ +/* Create your own: http://tmtheme-editor.herokuapp.com */ +/****************************************************************/ + +.cm-s-mbo.CodeMirror { background: #2c2c2c; color: #ffffec; } +.cm-s-mbo div.CodeMirror-selected { background: #716C62; } +.cm-s-mbo .CodeMirror-line::selection, .cm-s-mbo .CodeMirror-line > span::selection, .cm-s-mbo .CodeMirror-line > span > span::selection { background: rgba(113, 108, 98, .99); } +.cm-s-mbo .CodeMirror-line::-moz-selection, .cm-s-mbo .CodeMirror-line > span::-moz-selection, .cm-s-mbo .CodeMirror-line > span > span::-moz-selection { background: rgba(113, 108, 98, .99); } +.cm-s-mbo .CodeMirror-gutters { background: #4e4e4e; border-right: 0px; } +.cm-s-mbo .CodeMirror-guttermarker { color: white; } +.cm-s-mbo .CodeMirror-guttermarker-subtle { color: grey; } +.cm-s-mbo .CodeMirror-linenumber { color: #dadada; } +.cm-s-mbo .CodeMirror-cursor { border-left: 1px solid #ffffec; } + +.cm-s-mbo span.cm-comment { color: #95958a; } +.cm-s-mbo span.cm-atom { color: #00a8c6; } +.cm-s-mbo span.cm-number { color: #00a8c6; } + +.cm-s-mbo span.cm-property, .cm-s-mbo span.cm-attribute { color: #9ddfe9; } +.cm-s-mbo span.cm-keyword { color: #ffb928; } +.cm-s-mbo span.cm-string { color: #ffcf6c; } +.cm-s-mbo span.cm-string.cm-property { color: #ffffec; } + +.cm-s-mbo span.cm-variable { color: #ffffec; } +.cm-s-mbo span.cm-variable-2 { color: #00a8c6; } +.cm-s-mbo span.cm-def { color: #ffffec; } +.cm-s-mbo span.cm-bracket { color: #fffffc; font-weight: bold; } +.cm-s-mbo span.cm-tag { color: #9ddfe9; } +.cm-s-mbo span.cm-link { color: #f54b07; } +.cm-s-mbo span.cm-error { border-bottom: #636363; color: #ffffec; } +.cm-s-mbo span.cm-qualifier { color: #ffffec; } + +.cm-s-mbo .CodeMirror-activeline-background { background: #494b41; } +.cm-s-mbo .CodeMirror-matchingbracket { color: #ffb928 !important; } +.cm-s-mbo .CodeMirror-matchingtag { background: rgba(255, 255, 255, .37); } diff --git a/www/code/codemirror-5.13.2/theme/mdn-like.css b/www/code/codemirror-5.13.2/theme/mdn-like.css new file mode 100644 index 000000000..f325d4500 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/mdn-like.css @@ -0,0 +1,46 @@ +/* + MDN-LIKE Theme - Mozilla + Ported to CodeMirror by Peter Kroon + Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues + GitHub: @peterkroon + + The mdn-like theme is inspired on the displayed code examples at: https://developer.mozilla.org/en-US/docs/Web/CSS/animation + +*/ +.cm-s-mdn-like.CodeMirror { color: #999; background-color: #fff; } +.cm-s-mdn-like div.CodeMirror-selected { background: #cfc; } +.cm-s-mdn-like .CodeMirror-line::selection, .cm-s-mdn-like .CodeMirror-line > span::selection, .cm-s-mdn-like .CodeMirror-line > span > span::selection { background: #cfc; } +.cm-s-mdn-like .CodeMirror-line::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span > span::-moz-selection { background: #cfc; } + +.cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8; border-left: 6px solid rgba(0,83,159,0.65); color: #333; } +.cm-s-mdn-like .CodeMirror-linenumber { color: #aaa; padding-left: 8px; } +.cm-s-mdn-like .CodeMirror-cursor { border-left: 2px solid #222; } + +.cm-s-mdn-like .cm-keyword { color: #6262FF; } +.cm-s-mdn-like .cm-atom { color: #F90; } +.cm-s-mdn-like .cm-number { color: #ca7841; } +.cm-s-mdn-like .cm-def { color: #8DA6CE; } +.cm-s-mdn-like span.cm-variable-2, .cm-s-mdn-like span.cm-tag { color: #690; } +.cm-s-mdn-like span.cm-variable-3, .cm-s-mdn-like span.cm-def { color: #07a; } + +.cm-s-mdn-like .cm-variable { color: #07a; } +.cm-s-mdn-like .cm-property { color: #905; } +.cm-s-mdn-like .cm-qualifier { color: #690; } + +.cm-s-mdn-like .cm-operator { color: #cda869; } +.cm-s-mdn-like .cm-comment { color:#777; font-weight:normal; } +.cm-s-mdn-like .cm-string { color:#07a; font-style:italic; } +.cm-s-mdn-like .cm-string-2 { color:#bd6b18; } /*?*/ +.cm-s-mdn-like .cm-meta { color: #000; } /*?*/ +.cm-s-mdn-like .cm-builtin { color: #9B7536; } /*?*/ +.cm-s-mdn-like .cm-tag { color: #997643; } +.cm-s-mdn-like .cm-attribute { color: #d6bb6d; } /*?*/ +.cm-s-mdn-like .cm-header { color: #FF6400; } +.cm-s-mdn-like .cm-hr { color: #AEAEAE; } +.cm-s-mdn-like .cm-link { color:#ad9361; font-style:italic; text-decoration:none; } +.cm-s-mdn-like .cm-error { border-bottom: 1px solid red; } + +div.cm-s-mdn-like .CodeMirror-activeline-background { background: #efefff; } +div.cm-s-mdn-like span.CodeMirror-matchingbracket { outline:1px solid grey; color: inherit; } + +.cm-s-mdn-like.CodeMirror { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFcAAAAyCAYAAAAp8UeFAAAHvklEQVR42s2b63bcNgyEQZCSHCdt2vd/0tWF7I+Q6XgMXiTtuvU5Pl57ZQKkKHzEAOtF5KeIJBGJ8uvL599FRFREZhFx8DeXv8trn68RuGaC8TRfo3SNp9dlDDHedyLyTUTeRWStXKPZrjtpZxaRw5hPqozRs1N8/enzIiQRWcCgy4MUA0f+XWliDhyL8Lfyvx7ei/Ae3iQFHyw7U/59pQVIMEEPEz0G7XiwdRjzSfC3UTtz9vchIntxvry5iMgfIhJoEflOz2CQr3F5h/HfeFe+GTdLaKcu9L8LTeQb/R/7GgbsfKedyNdoHsN31uRPWrfZ5wsj/NzzRQHuToIdU3ahwnsKPxXCjJITuOsi7XLc7SG/v5GdALs7wf8JjTFiB5+QvTEfRyGOfX3Lrx8wxyQi3sNq46O7QahQiCsRFgqddjBouVEHOKDgXAQHD9gJCr5sMKkEdjwsarG/ww3BMHBU7OBjXnzdyY7SfCxf5/z6ATccrwlKuwC/jhznnPF4CgVzhhVf4xp2EixcBActO75iZ8/fM9zAs2OMzKdslgXWJ9XG8PQoOAMA5fGcsvORgv0doBXyHrCwfLJAOwo71QLNkb8n2Pl6EWiR7OCibtkPaz4Kc/0NNAze2gju3zOwekALDaCFPI5vjPFmgGY5AZqyGEvH1x7QfIb8YtxMnA/b+QQ0aQDAwc6JMFg8CbQZ4qoYEEHbRwNojuK3EHwd7VALSgq+MNDKzfT58T8qdpADrgW0GmgcAS1lhzztJmkAzcPNOQbsWEALBDSlMKUG0Eq4CLAQWvEVQ9WU57gZJwZtgPO3r9oBTQ9WO8TjqXINx8R0EYpiZEUWOF3FxkbJkgU9B2f41YBrIj5ZfsQa0M5kTgiAAqM3ShXLgu8XMqcrQBvJ0CL5pnTsfMB13oB8athpAq2XOQmcGmoACCLydx7nToa23ATaSIY2ichfOdPTGxlasXMLaL0MLZAOwAKIM+y8CmicobGdCcbbK9DzN+yYGVoNNI5iUKTMyYOjPse4A8SM1MmcXgU0toOq1yO/v8FOxlASyc7TgeYaAMBJHcY1CcCwGI/TK4AmDbDyKYBBtFUkRwto8gygiQEaByFgJ00BH2M8JWwQS1nafDXQCidWyOI8AcjDCSjCLk8ngObuAm3JAHAdubAmOaK06V8MNEsKPJOhobSprwQa6gD7DclRQdqcwL4zxqgBrQcabUiBLclRDKAlWp+etPkBaNMA0AKlrHwTdEByZAA4GM+SNluSY6wAzcMNewxmgig5Ks0nkrSpBvSaQHMdKTBAnLojOdYyGpQ254602ZILPdTD1hdlggdIm74jbTp8vDwF5ZYUeLWGJpWsh6XNyXgcYwVoJQTEhhTYkxzZjiU5npU2TaB979TQehlaAVq4kaGpiPwwwLkYUuBbQwocyQTv1tA0+1UFWoJF3iv1oq+qoSk8EQdJmwHkziIF7oOZk14EGitibAdjLYYK78H5vZOhtWpoI0ATGHs0Q8OMb4Ey+2bU2UYztCtA0wFAs7TplGLRVQCcqaFdGSPCeTI1QNIC52iWNzof6Uib7xjEp07mNNoUYmVosVItHrHzRlLgBn9LFyRHaQCtVUMbtTNhoXWiTOO9k/V8BdAc1Oq0ArSQs6/5SU0hckNy9NnXqQY0PGYo5dWJ7nINaN6o958FWin27aBaWRka1r5myvLOAm0j30eBJqCxHLReVclxhxOEN2JfDWjxBtAC7MIH1fVaGdoOp4qJYDgKtKPSFNID2gSnGldrCqkFZ+5UeQXQBIRrSwocbdZYQT/2LwRahBPBXoHrB8nxaGROST62DKUbQOMMzZIC9abkuELfQzQALWTnDNAm8KHWFOJgJ5+SHIvTPcmx1xQyZRhNL5Qci689aXMEaN/uNIWkEwDAvFpOZmgsBaaGnbs1NPa1Jm32gBZAIh1pCtG7TSH4aE0y1uVY4uqoFPisGlpP2rSA5qTecWn5agK6BzSpgAyD+wFaqhnYoSZ1Vwr8CmlTQbrcO3ZaX0NAEyMbYaAlyquFoLKK3SPby9CeVUPThrSJmkCAE0CrKUQadi4DrdSlWhmah0YL9z9vClH59YGbHx1J8VZTyAjQepJjmXwAKTDQI3omc3p1U4gDUf6RfcdYfrUp5ClAi2J3Ba6UOXGo+K+bQrjjssitG2SJzshaLwMtXgRagUNpYYoVkMSBLM+9GGiJZMvduG6DRZ4qc04DMPtQQxOjEtACmhO7K1AbNbQDEggZyJwscFpAGwENhoBeUwh3bWolhe8BTYVKxQEWrSUn/uhcM5KhvUu/+eQu0Lzhi+VrK0PrZZNDQKs9cpYUuFYgMVpD4/NxenJTiMCNqdUEUf1qZWjppLT5qSkkUZbCwkbZMSuVnu80hfSkzRbQeqCZSAh6huR4VtoM2gHAlLf72smuWgE+VV7XpE25Ab2WFDgyhnSuKbs4GuGzCjR+tIoUuMFg3kgcWKLTwRqanJQ2W00hAsenfaApRC42hbCvK1SlE0HtE9BGgneJO+ELamitD1YjjOYnNYVcraGhtKkW0EqVVeDx733I2NH581k1NNxNLG0i0IJ8/NjVaOZ0tYZ2Vtr0Xv7tPV3hkWp9EFkgS/J0vosngTaSoaG06WHi+xObQkaAdlbanP8B2+2l0f90LmUAAAAASUVORK5CYII=); } diff --git a/www/code/codemirror-5.13.2/theme/midnight.css b/www/code/codemirror-5.13.2/theme/midnight.css new file mode 100644 index 000000000..e41f10560 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/midnight.css @@ -0,0 +1,45 @@ +/* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */ + +/**/ +.cm-s-midnight span.CodeMirror-matchhighlight { background: #494949; } +.cm-s-midnight.CodeMirror-focused span.CodeMirror-matchhighlight { background: #314D67 !important; } + +/**/ +.cm-s-midnight .CodeMirror-activeline-background { background: #253540; } + +.cm-s-midnight.CodeMirror { + background: #0F192A; + color: #D1EDFF; +} + +.cm-s-midnight.CodeMirror { border-top: 1px solid black; border-bottom: 1px solid black; } + +.cm-s-midnight div.CodeMirror-selected { background: #314D67; } +.cm-s-midnight .CodeMirror-line::selection, .cm-s-midnight .CodeMirror-line > span::selection, .cm-s-midnight .CodeMirror-line > span > span::selection { background: rgba(49, 77, 103, .99); } +.cm-s-midnight .CodeMirror-line::-moz-selection, .cm-s-midnight .CodeMirror-line > span::-moz-selection, .cm-s-midnight .CodeMirror-line > span > span::-moz-selection { background: rgba(49, 77, 103, .99); } +.cm-s-midnight .CodeMirror-gutters { background: #0F192A; border-right: 1px solid; } +.cm-s-midnight .CodeMirror-guttermarker { color: white; } +.cm-s-midnight .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-midnight .CodeMirror-linenumber { color: #D0D0D0; } +.cm-s-midnight .CodeMirror-cursor { border-left: 1px solid #F8F8F0; } + +.cm-s-midnight span.cm-comment { color: #428BDD; } +.cm-s-midnight span.cm-atom { color: #AE81FF; } +.cm-s-midnight span.cm-number { color: #D1EDFF; } + +.cm-s-midnight span.cm-property, .cm-s-midnight span.cm-attribute { color: #A6E22E; } +.cm-s-midnight span.cm-keyword { color: #E83737; } +.cm-s-midnight span.cm-string { color: #1DC116; } + +.cm-s-midnight span.cm-variable { color: #FFAA3E; } +.cm-s-midnight span.cm-variable-2 { color: #FFAA3E; } +.cm-s-midnight span.cm-def { color: #4DD; } +.cm-s-midnight span.cm-bracket { color: #D1EDFF; } +.cm-s-midnight span.cm-tag { color: #449; } +.cm-s-midnight span.cm-link { color: #AE81FF; } +.cm-s-midnight span.cm-error { background: #F92672; color: #F8F8F0; } + +.cm-s-midnight .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/www/code/codemirror-5.13.2/theme/monokai.css b/www/code/codemirror-5.13.2/theme/monokai.css new file mode 100644 index 000000000..7c8a4c5d0 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/monokai.css @@ -0,0 +1,36 @@ +/* Based on Sublime Text's Monokai theme */ + +.cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; } +.cm-s-monokai div.CodeMirror-selected { background: #49483E; } +.cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); } +.cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); } +.cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; } +.cm-s-monokai .CodeMirror-guttermarker { color: white; } +.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } + +.cm-s-monokai span.cm-comment { color: #75715e; } +.cm-s-monokai span.cm-atom { color: #ae81ff; } +.cm-s-monokai span.cm-number { color: #ae81ff; } + +.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; } +.cm-s-monokai span.cm-keyword { color: #f92672; } +.cm-s-monokai span.cm-builtin { color: #66d9ef; } +.cm-s-monokai span.cm-string { color: #e6db74; } + +.cm-s-monokai span.cm-variable { color: #f8f8f2; } +.cm-s-monokai span.cm-variable-2 { color: #9effff; } +.cm-s-monokai span.cm-variable-3 { color: #66d9ef; } +.cm-s-monokai span.cm-def { color: #fd971f; } +.cm-s-monokai span.cm-bracket { color: #f8f8f2; } +.cm-s-monokai span.cm-tag { color: #f92672; } +.cm-s-monokai span.cm-header { color: #ae81ff; } +.cm-s-monokai span.cm-link { color: #ae81ff; } +.cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; } + +.cm-s-monokai .CodeMirror-activeline-background { background: #373831; } +.cm-s-monokai .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/www/code/codemirror-5.13.2/theme/neat.css b/www/code/codemirror-5.13.2/theme/neat.css new file mode 100644 index 000000000..4267b1a37 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/neat.css @@ -0,0 +1,12 @@ +.cm-s-neat span.cm-comment { color: #a86; } +.cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; } +.cm-s-neat span.cm-string { color: #a22; } +.cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; } +.cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; } +.cm-s-neat span.cm-variable { color: black; } +.cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; } +.cm-s-neat span.cm-meta { color: #555; } +.cm-s-neat span.cm-link { color: #3a3; } + +.cm-s-neat .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-neat .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } diff --git a/www/code/codemirror-5.13.2/theme/neo.css b/www/code/codemirror-5.13.2/theme/neo.css new file mode 100644 index 000000000..b28d5c65f --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/neo.css @@ -0,0 +1,43 @@ +/* neo theme for codemirror */ + +/* Color scheme */ + +.cm-s-neo.CodeMirror { + background-color:#ffffff; + color:#2e383c; + line-height:1.4375; +} +.cm-s-neo .cm-comment { color:#75787b; } +.cm-s-neo .cm-keyword, .cm-s-neo .cm-property { color:#1d75b3; } +.cm-s-neo .cm-atom,.cm-s-neo .cm-number { color:#75438a; } +.cm-s-neo .cm-node,.cm-s-neo .cm-tag { color:#9c3328; } +.cm-s-neo .cm-string { color:#b35e14; } +.cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier { color:#047d65; } + + +/* Editor styling */ + +.cm-s-neo pre { + padding:0; +} + +.cm-s-neo .CodeMirror-gutters { + border:none; + border-right:10px solid transparent; + background-color:transparent; +} + +.cm-s-neo .CodeMirror-linenumber { + padding:0; + color:#e0e2e5; +} + +.cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; } +.cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; } + +.cm-s-neo .CodeMirror-cursor { + width: auto; + border: 0; + background: rgba(155,157,162,0.37); + z-index: 1; +} diff --git a/www/code/codemirror-5.13.2/theme/night.css b/www/code/codemirror-5.13.2/theme/night.css new file mode 100644 index 000000000..fd4e56193 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/night.css @@ -0,0 +1,27 @@ +/* Loosely based on the Midnight Textmate theme */ + +.cm-s-night.CodeMirror { background: #0a001f; color: #f8f8f8; } +.cm-s-night div.CodeMirror-selected { background: #447; } +.cm-s-night .CodeMirror-line::selection, .cm-s-night .CodeMirror-line > span::selection, .cm-s-night .CodeMirror-line > span > span::selection { background: rgba(68, 68, 119, .99); } +.cm-s-night .CodeMirror-line::-moz-selection, .cm-s-night .CodeMirror-line > span::-moz-selection, .cm-s-night .CodeMirror-line > span > span::-moz-selection { background: rgba(68, 68, 119, .99); } +.cm-s-night .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } +.cm-s-night .CodeMirror-guttermarker { color: white; } +.cm-s-night .CodeMirror-guttermarker-subtle { color: #bbb; } +.cm-s-night .CodeMirror-linenumber { color: #f8f8f8; } +.cm-s-night .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-night span.cm-comment { color: #8900d1; } +.cm-s-night span.cm-atom { color: #845dc4; } +.cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; } +.cm-s-night span.cm-keyword { color: #599eff; } +.cm-s-night span.cm-string { color: #37f14a; } +.cm-s-night span.cm-meta { color: #7678e2; } +.cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; } +.cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; } +.cm-s-night span.cm-bracket { color: #8da6ce; } +.cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; } +.cm-s-night span.cm-link { color: #845dc4; } +.cm-s-night span.cm-error { color: #9d1e15; } + +.cm-s-night .CodeMirror-activeline-background { background: #1C005A; } +.cm-s-night .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/www/code/codemirror-5.13.2/theme/paraiso-dark.css b/www/code/codemirror-5.13.2/theme/paraiso-dark.css new file mode 100644 index 000000000..aa9d207e6 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/paraiso-dark.css @@ -0,0 +1,38 @@ +/* + + Name: Paraíso (Dark) + Author: Jan T. Sott + + Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) + +*/ + +.cm-s-paraiso-dark.CodeMirror { background: #2f1e2e; color: #b9b6b0; } +.cm-s-paraiso-dark div.CodeMirror-selected { background: #41323f; } +.cm-s-paraiso-dark .CodeMirror-line::selection, .cm-s-paraiso-dark .CodeMirror-line > span::selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::selection { background: rgba(65, 50, 63, .99); } +.cm-s-paraiso-dark .CodeMirror-line::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(65, 50, 63, .99); } +.cm-s-paraiso-dark .CodeMirror-gutters { background: #2f1e2e; border-right: 0px; } +.cm-s-paraiso-dark .CodeMirror-guttermarker { color: #ef6155; } +.cm-s-paraiso-dark .CodeMirror-guttermarker-subtle { color: #776e71; } +.cm-s-paraiso-dark .CodeMirror-linenumber { color: #776e71; } +.cm-s-paraiso-dark .CodeMirror-cursor { border-left: 1px solid #8d8687; } + +.cm-s-paraiso-dark span.cm-comment { color: #e96ba8; } +.cm-s-paraiso-dark span.cm-atom { color: #815ba4; } +.cm-s-paraiso-dark span.cm-number { color: #815ba4; } + +.cm-s-paraiso-dark span.cm-property, .cm-s-paraiso-dark span.cm-attribute { color: #48b685; } +.cm-s-paraiso-dark span.cm-keyword { color: #ef6155; } +.cm-s-paraiso-dark span.cm-string { color: #fec418; } + +.cm-s-paraiso-dark span.cm-variable { color: #48b685; } +.cm-s-paraiso-dark span.cm-variable-2 { color: #06b6ef; } +.cm-s-paraiso-dark span.cm-def { color: #f99b15; } +.cm-s-paraiso-dark span.cm-bracket { color: #b9b6b0; } +.cm-s-paraiso-dark span.cm-tag { color: #ef6155; } +.cm-s-paraiso-dark span.cm-link { color: #815ba4; } +.cm-s-paraiso-dark span.cm-error { background: #ef6155; color: #8d8687; } + +.cm-s-paraiso-dark .CodeMirror-activeline-background { background: #4D344A; } +.cm-s-paraiso-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/www/code/codemirror-5.13.2/theme/paraiso-light.css b/www/code/codemirror-5.13.2/theme/paraiso-light.css new file mode 100644 index 000000000..ae0c755f8 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/paraiso-light.css @@ -0,0 +1,38 @@ +/* + + Name: Paraíso (Light) + Author: Jan T. Sott + + Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) + +*/ + +.cm-s-paraiso-light.CodeMirror { background: #e7e9db; color: #41323f; } +.cm-s-paraiso-light div.CodeMirror-selected { background: #b9b6b0; } +.cm-s-paraiso-light .CodeMirror-line::selection, .cm-s-paraiso-light .CodeMirror-line > span::selection, .cm-s-paraiso-light .CodeMirror-line > span > span::selection { background: #b9b6b0; } +.cm-s-paraiso-light .CodeMirror-line::-moz-selection, .cm-s-paraiso-light .CodeMirror-line > span::-moz-selection, .cm-s-paraiso-light .CodeMirror-line > span > span::-moz-selection { background: #b9b6b0; } +.cm-s-paraiso-light .CodeMirror-gutters { background: #e7e9db; border-right: 0px; } +.cm-s-paraiso-light .CodeMirror-guttermarker { color: black; } +.cm-s-paraiso-light .CodeMirror-guttermarker-subtle { color: #8d8687; } +.cm-s-paraiso-light .CodeMirror-linenumber { color: #8d8687; } +.cm-s-paraiso-light .CodeMirror-cursor { border-left: 1px solid #776e71; } + +.cm-s-paraiso-light span.cm-comment { color: #e96ba8; } +.cm-s-paraiso-light span.cm-atom { color: #815ba4; } +.cm-s-paraiso-light span.cm-number { color: #815ba4; } + +.cm-s-paraiso-light span.cm-property, .cm-s-paraiso-light span.cm-attribute { color: #48b685; } +.cm-s-paraiso-light span.cm-keyword { color: #ef6155; } +.cm-s-paraiso-light span.cm-string { color: #fec418; } + +.cm-s-paraiso-light span.cm-variable { color: #48b685; } +.cm-s-paraiso-light span.cm-variable-2 { color: #06b6ef; } +.cm-s-paraiso-light span.cm-def { color: #f99b15; } +.cm-s-paraiso-light span.cm-bracket { color: #41323f; } +.cm-s-paraiso-light span.cm-tag { color: #ef6155; } +.cm-s-paraiso-light span.cm-link { color: #815ba4; } +.cm-s-paraiso-light span.cm-error { background: #ef6155; color: #776e71; } + +.cm-s-paraiso-light .CodeMirror-activeline-background { background: #CFD1C4; } +.cm-s-paraiso-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/www/code/codemirror-5.13.2/theme/pastel-on-dark.css b/www/code/codemirror-5.13.2/theme/pastel-on-dark.css new file mode 100644 index 000000000..719750920 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/pastel-on-dark.css @@ -0,0 +1,53 @@ +/** + * Pastel On Dark theme ported from ACE editor + * @license MIT + * @copyright AtomicPages LLC 2014 + * @author Dennis Thompson, AtomicPages LLC + * @version 1.1 + * @source https://github.com/atomicpages/codemirror-pastel-on-dark-theme + */ + +.cm-s-pastel-on-dark.CodeMirror { + background: #2c2827; + color: #8F938F; + line-height: 1.5; + font-size: 14px; +} +.cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2); } +.cm-s-pastel-on-dark .CodeMirror-line::selection, .cm-s-pastel-on-dark .CodeMirror-line > span::selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::selection { background: rgba(221,240,255,0.2); } +.cm-s-pastel-on-dark .CodeMirror-line::-moz-selection, .cm-s-pastel-on-dark .CodeMirror-line > span::-moz-selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(221,240,255,0.2); } + +.cm-s-pastel-on-dark .CodeMirror-gutters { + background: #34302f; + border-right: 0px; + padding: 0 3px; +} +.cm-s-pastel-on-dark .CodeMirror-guttermarker { color: white; } +.cm-s-pastel-on-dark .CodeMirror-guttermarker-subtle { color: #8F938F; } +.cm-s-pastel-on-dark .CodeMirror-linenumber { color: #8F938F; } +.cm-s-pastel-on-dark .CodeMirror-cursor { border-left: 1px solid #A7A7A7; } +.cm-s-pastel-on-dark span.cm-comment { color: #A6C6FF; } +.cm-s-pastel-on-dark span.cm-atom { color: #DE8E30; } +.cm-s-pastel-on-dark span.cm-number { color: #CCCCCC; } +.cm-s-pastel-on-dark span.cm-property { color: #8F938F; } +.cm-s-pastel-on-dark span.cm-attribute { color: #a6e22e; } +.cm-s-pastel-on-dark span.cm-keyword { color: #AEB2F8; } +.cm-s-pastel-on-dark span.cm-string { color: #66A968; } +.cm-s-pastel-on-dark span.cm-variable { color: #AEB2F8; } +.cm-s-pastel-on-dark span.cm-variable-2 { color: #BEBF55; } +.cm-s-pastel-on-dark span.cm-variable-3 { color: #DE8E30; } +.cm-s-pastel-on-dark span.cm-def { color: #757aD8; } +.cm-s-pastel-on-dark span.cm-bracket { color: #f8f8f2; } +.cm-s-pastel-on-dark span.cm-tag { color: #C1C144; } +.cm-s-pastel-on-dark span.cm-link { color: #ae81ff; } +.cm-s-pastel-on-dark span.cm-qualifier,.cm-s-pastel-on-dark span.cm-builtin { color: #C1C144; } +.cm-s-pastel-on-dark span.cm-error { + background: #757aD8; + color: #f8f8f0; +} +.cm-s-pastel-on-dark .CodeMirror-activeline-background { background: rgba(255, 255, 255, 0.031); } +.cm-s-pastel-on-dark .CodeMirror-matchingbracket { + border: 1px solid rgba(255,255,255,0.25); + color: #8F938F !important; + margin: -1px -1px 0 -1px; +} diff --git a/www/code/codemirror-5.13.2/theme/railscasts.css b/www/code/codemirror-5.13.2/theme/railscasts.css new file mode 100644 index 000000000..aeff0449d --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/railscasts.css @@ -0,0 +1,34 @@ +/* + + Name: Railscasts + Author: Ryan Bates (http://railscasts.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-railscasts.CodeMirror {background: #2b2b2b; color: #f4f1ed;} +.cm-s-railscasts div.CodeMirror-selected {background: #272935 !important;} +.cm-s-railscasts .CodeMirror-gutters {background: #2b2b2b; border-right: 0px;} +.cm-s-railscasts .CodeMirror-linenumber {color: #5a647e;} +.cm-s-railscasts .CodeMirror-cursor {border-left: 1px solid #d4cfc9 !important;} + +.cm-s-railscasts span.cm-comment {color: #bc9458;} +.cm-s-railscasts span.cm-atom {color: #b6b3eb;} +.cm-s-railscasts span.cm-number {color: #b6b3eb;} + +.cm-s-railscasts span.cm-property, .cm-s-railscasts span.cm-attribute {color: #a5c261;} +.cm-s-railscasts span.cm-keyword {color: #da4939;} +.cm-s-railscasts span.cm-string {color: #ffc66d;} + +.cm-s-railscasts span.cm-variable {color: #a5c261;} +.cm-s-railscasts span.cm-variable-2 {color: #6d9cbe;} +.cm-s-railscasts span.cm-def {color: #cc7833;} +.cm-s-railscasts span.cm-error {background: #da4939; color: #d4cfc9;} +.cm-s-railscasts span.cm-bracket {color: #f4f1ed;} +.cm-s-railscasts span.cm-tag {color: #da4939;} +.cm-s-railscasts span.cm-link {color: #b6b3eb;} + +.cm-s-railscasts .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} +.cm-s-railscasts .CodeMirror-activeline-background { background: #303040; } diff --git a/www/code/codemirror-5.13.2/theme/rubyblue.css b/www/code/codemirror-5.13.2/theme/rubyblue.css new file mode 100644 index 000000000..76d33e779 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/rubyblue.css @@ -0,0 +1,25 @@ +.cm-s-rubyblue.CodeMirror { background: #112435; color: white; } +.cm-s-rubyblue div.CodeMirror-selected { background: #38566F; } +.cm-s-rubyblue .CodeMirror-line::selection, .cm-s-rubyblue .CodeMirror-line > span::selection, .cm-s-rubyblue .CodeMirror-line > span > span::selection { background: rgba(56, 86, 111, 0.99); } +.cm-s-rubyblue .CodeMirror-line::-moz-selection, .cm-s-rubyblue .CodeMirror-line > span::-moz-selection, .cm-s-rubyblue .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 86, 111, 0.99); } +.cm-s-rubyblue .CodeMirror-gutters { background: #1F4661; border-right: 7px solid #3E7087; } +.cm-s-rubyblue .CodeMirror-guttermarker { color: white; } +.cm-s-rubyblue .CodeMirror-guttermarker-subtle { color: #3E7087; } +.cm-s-rubyblue .CodeMirror-linenumber { color: white; } +.cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; line-height: 1em; } +.cm-s-rubyblue span.cm-atom { color: #F4C20B; } +.cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; } +.cm-s-rubyblue span.cm-keyword { color: #F0F; } +.cm-s-rubyblue span.cm-string { color: #F08047; } +.cm-s-rubyblue span.cm-meta { color: #F0F; } +.cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; } +.cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def { color: white; } +.cm-s-rubyblue span.cm-bracket { color: #F0F; } +.cm-s-rubyblue span.cm-link { color: #F4C20B; } +.cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; } +.cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; } +.cm-s-rubyblue span.cm-error { color: #AF2018; } + +.cm-s-rubyblue .CodeMirror-activeline-background { background: #173047; } diff --git a/www/code/codemirror-5.13.2/theme/seti.css b/www/code/codemirror-5.13.2/theme/seti.css new file mode 100644 index 000000000..6632d3fc7 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/seti.css @@ -0,0 +1,44 @@ +/* + + Name: seti + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original seti color scheme by Jesse Weed (https://github.com/jesseweed/seti-syntax) + +*/ + + +.cm-s-seti.CodeMirror { + background-color: #151718 !important; + color: #CFD2D1 !important; + border: none; +} +.cm-s-seti .CodeMirror-gutters { + color: #404b53; + background-color: #0E1112; + border: none; +} +.cm-s-seti .CodeMirror-cursor { border-left: solid thin #f8f8f0; } +.cm-s-seti .CodeMirror-linenumber { color: #6D8A88; } +.cm-s-seti.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } +.cm-s-seti .CodeMirror-line::selection, .cm-s-seti .CodeMirror-line > span::selection, .cm-s-seti .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-seti .CodeMirror-line::-moz-selection, .cm-s-seti .CodeMirror-line > span::-moz-selection, .cm-s-seti .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-seti span.cm-comment { color: #41535b; } +.cm-s-seti span.cm-string, .cm-s-seti span.cm-string-2 { color: #55b5db; } +.cm-s-seti span.cm-number { color: #cd3f45; } +.cm-s-seti span.cm-variable { color: #55b5db; } +.cm-s-seti span.cm-variable-2 { color: #a074c4; } +.cm-s-seti span.cm-def { color: #55b5db; } +.cm-s-seti span.cm-keyword { color: #ff79c6; } +.cm-s-seti span.cm-operator { color: #9fca56; } +.cm-s-seti span.cm-keyword { color: #e6cd69; } +.cm-s-seti span.cm-atom { color: #cd3f45; } +.cm-s-seti span.cm-meta { color: #55b5db; } +.cm-s-seti span.cm-tag { color: #55b5db; } +.cm-s-seti span.cm-attribute { color: #9fca56; } +.cm-s-seti span.cm-qualifier { color: #9fca56; } +.cm-s-seti span.cm-property { color: #a074c4; } +.cm-s-seti span.cm-variable-3 { color: #9fca56; } +.cm-s-seti span.cm-builtin { color: #9fca56; } +.cm-s-seti .CodeMirror-activeline-background { background: #101213; } +.cm-s-seti .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/www/code/codemirror-5.13.2/theme/solarized.css b/www/code/codemirror-5.13.2/theme/solarized.css new file mode 100644 index 000000000..7882c9376 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/solarized.css @@ -0,0 +1,163 @@ +/* +Solarized theme for code-mirror +http://ethanschoonover.com/solarized +*/ + +/* +Solarized color pallet +http://ethanschoonover.com/solarized/img/solarized-palette.png +*/ + +.solarized.base03 { color: #002b36; } +.solarized.base02 { color: #073642; } +.solarized.base01 { color: #586e75; } +.solarized.base00 { color: #657b83; } +.solarized.base0 { color: #839496; } +.solarized.base1 { color: #93a1a1; } +.solarized.base2 { color: #eee8d5; } +.solarized.base3 { color: #fdf6e3; } +.solarized.solar-yellow { color: #b58900; } +.solarized.solar-orange { color: #cb4b16; } +.solarized.solar-red { color: #dc322f; } +.solarized.solar-magenta { color: #d33682; } +.solarized.solar-violet { color: #6c71c4; } +.solarized.solar-blue { color: #268bd2; } +.solarized.solar-cyan { color: #2aa198; } +.solarized.solar-green { color: #859900; } + +/* Color scheme for code-mirror */ + +.cm-s-solarized { + line-height: 1.45em; + color-profile: sRGB; + rendering-intent: auto; +} +.cm-s-solarized.cm-s-dark { + color: #839496; + background-color: #002b36; + text-shadow: #002b36 0 1px; +} +.cm-s-solarized.cm-s-light { + background-color: #fdf6e3; + color: #657b83; + text-shadow: #eee8d5 0 1px; +} + +.cm-s-solarized .CodeMirror-widget { + text-shadow: none; +} + +.cm-s-solarized .cm-header { color: #586e75; } +.cm-s-solarized .cm-quote { color: #93a1a1; } + +.cm-s-solarized .cm-keyword { color: #cb4b16; } +.cm-s-solarized .cm-atom { color: #d33682; } +.cm-s-solarized .cm-number { color: #d33682; } +.cm-s-solarized .cm-def { color: #2aa198; } + +.cm-s-solarized .cm-variable { color: #839496; } +.cm-s-solarized .cm-variable-2 { color: #b58900; } +.cm-s-solarized .cm-variable-3 { color: #6c71c4; } + +.cm-s-solarized .cm-property { color: #2aa198; } +.cm-s-solarized .cm-operator { color: #6c71c4; } + +.cm-s-solarized .cm-comment { color: #586e75; font-style:italic; } + +.cm-s-solarized .cm-string { color: #859900; } +.cm-s-solarized .cm-string-2 { color: #b58900; } + +.cm-s-solarized .cm-meta { color: #859900; } +.cm-s-solarized .cm-qualifier { color: #b58900; } +.cm-s-solarized .cm-builtin { color: #d33682; } +.cm-s-solarized .cm-bracket { color: #cb4b16; } +.cm-s-solarized .CodeMirror-matchingbracket { color: #859900; } +.cm-s-solarized .CodeMirror-nonmatchingbracket { color: #dc322f; } +.cm-s-solarized .cm-tag { color: #93a1a1; } +.cm-s-solarized .cm-attribute { color: #2aa198; } +.cm-s-solarized .cm-hr { + color: transparent; + border-top: 1px solid #586e75; + display: block; +} +.cm-s-solarized .cm-link { color: #93a1a1; cursor: pointer; } +.cm-s-solarized .cm-special { color: #6c71c4; } +.cm-s-solarized .cm-em { + color: #999; + text-decoration: underline; + text-decoration-style: dotted; +} +.cm-s-solarized .cm-strong { color: #eee; } +.cm-s-solarized .cm-error, +.cm-s-solarized .cm-invalidchar { + color: #586e75; + border-bottom: 1px dotted #dc322f; +} + +.cm-s-solarized.cm-s-dark div.CodeMirror-selected { background: #073642; } +.cm-s-solarized.cm-s-dark.CodeMirror ::selection { background: rgba(7, 54, 66, 0.99); } +.cm-s-solarized.cm-s-dark .CodeMirror-line::-moz-selection, .cm-s-dark .CodeMirror-line > span::-moz-selection, .cm-s-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(7, 54, 66, 0.99); } + +.cm-s-solarized.cm-s-light div.CodeMirror-selected { background: #eee8d5; } +.cm-s-solarized.cm-s-light .CodeMirror-line::selection, .cm-s-light .CodeMirror-line > span::selection, .cm-s-light .CodeMirror-line > span > span::selection { background: #eee8d5; } +.cm-s-solarized.cm-s-light .CodeMirror-line::-moz-selection, .cm-s-ligh .CodeMirror-line > span::-moz-selection, .cm-s-ligh .CodeMirror-line > span > span::-moz-selection { background: #eee8d5; } + +/* Editor styling */ + + + +/* Little shadow on the view-port of the buffer view */ +.cm-s-solarized.CodeMirror { + -moz-box-shadow: inset 7px 0 12px -6px #000; + -webkit-box-shadow: inset 7px 0 12px -6px #000; + box-shadow: inset 7px 0 12px -6px #000; +} + +/* Gutter border and some shadow from it */ +.cm-s-solarized .CodeMirror-gutters { + border-right: 1px solid; +} + +/* Gutter colors and line number styling based of color scheme (dark / light) */ + +/* Dark */ +.cm-s-solarized.cm-s-dark .CodeMirror-gutters { + background-color: #002b36; + border-color: #00232c; +} + +.cm-s-solarized.cm-s-dark .CodeMirror-linenumber { + text-shadow: #021014 0 -1px; +} + +/* Light */ +.cm-s-solarized.cm-s-light .CodeMirror-gutters { + background-color: #fdf6e3; + border-color: #eee8d5; +} + +/* Common */ +.cm-s-solarized .CodeMirror-linenumber { + color: #586e75; + padding: 0 5px; +} +.cm-s-solarized .CodeMirror-guttermarker-subtle { color: #586e75; } +.cm-s-solarized.cm-s-dark .CodeMirror-guttermarker { color: #ddd; } +.cm-s-solarized.cm-s-light .CodeMirror-guttermarker { color: #cb4b16; } + +.cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text { + color: #586e75; +} + +.cm-s-solarized .CodeMirror-cursor { border-left: 1px solid #819090; } + +/* +Active line. Negative margin compensates left padding of the text in the +view-port +*/ +.cm-s-solarized.cm-s-dark .CodeMirror-activeline-background { + background: rgba(255, 255, 255, 0.10); +} +.cm-s-solarized.cm-s-light .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.10); +} diff --git a/www/code/codemirror-5.13.2/theme/the-matrix.css b/www/code/codemirror-5.13.2/theme/the-matrix.css new file mode 100644 index 000000000..3912a8dbd --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/the-matrix.css @@ -0,0 +1,30 @@ +.cm-s-the-matrix.CodeMirror { background: #000000; color: #00FF00; } +.cm-s-the-matrix div.CodeMirror-selected { background: #2D2D2D; } +.cm-s-the-matrix .CodeMirror-line::selection, .cm-s-the-matrix .CodeMirror-line > span::selection, .cm-s-the-matrix .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-the-matrix .CodeMirror-line::-moz-selection, .cm-s-the-matrix .CodeMirror-line > span::-moz-selection, .cm-s-the-matrix .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-the-matrix .CodeMirror-gutters { background: #060; border-right: 2px solid #00FF00; } +.cm-s-the-matrix .CodeMirror-guttermarker { color: #0f0; } +.cm-s-the-matrix .CodeMirror-guttermarker-subtle { color: white; } +.cm-s-the-matrix .CodeMirror-linenumber { color: #FFFFFF; } +.cm-s-the-matrix .CodeMirror-cursor { border-left: 1px solid #00FF00; } + +.cm-s-the-matrix span.cm-keyword { color: #008803; font-weight: bold; } +.cm-s-the-matrix span.cm-atom { color: #3FF; } +.cm-s-the-matrix span.cm-number { color: #FFB94F; } +.cm-s-the-matrix span.cm-def { color: #99C; } +.cm-s-the-matrix span.cm-variable { color: #F6C; } +.cm-s-the-matrix span.cm-variable-2 { color: #C6F; } +.cm-s-the-matrix span.cm-variable-3 { color: #96F; } +.cm-s-the-matrix span.cm-property { color: #62FFA0; } +.cm-s-the-matrix span.cm-operator { color: #999; } +.cm-s-the-matrix span.cm-comment { color: #CCCCCC; } +.cm-s-the-matrix span.cm-string { color: #39C; } +.cm-s-the-matrix span.cm-meta { color: #C9F; } +.cm-s-the-matrix span.cm-qualifier { color: #FFF700; } +.cm-s-the-matrix span.cm-builtin { color: #30a; } +.cm-s-the-matrix span.cm-bracket { color: #cc7; } +.cm-s-the-matrix span.cm-tag { color: #FFBD40; } +.cm-s-the-matrix span.cm-attribute { color: #FFF700; } +.cm-s-the-matrix span.cm-error { color: #FF0000; } + +.cm-s-the-matrix .CodeMirror-activeline-background { background: #040; } diff --git a/www/code/codemirror-5.13.2/theme/tomorrow-night-bright.css b/www/code/codemirror-5.13.2/theme/tomorrow-night-bright.css new file mode 100644 index 000000000..b6dd4a927 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/tomorrow-night-bright.css @@ -0,0 +1,35 @@ +/* + + Name: Tomorrow Night - Bright + Author: Chris Kempson + + Port done by Gerard Braad + +*/ + +.cm-s-tomorrow-night-bright.CodeMirror { background: #000000; color: #eaeaea; } +.cm-s-tomorrow-night-bright div.CodeMirror-selected { background: #424242; } +.cm-s-tomorrow-night-bright .CodeMirror-gutters { background: #000000; border-right: 0px; } +.cm-s-tomorrow-night-bright .CodeMirror-guttermarker { color: #e78c45; } +.cm-s-tomorrow-night-bright .CodeMirror-guttermarker-subtle { color: #777; } +.cm-s-tomorrow-night-bright .CodeMirror-linenumber { color: #424242; } +.cm-s-tomorrow-night-bright .CodeMirror-cursor { border-left: 1px solid #6A6A6A; } + +.cm-s-tomorrow-night-bright span.cm-comment { color: #d27b53; } +.cm-s-tomorrow-night-bright span.cm-atom { color: #a16a94; } +.cm-s-tomorrow-night-bright span.cm-number { color: #a16a94; } + +.cm-s-tomorrow-night-bright span.cm-property, .cm-s-tomorrow-night-bright span.cm-attribute { color: #99cc99; } +.cm-s-tomorrow-night-bright span.cm-keyword { color: #d54e53; } +.cm-s-tomorrow-night-bright span.cm-string { color: #e7c547; } + +.cm-s-tomorrow-night-bright span.cm-variable { color: #b9ca4a; } +.cm-s-tomorrow-night-bright span.cm-variable-2 { color: #7aa6da; } +.cm-s-tomorrow-night-bright span.cm-def { color: #e78c45; } +.cm-s-tomorrow-night-bright span.cm-bracket { color: #eaeaea; } +.cm-s-tomorrow-night-bright span.cm-tag { color: #d54e53; } +.cm-s-tomorrow-night-bright span.cm-link { color: #a16a94; } +.cm-s-tomorrow-night-bright span.cm-error { background: #d54e53; color: #6A6A6A; } + +.cm-s-tomorrow-night-bright .CodeMirror-activeline-background { background: #2a2a2a; } +.cm-s-tomorrow-night-bright .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/www/code/codemirror-5.13.2/theme/tomorrow-night-eighties.css b/www/code/codemirror-5.13.2/theme/tomorrow-night-eighties.css new file mode 100644 index 000000000..2a9debc32 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/tomorrow-night-eighties.css @@ -0,0 +1,38 @@ +/* + + Name: Tomorrow Night - Eighties + Author: Chris Kempson + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-tomorrow-night-eighties.CodeMirror { background: #000000; color: #CCCCCC; } +.cm-s-tomorrow-night-eighties div.CodeMirror-selected { background: #2D2D2D; } +.cm-s-tomorrow-night-eighties .CodeMirror-line::selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span::selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-tomorrow-night-eighties .CodeMirror-line::-moz-selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span::-moz-selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-tomorrow-night-eighties .CodeMirror-gutters { background: #000000; border-right: 0px; } +.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker { color: #f2777a; } +.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker-subtle { color: #777; } +.cm-s-tomorrow-night-eighties .CodeMirror-linenumber { color: #515151; } +.cm-s-tomorrow-night-eighties .CodeMirror-cursor { border-left: 1px solid #6A6A6A; } + +.cm-s-tomorrow-night-eighties span.cm-comment { color: #d27b53; } +.cm-s-tomorrow-night-eighties span.cm-atom { color: #a16a94; } +.cm-s-tomorrow-night-eighties span.cm-number { color: #a16a94; } + +.cm-s-tomorrow-night-eighties span.cm-property, .cm-s-tomorrow-night-eighties span.cm-attribute { color: #99cc99; } +.cm-s-tomorrow-night-eighties span.cm-keyword { color: #f2777a; } +.cm-s-tomorrow-night-eighties span.cm-string { color: #ffcc66; } + +.cm-s-tomorrow-night-eighties span.cm-variable { color: #99cc99; } +.cm-s-tomorrow-night-eighties span.cm-variable-2 { color: #6699cc; } +.cm-s-tomorrow-night-eighties span.cm-def { color: #f99157; } +.cm-s-tomorrow-night-eighties span.cm-bracket { color: #CCCCCC; } +.cm-s-tomorrow-night-eighties span.cm-tag { color: #f2777a; } +.cm-s-tomorrow-night-eighties span.cm-link { color: #a16a94; } +.cm-s-tomorrow-night-eighties span.cm-error { background: #f2777a; color: #6A6A6A; } + +.cm-s-tomorrow-night-eighties .CodeMirror-activeline-background { background: #343600; } +.cm-s-tomorrow-night-eighties .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/www/code/codemirror-5.13.2/theme/ttcn.css b/www/code/codemirror-5.13.2/theme/ttcn.css new file mode 100644 index 000000000..b3d465645 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/ttcn.css @@ -0,0 +1,64 @@ +.cm-s-ttcn .cm-quote { color: #090; } +.cm-s-ttcn .cm-negative { color: #d44; } +.cm-s-ttcn .cm-positive { color: #292; } +.cm-s-ttcn .cm-header, .cm-strong { font-weight: bold; } +.cm-s-ttcn .cm-em { font-style: italic; } +.cm-s-ttcn .cm-link { text-decoration: underline; } +.cm-s-ttcn .cm-strikethrough { text-decoration: line-through; } +.cm-s-ttcn .cm-header { color: #00f; font-weight: bold; } + +.cm-s-ttcn .cm-atom { color: #219; } +.cm-s-ttcn .cm-attribute { color: #00c; } +.cm-s-ttcn .cm-bracket { color: #997; } +.cm-s-ttcn .cm-comment { color: #333333; } +.cm-s-ttcn .cm-def { color: #00f; } +.cm-s-ttcn .cm-em { font-style: italic; } +.cm-s-ttcn .cm-error { color: #f00; } +.cm-s-ttcn .cm-hr { color: #999; } +.cm-s-ttcn .cm-invalidchar { color: #f00; } +.cm-s-ttcn .cm-keyword { font-weight:bold; } +.cm-s-ttcn .cm-link { color: #00c; text-decoration: underline; } +.cm-s-ttcn .cm-meta { color: #555; } +.cm-s-ttcn .cm-negative { color: #d44; } +.cm-s-ttcn .cm-positive { color: #292; } +.cm-s-ttcn .cm-qualifier { color: #555; } +.cm-s-ttcn .cm-strikethrough { text-decoration: line-through; } +.cm-s-ttcn .cm-string { color: #006400; } +.cm-s-ttcn .cm-string-2 { color: #f50; } +.cm-s-ttcn .cm-strong { font-weight: bold; } +.cm-s-ttcn .cm-tag { color: #170; } +.cm-s-ttcn .cm-variable { color: #8B2252; } +.cm-s-ttcn .cm-variable-2 { color: #05a; } +.cm-s-ttcn .cm-variable-3 { color: #085; } + +.cm-s-ttcn .cm-invalidchar { color: #f00; } + +/* ASN */ +.cm-s-ttcn .cm-accessTypes, +.cm-s-ttcn .cm-compareTypes { color: #27408B; } +.cm-s-ttcn .cm-cmipVerbs { color: #8B2252; } +.cm-s-ttcn .cm-modifier { color:#D2691E; } +.cm-s-ttcn .cm-status { color:#8B4545; } +.cm-s-ttcn .cm-storage { color:#A020F0; } +.cm-s-ttcn .cm-tags { color:#006400; } + +/* CFG */ +.cm-s-ttcn .cm-externalCommands { color: #8B4545; font-weight:bold; } +.cm-s-ttcn .cm-fileNCtrlMaskOptions, +.cm-s-ttcn .cm-sectionTitle { color: #2E8B57; font-weight:bold; } + +/* TTCN */ +.cm-s-ttcn .cm-booleanConsts, +.cm-s-ttcn .cm-otherConsts, +.cm-s-ttcn .cm-verdictConsts { color: #006400; } +.cm-s-ttcn .cm-configOps, +.cm-s-ttcn .cm-functionOps, +.cm-s-ttcn .cm-portOps, +.cm-s-ttcn .cm-sutOps, +.cm-s-ttcn .cm-timerOps, +.cm-s-ttcn .cm-verdictOps { color: #0000FF; } +.cm-s-ttcn .cm-preprocessor, +.cm-s-ttcn .cm-templateMatch, +.cm-s-ttcn .cm-ttcn3Macros { color: #27408B; } +.cm-s-ttcn .cm-types { color: #A52A2A; font-weight:bold; } +.cm-s-ttcn .cm-visibilityModifiers { font-weight:bold; } diff --git a/www/code/codemirror-5.13.2/theme/twilight.css b/www/code/codemirror-5.13.2/theme/twilight.css new file mode 100644 index 000000000..d342b899f --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/twilight.css @@ -0,0 +1,32 @@ +.cm-s-twilight.CodeMirror { background: #141414; color: #f7f7f7; } /**/ +.cm-s-twilight div.CodeMirror-selected { background: #323232; } /**/ +.cm-s-twilight .CodeMirror-line::selection, .cm-s-twilight .CodeMirror-line > span::selection, .cm-s-twilight .CodeMirror-line > span > span::selection { background: rgba(50, 50, 50, 0.99); } +.cm-s-twilight .CodeMirror-line::-moz-selection, .cm-s-twilight .CodeMirror-line > span::-moz-selection, .cm-s-twilight .CodeMirror-line > span > span::-moz-selection { background: rgba(50, 50, 50, 0.99); } + +.cm-s-twilight .CodeMirror-gutters { background: #222; border-right: 1px solid #aaa; } +.cm-s-twilight .CodeMirror-guttermarker { color: white; } +.cm-s-twilight .CodeMirror-guttermarker-subtle { color: #aaa; } +.cm-s-twilight .CodeMirror-linenumber { color: #aaa; } +.cm-s-twilight .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-twilight .cm-keyword { color: #f9ee98; } /**/ +.cm-s-twilight .cm-atom { color: #FC0; } +.cm-s-twilight .cm-number { color: #ca7841; } /**/ +.cm-s-twilight .cm-def { color: #8DA6CE; } +.cm-s-twilight span.cm-variable-2, .cm-s-twilight span.cm-tag { color: #607392; } /**/ +.cm-s-twilight span.cm-variable-3, .cm-s-twilight span.cm-def { color: #607392; } /**/ +.cm-s-twilight .cm-operator { color: #cda869; } /**/ +.cm-s-twilight .cm-comment { color:#777; font-style:italic; font-weight:normal; } /**/ +.cm-s-twilight .cm-string { color:#8f9d6a; font-style:italic; } /**/ +.cm-s-twilight .cm-string-2 { color:#bd6b18; } /*?*/ +.cm-s-twilight .cm-meta { background-color:#141414; color:#f7f7f7; } /*?*/ +.cm-s-twilight .cm-builtin { color: #cda869; } /*?*/ +.cm-s-twilight .cm-tag { color: #997643; } /**/ +.cm-s-twilight .cm-attribute { color: #d6bb6d; } /*?*/ +.cm-s-twilight .cm-header { color: #FF6400; } +.cm-s-twilight .cm-hr { color: #AEAEAE; } +.cm-s-twilight .cm-link { color:#ad9361; font-style:italic; text-decoration:none; } /**/ +.cm-s-twilight .cm-error { border-bottom: 1px solid red; } + +.cm-s-twilight .CodeMirror-activeline-background { background: #27282E; } +.cm-s-twilight .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/www/code/codemirror-5.13.2/theme/vibrant-ink.css b/www/code/codemirror-5.13.2/theme/vibrant-ink.css new file mode 100644 index 000000000..ac4ec6d87 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/vibrant-ink.css @@ -0,0 +1,34 @@ +/* Taken from the popular Visual Studio Vibrant Ink Schema */ + +.cm-s-vibrant-ink.CodeMirror { background: black; color: white; } +.cm-s-vibrant-ink div.CodeMirror-selected { background: #35493c; } +.cm-s-vibrant-ink .CodeMirror-line::selection, .cm-s-vibrant-ink .CodeMirror-line > span::selection, .cm-s-vibrant-ink .CodeMirror-line > span > span::selection { background: rgba(53, 73, 60, 0.99); } +.cm-s-vibrant-ink .CodeMirror-line::-moz-selection, .cm-s-vibrant-ink .CodeMirror-line > span::-moz-selection, .cm-s-vibrant-ink .CodeMirror-line > span > span::-moz-selection { background: rgba(53, 73, 60, 0.99); } + +.cm-s-vibrant-ink .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-vibrant-ink .CodeMirror-guttermarker { color: white; } +.cm-s-vibrant-ink .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-vibrant-ink .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-vibrant-ink .cm-keyword { color: #CC7832; } +.cm-s-vibrant-ink .cm-atom { color: #FC0; } +.cm-s-vibrant-ink .cm-number { color: #FFEE98; } +.cm-s-vibrant-ink .cm-def { color: #8DA6CE; } +.cm-s-vibrant-ink span.cm-variable-2, .cm-s-vibrant span.cm-tag { color: #FFC66D; } +.cm-s-vibrant-ink span.cm-variable-3, .cm-s-vibrant span.cm-def { color: #FFC66D; } +.cm-s-vibrant-ink .cm-operator { color: #888; } +.cm-s-vibrant-ink .cm-comment { color: gray; font-weight: bold; } +.cm-s-vibrant-ink .cm-string { color: #A5C25C; } +.cm-s-vibrant-ink .cm-string-2 { color: red; } +.cm-s-vibrant-ink .cm-meta { color: #D8FA3C; } +.cm-s-vibrant-ink .cm-builtin { color: #8DA6CE; } +.cm-s-vibrant-ink .cm-tag { color: #8DA6CE; } +.cm-s-vibrant-ink .cm-attribute { color: #8DA6CE; } +.cm-s-vibrant-ink .cm-header { color: #FF6400; } +.cm-s-vibrant-ink .cm-hr { color: #AEAEAE; } +.cm-s-vibrant-ink .cm-link { color: blue; } +.cm-s-vibrant-ink .cm-error { border-bottom: 1px solid red; } + +.cm-s-vibrant-ink .CodeMirror-activeline-background { background: #27282E; } +.cm-s-vibrant-ink .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/www/code/codemirror-5.13.2/theme/xq-dark.css b/www/code/codemirror-5.13.2/theme/xq-dark.css new file mode 100644 index 000000000..e3bd960bb --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/xq-dark.css @@ -0,0 +1,53 @@ +/* +Copyright (C) 2011 by MarkLogic Corporation +Author: Mike Brevoort + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +.cm-s-xq-dark.CodeMirror { background: #0a001f; color: #f8f8f8; } +.cm-s-xq-dark div.CodeMirror-selected { background: #27007A; } +.cm-s-xq-dark .CodeMirror-line::selection, .cm-s-xq-dark .CodeMirror-line > span::selection, .cm-s-xq-dark .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); } +.cm-s-xq-dark .CodeMirror-line::-moz-selection, .cm-s-xq-dark .CodeMirror-line > span::-moz-selection, .cm-s-xq-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); } +.cm-s-xq-dark .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } +.cm-s-xq-dark .CodeMirror-guttermarker { color: #FFBD40; } +.cm-s-xq-dark .CodeMirror-guttermarker-subtle { color: #f8f8f8; } +.cm-s-xq-dark .CodeMirror-linenumber { color: #f8f8f8; } +.cm-s-xq-dark .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-xq-dark span.cm-keyword { color: #FFBD40; } +.cm-s-xq-dark span.cm-atom { color: #6C8CD5; } +.cm-s-xq-dark span.cm-number { color: #164; } +.cm-s-xq-dark span.cm-def { color: #FFF; text-decoration:underline; } +.cm-s-xq-dark span.cm-variable { color: #FFF; } +.cm-s-xq-dark span.cm-variable-2 { color: #EEE; } +.cm-s-xq-dark span.cm-variable-3 { color: #DDD; } +.cm-s-xq-dark span.cm-property {} +.cm-s-xq-dark span.cm-operator {} +.cm-s-xq-dark span.cm-comment { color: gray; } +.cm-s-xq-dark span.cm-string { color: #9FEE00; } +.cm-s-xq-dark span.cm-meta { color: yellow; } +.cm-s-xq-dark span.cm-qualifier { color: #FFF700; } +.cm-s-xq-dark span.cm-builtin { color: #30a; } +.cm-s-xq-dark span.cm-bracket { color: #cc7; } +.cm-s-xq-dark span.cm-tag { color: #FFBD40; } +.cm-s-xq-dark span.cm-attribute { color: #FFF700; } +.cm-s-xq-dark span.cm-error { color: #f00; } + +.cm-s-xq-dark .CodeMirror-activeline-background { background: #27282E; } +.cm-s-xq-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/www/code/codemirror-5.13.2/theme/xq-light.css b/www/code/codemirror-5.13.2/theme/xq-light.css new file mode 100644 index 000000000..8d2fcb667 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/xq-light.css @@ -0,0 +1,43 @@ +/* +Copyright (C) 2011 by MarkLogic Corporation +Author: Mike Brevoort + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +.cm-s-xq-light span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; } +.cm-s-xq-light span.cm-atom { color: #6C8CD5; } +.cm-s-xq-light span.cm-number { color: #164; } +.cm-s-xq-light span.cm-def { text-decoration:underline; } +.cm-s-xq-light span.cm-variable { color: black; } +.cm-s-xq-light span.cm-variable-2 { color:black; } +.cm-s-xq-light span.cm-variable-3 { color: black; } +.cm-s-xq-light span.cm-property {} +.cm-s-xq-light span.cm-operator {} +.cm-s-xq-light span.cm-comment { color: #0080FF; font-style: italic; } +.cm-s-xq-light span.cm-string { color: red; } +.cm-s-xq-light span.cm-meta { color: yellow; } +.cm-s-xq-light span.cm-qualifier { color: grey; } +.cm-s-xq-light span.cm-builtin { color: #7EA656; } +.cm-s-xq-light span.cm-bracket { color: #cc7; } +.cm-s-xq-light span.cm-tag { color: #3F7F7F; } +.cm-s-xq-light span.cm-attribute { color: #7F007F; } +.cm-s-xq-light span.cm-error { color: #f00; } + +.cm-s-xq-light .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-xq-light .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; } diff --git a/www/code/codemirror-5.13.2/theme/yeti.css b/www/code/codemirror-5.13.2/theme/yeti.css new file mode 100644 index 000000000..c70d4d214 --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/yeti.css @@ -0,0 +1,44 @@ +/* + + Name: yeti + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original yeti color scheme by Jesse Weed (https://github.com/jesseweed/yeti-syntax) + +*/ + + +.cm-s-yeti.CodeMirror { + background-color: #ECEAE8 !important; + color: #d1c9c0 !important; + border: none; +} + +.cm-s-yeti .CodeMirror-gutters { + color: #adaba6; + background-color: #E5E1DB; + border: none; +} +.cm-s-yeti .CodeMirror-cursor { border-left: solid thin #d1c9c0; } +.cm-s-yeti .CodeMirror-linenumber { color: #adaba6; } +.cm-s-yeti.CodeMirror-focused div.CodeMirror-selected { background: #DCD8D2; } +.cm-s-yeti .CodeMirror-line::selection, .cm-s-yeti .CodeMirror-line > span::selection, .cm-s-yeti .CodeMirror-line > span > span::selection { background: #DCD8D2; } +.cm-s-yeti .CodeMirror-line::-moz-selection, .cm-s-yeti .CodeMirror-line > span::-moz-selection, .cm-s-yeti .CodeMirror-line > span > span::-moz-selection { background: #DCD8D2; } +.cm-s-yeti span.cm-comment { color: #d4c8be; } +.cm-s-yeti span.cm-string, .cm-s-yeti span.cm-string-2 { color: #96c0d8; } +.cm-s-yeti span.cm-number { color: #a074c4; } +.cm-s-yeti span.cm-variable { color: #55b5db; } +.cm-s-yeti span.cm-variable-2 { color: #a074c4; } +.cm-s-yeti span.cm-def { color: #55b5db; } +.cm-s-yeti span.cm-operator { color: #9fb96e; } +.cm-s-yeti span.cm-keyword { color: #9fb96e; } +.cm-s-yeti span.cm-atom { color: #a074c4; } +.cm-s-yeti span.cm-meta { color: #96c0d8; } +.cm-s-yeti span.cm-tag { color: #96c0d8; } +.cm-s-yeti span.cm-attribute { color: #9fb96e; } +.cm-s-yeti span.cm-qualifier { color: #96c0d8; } +.cm-s-yeti span.cm-property { color: #a074c4; } +.cm-s-yeti span.cm-builtin { color: #a074c4; } +.cm-s-yeti span.cm-variable-3 { color: #96c0d8; } +.cm-s-yeti .CodeMirror-activeline-background { background: #E7E4E0; } +.cm-s-yeti .CodeMirror-matchingbracket { text-decoration: underline; } diff --git a/www/code/codemirror-5.13.2/theme/zenburn.css b/www/code/codemirror-5.13.2/theme/zenburn.css new file mode 100644 index 000000000..781c40aca --- /dev/null +++ b/www/code/codemirror-5.13.2/theme/zenburn.css @@ -0,0 +1,37 @@ +/** + * " + * Using Zenburn color palette from the Emacs Zenburn Theme + * https://github.com/bbatsov/zenburn-emacs/blob/master/zenburn-theme.el + * + * Also using parts of https://github.com/xavi/coderay-lighttable-theme + * " + * From: https://github.com/wisenomad/zenburn-lighttable-theme/blob/master/zenburn.css + */ + +.cm-s-zenburn .CodeMirror-gutters { background: #3f3f3f !important; } +.cm-s-zenburn .CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { color: #999; } +.cm-s-zenburn .CodeMirror-cursor { border-left: 1px solid white; } +.cm-s-zenburn { background-color: #3f3f3f; color: #dcdccc; } +.cm-s-zenburn span.cm-builtin { color: #dcdccc; font-weight: bold; } +.cm-s-zenburn span.cm-comment { color: #7f9f7f; } +.cm-s-zenburn span.cm-keyword { color: #f0dfaf; font-weight: bold; } +.cm-s-zenburn span.cm-atom { color: #bfebbf; } +.cm-s-zenburn span.cm-def { color: #dcdccc; } +.cm-s-zenburn span.cm-variable { color: #dfaf8f; } +.cm-s-zenburn span.cm-variable-2 { color: #dcdccc; } +.cm-s-zenburn span.cm-string { color: #cc9393; } +.cm-s-zenburn span.cm-string-2 { color: #cc9393; } +.cm-s-zenburn span.cm-number { color: #dcdccc; } +.cm-s-zenburn span.cm-tag { color: #93e0e3; } +.cm-s-zenburn span.cm-property { color: #dfaf8f; } +.cm-s-zenburn span.cm-attribute { color: #dfaf8f; } +.cm-s-zenburn span.cm-qualifier { color: #7cb8bb; } +.cm-s-zenburn span.cm-meta { color: #f0dfaf; } +.cm-s-zenburn span.cm-header { color: #f0efd0; } +.cm-s-zenburn span.cm-operator { color: #f0efd0; } +.cm-s-zenburn span.CodeMirror-matchingbracket { box-sizing: border-box; background: transparent; border-bottom: 1px solid; } +.cm-s-zenburn span.CodeMirror-nonmatchingbracket { border-bottom: 1px solid; background: none; } +.cm-s-zenburn .CodeMirror-activeline { background: #000000; } +.cm-s-zenburn .CodeMirror-activeline-background { background: #000000; } +.cm-s-zenburn div.CodeMirror-selected { background: #545454; } +.cm-s-zenburn .CodeMirror-focused div.CodeMirror-selected { background: #4f4f4f; } diff --git a/www/code/inner.html b/www/code/inner.html index bc9cdbed5..c031930ae 100644 --- a/www/code/inner.html +++ b/www/code/inner.html @@ -3,25 +3,25 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - -
    -

    Kotlin mode

    - - -
    - - -

    Mode for Kotlin (http://kotlin.jetbrains.org/)

    -

    Developed by Hadi Hariri (https://github.com/hhariri).

    -

    MIME type defined: text/x-kotlin.

    -
    diff --git a/www/code/mode/kotlin/kotlin.js b/www/code/mode/kotlin/kotlin.js deleted file mode 100644 index e9a6a94e6..000000000 --- a/www/code/mode/kotlin/kotlin.js +++ /dev/null @@ -1,284 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(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("kotlin", function (config, parserConfig) { - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - var multiLineStrings = parserConfig.multiLineStrings; - - var keywords = words( - "package continue return object while break class data trait interface throw super" + - " when type this else This try val var fun for is in if do as true false null get set"); - var softKeywords = words("import" + - " where by get set abstract enum open annotation override private public internal" + - " protected catch out vararg inline finally final ref"); - var blockKeywords = words("catch class do else finally for if where try while enum"); - var atoms = words("null true false this"); - - var curPunc; - - function tokenBase(stream, state) { - var ch = stream.next(); - if (ch == '"' || ch == "'") { - return startString(ch, stream, state); - } - // Wildcard import w/o trailing semicolon (import smth.*) - if (ch == "." && stream.eat("*")) { - return "word"; - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - curPunc = ch; - return null; - } - if (/\d/.test(ch)) { - if (stream.eat(/eE/)) { - stream.eat(/\+\-/); - stream.eatWhile(/\d/); - } - return "number"; - } - if (ch == "/") { - if (stream.eat("*")) { - state.tokenize.push(tokenComment); - return tokenComment(stream, state); - } - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - if (expectExpression(state.lastToken)) { - return startString(ch, stream, state); - } - } - // Commented - if (ch == "-" && stream.eat(">")) { - curPunc = "->"; - return null; - } - if (/[\-+*&%=<>!?|\/~]/.test(ch)) { - stream.eatWhile(/[\-+*&%=<>|~]/); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - - var cur = stream.current(); - if (atoms.propertyIsEnumerable(cur)) { - return "atom"; - } - if (softKeywords.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "softKeyword"; - } - - if (keywords.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "keyword"; - } - return "word"; - } - - tokenBase.isBase = true; - - function startString(quote, stream, state) { - var tripleQuoted = false; - if (quote != "/" && stream.eat(quote)) { - if (stream.eat(quote)) tripleQuoted = true; - else return "string"; - } - function t(stream, state) { - var escaped = false, next, end = !tripleQuoted; - - while ((next = stream.next()) != null) { - if (next == quote && !escaped) { - if (!tripleQuoted) { - break; - } - if (stream.match(quote + quote)) { - end = true; - break; - } - } - - if (quote == '"' && next == "$" && !escaped && stream.eat("{")) { - state.tokenize.push(tokenBaseUntilBrace()); - return "string"; - } - - if (next == "$" && !escaped && !stream.eat(" ")) { - state.tokenize.push(tokenBaseUntilSpace()); - return "string"; - } - escaped = !escaped && next == "\\"; - } - if (multiLineStrings) - state.tokenize.push(t); - if (end) state.tokenize.pop(); - return "string"; - } - - state.tokenize.push(t); - return t(stream, state); - } - - function tokenBaseUntilBrace() { - var depth = 1; - - function t(stream, state) { - if (stream.peek() == "}") { - depth--; - if (depth == 0) { - state.tokenize.pop(); - return state.tokenize[state.tokenize.length - 1](stream, state); - } - } else if (stream.peek() == "{") { - depth++; - } - return tokenBase(stream, state); - } - - t.isBase = true; - return t; - } - - function tokenBaseUntilSpace() { - function t(stream, state) { - if (stream.eat(/[\w]/)) { - var isWord = stream.eatWhile(/[\w]/); - if (isWord) { - state.tokenize.pop(); - return "word"; - } - } - state.tokenize.pop(); - return "string"; - } - - t.isBase = true; - return t; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize.pop(); - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function expectExpression(last) { - return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) || - last == "newstatement" || last == "keyword" || last == "proplabel"; - } - - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - - function pushContext(state, col, type) { - return state.context = new Context(state.indented, col, type, null, state.context); - } - - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - // Interface - - return { - startState: function (basecolumn) { - return { - tokenize: [tokenBase], - context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false), - indented: 0, - startOfLine: true, - lastToken: null - }; - }, - - token: function (stream, state) { - var ctx = state.context; - if (stream.sol()) { - if (ctx.align == null) ctx.align = false; - state.indented = stream.indentation(); - state.startOfLine = true; - // Automatic semicolon insertion - if (ctx.type == "statement" && !expectExpression(state.lastToken)) { - popContext(state); - ctx = state.context; - } - } - if (stream.eatSpace()) return null; - curPunc = null; - var style = state.tokenize[state.tokenize.length - 1](stream, state); - if (style == "comment") return style; - if (ctx.align == null) ctx.align = true; - if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); - // Handle indentation for {x -> \n ... } - else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") { - popContext(state); - state.context.align = false; - } - else if (curPunc == "{") pushContext(state, stream.column(), "}"); - else if (curPunc == "[") pushContext(state, stream.column(), "]"); - else if (curPunc == "(") pushContext(state, stream.column(), ")"); - else if (curPunc == "}") { - while (ctx.type == "statement") ctx = popContext(state); - if (ctx.type == "}") ctx = popContext(state); - while (ctx.type == "statement") ctx = popContext(state); - } - else if (curPunc == ctx.type) popContext(state); - else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) - pushContext(state, stream.column(), "statement"); - state.startOfLine = false; - state.lastToken = curPunc || style; - return style; - }, - - indent: function (state, textAfter) { - if (!state.tokenize[state.tokenize.length - 1].isBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), ctx = state.context; - if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev; - var closing = firstChar == ctx.type; - if (ctx.type == "statement") { - return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit); - } - else if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indented + (closing ? 0 : config.indentUnit); - }, - - closeBrackets: {triples: "'\""}, - electricChars: "{}", - blockCommentStart: "/*", - blockCommentEnd: "*/", - lineComment: "//" - }; -}); - -CodeMirror.defineMIME("text/x-kotlin", "kotlin"); - -}); diff --git a/www/code/mode/mscgen/index.html b/www/code/mode/mscgen/index.html deleted file mode 100644 index 0517825fb..000000000 --- a/www/code/mode/mscgen/index.html +++ /dev/null @@ -1,65 +0,0 @@ - - -CodeMirror: Oz mode - - - - - - - - - -
    -

    MscGen mode

    - -
    - - - -

    MIME types defined: text/x-mscgen

    -
    diff --git a/www/code/mode/mscgen/index_msgenny.html b/www/code/mode/mscgen/index_msgenny.html deleted file mode 100644 index 1664f3051..000000000 --- a/www/code/mode/mscgen/index_msgenny.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - CodeMirror: msgenny mode - - - - - - -

    CodeMirror: msgenny mode

    - -
    - - - -

    MIME types defined: text/x-msgenny

    - - diff --git a/www/code/mode/mscgen/index_xu.html b/www/code/mode/mscgen/index_xu.html deleted file mode 100644 index 2f7bf9ec0..000000000 --- a/www/code/mode/mscgen/index_xu.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - CodeMirror: xu mode - - - - - - -

    CodeMirror: xù mode

    - -
    - - - -

    MIME types defined: text/x-xu

    - - diff --git a/www/code/mode/swift/swift.js b/www/code/mode/swift/swift.js deleted file mode 100644 index cf784feff..000000000 --- a/www/code/mode/swift/swift.js +++ /dev/null @@ -1,203 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -// Swift mode created by Michael Kaminsky https://github.com/mkaminsky11 - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") - mod(require("../../lib/codemirror")) - else if (typeof define == "function" && define.amd) - define(["../../lib/codemirror"], mod) - else - mod(CodeMirror) -})(function(CodeMirror) { - "use strict" - - function trim(str) { return /^\s*(.*?)\s*$/.exec(str)[1] } - - var separators = [" ","\\\+","\\\-","\\\(","\\\)","\\\*","/",":","\\\?","\\\<","\\\>"," ","\\\."] - var tokens = new RegExp(separators.join("|"),"g") - - function getWord(string, pos) { - var index = -1, count = 1 - var words = string.split(tokens) - for (var i = 0; i < words.length; i++) { - for(var j = 1; j <= words[i].length; j++) { - if (count==pos) index = i - count++ - } - count++ - } - var ret = ["", ""] - if (pos == 0) { - ret[1] = words[0] - ret[0] = null - } else { - ret[1] = words[index] - ret[0] = words[index-1] - } - return ret - } - - CodeMirror.defineMode("swift", function() { - var keywords=["var","let","class","deinit","enum","extension","func","import","init","let","protocol","static","struct","subscript","typealias","var","as","dynamicType","is","new","super","self","Self","Type","__COLUMN__","__FILE__","__FUNCTION__","__LINE__","break","case","continue","default","do","else","fallthrough","if","in","for","return","switch","where","while","associativity","didSet","get","infix","inout","left","mutating","none","nonmutating","operator","override","postfix","precedence","prefix","right","set","unowned","unowned(safe)","unowned(unsafe)","weak","willSet"] - var commonConstants=["Infinity","NaN","undefined","null","true","false","on","off","yes","no","nil","null","this","super"] - var types=["String","bool","int","string","double","Double","Int","Float","float","public","private","extension"] - var numbers=["0","1","2","3","4","5","6","7","8","9"] - var operators=["+","-","/","*","%","=","|","&","<",">"] - var punc=[";",",",".","(",")","{","}","[","]"] - var delimiters=/^(?:[()\[\]{},:`=;]|\.\.?\.?)/ - var identifiers=/^[_A-Za-z$][_A-Za-z$0-9]*/ - var properties=/^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/ - var regexPrefixes=/^(\/{3}|\/)/ - - return { - startState: function() { - return { - prev: false, - string: false, - escape: false, - inner: false, - comment: false, - num_left: 0, - num_right: 0, - doubleString: false, - singleString: false - } - }, - token: function(stream, state) { - if (stream.eatSpace()) return null - - var ch = stream.next() - if (state.string) { - if (state.escape) { - state.escape = false - return "string" - } else { - if ((ch == "\"" && (state.doubleString && !state.singleString) || - (ch == "'" && (!state.doubleString && state.singleString))) && - !state.escape) { - state.string = false - state.doubleString = false - state.singleString = false - return "string" - } else if (ch == "\\" && stream.peek() == "(") { - state.inner = true - state.string = false - return "keyword" - } else if (ch == "\\" && stream.peek() != "(") { - state.escape = true - state.string = true - return "string" - } else { - return "string" - } - } - } else if (state.comment) { - if (ch == "*" && stream.peek() == "/") { - state.prev = "*" - return "comment" - } else if (ch == "/" && state.prev == "*") { - state.prev = false - state.comment = false - return "comment" - } - return "comment" - } else { - if (ch == "/") { - if (stream.peek() == "/") { - stream.skipToEnd() - return "comment" - } - if (stream.peek() == "*") { - state.comment = true - return "comment" - } - } - if (ch == "(" && state.inner) { - state.num_left++ - return null - } - if (ch == ")" && state.inner) { - state.num_right++ - if (state.num_left == state.num_right) { - state.inner=false - state.string=true - } - return null - } - - var ret = getWord(stream.string, stream.pos) - var the_word = ret[1] - var prev_word = ret[0] - - if (operators.indexOf(ch + "") > -1) return "operator" - if (punc.indexOf(ch) > -1) return "punctuation" - - if (typeof the_word != "undefined") { - the_word = trim(the_word) - if (typeof prev_word != "undefined") prev_word = trim(prev_word) - if (the_word.charAt(0) == "#") return null - - if (types.indexOf(the_word) > -1) return "def" - if (commonConstants.indexOf(the_word) > -1) return "atom" - if (numbers.indexOf(the_word) > -1) return "number" - - if ((numbers.indexOf(the_word.charAt(0) + "") > -1 || - operators.indexOf(the_word.charAt(0) + "") > -1) && - numbers.indexOf(ch) > -1) { - return "number" - } - - if (keywords.indexOf(the_word) > -1 || - keywords.indexOf(the_word.split(tokens)[0]) > -1) - return "keyword" - if (keywords.indexOf(prev_word) > -1) return "def" - } - if (ch == '"' && !state.doubleString) { - state.string = true - state.doubleString = true - return "string" - } - if (ch == "'" && !state.singleString) { - state.string = true - state.singleString = true - return "string" - } - if (ch == "(" && state.inner) - state.num_left++ - if (ch == ")" && state.inner) { - state.num_right++ - if (state.num_left == state.num_right) { - state.inner = false - state.string = true - } - return null - } - if (stream.match(/^-?[0-9\.]/, false)) { - if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i) || - stream.match(/^-?\d+\.\d*/) || - stream.match(/^-?\.\d+/)) { - if (stream.peek() == ".") stream.backUp(1) - return "number" - } - if (stream.match(/^-?0x[0-9a-f]+/i) || - stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/) || - stream.match(/^-?0(?![\dx])/i)) - return "number" - } - if (stream.match(regexPrefixes)) { - if (stream.current()!="/" || stream.match(/^.*\//,false)) return "string" - else stream.backUp(1) - } - if (stream.match(delimiters)) return "punctuation" - if (stream.match(identifiers)) return "variable" - if (stream.match(properties)) return "property" - return "variable" - } - } - } - }) - - CodeMirror.defineMIME("text/x-swift","swift") -}) diff --git a/www/code/rt_codemirror.js b/www/code/rt_codemirror.js index 7135651ae..1e3b61ea3 100644 --- a/www/code/rt_codemirror.js +++ b/www/code/rt_codemirror.js @@ -309,7 +309,6 @@ define([ var incomingPatch = function () { if (isErrorState || initializing) { return; } var textAreaVal = $(textArea).val(); - console.log($(textArea).val()); userDocBeforePatch = userDocBeforePatch || textAreaVal; if (userDocBeforePatch !== textAreaVal) { //error(false, "userDocBeforePatch !== textAreaVal"); @@ -346,7 +345,8 @@ define([ } $(textArea).val(newValue); userDocBeforePatch = newValue; - cmEditor.setValue(newValue); + cmEditor.setCursor({line:0, ch:0}); + try { cmEditor.setValue(newValue); } catch (err) { console.error(err); } if(newCursor) { cmEditor.setCursor(newCursor); } diff --git a/www/code/toolbar.js b/www/code/toolbar.js index 8871833ff..ea0afa9c8 100644 --- a/www/code/toolbar.js +++ b/www/code/toolbar.js @@ -123,19 +123,6 @@ define([ return $container.find('#'+id)[0]; }; - var getOtherUsers = function(myUserName, userList) { - var i = 0; - var list = ''; - userList.forEach(function(user) { - if(user !== myUserName) { - if(i === 0) list = ' : '; - list += user + ', '; - i++; - } - }); - return (i > 0) ? list.slice(0, -2) : list; - } - var updateUserList = function (myUserName, listElement, userList) { var meIdx = userList.indexOf(myUserName); if (meIdx === -1) { @@ -143,11 +130,11 @@ define([ return; } if (userList.length === 1) { - listElement.innerHTML = Messages.editingAlone; + listElement.textContent = Messages.editingAlone; } else if (userList.length === 2) { - listElement.innerHTML = Messages.editingWithOneOtherPerson + getOtherUsers(myUserName, userList); + listElement.textContent = Messages.editingWithOneOtherPerson; } else { - listElement.innerHTML = Messages.editingWith + ' ' + (userList.length - 1) + ' ' + Messages.otherPeople + getOtherUsers(myUserName, userList); + listElement.textContent = Messages.editingWith + ' ' + (userList.length - 1) + ' ' + Messages.otherPeople; } }; From 360fee9febdfcc172cb656cb4d0ccd4a7d63dbfb Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 11 Apr 2016 17:58:30 +0200 Subject: [PATCH 47/65] Fix a new cursor error with Codepad --- www/code/rt_codemirror.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/www/code/rt_codemirror.js b/www/code/rt_codemirror.js index 1e3b61ea3..08b0d625e 100644 --- a/www/code/rt_codemirror.js +++ b/www/code/rt_codemirror.js @@ -173,8 +173,8 @@ define([ newCursor.ch = removedTextColumnIndex; } } - else if(cursor.line === removedTextLineNumber && cursor.ch > removedTextLineNumber) { - newCursor.ch -= Math.min(length, cursor.ch-removedTextLineNumber); + else if(cursor.line === removedTextLineNumber && cursor.ch > removedTextColumnIndex) { + newCursor.ch -= Math.min(length, cursor.ch-removedTextColumnIndex); } return newCursor; }; @@ -327,7 +327,8 @@ define([ var oldCursorCMEnd = cmEditor.getCursor('to'); var newCursor; var newSelection; - if(oldCursorCMStart !== oldCursorCMEnd) { // Selection + if(oldCursorCMStart.line !== oldCursorCMEnd.line + && oldCursorCMStart.ch !== oldCursorCMEnd.ch) { // Selection if (op.toRemove > 0) { newSelection = [transformCursorCMRemove(oldValue, oldCursorCMStart, op.offset, op.toRemove), transformCursorCMRemove(oldValue, oldCursorCMEnd, op.offset, op.toRemove)]; } From 94e57e4a2641e491531198c9a2a3ebec648145d2 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 14:21:31 +0200 Subject: [PATCH 48/65] update location of text-patcher.js --- www/common/realtime-input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index 908d20110..d417dfbc0 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -20,7 +20,7 @@ define([ '/common/netflux-client.js', '/common/crypto.js', '/common/toolbar.js', - '/_socket/text-patcher.js', + '/common/TextPatcher.js', '/common/es6-promise.min.js', '/common/chainpad.js', '/bower_components/jquery/dist/jquery.min.js', From e6ab03164cee5e4a5f0c61e76b219424286d50df Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 14:34:47 +0200 Subject: [PATCH 49/65] import TypingTests.js and expose at the console RTWYSIWYG-54 > implement tests for components of the WYSIWYG editor --- www/pad/main.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/www/pad/main.js b/www/pad/main.js index b5f43e722..b29c4d8ac 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -8,10 +8,11 @@ define([ '/common/toolbar.js', '/common/cursor.js', '/common/json-ot.js', + '/common/TypingTests.js', '/bower_components/diff-dom/diffDOM.js', '/bower_components/jquery/dist/jquery.min.js', '/customize/pad.js' -], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT) { +], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT, TypingTest) { var $ = window.jQuery; var ifrw = $('#pad-iframe')[0].contentWindow; var Ckeditor; // to be initialized later... @@ -328,6 +329,17 @@ define([ inner.addEventListener('keydown', cursor.brFix); editor.on('change', propogate); + + // export the typing tests to the window. + // call like `test = easyTest()` + // terminate the test like `test.cancel()` + var easyTest = window.easyTest = function () { + cursor.update(); + var start = cursor.Range.start; + var test = TypingTest.testInput(inner, start.el, start.offset, propogate); + propogate(); + return test; + }; }); }; From 07fdeebe17929e3f1feb74940cf2d401474983a1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 14:50:32 +0200 Subject: [PATCH 50/65] LogStore.js : JSHint compliance and slight reformat --- storage/LogStore.js | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/storage/LogStore.js b/storage/LogStore.js index 4f1b7ad6f..d588c82f5 100644 --- a/storage/LogStore.js +++ b/storage/LogStore.js @@ -1,20 +1,18 @@ - var Fs = require("fs"); +var Fs = require("fs"); - var message = function(file, msg) { - file.write(msg+"\n"); - } +var message = function(file, msg) { + file.write(msg+"\n"); +}; - var create = module.exports.create = function(filePath, backingStore) { +var create = module.exports.create = function(filePath, backingStore) { - var file = Fs.createWriteStream(filePath, {flags: 'a+'}); + var file = Fs.createWriteStream(filePath, {flags: 'a+'}); - return { - message: function(channel, msg, callback) { - message(file, msg); - backingStore.message(channel, msg, callback); - }, - getMessages: backingStore.getMessages - } - - } - + return { + message: function(channel, msg, callback) { + message(file, msg); + backingStore.message(channel, msg, callback); + }, + getMessages: backingStore.getMessages + }; +}; From 5cead3fad336fda4058735e92af43e06da4de383 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 14:51:06 +0200 Subject: [PATCH 51/65] add missing semicolons to TextPatcher.js --- www/common/TextPatcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/TextPatcher.js b/www/common/TextPatcher.js index 87cefa9dc..66858826d 100644 --- a/www/common/TextPatcher.js +++ b/www/common/TextPatcher.js @@ -40,7 +40,7 @@ var diff = function (oldval, newval) { toInsert: toInsert, toRemove: toRemove }; -} +}; /* patch accepts a realtime facade and an operation (which might be falsey) it applies the operation to the realtime as components (remove/insert) @@ -94,7 +94,7 @@ var create = function(config) { // *** remote -> local changes ctx.onPatch(function(pos, length) { - content = ctx.getUserDoc() + content = ctx.getUserDoc(); }); // propogate() From 3b8238ef3b62a6f988afd497ab27da4dbab90789 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 14:51:32 +0200 Subject: [PATCH 52/65] add missing semicolons to pad/main.js jshint compliance --- www/pad/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/pad/main.js b/www/pad/main.js index b29c4d8ac..7e2e594f5 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -156,7 +156,7 @@ define([ var createChangeName = function(id, $container) { var buttonElmt = $container.find('#'+id)[0]; buttonElmt.addEventListener("click", function() { - var newName = prompt("Change your name :", myUserName) + var newName = window.prompt("Change your name :", myUserName); if (newName && newName.trim()) { var myUserNameTemp = newName.trim(); if(newName.trim().length > 32) { From 06a3b63923e309b7ad2f8ead02e0583cd3cac334 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 14:58:24 +0200 Subject: [PATCH 53/65] jshint compliance for toolbar.js --- www/common/toolbar.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 6b6323684..942fb586d 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -134,20 +134,20 @@ define([ var data = (userData) ? (userData[user] || null) : null; var userName = (data) ? data.name : null; if(userName) { - if(i === 0) list = ' : '; + if(i === 0) { list = ' : '; } list += userName + ', '; i++; } } }); return (i > 0) ? list.slice(0, -2) : list; - } + }; var createChangeName = function($container, userList, buttonID) { var id = uid(); userList.innerHTML = 'Change name'; return $container.find('#'+id)[0]; - } + }; var updateUserList = function (myUserName, listElement, userList, userData) { var meIdx = userList.indexOf(myUserName); @@ -241,7 +241,7 @@ define([ userData = newUserData; } updateUserList(myUserName, userListElement, users, userData); - } + }; var ks = function () { if (connected) { kickSpinner(spinner, false); } From 793149a608f62432cb2cf030c13678c56d9ee65e Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 15:00:46 +0200 Subject: [PATCH 54/65] json-ot.js : jshint compliance --- www/common/json-ot.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/www/common/json-ot.js b/www/common/json-ot.js index 98e1af4ec..d8d75b7b6 100644 --- a/www/common/json-ot.js +++ b/www/common/json-ot.js @@ -5,15 +5,16 @@ define([ var JsonOT = {}; var validate = JsonOT.validate = function (text, toTransform, transformBy) { + var resultOp, text2, text3; try { // text = O (mutual common ancestor) // toTransform = A (the first incoming operation) // transformBy = B (the second incoming operation) // threeway merge (0, A, B) - var resultOp = ChainPad.Operation.transform0(text, toTransform, transformBy); - var text2 = ChainPad.Operation.apply(transformBy, text); - var text3 = ChainPad.Operation.apply(resultOp, text2); + resultOp = ChainPad.Operation.transform0(text, toTransform, transformBy); + text2 = ChainPad.Operation.apply(transformBy, text); + text3 = ChainPad.Operation.apply(resultOp, text2); try { JSON.parse(text3); return resultOp; @@ -35,8 +36,7 @@ define([ } } catch (x) { console.error(x); - console.error(e); - var info = window.REALTIME_MODULE.ot_applyError = { + window.REALTIME_MODULE.ot_applyError = { type: 'resultParseError', resultOp: resultOp, @@ -46,7 +46,7 @@ define([ text1: text, text2: text2, text3: text3, - error: e + error: x }; console.log('Debugging info available at `window.REALTIME_MODULE.ot_applyError`'); } From 9f9cd8d8cd32f5118c06f7094c464d72fc888047 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 15:07:05 +0200 Subject: [PATCH 55/65] realtime-input.js : fixes for jshint compliance --- www/common/realtime-input.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index d417dfbc0..949c1ee37 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -window.Reflect = { has: (x,y) => { return (y in x); } }; +window.Reflect = { has: function (x,y) { return (y in x); } }; define([ '/common/messages.js', '/common/netflux-client.js', @@ -130,7 +130,10 @@ define([ var hc = (wc && wc.history_keeper) ? wc.history_keeper : null; if(wc && (msg === 0 || msg === '0')) { + // FIXME onReady is defined below, so JSHINT complains + /* jshint ignore:start */ onReady(wc, network); + /* jshint ignore:end */ return; } else if (peer === hc){ @@ -255,7 +258,7 @@ define([ realtime: realtime }); } - } + }; var onOpen = function(wc, network) { channel = wc.id; @@ -314,10 +317,10 @@ define([ var findChannelById = function(webChannels, channelId) { var webChannel; webChannels.forEach(function(chan) { - if(chan.id == channelId) { webChannel = chan; return;} + if(chan.id === channelId) { webChannel = chan; return;} }); return webChannel; - } + }; // Connect to the WebSocket channel Netflux.connect(websocketUrl).then(function(network) { @@ -331,7 +334,7 @@ define([ onOpen(wc, network); }, function(error) { console.error(error); - }) + }); }, function(error) { warn(error); }); From a25152d1b685681625f171dba83836b03a68e4c5 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 15:07:31 +0200 Subject: [PATCH 56/65] ignore external files and anything relying on es6 --- .jshintignore | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.jshintignore b/.jshintignore index b34fb0d40..3780e2719 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,6 +1,6 @@ node_modules/ www/bower_components/ -www/code/codemirror-5.7/ +www/code/codemirror* www/code/mode/ www/code/codemirror.js www/pad/rangy.js @@ -12,5 +12,14 @@ storage/kad.js www/common/otaml.js www/common/diffDOM.js www/common/netflux.js + +www/padrtc +www/common/netflux-client.js +www/common/es6-promise.min.js +www/_pad + +NetFluxWebsocketSrv.js NetFluxWebsocketServer.js WebRTCSrv.js + +www/assert/hyperscript.js From ca94d30386217c2d6865e1e1660cfcead60457f9 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 15:35:07 +0200 Subject: [PATCH 57/65] detect falsey operations in OT and ignore them --- www/common/json-ot.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/www/common/json-ot.js b/www/common/json-ot.js index d8d75b7b6..a785d524c 100644 --- a/www/common/json-ot.js +++ b/www/common/json-ot.js @@ -4,6 +4,9 @@ define([ var ChainPad = window.ChainPad; var JsonOT = {}; +/* FIXME + resultOp after transform0() might be null, in which case you should return null + because it is simply a transformation which yields a "do nothing" operation */ var validate = JsonOT.validate = function (text, toTransform, transformBy) { var resultOp, text2, text3; try { @@ -13,6 +16,11 @@ define([ // threeway merge (0, A, B) resultOp = ChainPad.Operation.transform0(text, toTransform, transformBy); + + /* if after operational transform we find that no op is necessary + return null to ignore this patch */ + if (!resultOp) { return null; } + text2 = ChainPad.Operation.apply(transformBy, text); text3 = ChainPad.Operation.apply(resultOp, text2); try { From a0999d1d2b60a4cb8e13bcec4b403878d806c8b3 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 15:36:03 +0200 Subject: [PATCH 58/65] add a comment about why hashes are being stubbed --- www/pad/main.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/pad/main.js b/www/pad/main.js index 7e2e594f5..e2e400cf2 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -45,6 +45,11 @@ define([ }; var andThen = function (Ckeditor) { + /* This is turned off because we prefer that the channel name + be chosen by the server, not generated by the client. + + We still need a key, so we use genKey() + */ // $(window).on('hashchange', function() { // window.location.reload(); // }); From 9f45ccb2d96c6dba4ff994fbda27f289f7d66292 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 16:47:30 +0200 Subject: [PATCH 59/65] use console.error for stack traces --- www/common/netflux-client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/netflux-client.js b/www/common/netflux-client.js index 38c6bf44b..902abb483 100644 --- a/www/common/netflux-client.js +++ b/www/common/netflux-client.js @@ -151,7 +151,7 @@ const onMessage = (ctx, evt) => { handlers = chan._.onMessage; } handlers.forEach((h) => { - try { h(msg[4], msg[1]); } catch (e) { console.log(e.stack); } + try { h(msg[4], msg[1]); } catch (e) { console.error(e); } }); } From f75c246029e543ae26bb4bceb27198e3b6cf4896 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 17:17:14 +0200 Subject: [PATCH 60/65] remove dead code from pad/main.js --- www/pad/main.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/www/pad/main.js b/www/pad/main.js index e2e400cf2..1465681ae 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -181,13 +181,6 @@ define([ // apply patches, and try not to lose the cursor in the process! var applyHjson = function (shjson) { - // var hjson = JSON.parse(shjson); - // var peerUserList = hjson[hjson.length-1]; - // if(peerUserList.metadata) { - // var userData = peerUserList.metadata; - // addToUserList(userData); - // delete hjson[hjson.length-1]; - // } var userDocStateDom = hjsonToDom(JSON.parse(shjson)); userDocStateDom.setAttribute("contenteditable", "true"); // lol wtf var patch = (DD).diff(inner, userDocStateDom); @@ -296,8 +289,6 @@ define([ return hj; }; - // $textarea.val(JSON.stringify(Convert.dom.to.hjson(inner))); - /* It's incredibly important that you assign 'rti.onLocal' It's used inside of realtimeInput to make sure that all changes make it into chainpad. @@ -307,16 +298,14 @@ define([ the code less extensible. */ var propogate = rti.onLocal = function () { - /* if the problem were a matter of external patches being - applied while a local patch were in progress, then we would - expect to be able to check and find - 'module.localChangeInProgress' with a non-zero value while - we were applying a remote change. - */ + // serialize your DOM into an object var hjson = Hyperjson.fromDOM(inner, isNotMagicLine, brFilter); + + // append the userlist to the hyperjson structure if(Object.keys(myData).length > 0) { hjson[hjson.length] = {metadata: userList}; } + // stringify the json and send it into chainpad var shjson = JSON.stringify(hjson); if (!rti.patchText(shjson)) { return; From 19cd991dba679112c9b75166f2891f0a3cfa3780 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 12 Apr 2016 17:29:20 +0200 Subject: [PATCH 61/65] trim dead code, comment, very minor optimizations --- www/common/realtime-input.js | 156 +++++++++++++++++------------------ 1 file changed, 76 insertions(+), 80 deletions(-) diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index 949c1ee37..bf173263d 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -89,15 +89,12 @@ define([ var allMessages = []; var initializing = true; - var recoverableErrorCount = 0; + var recoverableErrorCount = 0; // unused var toReturn = {}; var messagesHistory = []; var chainpadAdapter = {}; var realtime; - // define this in case it gets called before the rest of our stuff is ready. - var onEvent = toReturn.onEvent = function (newText) { }; - var parseMessage = function (msg) { var res ={}; // two or more? use a for @@ -122,67 +119,66 @@ define([ content.length + ':' + content; }; - var whoami = new RegExp(userName.replace(/[\/\+]/g, function (c) { - return '\\' +c; - })); + var userList = { + onChange : function() {}, + users: [] + }; - var onMessage = function(peer, msg, wc, network) { + var onJoining = function(peer) { + if(peer.length !== 32) { return; } + var list = userList.users; + var index = list.indexOf(peer); + if(index === -1) { + userList.users.push(peer); + } + userList.onChange(); + }; + + var onReady = function(wc, network) { + if(config.onInit) { + config.onInit({ + myID: wc.myID, + realtime: realtime, + getLag: network.getLag, + userList: userList + }); + } + // Trigger onJoining with our own Cryptpad username to tell the toolbar that we are synced + onJoining(wc.myID); + // we're fully synced + initializing = false; + + // execute an onReady callback if one was supplied + if (config.onReady) { + config.onReady({ + realtime: realtime + }); + } + }; + + var onMessage = function(peer, msg, wc, network) { + // unpack the history keeper from the webchannel var hc = (wc && wc.history_keeper) ? wc.history_keeper : null; + if(wc && (msg === 0 || msg === '0')) { - // FIXME onReady is defined below, so JSHINT complains - /* jshint ignore:start */ onReady(wc, network); - /* jshint ignore:end */ return; } - else if (peer === hc){ + if (peer === hc){ + // if the peer is the 'history keeper', extract their message msg = JSON.parse(msg)[4]; } var message = chainpadAdapter.msgIn(peer, msg); verbose(message); allMessages.push(message); - // if (!initializing) { - // if (toReturn.onLocal) { - // toReturn.onLocal(); - // } - // } - realtime.message(message); - if (/\[5,/.test(message)) { verbose("pong"); } - - if (!initializing) { - if (/\[2,/.test(message)) { - //verbose("Got a patch"); - if (whoami.test(message)) { - //verbose("Received own message"); - } else { - //verbose("Received remote message"); - // obviously this is only going to get called if - if (config.onRemote) { - config.onRemote({ - realtime: realtime - }); - } - } - } - } - }; - var userList = { - onChange : function() {}, - users: [] - }; - var onJoining = function(peer) { - if(peer.length !== 32) { return; } - var list = userList.users; - var index = list.indexOf(peer); - if(index === -1) { - userList.users.push(peer); - } - userList.onChange(); + // pass the message into Chainpad + realtime.message(message); }; + // update UI components to show that one of the other peers has left var onLeaving = function(peer) { var list = userList.users; var index = list.indexOf(peer); @@ -192,6 +188,7 @@ define([ userList.onChange(); }; + // shim between chainpad and netflux chainpadAdapter = { msgIn : function(peerId, msg) { var parsed = parseMessage(msg); @@ -223,10 +220,6 @@ define([ } }; - var options = { - key: '' - }; - var createRealtime = function(chan) { return ChainPad.create(userName, passwd, @@ -237,28 +230,6 @@ define([ }); }; - var onReady = function(wc, network) { - if(config.onInit) { - config.onInit({ - myID: wc.myID, - realtime: realtime, - getLag: network.getLag, - userList: userList - }); - } - // Trigger onJoining with our own Cryptpad username to tell the toolbar that we are synced - onJoining(wc.myID); - - // we're fully synced - initializing = false; - - // execute an onReady callback if one was supplied - if (config.onReady) { - config.onReady({ - realtime: realtime - }); - } - }; var onOpen = function(wc, network) { channel = wc.id; @@ -283,6 +254,13 @@ define([ // Open a Chainpad session realtime = createRealtime(); + toReturn.onEvent = function (newText) { + // assert to show that we're not out of sync + if (realtime.getUserDoc() !== newText) { + warn("realtime.getUserDoc() !== newText"); + } + }; + // Sending a message... realtime.onMessage(function(message) { // Filter messages sent by Chainpad to make it compatible with Netflux @@ -298,6 +276,14 @@ define([ } }); + realtime.onPatch(function () { + if (config.onRemote) { + config.onRemote({ + realtime: realtime + }); + } + }); + // Get the channel history var hc; wc.members.forEach(function (p) { @@ -315,21 +301,31 @@ define([ }; var findChannelById = function(webChannels, channelId) { - var webChannel; - webChannels.forEach(function(chan) { - if(chan.id === channelId) { webChannel = chan; return;} - }); - return webChannel; + var webChannel; + + // Array.some terminates once a truthy value is returned + // best case is faster than forEach, though webchannel arrays seem + // to consistently have a length of 1 + webChannels.some(function(chan) { + if(chan.id === channelId) { webChannel = chan; return true;} + }); + return webChannel; }; // Connect to the WebSocket channel Netflux.connect(websocketUrl).then(function(network) { + // pass messages that come out of netflux into our local handler + + // TODO avoid calling findChannelById for each message + // but only if we can prove it won't introduce bugs network.on('message', function (msg, sender) { // Direct message var wchan = findChannelById(network.webChannels, channel); if(wchan) { onMessage(sender, msg, wchan, network); } }); + + // join the netflux network, promise to handle opening of the channel network.join(channel || null).then(function(wc) { onOpen(wc, network); }, function(error) { From 9683fd9aeca0c5d17dc23bc07005bbbf76bd96b5 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Wed, 13 Apr 2016 16:46:31 +0200 Subject: [PATCH 62/65] Always serialize the DOM in one way. --- www/pad/main.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/www/pad/main.js b/www/pad/main.js index 1465681ae..5d2b168d7 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -44,6 +44,16 @@ define([ return true; }; + /* catch `type="_moz"` before it goes over the wire */ + var brFilter = function (hj) { + if (hj[1].type === '_moz') { hj[1].type = undefined; } + return hj; + }; + + var stringifyDOM = function (dom) { + return JSON.stringify(Hyperjson.fromDOM(dom, isNotMagicLine, brFilter)); + }; + var andThen = function (Ckeditor) { /* This is turned off because we prefer that the channel name be chosen by the server, not generated by the client. @@ -189,7 +199,7 @@ define([ var realtimeOptions = { // provide initialstate... - initialState: JSON.stringify(Hyperjson.fromDOM(inner, isNotMagicLine)), + initialState: stringifyDOM(inner) || '{}', // the websocket URL websocketURL: Config.websocketURL, @@ -241,8 +251,7 @@ define([ // Build a new stringified Chainpad hyperjson without metadata to compare with the one build from the dom shjson = JSON.stringify(hjson); - var hjson2 = Hyperjson.fromDOM(inner); - var shjson2 = JSON.stringify(hjson2); + var shjson2 = stringifyDOM(inner); if (shjson2 !== shjson) { console.error("shjson2 !== shjson"); module.realtimeInput.patchText(shjson2); @@ -283,12 +292,6 @@ define([ var rti = module.realtimeInput = realtimeInput.start(realtimeOptions); - /* catch `type="_moz"` before it goes over the wire */ - var brFilter = function (hj) { - if (hj[1].type === '_moz') { hj[1].type = undefined; } - return hj; - }; - /* It's incredibly important that you assign 'rti.onLocal' It's used inside of realtimeInput to make sure that all changes make it into chainpad. From 319f5b95f7e33f42184a0f48e1cd44a163a8b977 Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Wed, 13 Apr 2016 16:43:59 +0200 Subject: [PATCH 63/65] Use canonical serializer for json serialization --- bower.json | 3 ++- www/_socket/main.js | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index ec66645b9..cd96b4d24 100644 --- a/bower.json +++ b/bower.json @@ -28,6 +28,7 @@ "reconnectingWebsocket": "", "diff-dom": "https://github.com/fiduswriter/diffDOM.git#6fdb82c8a4f2096c07c129797a313fe13769e094", "marked": "~0.3.5", - "rangy": "rangy-release#~1.3.0" + "rangy": "rangy-release#~1.3.0", + "json.sortify": "~2.1.0" } } diff --git a/www/_socket/main.js b/www/_socket/main.js index 042bd3c20..73831ac71 100644 --- a/www/_socket/main.js +++ b/www/_socket/main.js @@ -1,3 +1,4 @@ +require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } }); define([ '/api/config?cb=' + Math.random().toString(16).substring(2), '/common/messages.js', @@ -9,15 +10,20 @@ define([ '/common/cursor.js', '/common/json-ot.js', '/common/TypingTests.js', + 'json.sortify', '/bower_components/diff-dom/diffDOM.js', '/bower_components/jquery/dist/jquery.min.js', '/customize/pad.js' -], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT, TypingTest) { +], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT, TypingTest, JSONSortify) { var $ = window.jQuery; var ifrw = $('#pad-iframe')[0].contentWindow; var Ckeditor; // to be initialized later... var DiffDom = window.diffDOM; + var stringify = function (obj) { + return JSONSortify(obj); + }; + window.Hyperjson = Hyperjson; var hjsonToDom = function (H) { @@ -52,7 +58,7 @@ define([ }; var stringifyDOM = function (dom) { - return JSON.stringify(Hyperjson.fromDOM(dom, isNotMagicLine, brFilter)); + return stringify(Hyperjson.fromDOM(dom, isNotMagicLine, brFilter)); }; var andThen = function (Ckeditor) { @@ -233,7 +239,6 @@ define([ applyHjson(shjson); var shjson2 = stringifyDOM(inner); - if (shjson2 !== shjson) { console.error("shjson2 !== shjson"); module.realtimeInput.patchText(shjson2); From ecc41d14af455837ce704c6b2ed2d5d9a57ed535 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Wed, 13 Apr 2016 17:46:41 +0200 Subject: [PATCH 64/65] Use canonical serializer for json serialization --- www/_socket/main.js | 2 +- www/pad/main.js | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/www/_socket/main.js b/www/_socket/main.js index 73831ac71..c93eb82e0 100644 --- a/www/_socket/main.js +++ b/www/_socket/main.js @@ -189,7 +189,7 @@ define([ // reject patch if it results in invalid JSON transformFunction : JsonOT.validate, - websocketURL: Config.websocketURL, + websocketURL: Config.websocketURL+'_old', // username userName: userName, diff --git a/www/pad/main.js b/www/pad/main.js index 5d2b168d7..91b9d009c 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -1,3 +1,4 @@ +require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } }); define([ '/api/config?cb=' + Math.random().toString(16).substring(2), '/common/messages.js', @@ -9,14 +10,19 @@ define([ '/common/cursor.js', '/common/json-ot.js', '/common/TypingTests.js', + 'json.sortify', '/bower_components/diff-dom/diffDOM.js', '/bower_components/jquery/dist/jquery.min.js', '/customize/pad.js' -], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT, TypingTest) { +], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT, TypingTest, JSONSortify) { var $ = window.jQuery; var ifrw = $('#pad-iframe')[0].contentWindow; var Ckeditor; // to be initialized later... var DiffDom = window.diffDOM; + + var stringify = function (obj) { + return JSONSortify(obj); + }; window.Toolbar = Toolbar; window.Hyperjson = Hyperjson; @@ -51,7 +57,7 @@ define([ }; var stringifyDOM = function (dom) { - return JSON.stringify(Hyperjson.fromDOM(dom, isNotMagicLine, brFilter)); + return stringify(Hyperjson.fromDOM(dom, isNotMagicLine, brFilter)); }; var andThen = function (Ckeditor) { @@ -249,7 +255,7 @@ define([ applyHjson(shjson); // Build a new stringified Chainpad hyperjson without metadata to compare with the one build from the dom - shjson = JSON.stringify(hjson); + shjson = stringify(hjson); var shjson2 = stringifyDOM(inner); if (shjson2 !== shjson) { @@ -309,7 +315,7 @@ define([ hjson[hjson.length] = {metadata: userList}; } // stringify the json and send it into chainpad - var shjson = JSON.stringify(hjson); + var shjson = stringify(hjson); if (!rti.patchText(shjson)) { return; } From feaae609ad6463ba1d4e29f303c80db8c4c7ddf9 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 14 Apr 2016 10:48:25 +0200 Subject: [PATCH 65/65] fix broken path for padrtc --- www/padrtc/realtime-input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/padrtc/realtime-input.js b/www/padrtc/realtime-input.js index af0f22a1d..ae2f8c72c 100644 --- a/www/padrtc/realtime-input.js +++ b/www/padrtc/realtime-input.js @@ -20,7 +20,7 @@ define([ '/padrtc/netflux.js', '/common/crypto.js', '/common/toolbar.js', - '/_socket/text-patcher.js', + '/common/TextPatcher.js', '/common/es6-promise.min.js', '/common/chainpad.js', '/bower_components/jquery/dist/jquery.min.js',