resolve merge conflict

This commit is contained in:
ansuz 2016-06-10 11:36:03 +02:00
commit 50695f41ed
817 changed files with 10117 additions and 71217 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@ customization
.*.swp
*.db
/customize/
messages.log
.DS_Store

View File

@ -1,8 +1,25 @@
language: node_js
env:
matrix:
- "BROWSER='firefox:19:Windows 2012'"
- "BROWSER='chrome::Windows 2008'"
branches:
only:
- master
- diffdom
node_js:
- "0.12"
- master
- diffdom
- beta
- netflux
node_js:
- "4.2.1"
before_script:
- npm run-script lint
- cp config.js.dist config.js
- npm install bower
- ./node_modules/bower/bin/bower install
- node ./server.js &
- sleep 2
addons:
sauce_connect:
username: "cjdelisle"
access_key:
secure: "pgGh8YGXLPq6fpdwwK2jnjRtwXPbVWQ/HIFvwX7E6HBpzxxcF2edE8sCdonWW9TP2LQisZFmVLqoSnZWMnjBr2CBAMKMFvaHQDJDQCo4v3BXkID7KgqyKmNcwW+FPfSJ5MxNBro8/GE/awkhZzJLYGUTS5zi/gVuIUwdi6cHI8s="

View File

@ -1,196 +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 <http://www.gnu.org/licenses/>.
*/
// triggers jsh because we're not in a browser and WebSocket is a thing
var WebSocket = require('ws'); // jshint ignore:line
var REGISTER = 0;
var REGISTER_ACK = 1;
var PATCH = 2;
var DISCONNECT = 3;
var PING = 4;
var PONG = 5;
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;
};
// get the password off the message before sending it to other clients.
var popPassword = function (msg) {
var passLen = msg.substring(0,msg.indexOf(':'));
return msg.substring(passLen.length+1 + Number(passLen));
};
var sendMsg = function (msg, socket) {
socket.send(msg);
};
var sendChannelMessage = function (ctx, channel, msg, cb) {
ctx.store.message(channel.name, msg, function () {
channel.forEach(function (user) {
try {
sendMsg(msg, user.socket);
} catch (e) {
console.log(e.stack);
try { user.socket.close(); } catch (e) { }
}
});
if (cb) { cb(); }
});
};
var mkMessage = function (user, channel, content) {
content = JSON.stringify(content);
return user.length + ':' + user +
channel.length + ':' + channel +
content.length + ':' + content;
};
var dropClient = function (ctx, userpass) {
var client = ctx.registeredClients[userpass];
if (client.socket.readyState !== WebSocket.CLOSING
&& client.socket.readyState !== WebSocket.CLOSED)
{
try {
client.socket.close();
} catch (e) {
console.log("Failed to disconnect ["+client.userName+"], attempting to terminate");
try {
client.socket.terminate();
} catch (ee) {
console.log("Failed to terminate ["+client.userName+"] *shrug*");
}
}
}
for (var i = 0; i < client.channels.length; i++) {
var chanName = client.channels[i];
var chan = ctx.channels[chanName];
var idx = chan.indexOf(client);
if (idx < 0) { continue; }
console.log("Removing ["+client.userName+"] 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, mkMessage(client.userName, chanName, [DISCONNECT,0]));
}
}
delete ctx.registeredClients[userpass];
};
var handleMessage = function (ctx, socket, msg) {
var parsed = parseMessage(msg);
var userPass = parsed.user + ':' + parsed.pass;
msg = popPassword(msg);
if (parsed.content[0] === REGISTER) {
if (parsed.user.length === 0) { throw new Error(); }
console.log("[" + userPass + "] registered");
var user = ctx.registeredClients[userPass] = ctx.registeredClients[userPass] || {
channels: [parsed.channelId],
userName: parsed.user
};
if (user.socket && user.socket !== socket) { user.socket.close(); }
user.socket = socket;
var chan = ctx.channels[parsed.channelId] = ctx.channels[parsed.channelId] || [];
var newChan = (chan.length === 0);
chan.name = parsed.channelId;
// we send a register ack right away but then we fallthrough
// to let other users know that we were registered.
sendMsg(mkMessage('', parsed.channelId, [1,0]), socket);
var sendMsgs = function () {
sendChannelMessage(ctx, chan, msg, function () {
chan.push(user);
ctx.store.getMessages(chan.name, function (msg) {
try {
sendMsg(msg, socket);
} catch (e) {
console.log(e.stack);
try { socket.close(); } catch (e) { }
}
});
});
};
if (newChan) {
sendChannelMessage(ctx, chan, mkMessage('', chan.name, [DISCONNECT,0]), sendMsgs);
} else {
sendMsgs();
}
return;
}
if (parsed.content[0] === PING) {
// 31:xwiki:XWiki.Admin-141475016907510:RWJ5xF2+SL17:[5,1414752676547]
// 1:y31:xwiki:XWiki.Admin-141475016907510:RWJ5xF2+SL17:[4,1414752676547]
sendMsg(mkMessage(parsed.user, parsed.channelId, [ PONG, parsed.content[1] ]), socket);
return;
}
var client = ctx.registeredClients[userPass];
if (typeof(client) === 'undefined') { throw new Error('unregistered'); }
var channel = ctx.channels[parsed.channelId];
if (typeof(channel) === 'undefined') { throw new Error('no such channel'); }
if (channel.indexOf(client) === -1) { throw new Error('client not in channel'); }
sendChannelMessage(ctx, channel, msg);
};
var create = module.exports.create = function (socketServer, store) {
var ctx = {
registeredClients: {},
channels: {},
store: store
};
socketServer.on('connection', function(socket) {
socket.on('message', function(message) {
try {
handleMessage(ctx, socket, message);
} catch (e) {
console.log(e.stack);
try { socket.close(); } catch (e) { }
}
});
socket.on('close', function (evt) {
for (var client in ctx.registeredClients) {
if (ctx.registeredClients[client].socket === socket) {
dropClient(ctx, client);
}
}
});
});
};

259
NetfluxWebsocketSrv.js Normal file
View File

@ -0,0 +1,259 @@
;(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;
const USE_FILE_BACKUP_STORAGE = true;
let dropUser;
const now = function () { return (new Date()).getTime(); };
const sendMsg = function (ctx, user, msg) {
try {
if (ctx.config.logToStdout) { 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];
/* Call removeChannel if it is a function and channel removal is
set to true in the config file */
if (ctx.config.removeChannels) {
if (typeof(ctx.store.removeChannel) === 'function') {
ctx.timeouts[chanName] = setTimeout(function () {
ctx.store.removeChannel(chanName, function (err) {
if (err) { console.error("[removeChannelErr]: %s", err); }
else {
console.log("Deleted channel [%s] history from database...", chanName);
}
});
}, ctx.config.channelRemovalTimeout);
} else {
console.error("You have configured your server to remove empty channels, " +
"however, the database adaptor you are using has not implemented this behaviour.");
}
}
} else {
sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chanName, 'Quit: [ dropUser() ]']);
}
});
};
const getHistory = function (ctx, channelName, handler, cb) {
var messageBuf = [];
ctx.store.getMessages(channelName, function (msgStr) {
messageBuf.push(JSON.parse(msgStr));
}, function () {
var startPoint;
var cpCount = 0;
var msgBuff2 = [];
for (startPoint = messageBuf.length - 1; startPoint >= 0; startPoint--) {
var msg = messageBuf[startPoint];
msgBuff2.push(msg);
if (msg[2] === 'MSG' && msg[4].indexOf('cp|') === 0) {
cpCount++;
if (cpCount >= 2) {
for (var x = msgBuff2.pop(); x; x = msgBuff2.pop()) { handler(x); }
break;
}
}
//console.log(messageBuf[startPoint]);
}
if (cpCount < 2) {
// no checkpoints.
for (var x = msgBuff2.pop(); x; x = msgBuff2.pop()) { handler(x); }
}
cb();
});
};
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, 'JACK', chanName]);
let chan = ctx.channels[chanName] = ctx.channels[chanName] || [];
// prevent removal of the channel if there is a pending timeout
if (ctx.config.removeChannels && ctx.timeouts[chanName]) {
clearTimeout(ctx.timeouts[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) { console.error(err); return; }
if (parsed[0] === 'GET_HISTORY') {
sendMsg(ctx, user, [seq, 'ACK']);
getHistory(ctx, parsed[1], function (msg) {
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)]);
}, function () {
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'; obj = 'undefined';}
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, obj]);
return;
}
sendMsg(ctx, user, [seq, 'ACK']);
json.unshift(user.id);
sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chan.id]);
chan.splice(idx, 1);
}
if (cmd === 'PING') {
sendMsg(ctx, user, [seq, 'ACK']);
return;
}
};
let run = module.exports.run = function (storage, socketServer, config) {
/* Channel removal timeout defaults to 60000ms (one minute) */
config.channelRemovalTimeout =
typeof(config.channelRemovalTimeout) === 'number'?
config.channelRemovalTimeout:
60000;
let ctx = {
users: {},
channels: {},
timeouts: {},
store: (USE_FILE_BACKUP_STORAGE) ? LogStore.create('messages.log', storage) : storage,
config: config
};
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) {
if(socket.upgradeReq.url !== '/cryptpad_websocket') { return; }
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) {
if (ctx.config.logToStdout) { 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]);
}
}
});
});
};
}());

30
TestSelenium.js Normal file
View File

@ -0,0 +1,30 @@
/* global process */
var WebDriver = require("selenium-webdriver");
var driver;
if (process.env.SAUCE_USERNAME !== undefined) {
var browserArray = process.env.BROWSER.split(':');
driver = new WebDriver.Builder().usingServer(
'http://'+ process.env.SAUCE_USERNAME+':'+process.env.SAUCE_ACCESS_KEY+'@ondemand.saucelabs.com:80/wd/hub'
).withCapabilities({
"tunnel-identifier": process.env.TRAVIS_JOB_NUMBER,
"build": process.env.TRAVIS_JOB_NUMBER,
"username": process.env.SAUCE_USERNAME,
"accessKey": process.env.SAUCE_ACCESS_KEY,
}).forBrowser(browserArray[0], browserArray[1], browserArray[2]).build();
} else {
driver = new WebDriver.Builder().withCapabilities({ browserName: "chrome" }).build();
}
driver.get('http://localhost:3000/assert/');
var report = driver.wait(WebDriver.until.elementLocated(WebDriver.By.className("report")), 5000);
report.getAttribute("class").then(function (cls) {
driver.quit();
if (!cls) {
throw new Error("cls is null");
} else if (cls.indexOf("failure") !== -1) {
throw new Error("cls contains the word failure");
} else if (cls.indexOf("success") === -1) {
throw new Error("cls does not contain the word success");
}
});

61
WebRTCSrv.js Normal file
View File

@ -0,0 +1,61 @@
'use strict'
let WebSocketServer = require('ws').Server
const UNSUPPORTED_DATA = 1007
const POLICY_VIOLATION = 1008
const CLOSE_UNSUPPORTED = 1003
var run = module.exports.run = function(server) {
server.on('connection', (socket) => {
if(socket.upgradeReq.url !== '/cryptpad_webrtc') { return; }
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')
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) {
socket.master = master
master.joiningClients.push(socket)
let id = master.joiningClients.length - 1
master.send(JSON.stringify({id, data: msg.data}))
return
}
}
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')
}
}
});
})
}

View File

@ -18,8 +18,6 @@
"tests"
],
"dependencies": {
"markdown": "~0.5.0",
"jquery.sheet": "master",
"jquery": "~2.1.3",
"tweetnacl": "~0.12.2",
"ckeditor": "~4.5.6",
@ -28,6 +26,17 @@
"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",
"fabric.js": "fabric#~1.6.0",
"hyperjson": "~1.2.2",
"textpatcher": "^1.2.0",
"proxy-polyfill": "^0.1.5",
"chainpad": "^0.2.2",
"chainpad-json-validator": "^0.1.1",
"chainpad-crypto": "^0.1.1",
"netflux-websocket": "^0.1.0",
"chainpad-netflux": "^0.1.0",
"chainpad-listmap": "^0.1.0"
}
}

View File

@ -10,7 +10,21 @@ module.exports = {
// the port on which your httpd will listen
httpPort: 3000,
// the port used for websockets
websocketPort: 3001,
websocketPort: 3000,
/* Cryptpad can log activity to stdout
* This may be useful for debugging
*/
logToStdout: false,
/* Cryptpad can be configured to remove channels some number of ms
after the last remaining client has disconnected.
Default behaviour is to keep channels forever.
If you enable channel removal, the default removal time is one minute
*/
removeChannels: false,
channelRemovalTimeout: 60000,
// You now have a choice of storage engines
@ -19,7 +33,7 @@ module.exports = {
* it will not scale well if your server stays alive for a long time.
* but it is completely dependency free
*/
storage: './storage/amnesia',
//storage: './storage/amnesia',
/* the 'lvl' storage module uses leveldb
* it persists, and will perform better than amnesiadb
@ -31,8 +45,8 @@ module.exports = {
*
* to delete all pads, run `rm -rf $YOUR_DB`
*/
// storage: './storage/lvl',
// levelPath: './test.level.db'
storage: './storage/lvl',
levelPath: './test.level.db'
/* mongo is the original storage engine for cryptpad
* it has been more thoroughly tested, but requires a little more setup

View File

@ -122,9 +122,12 @@
</p>
</noscript>
<script>
require(['/common/crypto.js'], function (Crypto) {
require(['/common/crypto.js', '/api/config?cb=' + Math.random().toString(16).substring(2)], function (Crypto, Config) {
document.getElementById('buttons').setAttribute('style', '');
document.getElementById('create-pad').setAttribute('href', '/pad/#' + Crypto.genKey());
document.getElementById('create-pad').setAttribute('href', '/pad/');
if(Config.webrtcURL !== '') {
document.getElementById('create-rtcpad').setAttribute('href', '/padrtc/');
}
document.getElementById('create-sheet').setAttribute('href', '/sheet/#' + Crypto.genKey());
document.getElementById('create-code').setAttribute('href', '/code/#' + Crypto.genKey());
});
@ -215,7 +218,8 @@
<div id="buttons" class="buttons" style="display:none;">
<a id="create-pad" class="button create" href="pad">CREATE NEW PAD</a>
<a id="create-sheet" class="button create" href="sheet">CREATE NEW SPREADSHEET</a>
<a id="create-rtcpad" class="button create" href="pad">CREATE NEW WEBRTC PAD</a>
<!--<a id="create-sheet" class="button create" href="sheet">CREATE NEW SPREADSHEET</a>-->
<a id="create-code" class="button create" href="code">CREATE NEW CODE COLLABORATION PAD</a>
</div>
</center>

View File

@ -9,9 +9,11 @@
"nthen": "~0.1.0"
},
"devDependencies": {
"jshint": "~2.9.1"
"jshint": "~2.9.1",
"selenium-webdriver": "^2.53.1"
},
"scripts": {
"lint": "jshint --config .jshintrc --exclude-path .jshintignore ."
"lint": "jshint --config .jshintrc --exclude-path .jshintignore .",
"test": "node TestSelenium.js"
}
}

View File

@ -25,7 +25,7 @@ To install:
npm install -g bower ## if necessary
bower install
## copy config.js.dist to config.js
## copy config.js.dist to config.js
cp config.js.dist config.js
## modify configuration to use your own mongodb instance
@ -67,6 +67,13 @@ rm -rf ./cryptpad.db
If you are using the mongodb adaptor, [drop the relevant collection](https://docs.mongodb.org/manual/reference/method/db.collection.drop/#db.collection.drop).
## Testing
To test CryptPad, go to http://your.server:3000/assert/
You can use WebDriver to run this test automatically by running TestSelenium.js but you will need chromedriver installed.
If you use Mac, you can `brew install chromedriver`.
## Security
CryptPad is *private*, not *anonymous*. Privacy protects your data, anonymity protects you.

View File

@ -6,7 +6,8 @@ var Http = require('http');
var Https = require('https');
var Fs = require('fs');
var WebSocketServer = require('ws').Server;
var ChainPadSrv = require('./ChainPadSrv');
var NetfluxSrv = require('./NetfluxWebsocketSrv');
var WebRTCSrv = require('./WebRTCSrv');
var config = require('./config');
config.websocketPort = config.websocketPort || config.httpPort;
@ -17,12 +18,6 @@ var Storage = require(config.storage||'./storage/mongo');
var app = Express();
app.use(Express.static(__dirname + '/www'));
// Bower is broken and does not allow components nested within components...
// And jquery.sheet expects it!
// *Workaround*
app.use("/bower_components/jquery.sheet/bower_components",
Express.static(__dirname + '/www/bower_components'));
var customize = "/customize";
if (!Fs.existsSync(__dirname + "/customize")) {
customize = "/customize.dist";
@ -60,7 +55,9 @@ app.get('/api/config', function(req, res){
res.setHeader('Content-Type', 'text/javascript');
res.send('define(' + JSON.stringify({
websocketURL:'ws' + ((httpsOpts) ? 's' : '') + '://' + host + ':' +
config.websocketPort + '/cryptpad_websocket'
config.websocketPort + '/cryptpad_websocket',
webrtcURL:'ws' + ((httpsOpts) ? 's' : '') + '://' + host + ':' +
config.websocketPort + '/cryptpad_webrtc',
}) + ');');
});
@ -75,9 +72,9 @@ if (config.websocketPort !== config.httpPort) {
console.log("setting up a new websocket server");
wsConfig = { port: config.websocketPort};
}
var wsSrv = new WebSocketServer(wsConfig);
Storage.create(config, function (store) {
console.log('DB connected');
ChainPadSrv.create(wsSrv, store);
NetfluxSrv.run(store, wsSrv, config);
WebRTCSrv.run(wsSrv);
});

19
storage/LogStore.js Normal file
View File

@ -0,0 +1,19 @@
var Fs = require("fs");
var message = function(file, msg) {
file.write(msg+"\n");
};
var create = module.exports.create = function(filePath, backingStore) {
var file = Fs.createWriteStream(filePath, {flags: 'a+'});
var originalMessageFunction = backingStore.message;
backingStore.message = function(channel, msg, callback) {
message(file, msg);
originalMessageFunction(channel, msg, callback);
};
return backingStore;
};

View File

@ -21,21 +21,32 @@ That function must accept two arguments:
## Methods
### message(channelName, content, callback)
### message(channelName, content, handler)
When Cryptpad receives a message, it saves it into its datastore using its equivalent of a table for its channel name, and then relays the message to every other client which is participating in the same channel.
Relaying logic exists outside of the storage module, you simply need to store the message then execute the callback on success.
Relaying logic exists outside of the storage module, you simply need to store the message then execute the handler on success.
### getMessages(channelName, callback)
### getMessages(channelName, handler, callback)
When a new client joins, they request the entire history of messages for a particular channel.
This method retreives those messages, and delivers them in order.
In theory, it should be possible for Chainpad to make sense of out of order messages, however, this has not yet been implemented.
In practice, out of order messages make your clientside application likely to fail.
In practice, out of order messages make your clientside application more likely to fail, however, they are generally tolerated.
As a channel accumulates a greater number of messages, the likelihood of the application receiving them in the wrong order becomes greater.
This results in older sessions becoming less reliable
This results in older sessions becoming less reliable.
This function accepts the name of the channel in which the user is interested, the handler for each message, and the callback to be executed when the last message has been fetched and handled.
**Note**, the callback is a new addition to this API.
It is only implemented within the leveldb adaptor, making our latest code incompatible with the other back ends.
While we migrate to our new Netflux API, only the leveldb adaptor will be supported.
## removeChannel(channelName, callback)
This method is called (optionally, see config.js.dist for more info) some amount of time after the last client in a channel disconnects.
It should remove any history of that channel, and execute a callback which takes an error message as an argument.
## Documenting your adaptor

View File

@ -18,6 +18,11 @@ module.exports.create = function(conf, cb){
var db=[],
index=0;
if (conf.removeChannels) {
console.log("Server is set to remove channels %sms after the last remaining client leaves.", conf.channelRemovalTimeout);
}
cb({
message: function(channelName, content, cb){
var val = {
@ -27,17 +32,25 @@ module.exports.create = function(conf, cb){
time: new Date().getTime(),
};
db.push(val);
cb();
if (cb) { cb(); }
},
getMessages: function(channelName, cb){
getMessages: function(channelName, handler, cb){
db.sort(function(a,b){
return a.id - b.id;
});
db.filter(function(val){
return val.chan === channelName;
}).forEach(function(doc){
cb(doc.msg);
handler(doc.msg);
});
if (cb) { cb(); }
},
removeChannel: function (channelName, cb) {
var err = false;
db = db.filter(function (msg) {
return msg.chan !== channelName;
});
cb(err);
},
});
};

View File

@ -17,39 +17,57 @@ var getIndex = function(db, cName, cb) {
var insert = function (db, channelName, content, cb) {
var index;
nThen(function (waitFor) {
getIndex(db, channelName, waitFor(function (i) { index = i+1; }));
}).nThen(function (waitFor) {
db.put(channelName+'=>'+index, content, waitFor(function (e) { if (e) { throw e; } }));
}).nThen(function (waitFor) {
db.put(channelName+'=>index', ''+index, waitFor(function (e) { if (e) { throw e; } }));
}).nThen(cb);
var doIt = function () {
db.locked = true;
nThen(function (waitFor) {
getIndex(db, channelName, waitFor(function (i) { index = i+1; }));
}).nThen(function (waitFor) {
db.put(channelName+'=>'+index, content, waitFor(function (e) { if (e) { throw e; } }));
}).nThen(function (waitFor) {
db.put(channelName+'=>index', ''+index, waitFor(function (e) { if (e) { throw e; } }));
}).nThen(function (waitFor) {
db.locked = false;
if (!db.queue.length) { return; }
db.queue.shift()();
}).nThen(cb);
};
if (db.locked) {
db.queue.push(doIt);
} else {
doIt();
}
};
var getMessages = function (db, channelName, msgHandler) {
var getMessages = function (db, channelName, msgHandler, cb) {
var index;
nThen(function (waitFor) {
getIndex(db, channelName, waitFor(function (i) { index = i; }));
getIndex(db, channelName, waitFor(function (i) {
index = i;
}));
}).nThen(function (waitFor) {
var again = function (i) {
db.get(channelName + '=>' + i, waitFor(function (e, out) {
if (e) { throw e; }
msgHandler(out);
if (i < index) { again(i+1); }
else if (cb) { cb(); }
}));
};
if (index > -1) { again(0); }
else if (cb) { cb(); }
});
};
module.exports.create = function (conf, cb) {
var db = Level(conf.levelPath || './test.level.db');
db.locked = false;
db.queue = [];
cb({
message: function (channelName, content, cb) {
insert(db, channelName, content, cb);
},
getMessages: function (channelName, msgHandler) {
getMessages(db, channelName, msgHandler);
getMessages: function (channelName, msgHandler, cb) {
getMessages(db, channelName, msgHandler, cb);
}
});
};

View File

@ -1,124 +0,0 @@
define([], function () {
// this makes recursing a lot simpler
var isArray = function (A) {
return Object.prototype.toString.call(A)==='[object Array]';
};
var parseStyle = function(el){
var style = el.style;
var output = {};
for (var i = 0; i < style.length; ++i) {
var item = style.item(i);
output[item] = style[item];
}
return output;
};
var callOnHyperJSON = function (hj, cb) {
var children;
if (hj && hj[2]) {
children = hj[2].map(function (child) {
if (isArray(child)) {
// if the child is an array, recurse
return callOnHyperJSON(child, cb);
} else if (typeof (child) === 'string') {
// string nodes have leading and trailing quotes
return child.replace(/(^"|"$)/g,"");
} else {
// the above branches should cover all methods
// if we hit this, there is a problem
throw new Error();
}
});
} else {
children = [];
}
// this should return the top level element of your new DOM
return cb(hj[0], hj[1], children);
};
var classify = function (token) {
return '.' + token.trim();
};
var isValidClass = function (x) {
if (x && /\S/.test(x)) {
return true;
}
};
var isTruthy = function (x) {
return x;
};
var DOM2HyperJSON = function(el, predicate, filter){
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;
for(;i < el.attributes.length; i++){
var attr = el.attributes[i];
if(attr.name && attr.value){
if(attr.name === "style"){
attributes.style = parseStyle(el);
}
else{
attributes[attr.name] = attr.value;
}
}
}
// this should never be longer than three elements
var result = [];
// get the element type, id, and classes of the element
// and push them to the result array
var sel = el.tagName;
if(attributes.id){
// we don't have to do much to validate IDs because the browser
// will only permit one id to exist
// unless we come across a strange browser in the wild
sel = sel +'#'+ attributes.id;
delete attributes.id;
}
result.push(sel);
// second element of the array is the element attributes
result.push(attributes);
// third element of the array is an array of child nodes
var children = [];
// js hint complains if we use 'var' here
i = 0;
for(; i < el.childNodes.length; i++){
children.push(DOM2HyperJSON(el.childNodes[i], predicate, filter));
}
result.push(children.filter(isTruthy));
if (filter) {
return filter(result);
} else {
return result;
}
};
return {
fromDOM: DOM2HyperJSON,
callOn: callOnHyperJSON
};
});

View File

@ -1,400 +0,0 @@
define([], function () {
var Hyperscript;
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var split = require('browser-split')
var ClassList = require('class-list')
require('html-element')
function context () {
var cleanupFuncs = []
function h() {
var args = [].slice.call(arguments), e = null
function item (l) {
var r
function parseClass (string) {
// Our minimal parser doesnt understand escaping CSS special
// characters like `#`. Dont use them. More reading:
// https://mathiasbynens.be/notes/css-escapes .
var m = split(string, /([\.#]?[^\s#.]+)/)
if(/^\.|#/.test(m[1]))
e = document.createElement('div')
forEach(m, function (v) {
var s = v.substring(1,v.length)
if(!v) return
if(!e)
e = document.createElement(v)
else if (v[0] === '.')
ClassList(e).add(s)
else if (v[0] === '#')
e.setAttribute('id', s)
})
}
if(l == null)
;
else if('string' === typeof l) {
if(!e)
parseClass(l)
else
e.appendChild(r = document.createTextNode(l))
}
else if('number' === typeof l
|| 'boolean' === typeof l
|| l instanceof Date
|| l instanceof RegExp ) {
e.appendChild(r = document.createTextNode(l.toString()))
}
//there might be a better way to handle this...
else if (isArray(l))
forEach(l, item)
else if(isNode(l))
e.appendChild(r = l)
else if(l instanceof Text)
e.appendChild(r = l)
else if ('object' === typeof l) {
for (var k in l) {
if('function' === typeof l[k]) {
if(/^on\w+/.test(k)) {
(function (k, l) { // capture k, l in the closure
if (e.addEventListener){
e.addEventListener(k.substring(2), l[k], false)
cleanupFuncs.push(function(){
e.removeEventListener(k.substring(2), l[k], false)
})
}else{
e.attachEvent(k, l[k])
cleanupFuncs.push(function(){
e.detachEvent(k, l[k])
})
}
})(k, l)
} else {
// observable
e[k] = l[k]()
cleanupFuncs.push(l[k](function (v) {
e[k] = v
}))
}
}
else if(k === 'style') {
if('string' === typeof l[k]) {
e.style.cssText = l[k]
}else{
for (var s in l[k]) (function(s, v) {
if('function' === typeof v) {
// observable
e.style.setProperty(s, v())
cleanupFuncs.push(v(function (val) {
e.style.setProperty(s, val)
}))
} else
e.style.setProperty(s, l[k][s])
})(s, l[k][s])
}
} else if (k.substr(0, 5) === "data-") {
e.setAttribute(k, l[k])
} else {
e.setAttribute(k, l[k])
if (e.getAttribute(k) !== l[k]) {
e[k] = l[k]
}
}
}
} else if ('function' === typeof l) {
//assume it's an observable!
var v = l()
e.appendChild(r = isNode(v) ? v : document.createTextNode(v))
cleanupFuncs.push(l(function (v) {
if(isNode(v) && r.parentElement)
r.parentElement.replaceChild(v, r), r = v
else
r.textContent = v
}))
}
return r
}
while(args.length)
item(args.shift())
return e
}
h.cleanup = function () {
for (var i = 0; i < cleanupFuncs.length; i++){
cleanupFuncs[i]()
}
cleanupFuncs.length = 0
}
return h
}
var h = module.exports = context()
h.context = context
Hyperscript = h;
function isNode (el) {
return el && el.nodeName && el.nodeType
}
function forEach (arr, fn) {
if (arr.forEach) return arr.forEach(fn)
for (var i = 0; i < arr.length; i++) fn(arr[i], i)
}
function isArray (arr) {
return Object.prototype.toString.call(arr) == '[object Array]'
}
},{"browser-split":2,"class-list":3,"html-element":6}],2:[function(require,module,exports){
/*!
* Cross-Browser Split 1.1.1
* Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
* Available under the MIT License
* ECMAScript compliant, uniform cross-browser split method
*/
/**
* Splits a string into an array of strings using a regex or string separator. Matches of the
* separator are not included in the result array. However, if `separator` is a regex that contains
* capturing groups, backreferences are spliced into the result each time `separator` is matched.
* Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
* cross-browser.
* @param {String} str String to split.
* @param {RegExp|String} separator Regex or string to use for separating the string.
* @param {Number} [limit] Maximum number of items to include in the result array.
* @returns {Array} Array of substrings.
* @example
*
* // Basic use
* split('a b c d', ' ');
* // -> ['a', 'b', 'c', 'd']
*
* // With limit
* split('a b c d', ' ', 2);
* // -> ['a', 'b']
*
* // Backreferences in result array
* split('..word1 word2..', /([a-z]+)(\d+)/i);
* // -> ['..', 'word', '1', ' ', 'word', '2', '..']
*/
module.exports = (function split(undef) {
var nativeSplit = String.prototype.split,
compliantExecNpcg = /()??/.exec("")[1] === undef,
// NPCG: nonparticipating capturing group
self;
self = function(str, separator, limit) {
// If `separator` is not a regex, use `nativeSplit`
if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
return nativeSplit.call(str, separator, limit);
}
var output = [],
flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6
(separator.sticky ? "y" : ""),
// Firefox 3+
lastLastIndex = 0,
// Make `global` and avoid `lastIndex` issues by working with a copy
separator = new RegExp(separator.source, flags + "g"),
separator2, match, lastIndex, lastLength;
str += ""; // Type-convert
if (!compliantExecNpcg) {
// Doesn't need flags gy, but they don't hurt
separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
}
/* Values for `limit`, per the spec:
* If undefined: 4294967295 // Math.pow(2, 32) - 1
* If 0, Infinity, or NaN: 0
* If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
* If negative number: 4294967296 - Math.floor(Math.abs(limit))
* If other: Type-convert, then use the above rules
*/
limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1
limit >>> 0; // ToUint32(limit)
while (match = separator.exec(str)) {
// `separator.lastIndex` is not reliable cross-browser
lastIndex = match.index + match[0].length;
if (lastIndex > lastLastIndex) {
output.push(str.slice(lastLastIndex, match.index));
// Fix browsers whose `exec` methods don't consistently return `undefined` for
// nonparticipating capturing groups
if (!compliantExecNpcg && match.length > 1) {
match[0].replace(separator2, function() {
for (var i = 1; i < arguments.length - 2; i++) {
if (arguments[i] === undef) {
match[i] = undef;
}
}
});
}
if (match.length > 1 && match.index < str.length) {
Array.prototype.push.apply(output, match.slice(1));
}
lastLength = match[0].length;
lastLastIndex = lastIndex;
if (output.length >= limit) {
break;
}
}
if (separator.lastIndex === match.index) {
separator.lastIndex++; // Avoid an infinite loop
}
}
if (lastLastIndex === str.length) {
if (lastLength || !separator.test("")) {
output.push("");
}
} else {
output.push(str.slice(lastLastIndex));
}
return output.length > limit ? output.slice(0, limit) : output;
};
return self;
})();
},{}],3:[function(require,module,exports){
// contains, add, remove, toggle
var indexof = require('indexof')
module.exports = ClassList
function ClassList(elem) {
var cl = elem.classList
if (cl) {
return cl
}
var classList = {
add: add
, remove: remove
, contains: contains
, toggle: toggle
, toString: $toString
, length: 0
, item: item
}
return classList
function add(token) {
var list = getTokens()
if (indexof(list, token) > -1) {
return
}
list.push(token)
setTokens(list)
}
function remove(token) {
var list = getTokens()
, index = indexof(list, token)
if (index === -1) {
return
}
list.splice(index, 1)
setTokens(list)
}
function contains(token) {
return indexof(getTokens(), token) > -1
}
function toggle(token) {
if (contains(token)) {
remove(token)
return false
} else {
add(token)
return true
}
}
function $toString() {
return elem.className
}
function item(index) {
var tokens = getTokens()
return tokens[index] || null
}
function getTokens() {
var className = elem.className
return filter(className.split(" "), isTruthy)
}
function setTokens(list) {
var length = list.length
elem.className = list.join(" ")
classList.length = length
for (var i = 0; i < list.length; i++) {
classList[i] = list[i]
}
delete list[length]
}
}
function filter (arr, fn) {
var ret = []
for (var i = 0; i < arr.length; i++) {
if (fn(arr[i])) ret.push(arr[i])
}
return ret
}
function isTruthy(value) {
return !!value
}
},{"indexof":4}],4:[function(require,module,exports){
var indexOf = [].indexOf;
module.exports = function(arr, obj){
if (indexOf) return arr.indexOf(obj);
for (var i = 0; i < arr.length; ++i) {
if (arr[i] === obj) return i;
}
return -1;
};
},{}],5:[function(require,module,exports){
var h = require("./index.js");
module.exports = h;
/*
$(function () {
var newDoc = h('p',
h('ul', 'bang bang bang'.split(/\s/).map(function (word) {
return h('li', word);
}))
);
$('body').html(newDoc.outerHTML);
});
*/
},{"./index.js":1}],6:[function(require,module,exports){
},{}]},{},[5]);
return Hyperscript;
});

View File

@ -4,6 +4,24 @@
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
<style>
.report {
font-size: 40px;
}
.success {
border: 3px solid green;
}
.failure {
border: 3px solid red;
}
.error {
border: 1px solid red;
}
</style>
</head>
<body>
@ -12,7 +30,12 @@
<h2>Test 1</h2>
<h3>class strings</h3>
<!-- put in weird HTML that might cause problems -->
<div id="target"><p class=" alice bob charlie has.dot" id="bang">pewpewpew</p></div>
<div id="target"><p class=" alice bob charlie has.dot">pewpewpew</p></div>
<h2>Test 1</h2>
<h3>paragraph text</h3>
<!-- -->
<div id="quot"><p>"pewpewpew"</p></div>
<hr>
@ -20,6 +43,6 @@
<h3>XWiki Macros</h3>
<!-- Can we serialize XWiki Macros? -->
<div id="widget"><div data-cke-widget-id="0" tabindex="-1" data-cke-widget-wrapper="1" data-cke-filter="off" class="cke_widget_wrapper cke_widget_block" data-cke-display-name="macro:velocity" contenteditable="false"><div class="macro cke_widget_element" data-macro="startmacro:velocity|-||-|Here is a macro" data-cke-widget-data="%7B%22classes%22%3A%7B%22macro%22%3A1%7D%7D" data-cke-widget-upcasted="1" data-cke-widget-keep-attr="0" data-widget="xwiki-macro"><p>Here is a macro</p></div><span style='background: rgba(220, 220, 220, 0.5) url("/common/cryptofist.png") repeat scroll 0% 0%; top: -15px; left: 0px; display: block;' class="cke_reset cke_widget_drag_handler_container"><img title="Click and drag to move" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" data-cke-widget-drag-handler="1" class="cke_reset cke_widget_drag_handler" height="15" width="15"></span></div></div>
<div id="widget"><div data-cke-widget-id="0" tabindex="-1" data-cke-widget-wrapper="1" data-cke-filter="off" class="cke_widget_wrapper cke_widget_block" data-cke-display-name="macro:velocity" contenteditable="false"><div class="macro cke_widget_element" data-macro="startmacro:velocity|-||-|Here is a macro" data-cke-widget-data="%7B%22classes%22%3A%7B%22macro%22%3A1%7D%7D" data-cke-widget-upcasted="1" data-cke-widget-keep-attr="0" data-widget="xwiki-macro"><p>Here is a macro</p></div><span style='background: rgba(220, 220, 220, 0.5) url("/customize/cryptofist_small.png") repeat scroll 0% 0%; top: -15px; left: 0px; display: block;' class="cke_reset cke_widget_drag_handler_container"><img title="Click and drag to move" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" data-cke-widget-drag-handler="1" class="cke_reset cke_widget_drag_handler" height="15" width="15"></span></div></div>
<hr>

View File

@ -1,27 +1,111 @@
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
define([
'/bower_components/jquery/dist/jquery.min.js',
'/assert/hyperjson.js', // serializing classes as an attribute
'/assert/hyperscript.js', // using setAttribute
'/common/TextPatcher.js'
], function (jQuery, Hyperjson, Hyperscript, TextPatcher) {
'/bower_components/hyperjson/hyperjson.amd.js', // serializing classes as an attribute
'/common/hyperscript.js', // using setAttribute
'/bower_components/textpatcher/TextPatcher.amd.js',
'json.sortify',
], function (jQuery, Hyperjson, Hyperscript, TextPatcher, Sortify) {
var $ = window.jQuery;
window.Hyperjson = Hyperjson;
window.Hyperscript = Hyperscript;
window.TextPatcher = TextPatcher;
window.Sortify = Sortify;
var assertions = 0;
var failed = false;
var failedOn;
var failMessages = [];
var ASSERTS = [];
var runASSERTS = function () {
ASSERTS.forEach(function (f, index) {
f(index);
});
};
var assert = function (test, msg) {
if (test()) {
assertions++;
} else {
throw new Error(msg || '');
}
ASSERTS.push(function (i) {
var returned = test();
if (returned === true) {
assertions++;
} else {
failed = true;
failedOn = assertions;
failMessages.push({
test: i,
message: msg,
output: returned,
});
}
});
};
var $body = $('body');
var roundTrip = function (target) {
var HJSON_list = [
'["DIV#target",{},[["P#bang",{"class":" alice bob charlie has.dot"},["pewpewpew"]]]]',
'["DIV#quot",{},[["P",{},["\\"pewpewpew\\""]]]]',
'["DIV#widget",{},[["DIV",{"class":"cke_widget_wrapper cke_widget_block","contenteditable":"false","data-cke-display-name":"macro:velocity","data-cke-filter":"off","data-cke-widget-id":"0","data-cke-widget-wrapper":"1","tabindex":"-1"},[["DIV",{"class":"macro cke_widget_element","data-cke-widget-data":"%7B%22classes%22%3A%7B%22macro%22%3A1%7D%7D","data-cke-widget-keep-attr":"0","data-cke-widget-upcasted":"1","data-macro":"startmacro:velocity|-||-|Here is a macro","data-widget":"xwiki-macro"},[["P",{},["Here is a macro"]]]],["SPAN",{"class":"cke_reset cke_widget_drag_handler_container","style":"background: rgba(220, 220, 220, 0.5) url(\\"/customize/cryptofist_small.png\\") repeat scroll 0% 0%; top: -15px; left: 0px; display: block;"},[["IMG",{"class":"cke_reset cke_widget_drag_handler","data-cke-widget-drag-handler":"1","height":"15","src":"data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==","title":"Click and drag to move","width":"15"},[]]]]]]]]',
];
var elementFilter = function () {
// pass everything
return true;
};
var attributeFilter = function (h) {
// don't filter anything
return h;
};
var HJSON_equal = function (shjson) {
assert(function () {
// parse your stringified Hyperjson
var hjson;
try {
hjson = JSON.parse(shjson);
} catch (e) {
console.log(e);
return false;
}
// turn it into a DOM
var DOM = Hyperjson.callOn(hjson, Hyperscript);
// turn it back into stringified Hyperjson, but apply filters
var shjson2 = Sortify(Hyperjson.fromDOM(DOM, elementFilter, attributeFilter));
var success = shjson === shjson2;
var op = TextPatcher.diff(shjson, shjson2);
//console.log(TextPatcher.format(shjson, op));
var diff = TextPatcher.format(shjson, op);
if (success) {
return true;
} else {
return '<br><br>insert: ' + diff.insert + '<br><br>' +
'remove: ' + diff.remove + '<br><br>';
}
}, "expected hyperjson equality");
};
HJSON_list.map(HJSON_equal);
/* FIXME
This test is not correct. It passes in Firefox, but fails in Chrome,
even though for our purposes the produced code is valid. Passing
`<p class="bob" id="alice"></p>` through the function yields
`<p id="alice" class="bob"></p>`. This is the same element, but string
equality is not a correct metric. */
var roundTrip = function (sel) {
var target = $(sel)[0];
assert(function () {
var hjson = Hyperjson.fromDOM(target);
var cloned = Hyperjson.callOn(hjson, Hyperscript);
@ -29,22 +113,117 @@ define([
var success = cloned.outerHTML === target.outerHTML;
if (!success) {
var op = TextPatcher.diff(target.outerHTML, cloned.outerHTML);
window.DEBUG = {
error: "Expected equality between A and B",
A: target.outerHTML,
B: cloned.outerHTML,
target: target,
diff: TextPatcher.diff(target.outerHTML, cloned.outerHTML)
diff: op
};
console.log(JSON.stringify(window.DEBUG, null, 2));
//console.log(JSON.stringify(window.DEBUG, null, 2));
console.log("DIFF:");
TextPatcher.log(target.outerHTML, op);
}
return success;
}, "Round trip serialization introduced artifacts.");
};
roundTrip($('#target')[0]);
roundTrip($('#widget')[0]);
var HTML_list = [
'#target',
'#widget', // fails in Firefox 19?
'#quot',
];
HTML_list.forEach(roundTrip);
var strungJSON = function (orig) {
var result;
assert(function () {
result = JSON.stringify(JSON.parse(orig));
return result === orig;
}, "expected result (" + result + ") to equal original (" + orig + ")");
};
[ '{"border":"1","style":{"width":"500px"}}',
'{"style":"width: 500px;","border":"1"}',
].forEach(function (orig) {
strungJSON(orig);
});
//assert(function () { }, "this is expected to fail");
/* TODO Test how browsers handle weird elements
like "_moz-resizing":"true"
and anything else you can think of.
Start with Hyperjson, turn it into a DOM and come back
*/
var swap = function (str, dict) {
return str.replace(/\{\{(.*?)\}\}/g, function (all, key) {
return typeof dict[key] !== 'undefined'? dict[key] : all;
});
};
var multiline = function (f) {
var str;
f.toString().replace(/\/\*([\s\S]*)\*\//g, function (all, out) {
str = out;
});
return str || '';
};
var formatFailures = function () {
var template = multiline(function () { /*
<p class="error">
Failed on test number {{test}} with error message:
"{{message}}"
</p>
<p>
The test returned:
{{output}}
</p>
<br>
*/});
return failMessages.map(function (obj) {
console.log(obj);
return swap(template, obj);
}).join("\n");
};
runASSERTS();
$("body").html(function (i, val) {
var dict = {
previous: val,
totalAssertions: ASSERTS.length,
passedAssertions: assertions,
plural: (assertions === 1? '' : 's'),
failMessages: formatFailures()
};
var SUCCESS = swap(multiline(function(){/*
<div class="report">{{passedAssertions}} / {{totalAssertions}} test{{plural}} passed.
{{failMessages}}
</div>
{{previous}}
*/}), dict);
var report = SUCCESS;
return report;
});
var $report = $('.report');
$report.addClass(failed?'failure':'success');
console.log("%s test%s passed", assertions, assertions === 1? '':'s');
});

78
www/canvas/index.html Normal file
View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
<style>
html, body{
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
}
textarea{
width: 100%;
height: 100vh;
max-width: 100%;
max-height: 100vh;
font-size: 18px;
background-color: #073642;
color: #839496;
overflow-x: hidden;
/* disallow textarea resizes */
resize: none;
}
canvas {
border: 5px solid black;
}
#clear {
display: inline;
}
#colors {
z-index: 100;
border: 3px solid black;
padding: 5px;
}
#copy {
padding-left: 75px;
}
span.palette {
height: 4vw;
width: 4vw;
display: inline-block;
margin: 5px;
border: 2px solid black;
}
#controls {
display: block;
position: relative;
border: 3px solid black;
}
#width, #colors {
position: relative;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="600" ></canvas>
<div id="copy">
<h2>Welcome to CryptCanvas!</h2>
<h3>Zero Knowledge Realtime Collaborative Canvas Editing</h3>
</div>
<div id="controls">
<button id="clear">Clear</button>
<input id="width" type="number" value="5"></input>
<div id="colors">&nbsp;</div>
</div>
</body>
</html>

120
www/canvas/main.js Normal file
View File

@ -0,0 +1,120 @@
require.config({ paths: {
'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify'
}});
define([
'/api/config?cb=' + Math.random().toString(16).substring(2),
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/common/messages.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/textpatcher/TextPatcher.amd.js',
'json.sortify',
'/bower_components/chainpad-json-validator/json-ot.js',
'/bower_components/fabric.js/dist/fabric.min.js',
'/bower_components/jquery/dist/jquery.min.js',
'/customize/pad.js'
], function (Config, Realtime, Messages, Crypto, TextPatcher, JSONSortify, JsonOT) {
var module = window.APP = { };
var $ = module.$ = window.jQuery;
var Fabric = module.Fabric = window.fabric;
var key;
var channel = '';
if (!/#/.test(window.location.href)) {
key = Crypto.genKey();
} else {
var hash = window.location.hash.slice(1);
channel = hash.slice(0, 32);
key = hash.slice(32);
}
/* Initialize Fabric */
var canvas = module.canvas = new Fabric.Canvas('canvas');
var $canvas = $('canvas');
var $width = $('#width');
var updateBrushWidth = function () {
canvas.freeDrawingBrush.width = Number($width.val());
};
updateBrushWidth();
$width.on('change', updateBrushWidth);
var palette = ['red', 'blue', 'green', 'white', 'black', 'purple',
'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'];
var $colors = $('#colors');
$colors.html(function (i, val) {
return palette.map(function (c) {
return "<span class='palette' style='background-color:"+c+"'></span>";
}).join("");
});
$('.palette').on('click', function () {
var color = $(this).css('background-color');
canvas.freeDrawingBrush.color = color;
});
var setEditable = function (bool) {
canvas.isDrawingMode = bool;
$canvas.css('border-color', bool? 'black': 'red');
};
var initializing = true;
var config = module.config = {
// TODO initialState ?
websocketURL: Config.websocketURL,
userName: Crypto.rand64(8),
channel: channel,
cryptKey: key,
crypto: Crypto,
transformFunction: JsonOT.validate,
};
var onInit = config.onInit = function (info) {
window.location.hash = info.channel + key;
$(window).on('hashchange', function() {
window.location.reload();
});
};
var onRemote = config.onRemote = function () {
if (initializing) { return; }
var userDoc = module.realtime.getUserDoc();
canvas.loadFromJSON(userDoc);
canvas.renderAll();
};
var onLocal = config.onLocal = function () {
if (initializing) { return; }
var content = JSONSortify(canvas.toDatalessJSON());
module.patchText(content);
};
var onReady = config.onReady = function (info) {
var realtime = module.realtime = info.realtime;
module.patchText = TextPatcher.create({
realtime: realtime
});
setEditable(true);
initializing = false;
onRemote();
};
var onAbort = config.onAbort = function (info) {
setEditable(false);
window.alert("Server Connection Lost");
};
var rt = Realtime.start(config);
canvas.on('mouse:up', onLocal);
$('#clear').on('click', function () {
canvas.clear();
});
});

View File

@ -27,6 +27,8 @@ Alex Piggott
Aliaksei Chapyzhenka
Allen Sarkisyan
Amin Shali
Amin Ullah Khan
amshali@google.com
Amsul
amuntean
Amy
@ -59,10 +61,13 @@ 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
@ -70,6 +75,7 @@ benbro
Beni Cherniavsky-Paskin
Benjamin DeCoste
Ben Keen
Ben Mosher
Bernhard Sirlinger
Bert Chang
Billy Moon
@ -86,11 +92,14 @@ 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
@ -100,9 +109,11 @@ Christian Petrov
Christopher Brown
Christopher Mitchell
Christopher Pfohl
Chunliang Lyu
ciaranj
CodeAnimal
coderaiser
Cole R Lawrence
ComFreek
Curtis Gagliardi
dagsta
@ -114,6 +125,7 @@ Daniel, Dao Quang Minh
Daniele Di Sarli
Daniel Faust
Daniel Huigens
Daniel Kesler
Daniel KJ
Daniel Neel
Daniel Parnell
@ -126,8 +138,10 @@ David Barnett
David Mignot
David Pathakjee
David Vázquez
David Whittington
deebugger
Deep Thought
Devin Abbott
Devon Carew
dignifiedquire
Dimage Sapelkin
@ -139,13 +153,16 @@ 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
@ -167,19 +184,24 @@ 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
@ -190,12 +212,14 @@ 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
@ -212,6 +236,7 @@ Jan Jongboom
jankeromnes
Jan Keromnes
Jan Odvarko
Jan Schär
Jan T. Sott
Jared Forsyth
Jason
@ -227,12 +252,17 @@ 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
@ -240,6 +270,7 @@ Jon Malmaud
Jon Sangster
Joost-Wim Boekesteijn
Joseph Pecoraro
Josh Cohen
Joshua Newman
Josh Watzman
jots
@ -248,11 +279,15 @@ 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
@ -296,12 +331,15 @@ 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
@ -323,10 +361,12 @@ Max Kirsch
Max Schaefer
Max Xiantu
mbarkhau
McBrainy
melpon
Metatheos
Micah Dubinko
Michael
Michael Goderbauer
Michael Grey
Michael Kaminsky
Michael Lehenbauer
@ -361,6 +401,7 @@ Nicholas Bollweg
Nicholas Bollweg (Nick)
Nick Kreeger
Nick Small
Nicolò Ribaudo
Niels van Groningen
nightwing
Nikita Beloglazov
@ -373,6 +414,7 @@ noragrossman
Norman Rzepka
Oreoluwa Onatemowo
pablo
pabloferz
Page
Panupong Pasupat
paris
@ -382,18 +424,25 @@ 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
@ -420,10 +469,12 @@ Sascha Peilicke
satamas
satchmorun
sathyamoorthi
S. Chris Colbert
SCLINIC\jdecker
Scott Aikin
Scott Goodhew
Sebastian Zaha
Sergey Goder
Se-Won Kim
shaund
shaun gilchrist
@ -434,6 +485,7 @@ Shiv Deepak
Shmuel Englard
Shubham Jain
silverwind
sinkuu
snasa
soliton4
sonson
@ -443,14 +495,19 @@ 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
@ -471,6 +528,8 @@ Tom MacWright
Tony Jian
Travis Heppe
Triangle717
Tristan Tarrant
TSUYUSATO Kitsune
twifkak
Vestimir Markov
vf
@ -481,15 +540,18 @@ 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

View File

@ -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 (`<script>`, `<style>`, etc).
[Spreadsheet mode](http://codemirror.net/mode/spreadsheet/): Fix bug where the mode did not advance the stream when finding a backslash.
[XML mode](http://codemirror.net/mode/xml/): The mode now takes a `matchClosing` option to configure whether mismatched closing tags should be highlighted as errors.
## 5.11.0 (2016-01-20)
* New modes: [JSX](http://codemirror.net/mode/jsx/index.html), [literate Haskell](http://codemirror.net/mode/haskell-literate/index.html)
* The editor now forwards more [DOM events](http://codemirror.net/doc/manual.html#event_dom): `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](http://codemirror.net/mode/swift/index.html) mode now supports auto-indentation
* Frontmatters in the [YAML frontmatter](http://codemirror.net/mode/yaml-frontmatter/index.html) mode are now optional as intended
## 5.10.0 (2015-12-21)
* Modify the way [atomic ranges](http://codemirror.net/doc/manual.html#mark_atomic) are skipped by selection to try and make it less surprising.
* The [Swift mode](http://codemirror.net/mode/swift/index.html) was rewritten.
* New addon: [jump-to-line](http://codemirror.net/doc/manual.html#addon_jump-to-line).
* New method: [`isReadOnly`](http://codemirror.net/doc/manual.html#isReadOnly).
* The [show-hint addon](http://codemirror.net/doc/manual.html#addon_show-hint) now defaults to picking completions on single click.
* The object passed to [`"beforeSelectionChange"`](http://codemirror.net/doc/manual.html#event_beforeSelectionChange) events now has an `origin` property.
* New mode: [Crystal](http://codemirror.net/mode/crystal/index.html).
## 5.9.0 (2015-11-23)
* Improve the way overlay (OS X-style) scrollbars are handled
* Make [annotatescrollbar](http://codemirror.net/doc/manual.html#addon_annotatescrollbar) and scrollpastend addons work properly together
* Make [show-hint](http://codemirror.net/doc/manual.html#addon_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
## 5.8.0 (2015-10-20)
* Fixes an infinite loop in the [hardwrap addon](http://codemirror.net/doc/manual.html#addon_hardwrap)
* New modes: [NSIS](http://codemirror.net/mode/nsis/index.html), [Ceylon](http://codemirror.net/mode/clike/index.html)
* The Kotlin mode is now a [clike](http://codemirror.net/mode/clike/index.html) dialect, rather than a stand-alone mode
* New option: [`allowDropFileTypes`](http://codemirror.net/doc/manual.html#option_allowDropFileTypes). Binary files can no longer be dropped into CodeMirror
* New themes: [bespin](http://codemirror.net/demo/theme.html#bespin), [hopscotch](http://codemirror.net/demo/theme.html#hopscotch), [isotope](http://codemirror.net/demo/theme.html#isotope), [railscasts](http://codemirror.net/demo/theme.html#railscasts)
## 5.7.0 (2015-09-21)
* New modes: [Vue](http://codemirror.net/mode/vue/index.html), [Oz](http://codemirror.net/mode/oz/index.html), [MscGen](http://codemirror.net/mode/mscgen/index.html) (and dialects), [Closure Stylesheets](http://codemirror.net/mode/css/gss.html)
* Implement [CommonMark](http://commonmark.org)-style flexible list indent and cross-line code spans in [Markdown](http://codemirror.net/mode/markdown/index.html) mode
* Add a replace-all button to the [search addon](http://codemirror.net/doc/manual.html#addon_search), and make the persistent search dialog transparent when it obscures the match
* Handle `acync`/`await` and ocal and binary numbers in [JavaScript mode](http://codemirror.net/mode/javascript/index.html)
* Fix various issues with the [Haxe mode](http://codemirror.net/mode/haxe/index.html)
* Make the [closebrackets addon](http://codemirror.net/doc/manual.html#addon_closebrackets) select only the wrapped text when wrapping selection in brackets
* Tokenize properties as properties in the [CoffeeScript mode](http://codemirror.net/mode/coffeescript/index.html)
* The [placeholder addon](http://codemirror.net/doc/manual.html#addon_placeholder) now accepts a DOM node as well as a string placeholder
## 5.6.0 (2015-08-20)
* 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](http://codemirror.net/mode/rust/index.html) 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](http://codemirror.net/demo/theme.html#abcdef)
* Lines longer than [`maxHighlightLength`](http://codemirror.net/doc/manual.html#option_maxHighlightLength) are now less likely to mess up indentation
* New addons: [`autorefresh`](http://codemirror.net/doc/manual.html#addon_autorefresh) for refreshing an editor the first time it becomes visible, and `html-lint` for using [HTMLHint](http://htmlhint.com/)
* The [`search`](http://codemirror.net/doc/manual.html#addon_search) addon now recognizes `\r` and `\n` in pattern and replacement input
## 5.5.0 (2015-07-20)
* New option: [`lineSeparator`](http://codemirror.net/doc/manual.html#option_lineSeparator) (with corresponding [method](http://codemirror.net/doc/manual.html#lineSeparator))
* New themes: [dracula](http://codemirror.net/demo/theme.html#dracula), [seti](http://codemirror.net/demo/theme.html#seti), [yeti](http://codemirror.net/demo/theme.html#yeti), [material](http://codemirror.net/demo/theme.html#material), and [icecoder](http://codemirror.net/demo/theme.html#icecoder)
* New modes: [Brainfuck](http://codemirror.net/mode/brainfuck/index.html), [VHDL](http://codemirror.net/mode/vhdl/index.html), Squirrel ([clike](http://codemirror.net/mode/clike/index.html) dialect)
* Define a `findPersistent` command in the [search](http://codemirror.net/demo/search.html) addon, for a dialog that stays open as you cycle through matches
* From this release on, the NPM module doesn't include documentation and demos
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.4.0...5.5.0)
## 5.4.0 (2015-06-25)
* New modes: [Twig](http://codemirror.net/mode/twig/index.html), [Elm](http://codemirror.net/mode/elm/index.html), [Factor](http://codemirror.net/mode/factor/index.html), [Swift](http://codemirror.net/mode/swift/index.html)
* Prefer clipboard API (if available) when pasting
* Refined definition highlighting in [clike](http://codemirror.net/mode/clike/index.html) mode
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.3.0...5.4.0)
## 5.3.0 (2015-05-20)
* Fix several regressions in the [`show-hint`](http://codemirror.net/doc/manual.html#addon_show-hint) addon (`completeSingle` option, `"shown"` and `"close"` events)
* The [vim mode](http://codemirror.net/demo/vim.html) API was [documented](http://codemirror.net/doc/manual.html#vimapi)
* New modes: [ASN.1](http://codemirror.net/mode/asn.1/index.html), [TTCN](http://codemirror.net/mode/ttcn/index.html), and [TTCN-CFG](http://codemirror.net/mode/ttcn-cfg/index.html)
* The [clike](http://codemirror.net/mode/clike/index.html) mode can now deep-indent `switch` statements, and roughly recognizes types and defined identifiers
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.2.0...5.3.0)
## 5.2.0 (2015-04-20)
* Fix several race conditions in [`show-hint`](http://codemirror.net/doc/manual.html#addon_show-hint)'s asynchronous mode
* Fix backspace binding in [Sublime bindings](http://codemirror.net/demo/sublime.html)
* Change the way IME is handled in the `"textarea"` [input style](http://codemirror.net/doc/manual.html#option_inputStyle)
* New modes: [MUMPS](http://codemirror.net/mode/mumps/index.html), [Handlebars](http://codemirror.net/mode/handlebars/index.html)
* Rewritten modes: [Django](http://codemirror.net/mode/django/index.html), [Z80](http://codemirror.net/mode/z80/index.html)
* New theme: [Liquibyte](http://codemirror.net/demo/theme.html#liquibyte)
* New option: [`lineWiseCopyCut`](http://codemirror.net/doc/manual.html#option_lineWiseCopyCut)
* The [Vim mode](http://codemirror.net/demo/vim.html) now supports buffer-local options and the `filetype` setting
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.1.0...5.2.0)
## 5.1.0 (2015-03-23)
* New modes: [ASCII armor](http://codemirror.net/mode/asciiarmor/index.html) (PGP data), [Troff](http://codemirror.net/mode/troff/index.html), and [CMake](http://codemirror.net/mode/cmake/index.html).
* Remove SmartyMixed mode, rewrite [Smarty](http://codemirror.net/mode/smarty/index.html) mode to supersede it.
* New commands in the [merge addon](http://codemirror.net/doc/manual.html#addon_merge): `goNextDiff` and `goPrevDiff`.
* The [closebrackets addon](http://codemirror.net/doc/manual.html#addon_closebrackets) can now be configured per mode.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.0.0...5.1.0).
## 5.0.0 (2015-02-20)
* Experimental mobile support (tested on iOS, Android Chrome, stock Android browser)
* New option [`inputStyle`](http://codemirror.net/doc/manual.html#option_inputStyle) to switch between hidden textarea and contenteditable input.
* The [`getInputField`](http://codemirror.net/doc/manual.html#getInputField) method is no longer guaranteed to return a textarea.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.13.0...5.0.0).
## 4.13.0 (2015-02-20)
* Fix the way the [`closetag`](http://codemirror.net/demo/closetag.html) demo handles the slash character.
* New modes: [Forth](http://codemirror.net/mode/forth/index.html), [Stylus](http://codemirror.net/mode/stylus/index.html).
* Make the [CSS mode](http://codemirror.net/mode/css/index.html) understand some modern CSS extensions.
* Have the [Scala mode](http://codemirror.net/mode/clike/index.html) handle symbols and triple-quoted strings.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.12.0...4.13.0).
## 4.12.0 (2015-01-22)
* The [`closetag`](http://codemirror.net/doc/manual.html#addon_closetag) addon now defines a `"closeTag"` command.
* Adds a `findModeByFileName` to the [mode metadata](http://codemirror.net/doc/manual.html#addon_meta) addon.
* [Simple mode](http://codemirror.net/demo/simplemode.html) rules can now contain a `sol` property to only match at the start of a line.
* New addon: [`selection-pointer`](http://codemirror.net/doc/manual.html#addon_selection-pointer) to style the mouse cursor over the selection.
* Improvements to the [Sass mode](http://codemirror.net/mode/sass/index.html)'s indentation.
* The [Vim keymap](http://codemirror.net/demo/vim.html)'s search functionality now supports [scrollbar annotation](http://codemirror.net/doc/manual.html#addon_matchesonscrollbar).
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.11.0...4.12.0).
## 4.11.0 (2015-01-09)
Unfortunately, 4.10 did not take care of the Firefox scrolling issue entirely. This release adds two more patches to address that.
## 4.10.0 (2014-12-29)
Emergency single-patch update to 4.9\. Fixes Firefox-specific problem where the cursor could end up behind the horizontal scrollbar.
## 4.9.0 (2014-12-23)
* Overhauled scroll bar handling. Add pluggable [scrollbar implementations](http://codemirror.net/demo/simplescrollbars.html).
* Tweaked behavior for the [completion addons](http://codemirror.net/doc/manual.html#addon_show-hint) to not take text after cursor into account.
* Two new optional features in the [merge addon](http://codemirror.net/doc/manual.html#addon_merge): aligning editors, and folding unchanged text.
* New modes: [Dart](http://codemirror.net/mode/dart/index.html), [EBNF](http://codemirror.net/mode/ebnf/index.html), [spreadsheet](http://codemirror.net/mode/spreadsheet/index.html), and [Soy](http://codemirror.net/mode/soy/index.html).
* New [addon](http://codemirror.net/demo/panel.html) to show persistent panels below/above an editor.
* New themes: [zenburn](http://codemirror.net/demo/theme.html#zenburn) and [tomorrow night bright](http://codemirror.net/demo/theme.html#tomorrow-night-bright).
* Allow ctrl-click to clear existing cursors.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.8.0...4.9.0).
## 4.8.0 (2014-11-22)
* Built-in support for [multi-stroke key bindings](http://codemirror.net/doc/manual.html#normalizeKeyMap).
* New method: [`getLineTokens`](http://codemirror.net/doc/manual.html#getLineTokens).
* New modes: [dockerfile](http://codemirror.net/mode/dockerfile/index.html), [IDL](http://codemirror.net/mode/idl/index.html), [Objective C](http://codemirror.net/mode/clike/index.html) (crude).
* Support styling of gutter backgrounds, allow `"gutter"` styles in [`addLineClass`](http://codemirror.net/doc/manual.html#addLineClass).
* Many improvements to the [Vim mode](http://codemirror.net/demo/vim.html), rewritten visual mode.
* Improvements to modes: [gfm](http://codemirror.net/mode/gfm/index.html) (strikethrough), [SPARQL](http://codemirror.net/mode/sparql/index.html) (version 1.1 support), and [sTeX](http://codemirror.net/mode/stex/index.html) (no more runaway math mode).
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.7.0...4.8.0).
## 4.7.0 (2014-10-20)
* **Incompatible**: The [lint addon](http://codemirror.net/demo/lint.html) 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](http://codemirror.net/mode/coffeescript/index.html) (indentation), [Verilog](http://codemirror.net/mode/verilog/index.html) (indentation), [Scala](http://codemirror.net/mode/clike/index.html) (indentation, triple-quoted strings), and [PHP](http://codemirror.net/mode/php/index.html) (interpolated variables in heredoc strings).
* New modes: [Textile](http://codemirror.net/mode/textile/index.html) and [Tornado templates](http://codemirror.net/mode/tornado/index.html).
* Experimental new [way to define modes](http://codemirror.net/demo/simplemode.html).
* Improvements to the [Vim bindings](http://codemirror.net/demo/vim.html): Arbitrary insert mode key mappings are now possible, and text objects are supported in visual mode.
* The mode [meta-information file](http://codemirror.net/mode/meta.js) now includes information about file extensions, and [helper functions](http://codemirror.net/doc/manual.html#addon_meta) `findModeByMIME` and `findModeByExtension`.
* New logo!
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.6.0...4.7.0).
## 4.6.0 (2014-09-19)
* New mode: [Modelica](http://codemirror.net/mode/modelica/index.html)
* New method: [`findWordAt`](http://codemirror.net/doc/manual.html#findWordAt)
* Make it easier to [use text background styling](http://codemirror.net/demo/markselection.html)
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.5.0...4.6.0).
## 4.5.0 (2014-08-21)
* Fix several serious bugs with horizontal scrolling
* New mode: [Slim](http://codemirror.net/mode/slim/index.html)
* New command: [`goLineLeftSmart`](http://codemirror.net/doc/manual.html#command_goLineLeftSmart)
* More fixes and extensions for the [Vim](http://codemirror.net/demo/vim.html) visual block mode
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.4.0...4.5.0).
## 4.4.0 (2014-07-21)
* **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](http://codemirror.net/demo/vim.html) (<C-v>) is nearly complete
* New mode: [Kotlin](http://codemirror.net/mode/kotlin/index.html)
* Better multi-selection paste for text copied from multiple CodeMirror selections
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.3.0...4.4.0).
## 4.3.0 (2014-06-23)
* Several [vim bindings](http://codemirror.net/demo/vim.html) improvements: search and exCommand history, global flag for `:substitute`, `:global` command.
* Allow hiding the cursor by setting [`cursorBlinkRate`](http://codemirror.net/doc/manual.html#option_cursorBlinkRate) to a negative value.
* Make gutter markers themeable, use this in foldgutter.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.2.0...4.3.0).
## 4.2.0 (2014-05-19)
* 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](http://codemirror.net/mode/jade/index.html) mode.
* Various improvements to [Shell](http://codemirror.net/mode/shell/index.html) (support for more syntax) and [Python](http://codemirror.net/mode/python/index.html) (better indentation) modes.
* New mode: [Cypher](http://codemirror.net/mode/cypher/index.html).
* New theme: [Neo](http://codemirror.net/demo/theme.html#neo).
* Support direct styling options (color, line style, width) in the [rulers](http://codemirror.net/doc/manual.html#addon_rulers) addon.
* Recognize per-editor configuration for the [show-hint](http://codemirror.net/doc/manual.html#addon_show-hint) and [foldcode](http://codemirror.net/doc/manual.html#addon_foldcode) addons.
* More intelligent scanning for existing close tags in [closetag](http://codemirror.net/doc/manual.html#addon_closetag) addon.
* In the [Vim bindings](http://codemirror.net/demo/vim.html): Fix bracket matching, support case conversion in visual mode, visual paste, append action.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.1.0...4.2.0).
## 4.1.0 (2014-04-22)
* _Slightly incompatible_: The [`"cursorActivity"`](http://codemirror.net/doc/manual.html#event_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`](http://codemirror.net/doc/manual.html#command_insertSoftTab).
* New mode: [Django](http://codemirror.net/mode/django/index.html).
* Improved modes: [Verilog](http://codemirror.net/mode/verilog/index.html) (rewritten), [Jinja2](http://codemirror.net/mode/jinja2/index.html), [Haxe](http://codemirror.net/mode/haxe/index.html), [PHP](http://codemirror.net/mode/php/index.html) (string interpolation highlighted), [JavaScript](http://codemirror.net/mode/javascript/index.html) (indentation of trailing else, template strings), [LiveScript](http://codemirror.net/mode/livescript/index.html) (multi-line strings).
* Many small issues from the 3.x→4.x transition were found and fixed.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.0.3...4.1.0).
## 3.24.0 (2014-04-22)
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.
## 3.23.0 (2014-03-20)
* In the [XML mode](http://codemirror.net/mode/xml/index.html), add `brackets` style to angle brackets, fix case-sensitivity of tags for HTML.
* New mode: [Dylan](http://codemirror.net/mode/dylan/index.html).
* Many improvements to the [Vim bindings](http://codemirror.net/demo/vim.html).
## 3.22.0 (2014-02-21)
* Adds the [`findMarks`](http://codemirror.net/doc/manual.html#findMarks) method.
* New addons: [rulers](http://codemirror.net/doc/manual.html#addon_rulers), markdown-fold, yaml-lint.
* New theme: [mdn-like](http://codemirror.net/demo/theme.html#mdn-like).
* New mode: [Solr](http://codemirror.net/mode/solr/index.html).
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.21.0...3.22.0).
## 3.21.0 (2014-01-16)
* Auto-indenting a block will no longer add trailing whitespace to blank lines.
* Marking text has a new option [`clearWhenEmpty`](http://codemirror.net/doc/manual.html#markText) to control auto-removal.
* Several bugfixes in the handling of bidirectional text.
* The [XML](http://codemirror.net/mode/xml/index.html) and [CSS](http://codemirror.net/mode/css/index.html) modes were largely rewritten. [LESS](http://codemirror.net/mode/css/less.html) support was added to the CSS mode.
* The OCaml mode was moved to an [mllike](http://codemirror.net/mode/mllike/index.html) mode, F# support added.
* Make it possible to fetch multiple applicable helper values with [`getHelpers`](http://codemirror.net/doc/manual.html#getHelpers), and to register helpers matched on predicates with [`registerGlobalHelper`](http://codemirror.net/doc/manual.html#registerGlobalHelper).
* New theme [pastel-on-dark](http://codemirror.net/demo/theme.html#pastel-on-dark).
* Better ECMAScript 6 support in [JavaScript](http://codemirror.net/mode/javascript/index.html) mode.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.20.0...3.21.0).
## 3.20.0 (2013-11-21)
* New modes: [Julia](http://codemirror.net/mode/julia/index.html) and [PEG.js](http://codemirror.net/mode/pegjs/index.html).
* Support ECMAScript 6 in the [JavaScript mode](http://codemirror.net/mode/javascript/index.html).
* Improved indentation for the [CoffeeScript mode](http://codemirror.net/mode/coffeescript/index.html).
* Make non-printable-character representation [configurable](http://codemirror.net/doc/manual.html#option_specialChars).
* Add notification functionality to [dialog](http://codemirror.net/doc/manual.html#addon_dialog) addon.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.19.0...3.20.0).
## 3.19.0 (2013-10-21)
* New modes: [Eiffel](http://codemirror.net/mode/eiffel/index.html), [Gherkin](http://codemirror.net/mode/gherkin/index.html), [MSSQL dialect](http://codemirror.net/mode/sql/?mime=text/x-mssql).
* New addons: [hardwrap](http://codemirror.net/doc/manual.html#addon_hardwrap), [sql-hint](http://codemirror.net/doc/manual.html#addon_sql-hint).
* New theme: [MBO](http://codemirror.net/demo/theme.html#mbo).
* Add [support](http://codemirror.net/doc/manual.html#token_style_line) for line-level styling from mode tokenizers.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.18.0...3.19.0).
## 3.18.0 (2013-09-23)
Emergency release to fix a problem in 3.17 where `.setOption("lineNumbers", false)` would raise an error.
## 3.17.0 (2013-09-23)
* New modes: [Fortran](http://codemirror.net/mode/fortran/index.html), [Octave](http://codemirror.net/mode/octave/index.html) (Matlab), [TOML](http://codemirror.net/mode/toml/index.html), and [DTD](http://codemirror.net/mode/dtd/index.html).
* New addons: [`css-lint`](http://codemirror.net/addon/lint/css-lint.js), [`css-hint`](http://codemirror.net/doc/manual.html#addon_css-hint).
* Improve resilience to CSS 'frameworks' that globally mess up `box-sizing`.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.16.0...3.17.0).
## 3.16.0 (2013-08-21)
* The whole codebase is now under a single [license](http://codemirror.net/LICENSE) file.
* The project page was overhauled and redesigned.
* New themes: [Paraiso](http://codemirror.net/demo/theme.html#paraiso-dark) ([light](http://codemirror.net/demo/theme.html#paraiso-light)), [The Matrix](http://codemirror.net/demo/theme.html#the-matrix).
* Improved interaction between themes and [active-line](http://codemirror.net/doc/manual.html#addon_active-line)/[matchbrackets](http://codemirror.net/doc/manual.html#addon_matchbrackets) addons.
* New [folding](http://codemirror.net/doc/manual.html#addon_foldcode) function `CodeMirror.fold.comment`.
* Added [fullscreen](http://codemirror.net/doc/manual.html#addon_fullscreen) addon.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.15.0...3.16.0).
## 3.15.0 (2013-07-29)
* New modes: [Jade](http://codemirror.net/mode/jade/index.html), [Nginx](http://codemirror.net/mode/nginx/index.html).
* New addons: [Tern](http://codemirror.net/demo/tern.html), [matchtags](http://codemirror.net/doc/manual.html#addon_matchtags), and [foldgutter](http://codemirror.net/doc/manual.html#addon_foldgutter).
* Introduced [_helper_](http://codemirror.net/doc/manual.html#getHelper) concept ([context](https://groups.google.com/forum/#!msg/codemirror/cOc0xvUUEUU/nLrX1-qnidgJ)).
* New method: [`getModeAt`](http://codemirror.net/doc/manual.html#getModeAt).
* New themes: base16 [dark](http://codemirror.net/demo/theme.html#base16-dark)/[light](http://codemirror.net/demo/theme.html#base16-light), 3024 [dark](http://codemirror.net/demo/theme.html#3024-night)/[light](http://codemirror.net/demo/theme.html#3024-day), [tomorrow-night](http://codemirror.net/demo/theme.html#tomorrow-night-eighties).
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.14.0...3.15.0).
## 3.14.0 (2013-06-20)
* New addons: [trailing space highlight](http://codemirror.net/doc/manual.html#addon_trailingspace), [XML completion](http://codemirror.net/doc/manual.html#addon_xml-hint) (rewritten), and [diff merging](http://codemirror.net/doc/manual.html#addon_merge).
* [`markText`](http://codemirror.net/doc/manual.html#markText) and [`addLineWidget`](http://codemirror.net/doc/manual.html#addLineWidget) now take a `handleMouseEvents` option.
* New methods: [`lineAtHeight`](http://codemirror.net/doc/manual.html#lineAtHeight), [`getTokenTypeAt`](http://codemirror.net/doc/manual.html#getTokenTypeAt).
* More precise cleanness-tracking using [`changeGeneration`](http://codemirror.net/doc/manual.html#changeGeneration) and [`isClean`](http://codemirror.net/doc/manual.html#isClean).
* Many extensions to [Emacs](http://codemirror.net/demo/emacs.html) mode (prefixes, more navigation units, and more).
* New events [`"keyHandled"`](http://codemirror.net/doc/manual.html#event_keyHandled) and [`"inputRead"`](http://codemirror.net/doc/manual.html#event_inputRead).
* Various improvements to [Ruby](http://codemirror.net/mode/ruby/index.html), [Smarty](http://codemirror.net/mode/smarty/index.html), [SQL](http://codemirror.net/mode/sql/index.html), and [Vim](http://codemirror.net/demo/vim.html) modes.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.13.0...3.14.0).
## 3.13.0 (2013-05-20)
* New modes: [COBOL](http://codemirror.net/mode/cobol/index.html) and [HAML](http://codemirror.net/mode/haml/index.html).
* New options: [`cursorScrollMargin`](http://codemirror.net/doc/manual.html#option_cursorScrollMargin) and [`coverGutterNextToScrollbar`](http://codemirror.net/doc/manual.html#option_coverGutterNextToScrollbar).
* New addon: [commenting](http://codemirror.net/doc/manual.html#addon_comment).
* More features added to the [Vim keymap](http://codemirror.net/demo/vim.html).
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.12...3.13.0).
## 3.12.0 (2013-04-19)
* New mode: [GNU assembler](http://codemirror.net/mode/gas/index.html).
* New options: [`maxHighlightLength`](http://codemirror.net/doc/manual.html#option_maxHighlightLength) and [`historyEventDelay`](http://codemirror.net/doc/manual.html#option_historyEventDelay).
* Added [`addToHistory`](http://codemirror.net/doc/manual.html#mark_addToHistory) option for `markText`.
* Various fixes to JavaScript tokenization and indentation corner cases.
* Further improvements to the vim mode.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.11...v3.12).
## 3.11.0 (2013-03-20)
* **Removed code:** `collapserange`, `formatting`, and `simple-hint` addons. `plsql` and `mysql` modes (use [`sql`](http://codemirror.net/mode/sql/index.html) mode).
* **Moved code:** the range-finding functions for folding now have [their own files](http://codemirror.net/addon/fold/).
* **Changed interface:** the [`continuecomment`](http://codemirror.net/doc/manual.html#addon_continuecomment) addon now exposes an option, rather than a command.
* New modes: [SCSS](http://codemirror.net/mode/css/scss.html), [Tcl](http://codemirror.net/mode/tcl/index.html), [LiveScript](http://codemirror.net/mode/livescript/index.html), and [mIRC](http://codemirror.net/mode/mirc/index.html).
* New addons: [`placeholder`](http://codemirror.net/demo/placeholder.html), [HTML completion](http://codemirror.net/demo/html5complete.html).
* New methods: [`hasFocus`](http://codemirror.net/doc/manual.html#hasFocus), [`defaultCharWidth`](http://codemirror.net/doc/manual.html#defaultCharWidth).
* New events: [`beforeCursorEnter`](http://codemirror.net/doc/manual.html#event_beforeCursorEnter), [`renderLine`](http://codemirror.net/doc/manual.html#event_renderLine).
* Many improvements to the [`show-hint`](http://codemirror.net/doc/manual.html#addon_show-hint) completion dialog addon.
* Tweak behavior of by-word cursor motion.
* Further improvements to the [vim mode](http://codemirror.net/demo/vim.html).
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.1...v3.11).
## 3.02.0 (2013-01-25)
Single-bugfix release. Fixes a problem that prevents CodeMirror instances from being garbage-collected after they become unused.
## 3.01.0 (2013-01-21)
* Move all add-ons into an organized directory structure under [`/addon`](http://codemirror.net/addon/). **You might have to adjust your paths.**
* New modes: [D](http://codemirror.net/mode/d/index.html), [Sass](http://codemirror.net/mode/sass/index.html), [APL](http://codemirror.net/mode/apl/index.html), [SQL](http://codemirror.net/mode/sql/index.html) (configurable), and [Asterisk](http://codemirror.net/mode/asterisk/index.html).
* Several bugfixes in right-to-left text support.
* Add [`rtlMoveVisually`](http://codemirror.net/doc/manual.html#option_rtlMoveVisually) option.
* Improvements to vim keymap.
* Add built-in (lightweight) [overlay mode](http://codemirror.net/doc/manual.html#addOverlay) support.
* Support `showIfHidden` option for [line widgets](http://codemirror.net/doc/manual.html#addLineWidget).
* Add simple [Python hinter](http://codemirror.net/doc/manual.html#addon_python-hint).
* Bring back the [`fixedGutter`](http://codemirror.net/doc/manual.html#option_fixedGutter) option.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.0...v3.01).
## 3.1.0 (2013-02-21)
* **Incompatible:** key handlers may now _return_, rather than _throw_ `CodeMirror.Pass` to signal they didn't handle the key.
* Make documents a [first-class construct](http://codemirror.net/doc/manual.html#api_doc), support split views and subviews.
* Add a [new module](http://codemirror.net/doc/manual.html#addon_show-hint) for showing completion hints. Deprecate `simple-hint.js`.
* Extend [htmlmixed mode](http://codemirror.net/mode/htmlmixed/index.html) to allow custom handling of script types.
* Support an `insertLeft` option to [`setBookmark`](http://codemirror.net/doc/manual.html#setBookmark).
* Add an [`eachLine`](http://codemirror.net/doc/manual.html#eachLine) method to iterate over a document.
* New addon modules: [selection marking](http://codemirror.net/demo/markselection.html), [linting](http://codemirror.net/demo/lint.html), and [automatic bracket closing](http://codemirror.net/demo/closebrackets.html).
* Add [`"beforeChange"`](http://codemirror.net/doc/manual.html#event_beforeChange) and [`"beforeSelectionChange"`](http://codemirror.net/doc/manual.html#event_beforeSelectionChange) events.
* Add [`"hide"`](http://codemirror.net/doc/manual.html#event_hide) and [`"unhide"`](http://codemirror.net/doc/manual.html#event_unhide) events to marked ranges.
* Fix [`coordsChar`](http://codemirror.net/doc/manual.html#coordsChar)'s interpretation of its argument to match the documentation.
* New modes: [Turtle](http://codemirror.net/mode/turtle/index.html) and [Q](http://codemirror.net/mode/q/index.html).
* Further improvements to the [vim mode](http://codemirror.net/demo/vim.html).
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.01...v3.1).
## 3.0.0 (2012-12-10)
**New major version**. Only partially backwards-compatible. See the [upgrading guide](http://codemirror.net/doc/upgrade_v3.html) 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](https://github.com/codemirror/CodeMirror/compare/v3.0rc2...v3.0).
## 2.38.0 (2013-01-21)
Integrate some bugfixes, enhancements to the vim keymap, and new modes ([D](http://codemirror.net/mode/d/index.html), [Sass](http://codemirror.net/mode/sass/index.html), [APL](http://codemirror.net/mode/apl/index.html)) from the v3 branch.
## 2.37.0 (2012-12-20)
* New mode: [SQL](http://codemirror.net/mode/sql/index.html) (will replace [plsql](http://codemirror.net/mode/plsql/index.html) and [mysql](http://codemirror.net/mode/mysql/index.html) modes).
* Further work on the new VIM mode.
* Fix Cmd/Ctrl keys on recent Operas on OS X.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v2.36...v2.37).
## 2.36.0 (2012-11-20)
* New mode: [Z80 assembly](http://codemirror.net/mode/z80/index.html).
* New theme: [Twilight](http://codemirror.net/demo/theme.html#twilight).
* Add command-line compression helper.
* Make [`scrollIntoView`](http://codemirror.net/doc/manual.html#scrollIntoView) public.
* Add [`defaultTextHeight`](http://codemirror.net/doc/manual.html#defaultTextHeight) method.
* Various extensions to the vim keymap.
* Make [PHP mode](http://codemirror.net/mode/php/index.html) build on [mixed HTML mode](http://codemirror.net/mode/htmlmixed/index.html).
* Add [comment-continuing](http://codemirror.net/doc/manual.html#addon_continuecomment) add-on.
* Full [list of patches](http://codemirror.net/https://github.com/codemirror/CodeMirror/compare/v2.35...v2.36).
## 2.35.0 (2012-10-22)
* New (sub) mode: [TypeScript](http://codemirror.net/mode/javascript/typescript.html).
* Don't overwrite (insert key) when pasting.
* Fix several bugs in [`markText`](http://codemirror.net/doc/manual.html#markText)/undo interaction.
* Better indentation of JavaScript code without semicolons.
* Add [`defineInitHook`](http://codemirror.net/doc/manual.html#defineInitHook) function.
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v2.34...v2.35).
## 2.34.0 (2012-09-19)
* New mode: [Common Lisp](http://codemirror.net/mode/commonlisp/index.html).
* 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`](http://codemirror.net/doc/manual.html#version) property.
* More robust handling of nested modes in [formatting](http://codemirror.net/demo/formatting.html) and [closetag](http://codemirror.net/demo/closetag.html) plug-ins.
* Un/redo now preserves [marked text](http://codemirror.net/doc/manual.html#markText) and bookmarks.
* [Full list](https://github.com/codemirror/CodeMirror/compare/v2.33...v2.34) of patches.
## 2.33.0 (2012-08-23)
* New mode: [Sieve](http://codemirror.net/mode/sieve/index.html).
* New [`getViewPort`](http://codemirror.net/doc/manual.html#getViewport) and [`onViewportChange`](http://codemirror.net/doc/manual.html#option_onViewportChange) API.
* [Configurable](http://codemirror.net/doc/manual.html#option_cursorBlinkRate) 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](https://github.com/codemirror/CodeMirror/compare/v2.32...v2.33) of patches.
## 2.32.0 (2012-07-23)
Emergency fix for a bug where an editor with line wrapping on IE will break when there is _no_ scrollbar.
## 2.31.0 (2012-07-20)
* New modes: [OCaml](http://codemirror.net/mode/ocaml/index.html), [Haxe](http://codemirror.net/mode/haxe/index.html), and [VB.NET](http://codemirror.net/mode/vb/index.html).
* Several fixes to the new scrolling model.
* Add a [`setSize`](http://codemirror.net/doc/manual.html#setSize) method for programmatic resizing.
* Add [`getHistory`](http://codemirror.net/doc/manual.html#getHistory) and [`setHistory`](http://codemirror.net/doc/manual.html#setHistory) methods.
* Allow custom line separator string in [`getValue`](http://codemirror.net/doc/manual.html#getValue) and [`getRange`](http://codemirror.net/doc/manual.html#getRange).
* Support double- and triple-click drag, double-clicking whitespace.
* And more... [(all patches)](https://github.com/codemirror/CodeMirror/compare/v2.3...v2.31)
## 2.30.0 (2012-06-22)
* **New scrollbar implementation**. Should flicker less. Changes DOM structure of the editor.
* New theme: [vibrant-ink](http://codemirror.net/demo/theme.html#vibrant-ink).
* Many extensions to the VIM keymap (including text objects).
* Add [mode-multiplexing](http://codemirror.net/demo/multiplex.html) utility script.
* Fix bug where right-click paste works in read-only mode.
* Add a [`getScrollInfo`](http://codemirror.net/doc/manual.html#getScrollInfo) method.
* Lots of other [fixes](https://github.com/codemirror/CodeMirror/compare/v2.25...v2.3).
## 2.25.0 (2012-05-23)
* New mode: [Erlang](http://codemirror.net/mode/erlang/index.html).
* **Remove xmlpure mode** (use [xml.js](http://codemirror.net/mode/xml/index.html)).
* 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.
## 2.24.0 (2012-04-23)
* **Drop support for Internet Explorer 6**.
* New modes: [Shell](http://codemirror.net/mode/shell/index.html), [Tiki wiki](http://codemirror.net/mode/tiki/index.html), [Pig Latin](http://codemirror.net/mode/pig/index.html).
* New themes: [Ambiance](http://codemirror.net/demo/theme.html#ambiance), [Blackboard](http://codemirror.net/demo/theme.html#blackboard).
* More control over drag/drop with [`dragDrop`](http://codemirror.net/doc/manual.html#option_dragDrop) and [`onDragEvent`](http://codemirror.net/doc/manual.html#option_onDragEvent) options.
* Make HTML mode a bit less pedantic.
* Add [`compoundChange`](http://codemirror.net/doc/manual.html#compoundChange) API method.
* Several fixes in undo history and line hiding.
* Remove (broken) support for `catchall` in key maps, add `nofallthrough` boolean field instead.
## 2.23.0 (2012-03-26)
* Change **default binding for tab**. Starting in 2.23, these bindings are default:
* Tab: Insert tab character
* Shift-tab: Reset line indentation to default
* Ctrl/Cmd-[: Reduce line indentation (old tab behaviour)
* Ctrl/Cmd-]: Increase line indentation (old shift-tab behaviour)
* New modes: [XQuery](http://codemirror.net/mode/xquery/index.html) and [VBScript](http://codemirror.net/mode/vbscript/index.html).
* Two new themes: [lesser-dark](http://codemirror.net/mode/less/index.html) and [xq-dark](http://codemirror.net/mode/xquery/index.html).
* Differentiate between background and text styles in [`setLineClass`](http://codemirror.net/doc/manual.html#setLineClass).
* Fix drag-and-drop in IE9+.
* Extend [`charCoords`](http://codemirror.net/doc/manual.html#charCoords) and [`cursorCoords`](http://codemirror.net/doc/manual.html#cursorCoords) with a `mode` argument.
* Add [`autofocus`](http://codemirror.net/doc/manual.html#option_autofocus) option.
* Add [`findMarksAt`](http://codemirror.net/doc/manual.html#findMarksAt) method.
## 2.22.0 (2012-02-27)
* Allow [key handlers](http://codemirror.net/doc/manual.html#keymaps) to pass up events, allow binding characters.
* Add [`autoClearEmptyLines`](http://codemirror.net/doc/manual.html#option_autoClearEmptyLines) option.
* Properly use tab stops when rendering tabs.
* Make PHP mode more robust.
* Support indentation blocks in [code folder](http://codemirror.net/doc/manual.html#addon_foldcode).
* Add a script for [highlighting instances of the selection](http://codemirror.net/doc/manual.html#addon_match-highlighter).
* New [.properties](http://codemirror.net/mode/properties/index.html) mode.
* Fix many bugs.
## 2.21.0 (2012-01-27)
* Added [LESS](http://codemirror.net/mode/less/index.html), [MySQL](http://codemirror.net/mode/mysql/index.html), [Go](http://codemirror.net/mode/go/index.html), and [Verilog](http://codemirror.net/mode/verilog/index.html) modes.
* Add [`smartIndent`](http://codemirror.net/doc/manual.html#option_smartIndent) option.
* Support a cursor in [`readOnly`](http://codemirror.net/doc/manual.html#option_readOnly)-mode.
* Support assigning multiple styles to a token.
* Use a new approach to drawing the selection.
* Add [`scrollTo`](http://codemirror.net/doc/manual.html#scrollTo) method.
* Allow undo/redo events to span non-adjacent lines.
* Lots and lots of bugfixes.
## 2.20.0 (2011-12-20)
* Slightly incompatible API changes. Read [this](http://codemirror.net/doc/upgrade_v2.2.html).
* New approach to [binding](http://codemirror.net/doc/manual.html#option_extraKeys) keys, support for [custom bindings](http://codemirror.net/doc/manual.html#option_keyMap).
* Support for overwrite (insert).
* [Custom-width](http://codemirror.net/doc/manual.html#option_tabSize) and [stylable](http://codemirror.net/demo/visibletabs.html) tabs.
* Moved more code into [add-on scripts](http://codemirror.net/doc/manual.html#addons).
* Support for sane vertical cursor movement in wrapped lines.
* More reliable handling of editing [marked text](http://codemirror.net/doc/manual.html#markText).
* Add minimal [emacs](http://codemirror.net/demo/emacs.html) and [vim](http://codemirror.net/demo/vim.html) bindings.
* Rename `coordsFromIndex` to [`posFromIndex`](http://codemirror.net/doc/manual.html#posFromIndex), add [`indexFromPos`](http://codemirror.net/doc/manual.html#indexFromPos) method.
## 2.18.0 (2011-11-21)
Fixes `TextMarker.clear`, which is broken in 2.17.
## 2.17.0 (2011-11-21)
* Add support for [line wrapping](http://codemirror.net/doc/manual.html#option_lineWrapping) and [code folding](http://codemirror.net/doc/manual.html#hideLine).
* Add [Github-style Markdown](http://codemirror.net/mode/gfm/index.html) mode.
* Add [Monokai](http://codemirror.net/theme/monokai.css) and [Rubyblue](http://codemirror.net/theme/rubyblue.css) themes.
* Add [`setBookmark`](http://codemirror.net/doc/manual.html#setBookmark) method.
* Move some of the demo code into reusable components under [`lib/util`](http://codemirror.net/addon/).
* 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.
## 2.16.0 (2011-10-27)
* Add [Perl](http://codemirror.net/mode/perl/index.html), [Rust](http://codemirror.net/mode/rust/index.html), [TiddlyWiki](http://codemirror.net/mode/tiddlywiki/index.html), and [Groovy](http://codemirror.net/mode/groovy/index.html) modes.
* Dragging text inside the editor now moves, rather than copies.
* Add a [`coordsFromIndex`](http://codemirror.net/doc/manual.html#coordsFromIndex) method.
* **API change**: `setValue` now no longer clears history. Use [`clearHistory`](http://codemirror.net/doc/manual.html#clearHistory) for that.
* **API change**: [`markText`](http://codemirror.net/doc/manual.html#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.
## 2.15.0 (2011-09-26)
Fix bug that snuck into 2.14: Clicking the character that currently has the cursor didn't re-focus the editor.
## 2.14.0 (2011-09-26)
* Add [Clojure](http://codemirror.net/mode/clojure/index.html), [Pascal](http://codemirror.net/mode/pascal/index.html), [NTriples](http://codemirror.net/mode/ntriples/index.html), [Jinja2](http://codemirror.net/mode/jinja2/index.html), and [Markdown](http://codemirror.net/mode/markdown/index.html) modes.
* Add [Cobalt](http://codemirror.net/theme/cobalt.css) and [Eclipse](http://codemirror.net/theme/eclipse.css) themes.
* Add a [`fixedGutter`](http://codemirror.net/doc/manual.html#option_fixedGutter) option.
* Fix bug with `setValue` breaking cursor movement.
* Make gutter updates much more efficient.
* Allow dragging of text out of the editor (on modern browsers).
## 2.13.0 (2011-08-23)
* Add [Ruby](http://codemirror.net/mode/ruby/index.html), [R](http://codemirror.net/mode/r/index.html), [CoffeeScript](http://codemirror.net/mode/coffeescript/index.html), and [Velocity](http://codemirror.net/mode/velocity/index.html) modes.
* Add [`getGutterElement`](http://codemirror.net/doc/manual.html#getGutterElement) to API.
* Several fixes to scrolling and positioning.
* Add [`smartHome`](http://codemirror.net/doc/manual.html#option_smartHome) option.
* Add an experimental [pure XML](http://codemirror.net/mode/xmlpure/index.html) mode.
## 2.12.0 (2011-07-25)
* Add a [SPARQL](http://codemirror.net/mode/sparql/index.html) 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.
## 2.11.0 (2011-07-04)
* Add a [Scheme mode](http://codemirror.net/mode/scheme/index.html).
* Add a `replace` method to search cursors, for cursor-preserving replacements.
* Make the [C-like mode](http://codemirror.net/mode/clike/index.html) 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](http://codemirror.net/mode/clike/index.html).
* Add [full-screen editing](http://codemirror.net/demo/fullscreen.html) and [mode-changing](http://codemirror.net/demo/changemode.html) demos.
## 2.10.0 (2011-06-07)
Add a [theme](http://codemirror.net/doc/manual.html#option_theme) system ([demo](http://codemirror.net/demo/theme.html)). Note that this is not backwards-compatible—you'll have to update your styles and modes!
## 2.2.0 (2011-06-07)
* Add a [Lua mode](http://codemirror.net/mode/lua/index.html).
* 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](http://codemirror.net/demo/runmode.html).
* Immediately re-highlight text when typing.
* Fix problem with 'sticking' horizontal scrollbar.
## 2.1.0 (2011-05-26)
* Add a [Smalltalk mode](http://codemirror.net/mode/smalltalk/index.html).
* Add a [reStructuredText mode](http://codemirror.net/mode/rst/index.html).
* Add a [Python mode](http://codemirror.net/mode/python/index.html).
* Add a [PL/SQL mode](http://codemirror.net/mode/plsql/index.html).
* `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.
## 2.0.0 (2011-03-28)
CodeMirror 2 is a complete rewrite that's faster, smaller, simpler to use, and less dependent on browser quirks. See [this](http://codemirror.net/doc/internals.html) and [this](http://groups.google.com/group/codemirror/browse_thread/thread/5a8e894024a9f580) for more information.

View File

@ -1,8 +1,8 @@
# How to contribute
- [Getting help](#getting-help-)
- [Submitting bug reports](#submitting-bug-reports-)
- [Contributing code](#contributing-code-)
- [Getting help](#getting-help)
- [Submitting bug reports](#submitting-bug-reports)
- [Contributing code](#contributing-code)
## Getting help

View File

@ -1,4 +1,4 @@
Copyright (C) 2015 by Marijn Haverbeke <marijnh@gmail.com> and others
Copyright (C) 2016 by Marijn Haverbeke <marijnh@gmail.com> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -16,7 +16,7 @@ new functionality.
You can find more information (and the
[manual](http://codemirror.net/doc/manual.html)) on the [project
page](http://codemirror.net). For questions and discussion, use the
[discussion forum](http://discuss.codemirror.net/).
[discussion forum](https://discuss.codemirror.net/).
See
[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)

View File

@ -21,22 +21,28 @@
}
CodeMirror.commands.toggleComment = function(cm) {
var minLine = Infinity, ranges = cm.listSelections(), mode = null;
cm.toggleComment();
};
CodeMirror.defineExtension("toggleComment", function(options) {
if (!options) options = noOptions;
var cm = this;
var minLine = Infinity, ranges = this.listSelections(), mode = null;
for (var i = ranges.length - 1; i >= 0; i--) {
var from = ranges[i].from(), to = ranges[i].to();
if (from.line >= minLine) continue;
if (to.line >= minLine) to = Pos(minLine, 0);
minLine = from.line;
if (mode == null) {
if (cm.uncomment(from, to)) mode = "un";
else { cm.lineComment(from, to); mode = "line"; }
if (cm.uncomment(from, to, options)) mode = "un";
else { cm.lineComment(from, to, options); mode = "line"; }
} else if (mode == "un") {
cm.uncomment(from, to);
cm.uncomment(from, to, options);
} else {
cm.lineComment(from, to);
cm.lineComment(from, to, options);
}
}
};
});
CodeMirror.defineExtension("lineComment", function(from, to, options) {
if (!options) options = noOptions;
@ -57,7 +63,14 @@
self.operation(function() {
if (options.indent) {
var baseString = firstLine.slice(0, firstNonWS(firstLine));
var baseString = null;
for (var i = from.line; i < end; ++i) {
var line = self.getLine(i);
var whitespace = line.slice(0, firstNonWS(line));
if (baseString == null || baseString.length > whitespace.length) {
baseString = whitespace;
}
}
for (var i = from.line; i < end; ++i) {
var line = self.getLine(i), cut = baseString.length;
if (!blankLines && !nonWS.test(line)) continue;

View File

@ -56,6 +56,8 @@
var inp = dialog.getElementsByTagName("input")[0], button;
if (inp) {
inp.focus();
if (options.value) {
inp.value = options.value;
if (options.selectValueOnOpen !== false) {
@ -79,8 +81,6 @@
});
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
inp.focus();
} else if (button = dialog.getElementsByTagName("button")[0]) {
CodeMirror.on(button, "click", function() {
close();

View File

@ -14,10 +14,12 @@
if (val && !prev) {
cm.on("blur", onBlur);
cm.on("change", onChange);
cm.on("swapDoc", onChange);
onChange(cm);
} else if (!val && prev) {
cm.off("blur", onBlur);
cm.off("change", onChange);
cm.off("swapDoc", onChange);
clearPlaceholder(cm);
var wrapper = cm.getWrapperElement();
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");

View File

@ -63,7 +63,7 @@
}
for (var i = ranges.length - 1; i >= 0; i--) {
var cur = ranges[i].head;
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
}
}

View File

@ -28,7 +28,9 @@ CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
continue;
}
if (pass == 1 && found < start.ch) return;
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)))) {
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
(lineText.slice(found - endToken.length, found) == endToken ||
!/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
startCh = found + startToken.length;
break;
}

View File

@ -20,7 +20,7 @@
cm.off("viewportChange", onViewportChange);
cm.off("fold", onFold);
cm.off("unfold", onFold);
cm.off("swapDoc", updateInViewport);
cm.off("swapDoc", onChange);
}
if (val) {
cm.state.foldGutter = new State(parseOptions(val));
@ -30,7 +30,7 @@
cm.on("viewportChange", onViewportChange);
cm.on("fold", onFold);
cm.on("unfold", onFold);
cm.on("swapDoc", updateInViewport);
cm.on("swapDoc", onChange);
}
});

View File

@ -25,8 +25,18 @@
};
CodeMirror.defineExtension("showHint", function(options) {
// We want a single cursor position.
if (this.listSelections().length > 1 || this.somethingSelected()) return;
options = parseOptions(this, this.getCursor("start"), options);
var selections = this.listSelections()
if (selections.length > 1) return;
// By default, don't allow completion when something is selected.
// A hint function can have a `supportsSelection` property to
// indicate that it can handle selections.
if (this.somethingSelected()) {
if (!options.hint.supportsSelection) return;
// Don't try with cross-line selections
for (var i = 0; i < selections.length; i++)
if (selections[i].head.line != selections[i].anchor.line) return;
}
if (this.state.completionActive) this.state.completionActive.close();
var completion = this.state.completionActive = new Completion(this, options);
@ -38,12 +48,12 @@
function Completion(cm, options) {
this.cm = cm;
this.options = this.buildOptions(options);
this.options = options;
this.widget = null;
this.debounce = 0;
this.tick = 0;
this.startPos = this.cm.getCursor();
this.startLen = this.cm.getLine(this.startPos.line).length;
this.startPos = this.cm.getCursor("start");
this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
var self = this;
cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
@ -111,11 +121,13 @@
finishUpdate: function(data, first) {
if (this.data) CodeMirror.signal(this.data, "update");
if (data && this.data && CodeMirror.cmpPos(data.from, this.data.from)) data = null;
this.data = data;
var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
if (this.widget) this.widget.close();
if (data && this.data && isNewCompletion(this.data, data)) return;
this.data = data;
if (data && data.list.length) {
if (picked && data.list.length == 1) {
this.pick(data, 0);
@ -124,20 +136,26 @@
CodeMirror.signal(data, "shown");
}
}
},
buildOptions: function(options) {
var editor = this.cm.options.hintOptions;
var out = {};
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
if (editor) for (var prop in editor)
if (editor[prop] !== undefined) out[prop] = editor[prop];
if (options) for (var prop in options)
if (options[prop] !== undefined) out[prop] = options[prop];
return out;
}
};
function isNewCompletion(old, nw) {
var moved = CodeMirror.cmpPos(nw.from, old.from)
return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch
}
function parseOptions(cm, pos, options) {
var editor = cm.options.hintOptions;
var out = {};
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
if (editor) for (var prop in editor)
if (editor[prop] !== undefined) out[prop] = editor[prop];
if (options) for (var prop in options)
if (options[prop] !== undefined) out[prop] = options[prop];
if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
return out;
}
function getText(completion) {
if (typeof completion == "string") return completion;
else return completion.text;
@ -336,18 +354,61 @@
}
};
CodeMirror.registerHelper("hint", "auto", function(cm, options) {
var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
function applicableHelpers(cm, helpers) {
if (!cm.somethingSelected()) return helpers
var result = []
for (var i = 0; i < helpers.length; i++)
if (helpers[i].supportsSelection) result.push(helpers[i])
return result
}
function resolveAutoHints(cm, pos) {
var helpers = cm.getHelpers(pos, "hint"), words
if (helpers.length) {
for (var i = 0; i < helpers.length; i++) {
var cur = helpers[i](cm, options);
if (cur && cur.list.length) return cur;
var async = false, resolved
for (var i = 0; i < helpers.length; i++) if (helpers[i].async) async = true
if (async) {
resolved = function(cm, callback, options) {
var app = applicableHelpers(cm, helpers)
function run(i, result) {
if (i == app.length) return callback(null)
var helper = app[i]
if (helper.async) {
helper(cm, function(result) {
if (result) callback(result)
else run(i + 1)
}, options)
} else {
var result = helper(cm, options)
if (result) callback(result)
else run(i + 1)
}
}
run(0)
}
resolved.async = true
} else {
resolved = function(cm, options) {
var app = applicableHelpers(cm, helpers)
for (var i = 0; i < app.length; i++) {
var cur = app[i](cm, options)
if (cur && cur.list.length) return cur
}
}
}
resolved.supportsSelection = true
return resolved
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
if (words) return CodeMirror.hint.fromList(cm, {words: words});
return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
} else if (CodeMirror.hint.anyword) {
return CodeMirror.hint.anyword(cm, options);
return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
} else {
return function() {}
}
}
CodeMirror.registerHelper("hint", "auto", {
resolve: resolveAutoHints
});
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
@ -376,7 +437,7 @@
alignWithWord: true,
closeCharacters: /[\s()\[\]{};:>,]/,
closeOnUnfocus: true,
completeOnSingleClick: false,
completeOnSingleClick: true,
container: null,
customKeys: null,
extraKeys: null

View File

@ -20,6 +20,8 @@
};
var Pos = CodeMirror.Pos;
function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
function getKeywords(editor) {
var mode = editor.doc.modeOption;
if (mode === "sql") mode = "text/x-sql";
@ -30,10 +32,28 @@
return typeof item == "string" ? item : item.text;
}
function getItem(list, item) {
if (!list.slice) return list[item];
for (var i = list.length - 1; i >= 0; i--) if (getText(list[i]) == item)
return list[i];
function wrapTable(name, value) {
if (isArray(value)) value = {columns: value}
if (!value.text) value.text = name
return value
}
function parseTables(input) {
var result = {}
if (isArray(input)) {
for (var i = input.length - 1; i >= 0; i--) {
var item = input[i]
result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
}
} else if (input) {
for (var name in input)
result[name.toUpperCase()] = wrapTable(name, input[name])
}
return result
}
function getTable(name) {
return tables[name.toUpperCase()]
}
function shallowClone(object) {
@ -50,11 +70,18 @@
}
function addMatches(result, search, wordlist, formatter) {
for (var word in wordlist) {
if (!wordlist.hasOwnProperty(word)) continue;
if (wordlist.slice) word = wordlist[word];
if (match(search, word)) result.push(formatter(word));
if (isArray(wordlist)) {
for (var i = 0; i < wordlist.length; i++)
if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
} else {
for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
var val = wordlist[word]
if (!val || val === true)
val = word
else
val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
if (match(search, val)) result.push(formatter(val))
}
}
}
@ -115,13 +142,13 @@
var alias = false;
var aliasTable = table;
// Check if table is available. If not, find table by Alias
if (!getItem(tables, table)) {
if (!getTable(table)) {
var oldTable = table;
table = findTableByAlias(table, editor);
if (table !== oldTable) alias = true;
}
var columns = getItem(tables, table);
var columns = getTable(table);
if (columns && columns.columns)
columns = columns.columns;
@ -184,7 +211,7 @@
//find valid range
var prevItem = 0;
var current = convertCurToNumber(editor.getCursor());
for (var i=0; i< separator.length; i++) {
for (var i = 0; i < separator.length; i++) {
var _v = convertCurToNumber(separator[i]);
if (current > prevItem && current <= _v) {
validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) };
@ -199,7 +226,7 @@
var lineText = query[i];
eachWord(lineText, function(word) {
var wordUpperCase = word.toUpperCase();
if (wordUpperCase === aliasUpperCase && getItem(tables, previousWord))
if (wordUpperCase === aliasUpperCase && getTable(previousWord))
table = previousWord;
if (wordUpperCase !== CONS.ALIAS_KEYWORD)
previousWord = word;
@ -210,10 +237,10 @@
}
CodeMirror.registerHelper("hint", "sql", function(editor, options) {
tables = (options && options.tables) || {};
tables = parseTables(options && options.tables)
var defaultTableName = options && options.defaultTable;
var disableKeywords = options && options.disableKeywords;
defaultTable = defaultTableName && getItem(tables, defaultTableName);
defaultTable = defaultTableName && getTable(defaultTableName);
keywords = keywords || getKeywords(editor);
if (defaultTableName && !defaultTable)

View File

@ -186,9 +186,14 @@
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
}
function popupSpanTooltip(ann, e) {
function popupTooltips(annotations, e) {
var target = e.target || e.srcElement;
showTooltipFor(e, annotationTooltip(ann), target);
var tooltip = document.createDocumentFragment();
for (var i = 0; i < annotations.length; i++) {
var ann = annotations[i];
tooltip.appendChild(annotationTooltip(ann));
}
showTooltipFor(e, tooltip, target);
}
function onMouseOver(cm, e) {
@ -196,10 +201,12 @@
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
var annotations = [];
for (var i = 0; i < spans.length; ++i) {
var ann = spans[i].__annotation;
if (ann) return popupSpanTooltip(ann, e);
annotations.push(spans[i].__annotation);
}
if (annotations.length) popupTooltips(annotations, e);
}
CodeMirror.defineOption("lint", false, function(cm, val, old) {

View File

@ -60,6 +60,7 @@
position: absolute;
cursor: pointer;
color: #44c;
z-index: 3;
}
.CodeMirror-merge-copy-reverse {

View File

@ -5,7 +5,7 @@
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("diff_match_patch"));
mod(require("../../lib/codemirror")); // Note non-packaged dependency diff_match_patch
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "diff_match_patch"], mod);
else // Plain browser env
@ -427,8 +427,9 @@
function copyChunk(dv, to, from, chunk) {
if (dv.diffOutOfDate) return;
to.replaceRange(from.getRange(Pos(chunk.origFrom, 0), Pos(chunk.origTo, 0)),
Pos(chunk.editFrom, 0), Pos(chunk.editTo, 0));
var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0)
to.replaceRange(from.getRange(origStart, Pos(chunk.origTo, 0)), editStart, Pos(chunk.editTo, 0))
}
// Merge view, containing 0, 1, or 2 diff views.
@ -471,13 +472,10 @@
if (left) left.init(leftPane, origLeft, options);
if (right) right.init(rightPane, origRight, options);
if (options.collapseIdentical) {
updating = true;
if (options.collapseIdentical)
this.editor().operation(function() {
collapseIdenticalStretches(self, options.collapseIdentical);
});
updating = false;
}
if (options.connect == "align") {
this.aligners = [];
alignChunks(this.left || this.right, true);
@ -640,7 +638,7 @@
mark.clear();
cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
}
widget.addEventListener("click", clear);
CodeMirror.on(widget, "click", clear);
return {mark: mark, clear: clear};
}

View File

@ -51,7 +51,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
if (!other.parseDelimiters) stream.match(other.open);
state.innerActive = other;
state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
return other.delimStyle;
return other.delimStyle && (other.delimStyle + " " + other.delimStyle + "-open");
} else if (found != -1 && found < cutOff) {
cutOff = found;
}
@ -70,7 +70,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
if (found == stream.pos && !curInner.parseDelimiters) {
stream.match(curInner.close);
state.innerActive = state.inner = null;
return curInner.delimStyle;
return curInner.delimStyle && (curInner.delimStyle + " " + curInner.delimStyle + "-close");
}
if (found > -1) stream.string = oldContent.slice(0, found);
var innerToken = curInner.mode.token(stream, state.inner);
@ -80,7 +80,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
state.innerActive = state.inner = null;
if (curInner.innerStyle) {
if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle;
if (innerToken) innerToken = innerToken + " " + curInner.innerStyle;
else innerToken = curInner.innerStyle;
}

View File

@ -29,5 +29,5 @@
MT(
"stexInsideMarkdown",
"[strong **Equation:**] [delim $][inner&tag \\pi][delim $]");
"[strong **Equation:**] [delim&delim-open $][inner&tag \\pi][delim&delim-close $]");
})();

View File

@ -60,7 +60,7 @@
function ensureState(states, name) {
if (!states.hasOwnProperty(name))
throw new Error("Undefined state " + name + "in simple mode");
throw new Error("Undefined state " + name + " in simple mode");
}
function toRegex(val, caret) {

View File

@ -16,7 +16,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) {
var ie = /MSIE \d/.test(navigator.userAgent);
var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
if (callback.nodeType == 1) {
if (callback.appendChild) {
var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
var node = callback, col = 0;
node.innerHTML = "";

View File

@ -176,3 +176,4 @@ exports.runMode = function(string, modespec, callback, options) {
};
require.cache[require.resolve("../../lib/codemirror")] = require.cache[require.resolve("./runmode.node")];
require.cache[require.resolve("../../addon/runmode/runmode")] = require.cache[require.resolve("./runmode.node")];

View File

@ -51,7 +51,7 @@
Annotation.prototype.computeScale = function() {
var cm = this.cm;
var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
cm.heightAtLine(cm.lastLine() + 1, "local");
cm.getScrollerElement().scrollHeight
if (hScale != this.hScale) {
this.hScale = hScale;
return true;
@ -100,6 +100,9 @@
elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
+ (top + this.buttonHeight) + "px; height: " + height + "px";
elt.className = this.options.className;
if (ann.id) {
elt.setAttribute("annotation-id", ann.id);
}
}
this.div.textContent = "";
this.div.appendChild(frag);

View File

@ -59,16 +59,20 @@
CodeMirror.on(this.node, "DOMMouseScroll", onWheel);
}
Bar.prototype.moveTo = function(pos, update) {
Bar.prototype.setPos = function(pos) {
if (pos < 0) pos = 0;
if (pos > this.total - this.screen) pos = this.total - this.screen;
if (pos == this.pos) return;
if (pos == this.pos) return false;
this.pos = pos;
this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
(pos * (this.size / this.total)) + "px";
if (update !== false) this.scroll(pos, this.orientation);
return true
};
Bar.prototype.moveTo = function(pos) {
if (this.setPos(pos)) this.scroll(pos, this.orientation);
}
var minButtonSize = 10;
Bar.prototype.update = function(scrollSize, clientSize, barSize) {
@ -83,8 +87,7 @@
}
this.inner.style[this.orientation == "horizontal" ? "width" : "height"] =
buttonSize + "px";
this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
this.pos * (this.size / this.total) + "px";
this.setPos(this.pos);
};
function SimpleScrollbars(cls, place, scroll) {
@ -111,7 +114,6 @@
if (needsV) {
this.vert.update(measure.scrollHeight, measure.clientHeight,
measure.viewHeight - (needsH ? width : 0));
this.vert.node.style.display = "block";
this.vert.node.style.bottom = needsH ? width + "px" : "0";
}
if (needsH) {
@ -125,11 +127,11 @@
};
SimpleScrollbars.prototype.setScrollTop = function(pos) {
this.vert.moveTo(pos, false);
this.vert.setPos(pos);
};
SimpleScrollbars.prototype.setScrollLeft = function(pos) {
this.horiz.moveTo(pos, false);
this.horiz.setPos(pos);
};
SimpleScrollbars.prototype.clear = function() {

View File

@ -0,0 +1,49 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// Defines jumpToLine command. Uses dialog.js if present.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../dialog/dialog"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../dialog/dialog"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function dialog(cm, text, shortText, deflt, f) {
if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
else f(prompt(shortText, deflt));
}
var jumpDialog =
'Jump to line: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use line:column or scroll% syntax)</span>';
function interpretLine(cm, string) {
var num = Number(string)
if (/^[-+]/.test(string)) return cm.getCursor().line + num
else return num - 1
}
CodeMirror.commands.jumpToLine = function(cm) {
var cur = cm.getCursor();
dialog(cm, jumpDialog, "Jump to line:", (cur.line + 1) + ":" + cur.ch, function(posStr) {
if (!posStr) return;
var match;
if (match = /^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(posStr)) {
cm.setCursor(interpretLine(cm, match[1]), Number(match[2]))
} else if (match = /^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(posStr)) {
var line = Math.round(cm.lineCount() * Number(match[1]) / 100);
if (/^[-+]/.test(match[1])) line = cur.line + line + 1;
cm.setCursor(line - 1, cur.ch);
} else if (match = /^\s*\:?\s*([\+\-]?\d+)\s*/.exec(posStr)) {
cm.setCursor(interpretLine(cm, match[1]), cur.ch);
}
});
};
CodeMirror.keyMap["default"]["Alt-G"] = "jumpToLine";
});

View File

@ -16,13 +16,14 @@
// highlighted only if the selected text is a word. showToken, when enabled,
// will cause the current token to be highlighted when nothing is selected.
// delay is used to specify how much time to wait, in milliseconds, before
// highlighting the matches.
// highlighting the matches. If annotateScrollbar is enabled, the occurances
// will be highlighted on the scrollbar via the matchesonscrollbar addon.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
mod(require("../../lib/codemirror"), require("./matchesonscrollbar"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
define(["../../lib/codemirror", "./matchesonscrollbar"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
@ -40,18 +41,19 @@
this.showToken = options.showToken;
this.delay = options.delay;
this.wordsOnly = options.wordsOnly;
this.annotateScrollbar = options.annotateScrollbar;
}
if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
if (this.delay == null) this.delay = DEFAULT_DELAY;
if (this.wordsOnly == null) this.wordsOnly = DEFAULT_WORDS_ONLY;
this.overlay = this.timeout = null;
this.matchesonscroll = null;
}
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
var over = cm.state.matchHighlighter.overlay;
if (over) cm.removeOverlay(over);
removeOverlay(cm);
clearTimeout(cm.state.matchHighlighter.timeout);
cm.state.matchHighlighter = null;
cm.off("cursorActivity", cursorActivity);
@ -69,20 +71,39 @@
state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay);
}
function addOverlay(cm, query, hasBoundary, style) {
var state = cm.state.matchHighlighter;
cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
if (state.annotateScrollbar) {
var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query;
state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, true,
{className: "CodeMirror-selection-highlight-scrollbar"});
}
}
function removeOverlay(cm) {
var state = cm.state.matchHighlighter;
if (state.overlay) {
cm.removeOverlay(state.overlay);
state.overlay = null;
if (state.annotateScrollbar) {
state.matchesonscroll.clear();
state.matchesonscroll = null;
}
}
}
function highlightMatches(cm) {
cm.operation(function() {
var state = cm.state.matchHighlighter;
if (state.overlay) {
cm.removeOverlay(state.overlay);
state.overlay = null;
}
removeOverlay(cm);
if (!cm.somethingSelected() && state.showToken) {
var re = state.showToken === true ? /[\w$]/ : state.showToken;
var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
while (start && re.test(line.charAt(start - 1))) --start;
while (end < line.length && re.test(line.charAt(end))) ++end;
if (start < end)
cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style));
addOverlay(cm, line.slice(start, end), re, state.style);
return;
}
var from = cm.getCursor("from"), to = cm.getCursor("to");
@ -90,7 +111,7 @@
if (state.wordsOnly && !isWord(cm, from, to)) return;
var selection = cm.getRange(from, to).replace(/^\s+|\s+$/g, "");
if (selection.length >= state.minChars)
cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
addOverlay(cm, selection, false, state.style);
});
}

View File

@ -29,7 +29,7 @@
query.lastIndex = stream.pos;
var match = query.exec(stream.string);
if (match && match.index == stream.pos) {
stream.pos += match[0].length;
stream.pos += match[0].length || 1;
return "searching";
} else if (match) {
stream.pos = match.index;

View File

@ -18,6 +18,7 @@
"use strict";
var WRAP_CLASS = "CodeMirror-activeline";
var BACK_CLASS = "CodeMirror-activeline-background";
var GUTT_CLASS = "CodeMirror-activeline-gutter";
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
var prev = old && old != CodeMirror.Init;
@ -36,6 +37,7 @@
for (var i = 0; i < cm.state.activeLines.length; i++) {
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS);
}
}
@ -60,6 +62,7 @@
for (var i = 0; i < active.length; i++) {
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
cm.addLineClass(active[i], "background", BACK_CLASS);
cm.addLineClass(active[i], "gutter", GUTT_CLASS);
}
cm.state.activeLines = active;
});

View File

@ -135,6 +135,7 @@
},
destroy: function () {
closeArgHints(this)
if (this.worker) {
this.worker.terminate();
this.worker = null;

View File

@ -32,11 +32,13 @@
function findBreakPoint(text, column, wrapOn, killTrailingSpace) {
for (var at = column; at > 0; --at)
if (wrapOn.test(text.slice(at - 1, at + 1))) break;
if (at == 0) at = column;
var endOfText = at;
if (killTrailingSpace)
while (text.charAt(endOfText - 1) == " ") --endOfText;
return {from: endOfText, to: at};
for (var first = true;; first = false) {
var endOfText = at;
if (killTrailingSpace)
while (text.charAt(endOfText - 1) == " ") --endOfText;
if (endOfText == 0 && first) at = column;
else return {from: endOfText, to: at};
}
}
function wrapRange(cm, from, to, options) {
@ -86,7 +88,8 @@
if (changes.length) cm.operation(function() {
for (var i = 0; i < changes.length; ++i) {
var change = changes[i];
cm.replaceRange(change.text, change.from, change.to);
if (change.text || CodeMirror.cmpPos(change.from, change.to))
cm.replaceRange(change.text, change.from, change.to);
}
});
return changes.length ? {from: changes[0].from, to: CodeMirror.changeEnd(changes[changes.length - 1])} : null;

Some files were not shown because too many files have changed in this diff Show More