├── .gitignore
├── src
├── buffer
│ ├── segments.test.js
│ ├── indexer.js
│ ├── syntax.bench.js
│ ├── segments.bench.js
│ ├── bench.js
│ ├── tokens.bench.js
│ ├── prefixtree.js
│ ├── prefixtree.test.js
│ ├── tokens.js
│ ├── skipstring.bench.js
│ ├── syntax.js
│ ├── parts.js
│ ├── buffer.old.js
│ ├── segments.js
│ ├── test.js
│ ├── buffer.old.test.js
│ ├── skipstring.js
│ └── lines.js
├── views
│ ├── view.js
│ ├── ruler.js
│ ├── caret.js
│ ├── index.js
│ ├── rows.js
│ ├── mark.js
│ ├── find.js
│ ├── block.js
│ └── code.js
├── file.js
├── input
│ ├── index.js
│ ├── mouse.js
│ ├── text.js
│ └── bindings.js
├── style.css
├── theme.js
├── history.js
├── history.test.js
└── move.js
├── lib
├── merge.js
├── save.js
├── bind-raf.js
├── open.js
├── debounce.js
├── print.js
├── clone.js
├── throttle.js
├── diff.js
├── dialog
│ ├── test
│ │ ├── dialog.html.js
│ │ └── dialog.html
│ ├── style.css
│ └── index.js
├── diff.test.js
├── parse.js
├── range-gate-and.js
├── range-gate-not.js
├── binary-search.js
├── atomic.js
├── memoize.js
├── set-immediate.js
├── trim.js
├── event.js
├── range.js
├── trim.test.js
├── box.js
├── regexp.js
├── xhr-require.js
├── point.js
├── area.js
└── dom.js
├── test
├── index.html
├── test.js
├── bench.js
├── syntax.html
├── suite.js
├── index.css
├── broken.js
├── assert.js
├── index.js
└── syntax.html.js
├── theme
├── theme.css
├── color-picker.css
├── theme.js
├── generator.js
└── index.html
├── examples
├── default.html
├── default.js
└── style.css
├── package.json
├── Makefile
├── README.md
├── dist
└── jazz.css
└── Issues
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/src/buffer/segments.test.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/merge.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function merge(dest, src) {
3 | for (var key in src) {
4 | dest[key] = src[key];
5 | }
6 | return dest;
7 | };
8 |
--------------------------------------------------------------------------------
/lib/save.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = save;
3 |
4 | function save(url, src, cb) {
5 | return fetch(url, {
6 | method: 'POST',
7 | body: src,
8 | })
9 | .then(cb.bind(null, null))
10 | .catch(cb);
11 | }
12 |
--------------------------------------------------------------------------------
/lib/bind-raf.js:
--------------------------------------------------------------------------------
1 | module.exports = function(fn) {
2 | var request;
3 | return function rafWrap(a, b, c, d) {
4 | window.cancelAnimationFrame(request);
5 | request = window.requestAnimationFrame(fn.bind(this, a, b, c, d));
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/lib/open.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = open;
3 |
4 | function open(url, cb) {
5 | return fetch(url)
6 | .then(getText)
7 | .then(cb.bind(null, null))
8 | .catch(cb);
9 | }
10 |
11 | function getText(res) {
12 | return res.text();
13 | }
14 |
--------------------------------------------------------------------------------
/lib/debounce.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function(fn, ms) {
3 | var timeout;
4 |
5 | return function debounceWrap(a, b, c, d) {
6 | clearTimeout(timeout);
7 | timeout = setTimeout(fn.bind(this, a, b, c, d), ms);
8 | return timeout;
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/lib/print.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = print;
3 |
4 | function print(o, depth) {
5 | var inspect = require('util').inspect;
6 | if ('object' === typeof o) {
7 | console.log(inspect(o, null, depth || null, true));
8 | } else {
9 | console.log(o.length, o);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/clone.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function clone(obj) {
3 | var o = {};
4 | for (var key in obj) {
5 | var val = obj[key];
6 | if ('object' === typeof val) {
7 | o[key] = clone(val);
8 | } else {
9 | o[key] = val;
10 | }
11 | }
12 | return o;
13 | };
14 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/views/view.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = View;
3 |
4 | function View(editor) {
5 | this.editor = editor;
6 | }
7 |
8 | View.prototype.render = function() {
9 | throw new Error('render not implemented');
10 | };
11 |
12 | View.prototype.clear = function() {
13 | throw new Error('clear not implemented');
14 | };
15 |
--------------------------------------------------------------------------------
/lib/throttle.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function(fn, ms) {
3 | var running, timeout;
4 |
5 | return function(a, b, c) {
6 | if (running) return;
7 | running = true;
8 | fn.call(this, a, b, c);
9 | setTimeout(reset, ms);
10 | };
11 |
12 | function reset() {
13 | running = false;
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/lib/diff.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = diff;
3 |
4 | function diff(a, b) {
5 | if ('object' === typeof a) {
6 | var d = {};
7 | var i = 0;
8 | for (var k in b) {
9 | if (a[k] !== b[k]) {
10 | d[k] = b[k];
11 | i++;
12 | }
13 | }
14 | if (i) return d;
15 | } else {
16 | return a !== b;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/dialog/test/dialog.html.js:
--------------------------------------------------------------------------------
1 |
2 | var Dialog = require('../');
3 |
4 | var dialog = new Dialog('Find');
5 |
6 | dialog.on('submit', function(s) {
7 | console.log(s);
8 | });
9 |
10 | dialog.on('value', function(s) {
11 | console.log(s);
12 | });
13 |
14 | dialog.open();
15 |
16 | setTimeout(function() {
17 | dialog.info('5/16')
18 | }, 1000)
19 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = test;
3 |
4 | var meta = {
5 | left: '',
6 | right: ''
7 | };
8 |
9 | function test(fn) {
10 | var error = null;
11 | var pass;
12 |
13 | try {
14 | fn();
15 | pass = true;
16 | } catch(e) {
17 | e.meta = e.meta || meta;
18 | error = e;
19 | pass = false;
20 | }
21 |
22 | return {
23 | pass: pass,
24 | error: error
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/buffer/indexer.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = Indexer;
3 |
4 | function Indexer(buffer) {
5 | this.buffer = buffer;
6 | }
7 |
8 | Indexer.prototype.find = function(s) {
9 | if (!s) return [];
10 | var offsets = [];
11 | var text = this.buffer.raw;
12 | var len = s.length;
13 | var index;
14 | while (~(index = text.indexOf(s, index + len))) {
15 | offsets.push(index);
16 | }
17 | return offsets;
18 | };
19 |
--------------------------------------------------------------------------------
/lib/dialog/test/dialog.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Test
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/diff.test.js:
--------------------------------------------------------------------------------
1 |
2 | var assert = require('assert');
3 |
4 | module.exports = function(t, diff) {
5 | t('diff', function() {
6 | assert.equal({b:3}, diff({a:1,b:2},{a:1,b:3}));
7 | assert.equal({a:1,b:3}, diff({b:2},{a:1,b:3}));
8 | assert.equal({a:1,b:3}, diff({},{a:1,b:3}));
9 | assert.equal(undefined, diff({a:1,b:3},{a:1,b:3}));
10 | assert.equal(undefined, diff({a:1,b:3,c:4},{a:1,b:3}));
11 | assert.equal({a:2,d:5}, diff({a:1,b:3,c:4},{a:2,b:3,d:5}));
12 | })
13 | }
--------------------------------------------------------------------------------
/test/bench.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = bench;
3 |
4 | function bench(times, fn, name, mul) {
5 | var time = Date.now();
6 |
7 | for (var i = times; i--;) {
8 | fn();
9 | }
10 |
11 | var diff = Date.now() - time;
12 |
13 | times *= mul || 1;
14 |
15 | return {
16 | name: name,
17 | times: times,
18 | total: diff,
19 | persec: (1000 / (diff / times)).toFixed(2),
20 | perframe: ((1000 / 60) / (diff / times)).toFixed(2),
21 | single: (diff / times).toFixed(4)
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/lib/parse.js:
--------------------------------------------------------------------------------
1 | var TOKENS = /.+?\b|.\B|\b.+?/g;
2 | var WORD = /[./\\\(\)"'\-:,.;<>~!@#$%^&*\|\+=\[\]{}`~\? ]+/g;
3 |
4 | var parse = exports;
5 |
6 | parse.words = function(s) {
7 | var words = [];
8 | var word;
9 |
10 | while (word = WORD.exec(s)) {
11 | words.push(word);
12 | }
13 |
14 | return words;
15 | };
16 |
17 | parse.tokens = function(s) {
18 | var words = [];
19 | var word;
20 |
21 | while (word = TOKENS.exec(s)) {
22 | words.push(word);
23 | }
24 |
25 | return words;
26 | };
27 |
--------------------------------------------------------------------------------
/test/syntax.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Syntax
9 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/range-gate-and.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = AND;
3 |
4 | function AND(a, b) {
5 | var found = false;
6 | var range = null;
7 | var out = [];
8 |
9 | for (var i = a[0]; i <= a[1]; i++) {
10 | found = false;
11 |
12 | for (var j = 0; j < b.length; j++) {
13 | if (i >= b[j][0] && i <= b[j][1]) {
14 | found = true;
15 | break;
16 | }
17 | }
18 |
19 | if (found) {
20 | if (!range) {
21 | range = [i,i];
22 | out.push(range);
23 | }
24 | range[1] = i;
25 | } else {
26 | range = null;
27 | }
28 | }
29 |
30 | return out;
31 | }
32 |
--------------------------------------------------------------------------------
/lib/range-gate-not.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = NOT;
3 |
4 | function NOT(a, b) {
5 | var found = false;
6 | var range = null;
7 | var out = [];
8 |
9 | for (var i = a[0]; i <= a[1]; i++) {
10 | found = false;
11 |
12 | for (var j = 0; j < b.length; j++) {
13 | if (i >= b[j][0] && i <= b[j][1]) {
14 | found = true;
15 | break;
16 | }
17 | }
18 |
19 | if (!found) {
20 | if (!range) {
21 | range = [i,i];
22 | out.push(range);
23 | }
24 | range[1] = i;
25 | } else {
26 | range = null;
27 | }
28 | }
29 |
30 | return out;
31 | }
32 |
--------------------------------------------------------------------------------
/theme/theme.css:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
6 | }
7 |
8 | #pickers {
9 | position: fixed;
10 | left: 0;
11 | width: 300px;
12 | top: 0;
13 | }
14 |
15 | .Scp {
16 | float: left;
17 | }
18 |
19 | #randomize {
20 | position: fixed;
21 | top: 450px;
22 | left: 160px;
23 | padding: 30px;
24 | }
25 |
26 | #colors {
27 | white-space: pre;
28 | position: fixed;
29 | left: 350px;
30 | }
31 | #code {
32 | font-size: 14px;
33 | white-space: pre;
34 | float: right;
35 | margin-right: 100px;
36 | }
37 |
--------------------------------------------------------------------------------
/src/buffer/syntax.bench.js:
--------------------------------------------------------------------------------
1 | var Syntax = require('./syntax');
2 | var bench = require('../../test/bench');
3 | var print = require('../../lib/print');
4 | var read = require('fs').readFileSync;
5 |
6 | var TIMES = 10e3;
7 |
8 | var code =
9 | read(__dirname + '/../../test/syntax.html.js', 'utf8');
10 | // read(__dirname + '/../../babel.js', 'utf8');
11 |
12 | var syntax = new Syntax;
13 |
14 | // var res = bench(TIMES, function() {
15 | // syntax.set(code);
16 | // }, 'syntax set');
17 |
18 | // print(res);
19 |
20 | var res = bench(TIMES, function() {
21 | syntax.highlight(code);
22 | }, 'syntax highlight');
23 |
24 | print(res);
25 |
--------------------------------------------------------------------------------
/examples/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Jazz
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/suite.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = suite;
3 |
4 | function suite(root, name, paths) {
5 | var setup = require(normalize(root, name));
6 | var tests = paths
7 | .map(normalize.bind(null, root))
8 | .map(require)
9 | .map(load.bind(null, setup));
10 |
11 | return tests;
12 | }
13 |
14 | function load(setup, mod) {
15 | var t = {};
16 | setup(add(t), mod);
17 | return t;
18 | }
19 |
20 | function add(t) {
21 | return function(desc, fn) {
22 | t[desc] = fn;
23 | };
24 | }
25 |
26 | function normalize(root, path) {
27 | return root + '/' + path;
28 | }
29 |
30 | function merge(a, b) {
31 | for (var key in b) {
32 | a[key] = b[key];
33 | }
34 | return a;
35 | }
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jazz",
3 | "version": "0.0.1",
4 | "description": "Jazz is a code editor for the web",
5 | "author": "stagas",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+ssh://git@github.com/stagas/jazz.git"
9 | },
10 | "keywords": [
11 | "editor"
12 | ],
13 | "bugs": {
14 | "url": "https://github.com/stagas/jazz/issues"
15 | },
16 | "homepage": "https://github.com/stagas/jazz#readme",
17 | "license": "MIT",
18 | "scripts": {
19 | "test": "make test"
20 | },
21 | "main": "index.js",
22 | "dependencies": {},
23 | "devDependencies": {
24 | "css-modulesify": "^0.25.1",
25 | "live-server": "^1.1.0",
26 | "watchify": "^3.7.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/binary-search.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = binarySearch;
3 |
4 | function binarySearch(array, compare) {
5 | var index = -1;
6 | var prev = -1;
7 | var low = 0;
8 | var high = array.length;
9 | if (!high) return {
10 | item: null,
11 | index: 0
12 | };
13 |
14 | do {
15 | prev = index;
16 | index = low + (high - low >> 1);
17 | var item = array[index];
18 | var result = compare(item);
19 |
20 | if (result) low = index;
21 | else high = index;
22 | } while (prev !== index);
23 |
24 | if (item != null) {
25 | return {
26 | item: item,
27 | index: index
28 | };
29 | }
30 |
31 | return {
32 | item: null,
33 | index: ~low * -1 - 1
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | dev:
2 | @make dev-build & \
3 | ./node_modules/.bin/live-server \
4 | --no-browser \
5 | --port=3000 \
6 | --wait=200 \
7 | --watch=dist/jazz.js,examples
8 |
9 | dev-build:
10 | @./node_modules/.bin/watchify \
11 | --plugin [ css-modulesify -o dist/jazz.css ] \
12 | --verbose \
13 | --detect-globals=false \
14 | --standalone Jazz \
15 | --node \
16 | --debug \
17 | --entry index.js \
18 | --outfile dist/jazz.js
19 |
20 | install: package.json
21 | @npm install
22 |
23 | todo:
24 | @grep -A 1 --color=always -nd recurse TODO lib src index.js
25 |
26 | test:
27 | @./node_modules/.bin/live-server \
28 | --open=test \
29 | --wait=100 \
30 | --watch=lib,src,test
31 |
32 | .PHONY: dev dev-build todo test
33 |
--------------------------------------------------------------------------------
/lib/atomic.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = atomic;
3 |
4 | // function atomic(fn) {
5 | // var stage = false;
6 | // var n = 0;
7 |
8 | // function wrap() {
9 | // if (stage) return n++;
10 | // else fn.call(this);
11 | // }
12 |
13 | // wrap.hold = function() {
14 | // stage = true;
15 | // n = n || 0;
16 | // };
17 |
18 | // wrap.release = function(context) {
19 | // if (stage && n) {
20 | // stage = false;
21 | // n = 0;
22 | // fn.call(context);
23 | // }
24 | // };
25 |
26 | // return wrap;
27 | // }
28 |
29 | function atomic(fn) {
30 | var request;
31 |
32 | return function(a, b, c) {
33 | clearImmediate(request);
34 | request = setImmediate(fn.bind(this, a, b, c));
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/examples/default.js:
--------------------------------------------------------------------------------
1 | var options = {
2 | theme: 'redbliss',
3 | // debug_layers: ['code'],
4 | // scroll_speed: 95,
5 | // hide_rows: true,
6 | center_horizontal: true,
7 | // center_vertical: false,
8 | // margin_left: 15,
9 | // gutter_margin: 20,
10 | };
11 |
12 | var jazz = new Jazz(options);
13 |
14 | // jazz.use(document.body).open('ember.js', './').focus();
15 | // jazz.use(document.body).open('babel.js', './').focus();
16 | jazz.use(document.body).open('jazz.js', '../dist/').focus();
17 | // jazz.use(document.body).open('jquery.js', './').focus();
18 | // jazz.use(document.body).open('codemirror.js', './').focus();
19 | // jazz.use(document.body).open('broken.js', '../test/').focus();
20 | // jazz.use(document.body).open('index.js', '../').focus();
21 |
--------------------------------------------------------------------------------
/src/views/ruler.js:
--------------------------------------------------------------------------------
1 | var dom = require('../../lib/dom');
2 | var css = require('../style.css');
3 | var View = require('./view');
4 |
5 | module.exports = RulerView;
6 |
7 | function RulerView(editor) {
8 | View.call(this, editor);
9 | this.name = 'ruler';
10 | this.dom = dom(css.ruler);
11 | }
12 |
13 | RulerView.prototype.__proto__ = View.prototype;
14 |
15 | RulerView.prototype.use = function(target) {
16 | dom.append(target, this);
17 | };
18 |
19 | RulerView.prototype.render = function() {
20 | dom.style(this, {
21 | top: 0,
22 | height: (this.editor.rows + this.editor.page.height)
23 | * this.editor.char.height
24 | + this.editor.pageRemainder.height
25 | });
26 | };
27 |
28 | RulerView.prototype.clear = function() {
29 | dom.style(this, {
30 | height: 0
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jazz
2 |
3 | A code editor for the web
4 |
5 |
6 | ## Features
7 | - focus on performance
8 | - operate on large files no sweat
9 | - loads 100k loc file under a second
10 | - native scrolling (mobile also! woohoo)
11 | - fast text buffer (uses a skiplist)
12 | - fast syntax highlight & themes
13 | - a theme editor(!)
14 | - autocomplete (uses a prefixtree)
15 | - full text search
16 | - block highlight
17 | - keyboard bindings
18 | - undo/redo history
19 | - selection & cut/copy/paste
20 |
21 |
22 | It's an early release - there are bugs, edge cases, optimizations to be done, etc
23 |
24 |
25 | ### Contributions are welcome !
26 |
27 |
28 | ### Development
29 |
30 | ```bash
31 | $ make install && make dev
32 | ```
33 |
34 | ### Where to start?
35 | check out `./Issues` or `make todo`
36 |
37 | -------------------------
38 |
39 | mit licensed 2016 by stagas
40 |
--------------------------------------------------------------------------------
/src/views/caret.js:
--------------------------------------------------------------------------------
1 | var dom = require('../../lib/dom');
2 | var css = require('../style.css');
3 | var View = require('./view');
4 |
5 | module.exports = CaretView;
6 |
7 | function CaretView(editor) {
8 | View.call(this, editor);
9 | this.name = 'caret';
10 | this.dom = dom(css.caret);
11 | }
12 |
13 | CaretView.prototype.__proto__ = View.prototype;
14 |
15 | CaretView.prototype.use = function(target) {
16 | dom.append(target, this);
17 | };
18 |
19 | CaretView.prototype.render = function() {
20 | dom.style(this, {
21 | opacity: +this.editor.hasFocus,
22 | left: this.editor.caretPx.x + this.editor.marginLeft,
23 | top: this.editor.caretPx.y - 1,
24 | height: this.editor.char.height + 1
25 | });
26 | };
27 |
28 | CaretView.prototype.clear = function() {
29 | dom.style(this, {
30 | opacity: 0,
31 | left: 0,
32 | top: 0,
33 | height: 0
34 | });
35 | };
36 |
--------------------------------------------------------------------------------
/examples/style.css:
--------------------------------------------------------------------------------
1 | /* reset */
2 |
3 | * {
4 | box-sizing: border-box;
5 | padding: 0;
6 | margin: 0;
7 | border: none;
8 | background: none;
9 | list-style: none;
10 | font-size: 100%;
11 | font-weight: normal;
12 | font-family: sans-serif;
13 | text-decoration: none;
14 | outline: none;
15 | -webkit-text-size-adjust: 100%;
16 | -moz-text-size-adjust: 100%;
17 | -ms-text-size-adjust: 100%;
18 | -o-text-size-adjust: 100%;
19 | }
20 |
21 | /* style */
22 |
23 | html, body {
24 | padding: 0;
25 | margin: 0;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
30 | #debug {
31 | //display: none;
32 | position: fixed;
33 | top: 0;
34 | left: 0;
35 | width: 250px;
36 | margin: 0 30px;
37 | padding: 15px;
38 | color: #a1f1a1;
39 | background: rgba(0,0,0, .3);
40 | z-index: 1000;
41 | }
42 |
43 | mark {
44 | color: #fff;
45 | background: #b41;
46 | padding-top: 1px;
47 | padding-bottom: 1px;
48 | }
49 |
--------------------------------------------------------------------------------
/lib/memoize.js:
--------------------------------------------------------------------------------
1 | var clone = require('./clone');
2 |
3 | module.exports = function memoize(fn, diff, merge, pre) {
4 | diff = diff || function(a, b) { return a !== b };
5 | merge = merge || function(a, b) { return b };
6 | pre = pre || function(node, param) { return param };
7 |
8 | var nodes = [];
9 | var cache = [];
10 | var results = [];
11 |
12 | return function(node, param) {
13 | var args = pre(node, param);
14 | node = args[0];
15 | param = args[1];
16 |
17 | var index = nodes.indexOf(node);
18 | if (~index) {
19 | var d = diff(cache[index], param);
20 | if (!d) return results[index];
21 | else {
22 | cache[index] = merge(cache[index], param);
23 | results[index] = fn(node, param, d);
24 | }
25 | } else {
26 | cache.push(clone(param));
27 | nodes.push(node);
28 | index = results.push(fn(node, param, param));
29 | }
30 |
31 | return results[index];
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/src/views/index.js:
--------------------------------------------------------------------------------
1 | var RulerView = require('./ruler');
2 | var MarkView = require('./mark');
3 | var CodeView = require('./code');
4 | var CaretView = require('./caret');
5 | var BlockView = require('./block');
6 | var FindView = require('./find');
7 | var RowsView = require('./rows');
8 |
9 | module.exports = Views;
10 |
11 | function Views(editor) {
12 | this.editor = editor;
13 |
14 | this.views = [
15 | new RulerView(editor),
16 | new MarkView(editor),
17 | new CodeView(editor),
18 | new CaretView(editor),
19 | new BlockView(editor),
20 | new FindView(editor),
21 | new RowsView(editor),
22 | ];
23 |
24 | this.views.forEach(view => this[view.name] = view);
25 | this.forEach = this.views.forEach.bind(this.views);
26 | }
27 |
28 | Views.prototype.use = function(el) {
29 | this.forEach(view => view.use(el));
30 | };
31 |
32 | Views.prototype.render = function() {
33 | this.forEach(view => view.render());
34 | };
35 |
36 | Views.prototype.clear = function() {
37 | this.forEach(view => view.clear());
38 | };
39 |
--------------------------------------------------------------------------------
/lib/dialog/style.css:
--------------------------------------------------------------------------------
1 | .dialog, .dialog * {
2 | transform: translateZ(0);
3 | }
4 |
5 | .dialog {
6 | display: flex;
7 | flex-flow: row nowrap;
8 | align-items: center;
9 | align-content: center;
10 | position: fixed;
11 | bottom: 0;
12 | left: 0;
13 | right: 0;
14 | color: #777;
15 | padding: 10px 11px 11px 10px;
16 | font-size: 9.5pt;
17 | border-top: 1px solid #aaa;
18 | border-radius: 0px;
19 | background: #efefef;
20 | z-index: 100;
21 | }
22 |
23 | .dialog > .input {
24 | display: flex;
25 | color: inherit;
26 | flex-grow: 1;
27 | background: #f3f3f3;
28 | border: none;
29 | box-shadow: inset 0px 1px 5px -1px rgba(0,0,0,.4);
30 | border-radius: 3px;
31 | padding: 5px 6px;
32 | margin: 0px;
33 | }
34 |
35 | .dialog > .input > .text {
36 | flex-grow: 1;
37 | border: none;
38 | color: inherit;
39 | }
40 |
41 | .dialog > .label {
42 | margin-right: 10px;
43 | }
44 |
45 | .dialog > .input > .info {
46 | padding: 0 0 0 6px;
47 | margin-left: 6px;
48 | border-left: 1px solid #c9c9c9;
49 | color: #999;
50 | }
51 |
--------------------------------------------------------------------------------
/src/buffer/segments.bench.js:
--------------------------------------------------------------------------------
1 | var Buffer = require('./');
2 | var Point = require('../../lib/point');
3 | var bench = require('../../test/bench');
4 | var print = require('../../lib/print');
5 | var read = require('fs').readFileSync;
6 |
7 | var TIMES = 10e3;
8 |
9 | var code =
10 | // read(__dirname + '/../../test/syntax.html.js', 'utf8');
11 | read(__dirname + '/../../examples/babel.js', 'utf8');
12 |
13 | var buffer = new Buffer();
14 |
15 | buffer.set(code);
16 |
17 | // var res = bench(TIMES, function() {
18 | // syntax.set(code);
19 | // }, 'syntax set');
20 |
21 | // print(res);
22 |
23 | var res = bench(1, function() {
24 | buffer.segments.index(buffer.raw);
25 | }, 'segments index');
26 | print(res);
27 |
28 | // console.log(buffer.segments.segments.length)
29 |
30 | var res = bench(TIMES, function() {
31 | var x = 0;
32 | for (var i = 0; i < 80000; i+=Math.random() * 100 | 0) {
33 | x++;
34 | buffer.segments.get(i);
35 | if (x === 1000) break;
36 | }
37 | }, 'segments get', 1000);
38 | print(res);
39 |
40 | // console.log(buffer.segments.cache.point)
--------------------------------------------------------------------------------
/test/index.css:
--------------------------------------------------------------------------------
1 |
2 | html, body {
3 | margin: 0;
4 | padding: 0 5px 10px;
5 | font-family: monospace;
6 | color: #656565;
7 | font-size: 8.5pt;
8 | }
9 |
10 | table {
11 | width: 100%;
12 | border-sizing: border-box;
13 | border-collapse: collapse;
14 | }
15 |
16 | table, td {
17 | padding: 1px 15px;
18 | text-align: center;
19 | vertical-align: middle;
20 | }
21 |
22 | td, th {
23 | border-right: 1px solid #ccc;
24 | border-bottom: 1px solid #ccc;
25 | }
26 |
27 | td:last-child,
28 | th:last-child {
29 | border-right: none;
30 | }
31 |
32 | tr:last-child td {
33 | border-bottom: none;
34 | }
35 |
36 | td:nth-child(1) {
37 | width: 45%;
38 | text-align: right;
39 | }
40 |
41 | button {
42 | padding: 2px 7px;
43 | margin: 1px 0 5px;
44 | font-size: 10pt;
45 | letter-spacing: 0.5px;
46 | color: white;
47 | background: #4ab;
48 | border: none;
49 | border-radius: 2px;
50 | }
51 |
52 | .item .result {
53 | position: relative;
54 | }
55 |
56 | .item .header {
57 | margin: 25px 0;
58 | font-size: 14pt;
59 | text-align: center;
60 | }
61 |
--------------------------------------------------------------------------------
/lib/set-immediate.js:
--------------------------------------------------------------------------------
1 | // Note: You probably do not want to use this in production code, as Promise is
2 | // not supported by all browsers yet.
3 |
4 | (function() {
5 | "use strict";
6 |
7 | if (window.setImmediate) {
8 | return;
9 | }
10 |
11 | var pending = {},
12 | nextHandle = 1;
13 |
14 | function onResolve(handle) {
15 | var callback = pending[handle];
16 | if (callback) {
17 | delete pending[handle];
18 | callback.fn.apply(null, callback.args);
19 | }
20 | }
21 |
22 | window.setImmediate = function(fn) {
23 | var args = Array.prototype.slice.call(arguments, 1),
24 | handle;
25 |
26 | if (typeof fn !== "function") {
27 | throw new TypeError("invalid function");
28 | }
29 |
30 | handle = nextHandle++;
31 | pending[handle] = { fn: fn, args: args };
32 |
33 | new Promise(function(resolve) {
34 | resolve(handle);
35 | }).then(onResolve);
36 |
37 | return handle;
38 | };
39 |
40 | window.clearImmediate = function(handle) {
41 | delete pending[handle];
42 | };
43 | }());
--------------------------------------------------------------------------------
/lib/trim.js:
--------------------------------------------------------------------------------
1 |
2 | var trim = exports;
3 |
4 | trim.emptyLines = function(s) {
5 | var trailing = trim.trailingEmptyLines(s);
6 | var leading = trim.leadingEmptyLines(trailing.string);
7 | return {
8 | trailing: trailing.removed,
9 | leading: leading.removed,
10 | removed: trailing.removed + leading.removed,
11 | string: leading.string
12 | };
13 | };
14 |
15 | trim.trailingEmptyLines = function(s) {
16 | var index = s.length;
17 | var lastIndex = index;
18 | var n = 0;
19 | while (
20 | ~(index = s.lastIndexOf('\n', lastIndex - 1))
21 | && index - lastIndex === -1) {
22 | n++;
23 | lastIndex = index;
24 | }
25 |
26 | if (n) s = s.slice(0, lastIndex);
27 |
28 | return {
29 | removed: n,
30 | string: s
31 | };
32 | };
33 |
34 | trim.leadingEmptyLines = function(s) {
35 | var index = -1;
36 | var lastIndex = index;
37 | var n = 0;
38 |
39 | while (
40 | ~(index = s.indexOf('\n', lastIndex + 1))
41 | && index - lastIndex === 1) {
42 | n++;
43 | lastIndex = index;
44 | }
45 |
46 | if (n) s = s.slice(lastIndex + 1);
47 |
48 | return {
49 | removed: n,
50 | string: s
51 | };
52 | };
53 |
--------------------------------------------------------------------------------
/test/broken.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * {
4 | * ([])
5 | * }
6 | */
7 |
8 | hello = function() {
9 | while (true) {
10 | if (something) {
11 | return ['ok',[1]];
12 | }
13 | }
14 | }
15 |
16 |
17 |
18 |
19 | /*
20 | * Need to make sure that buffer isn't trying to write out of bounds.
21 | */
22 | function checkOffset (offset, ext, length) {
23 | if ((offset % 1) !== 0 || offset < 0)
24 | throw new RangeError('offset is not uint')
25 | if (offset + ext > length)
26 | throw new RangeError('Trying to access beyond buffer length')
27 | }
28 |
29 | /**
30 | * `
31 | */
32 |
33 | hello = function() {
34 |
35 | }
36 |
37 | /**
38 | * `
39 | */
40 |
41 | hello = function() {
42 |
43 | }
44 |
45 | /**
46 | * `
47 | */
48 |
49 | hello = function() {
50 |
51 | }
52 |
53 | /**
54 | * `
55 | */
56 |
57 | hello = function() {
58 | thing = '`';
59 | }
60 |
61 | /**
62 | * `
63 | */
64 |
65 | hello = function() {
66 |
67 | }
68 |
69 | /**
70 | * `
71 | */
72 |
73 | hello = function() {
74 |
75 | }
76 |
77 | /**
78 | * `
79 | */
80 |
81 | hello = function() {
82 |
83 | }
84 |
85 | /**
86 | * `
87 | */
88 |
89 | hello = function() {
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/file.js:
--------------------------------------------------------------------------------
1 | var open = require('../lib/open');
2 | var save = require('../lib/save');
3 | var Event = require('../lib/event');
4 | var Buffer = require('./buffer');
5 |
6 | module.exports = File;
7 |
8 | function File(editor) {
9 | Event.call(this);
10 |
11 | this.root = '';
12 | this.path = 'untitled';
13 | this.buffer = new Buffer;
14 | this.bindEvent();
15 | }
16 |
17 | File.prototype.__proto__ = Event.prototype;
18 |
19 | File.prototype.bindEvent = function() {
20 | this.buffer.on('raw', this.emit.bind(this, 'raw'));
21 | this.buffer.on('set', this.emit.bind(this, 'set'));
22 | this.buffer.on('update', this.emit.bind(this, 'change'));
23 | this.buffer.on('before update', this.emit.bind(this, 'before change'));
24 | };
25 |
26 | File.prototype.open = function(path, root, fn) {
27 | this.path = path;
28 | this.root = root;
29 | open(root + path, (err, text) => {
30 | if (err) {
31 | this.emit('error', err);
32 | fn && fn(err);
33 | return;
34 | }
35 | this.buffer.setText(text);
36 | this.emit('open');
37 | fn && fn(null, this);
38 | });
39 | };
40 |
41 | File.prototype.save = function(fn) {
42 | save(this.root + this.path, this.buffer.toString(), fn || noop);
43 | };
44 |
45 | File.prototype.set = function(text) {
46 | this.buffer.setText(text);
47 | this.emit('set');
48 | };
49 |
50 | function noop() {/* noop */}
51 |
--------------------------------------------------------------------------------
/src/buffer/bench.js:
--------------------------------------------------------------------------------
1 | var Buffer = require('./');
2 | var Point = require('../../lib/point');
3 | var bench = require('../../test/bench');
4 | var print = require('../../lib/print');
5 | var read = require('fs').readFileSync;
6 |
7 | var TIMES = 1000;
8 |
9 | var code =
10 | // read(__dirname + '/../../test/syntax.html.js', 'utf8');
11 | read(__dirname + '/../../examples/babel.js', 'utf8');
12 |
13 | var length = code.length - 200;
14 |
15 | var buffer = new Buffer;
16 |
17 | var i;
18 |
19 | var res = bench(1, function() {
20 | buffer.setText(code);
21 | }, 'setText');
22 | print(res);
23 |
24 | i = 50000;
25 | var res = bench(TIMES*100, function() {
26 | buffer.get([i, i]);
27 | }, 'get same line');
28 | print(res);
29 |
30 | i = 60000;
31 | var res = bench(TIMES*10, function() {
32 | buffer.get([i, i+50]);
33 | }, 'get same 50 lines');
34 | print(res);
35 |
36 | i = 80000
37 | var res = bench(TIMES, function() {
38 | buffer.get([i, i+50]);
39 | i = Math.random() * (buffer.loc() - 51) | 0;
40 | }, 'random gets of 50 lines');
41 | print(res);
42 |
43 | i = 0;
44 | var res = bench(TIMES*10, function() {
45 | buffer.get([i, i+50]);
46 | i += 50;
47 | }, 'sequential gets of 50 lines');
48 | print(res);
49 |
50 | var res = bench(TIMES*10, function() {
51 | i = Math.random() * buffer.loc() | 0;
52 | buffer.insert({ x:0, y:i }, '123\n{456}\n');
53 | }, 'random inserts');
54 | print(res);
55 |
--------------------------------------------------------------------------------
/src/buffer/tokens.bench.js:
--------------------------------------------------------------------------------
1 | var ChunkArray = require('./chunk-array');
2 | var Tokens = require('./tokens');
3 | var Point = require('../../lib/point');
4 | var bench = require('../../test/bench');
5 | var print = require('../../lib/print');
6 | var read = require('fs').readFileSync;
7 |
8 | var TIMES = 10;
9 |
10 | var code =
11 | // read(__dirname + '/../../test/syntax.html.js', 'utf8');
12 | read(__dirname + '/../../examples/babel.js', 'utf8');
13 |
14 | var length = code.length - 200;
15 |
16 | var tokens = new Tokens(() => new ChunkArray(5000));
17 |
18 | var res = bench(1, function() {
19 | tokens.index(code);
20 | }, 'tokens index');
21 | print(res);
22 |
23 | // var res = bench(TIMES * 1000, function() {
24 | // tokens.get('segments', Math.random() * length | 0);
25 | // }, 'tokens get');
26 | // print(res);
27 |
28 | // var res = bench(TIMES * 1000, function() {
29 | // tokens.shift(Math.random() * length | 0, 5);
30 | // }, 'tokens shift');
31 | // print(res);
32 |
33 | var text = '/* { [`foo`] } */\n';
34 | // var text = 'a';
35 | var i;
36 | var res = bench(TIMES * 1000, function() {
37 | i = Math.random() * length | 0;
38 | tokens.updateRange([i, i + text.length], text);
39 | }, 'tokens updateRange');
40 | print(res);
41 |
42 | // tokens.shift(0, 2)
43 |
44 | // var token = tokens.get('segments', 23204);
45 | // console.log(token)
46 | // console.log(Tokens.Type[code[token.offset-2]]);
47 |
--------------------------------------------------------------------------------
/src/views/rows.js:
--------------------------------------------------------------------------------
1 | var dom = require('../../lib/dom');
2 | var css = require('../style.css');
3 | var View = require('./view');
4 |
5 | module.exports = RowsView;
6 |
7 | function RowsView(editor) {
8 | View.call(this, editor);
9 | this.name = 'rows';
10 | this.dom = dom(css.rows);
11 | this.rows = -1;
12 | this.range = [-1,-1];
13 | this.html = '';
14 | }
15 |
16 | RowsView.prototype.__proto__ = View.prototype;
17 |
18 | RowsView.prototype.use = function(target) {
19 | dom.append(target, this);
20 | };
21 |
22 | RowsView.prototype.render = function() {
23 | var range = this.editor.getPageRange([-1,+1]);
24 |
25 | if ( range[0] >= this.range[0]
26 | && range[1] <= this.range[1]
27 | && ( this.range[1] !== this.rows
28 | || this.editor.rows === this.rows
29 | )) return;
30 |
31 | range = this.editor.getPageRange([-3,+3]);
32 | this.rows = this.editor.rows;
33 | this.range = range;
34 |
35 | var html = '';
36 | for (var i = range[0]; i <= range[1]; i++) {
37 | html += (i + 1) + '\n';
38 | }
39 |
40 | if (html !== this.html) {
41 | this.html = html;
42 |
43 | dom.html(this, html);
44 |
45 | dom.style(this, {
46 | top: range[0] * this.editor.char.height,
47 | height: (range[1] - range[0] + 1) * this.editor.char.height
48 | });
49 | }
50 | };
51 |
52 | RowsView.prototype.clear = function() {
53 | dom.style(this, {
54 | height: 0
55 | });
56 | };
57 |
--------------------------------------------------------------------------------
/theme/color-picker.css:
--------------------------------------------------------------------------------
1 | .Scp {
2 | width: 175px;
3 | height: 150px;
4 | -webkit-user-select: none;
5 | -moz-user-select: none;
6 | -ms-user-select: none;
7 | user-select: none;
8 | position: relative;
9 | }
10 | .Scp-saturation {
11 | position: relative;
12 | width: calc(100% - 25px);
13 | height: 100%;
14 | background: -webkit-linear-gradient(left, #fff, #f00);
15 | background: linear-gradient(to right, #fff, #f00);
16 | float: left;
17 | margin-right: 5px;
18 | }
19 | .Scp-brightness {
20 | width: 100%;
21 | height: 100%;
22 | background: -webkit-linear-gradient(rgba(255,255,255,0), #000);
23 | background: linear-gradient(rgba(255,255,255,0), #000);
24 | }
25 | .Scp-sbSelector {
26 | border: 2px solid #fff;
27 | position: absolute;
28 | width: 14px;
29 | height: 14px;
30 | background: #fff;
31 | border-radius: 10px;
32 | top: -7px;
33 | left: -7px;
34 | box-sizing: border-box;
35 | z-index: 10;
36 | }
37 | .Scp-hue {
38 | width: 20px;
39 | height: 100%;
40 | position: relative;
41 | float: left;
42 | background: -webkit-linear-gradient(#f00 0%, #f0f 17%, #00f 34%, #0ff 50%, #0f0 67%, #ff0 84%, #f00 100%);
43 | background: linear-gradient(#f00 0%, #f0f 17%, #00f 34%, #0ff 50%, #0f0 67%, #ff0 84%, #f00 100%);
44 | }
45 | .Scp-hSelector {
46 | position: absolute;
47 | background: #fff;
48 | border-bottom: 1px solid #000;
49 | right: -3px;
50 | width: 10px;
51 | height: 2px;
52 | }
--------------------------------------------------------------------------------
/lib/event.js:
--------------------------------------------------------------------------------
1 |
2 | var push = [].push;
3 | var slice = [].slice;
4 |
5 | module.exports = Event;
6 |
7 | function Event() {
8 | if (!(this instanceof Event)) return new Event;
9 |
10 | this._handlers = {};
11 | }
12 |
13 | Event.prototype._getHandlers = function(name) {
14 | this._handlers = this._handlers || {};
15 | return this._handlers[name] = this._handlers[name] || [];
16 | };
17 |
18 | Event.prototype.emit = function(name, a, b, c, d) {
19 | var handlers = this._getHandlers(name);
20 | for (var i = 0; i < handlers.length; i++) {
21 | handlers[i](a, b, c, d);
22 | };
23 | };
24 |
25 | Event.prototype.on = function(name) {
26 | var handlers;
27 | var newHandlers = slice.call(arguments, 1);
28 | if (Array.isArray(name)) {
29 | name.forEach(function(name) {
30 | handlers = this._getHandlers(name);
31 | push.apply(handlers, newHandlers[name]);
32 | }, this);
33 | } else {
34 | handlers = this._getHandlers(name);
35 | push.apply(handlers, newHandlers);
36 | }
37 | };
38 |
39 | Event.prototype.off = function(name, handler) {
40 | var handlers = this._getHandlers(name);
41 | var index = handlers.indexOf(handler);
42 | if (~index) handlers.splice(index, 1);
43 | };
44 |
45 | Event.prototype.once = function(name, fn) {
46 | var handlers = this._getHandlers(name);
47 | var handler = function(a, b, c, d) {
48 | fn(a, b, c, d);
49 | handlers.splice(handlers.indexOf(handler), 1);
50 | };
51 | handlers.push(handler);
52 | };
53 |
--------------------------------------------------------------------------------
/lib/range.js:
--------------------------------------------------------------------------------
1 | var AND = require('./range-gate-and');
2 | var NOT = require('./range-gate-not');
3 |
4 | module.exports = Range;
5 |
6 | function Range(r) {
7 | if (r) {
8 | this[0] = r[0];
9 | this[1] = r[1];
10 | } else {
11 | this[0] = 0;
12 | this[1] = 1;
13 | }
14 | };
15 |
16 | Range.AND = AND;
17 | Range.NOT = NOT;
18 |
19 | Range.sort = function(a, b) {
20 | return a.y === b.y
21 | ? a.x - b.x
22 | : a.y - b.y;
23 | };
24 |
25 | Range.equal = function(a, b) {
26 | return a[0] === b[0] && a[1] === b[1];
27 | };
28 |
29 | Range.clamp = function(a, b) {
30 | return new Range([
31 | Math.min(b[1], Math.max(a[0], b[0])),
32 | Math.min(a[1], b[1])
33 | ]);
34 | };
35 |
36 | Range.prototype.slice = function() {
37 | return new Range(this);
38 | };
39 |
40 | Range.ranges = function(items) {
41 | return items.map(function(item) { return item.range });
42 | };
43 |
44 | Range.prototype.inside = function(items) {
45 | var range = this;
46 | return items.filter(function(item) {
47 | return item.range[0] >= range[0] && item.range[1] <= range[1];
48 | });
49 | };
50 |
51 | Range.prototype.overlap = function(items) {
52 | var range = this;
53 | return items.filter(function(item) {
54 | return item.range[0] <= range[0] && item.range[1] >= range[1];
55 | });
56 | };
57 |
58 | Range.prototype.outside = function(items) {
59 | var range = this;
60 | return items.filter(function(item) {
61 | return item.range[1] < range[0] || item.range[0] > range[1];
62 | });
63 | };
64 |
--------------------------------------------------------------------------------
/src/input/index.js:
--------------------------------------------------------------------------------
1 | var Event = require('../../lib/event');
2 | var Mouse = require('./mouse');
3 | var Text = require('./text');
4 |
5 | module.exports = Input;
6 |
7 | function Input(editor) {
8 | this.editor = editor;
9 | this.mouse = new Mouse(this);
10 | this.text = new Text;
11 | this.bindEvent();
12 | }
13 |
14 | Input.prototype.__proto__ = Event.prototype;
15 |
16 | Input.prototype.bindEvent = function() {
17 | this.blur = this.blur.bind(this);
18 | this.focus = this.focus.bind(this);
19 | this.text.on(['key', 'text'], this.emit.bind(this, 'input'));
20 | this.text.on('focus', this.emit.bind(this, 'focus'));
21 | this.text.on('blur', this.emit.bind(this, 'blur'));
22 | this.text.on('text', this.emit.bind(this, 'text'));
23 | this.text.on('keys', this.emit.bind(this, 'keys'));
24 | this.text.on('key', this.emit.bind(this, 'key'));
25 | this.text.on('cut', this.emit.bind(this, 'cut'));
26 | this.text.on('copy', this.emit.bind(this, 'copy'));
27 | this.text.on('paste', this.emit.bind(this, 'paste'));
28 | this.mouse.on('up', this.emit.bind(this, 'mouseup'));
29 | this.mouse.on('click', this.emit.bind(this, 'mouseclick'));
30 | this.mouse.on('down', this.emit.bind(this, 'mousedown'));
31 | this.mouse.on('drag', this.emit.bind(this, 'mousedrag'));
32 | this.mouse.on('drag begin', this.emit.bind(this, 'mousedragbegin'));
33 | };
34 |
35 | Input.prototype.use = function(node) {
36 | this.mouse.use(node);
37 | this.text.reset();
38 | };
39 |
40 | Input.prototype.blur = function() {
41 | this.text.blur();
42 | };
43 |
44 | Input.prototype.focus = function() {
45 | this.text.focus();
46 | };
47 |
--------------------------------------------------------------------------------
/src/views/mark.js:
--------------------------------------------------------------------------------
1 | var dom = require('../../lib/dom');
2 | var css = require('../style.css');
3 | var View = require('./view');
4 |
5 | module.exports = MarkView;
6 |
7 | function MarkView(editor) {
8 | View.call(this, editor);
9 | this.name = 'mark';
10 | this.dom = dom(css.mark);
11 | }
12 |
13 | MarkView.prototype.__proto__ = View.prototype;
14 |
15 | MarkView.prototype.use = function(target) {
16 | dom.append(target, this);
17 | };
18 |
19 | MarkView.prototype.get = function(range, e) {
20 | var mark = e.mark.get();
21 | if (range[0] > mark.end.y) return false;
22 | if (range[1] < mark.begin.y) return false;
23 |
24 | var offsets = e.buffer.getLineRangeOffsets(range);
25 | var area = e.buffer.getAreaOffsetRange(mark);
26 | var code = e.buffer.text.getRange(offsets);
27 |
28 | area[0] -= offsets[0];
29 | area[1] -= offsets[0];
30 |
31 | var above = code.substring(0, area[0]);
32 | var middle = code.substring(area[0], area[1]);
33 | var html = e.syntax.entities(above)
34 | + '' + e.syntax.entities(middle) + '';
35 |
36 | html = html.replace(/\n/g, ' \n');
37 |
38 | return html;
39 | };
40 |
41 | MarkView.prototype.render = function() {
42 | if (!this.editor.mark.active) return this.clear();
43 |
44 | var page = this.editor.getPageRange([-.5,+.5]);
45 | var html = this.get(page, this.editor);
46 |
47 | dom.html(this, html);
48 |
49 | dom.style(this, {
50 | top: page[0] * this.editor.char.height,
51 | height: 'auto'
52 | });
53 | };
54 |
55 | MarkView.prototype.clear = function() {
56 | dom.style(this, {
57 | top: 0,
58 | height: 0
59 | });
60 | };
61 |
--------------------------------------------------------------------------------
/lib/trim.test.js:
--------------------------------------------------------------------------------
1 |
2 | var assert = require('assert');
3 |
4 | module.exports = function(t, trim) {
5 |
6 | var text = '\n\n\n\nfoo bar\n\n\n\n';
7 | var edge1 = '\nfoo bar\n';
8 | var edge2 = '\n foo bar \n';
9 | var edge3 = '\n \nfoo bar\n \n';
10 |
11 | t('empty lines', function() {
12 | assert.equal('foo bar', trim.emptyLines(text).string);
13 | assert.equal(8, trim.emptyLines(text).removed);
14 | assert.equal(4, trim.emptyLines(text).trailing);
15 | assert.equal(4, trim.emptyLines(text).leading);
16 | })
17 |
18 | t('trailing empty lines', function() {
19 | assert.equal('\n\n\n\nfoo bar', trim.trailingEmptyLines(text).string);
20 | assert.equal(4, trim.trailingEmptyLines(text).removed);
21 | assert.equal('\nfoo bar', trim.trailingEmptyLines(edge1).string);
22 | assert.equal(1, trim.trailingEmptyLines(edge1).removed);
23 | assert.equal('\n foo bar ', trim.trailingEmptyLines(edge2).string);
24 | assert.equal(1, trim.trailingEmptyLines(edge2).removed);
25 | assert.equal('\n \nfoo bar\n ', trim.trailingEmptyLines(edge3).string);
26 | assert.equal(1, trim.trailingEmptyLines(edge3).removed);
27 | })
28 |
29 | t('leading empty lines', function() {
30 | assert.equal('foo bar\n\n\n\n', trim.leadingEmptyLines(text).string);
31 | assert.equal(4, trim.leadingEmptyLines(text).removed);
32 | assert.equal('foo bar\n', trim.leadingEmptyLines(edge1).string);
33 | assert.equal(1, trim.leadingEmptyLines(edge1).removed);
34 | assert.equal(' foo bar \n', trim.leadingEmptyLines(edge2).string);
35 | assert.equal(1, trim.leadingEmptyLines(edge2).removed);
36 | assert.equal(' \nfoo bar\n \n', trim.leadingEmptyLines(edge3).string);
37 | assert.equal(1, trim.leadingEmptyLines(edge3).removed);
38 | })
39 |
40 | };
41 |
--------------------------------------------------------------------------------
/src/views/find.js:
--------------------------------------------------------------------------------
1 | var dom = require('../../lib/dom');
2 | var css = require('../style.css');
3 | var View = require('./view');
4 |
5 | module.exports = FindView;
6 |
7 | function FindView(editor) {
8 | View.call(this, editor);
9 | this.name = 'find';
10 | this.dom = dom(css.find);
11 | }
12 |
13 | FindView.prototype.__proto__ = View.prototype;
14 |
15 | FindView.prototype.use = function(target) {
16 | dom.append(target, this);
17 | };
18 |
19 | FindView.prototype.get = function(range, e) {
20 | var results = e.findResults;
21 |
22 | var begin = 0;
23 | var end = results.length;
24 | var prev = -1;
25 | var i = -1;
26 |
27 | do {
28 | prev = i;
29 | i = begin + (end - begin) / 2 | 0;
30 | if (results[i].y < range[0] - 1) begin = i;
31 | else end = i;
32 | } while (prev !== i);
33 |
34 | var width = e.findValue.length * e.char.width + 'px';
35 |
36 | var html = '';
37 | var tabs;
38 | var r;
39 | while (results[i] && results[i].y < range[1]) {
40 | r = results[i++];
41 | tabs = e.getPointTabs(r);
42 | html += '';
48 | }
49 |
50 | return html;
51 | };
52 |
53 | FindView.prototype.render = function() {
54 | if (!this.editor.find.isOpen || !this.editor.findResults.length) return;
55 |
56 | var page = this.editor.getPageRange([-.5,+.5]);
57 | var html = this.get(page, this.editor);
58 |
59 | dom.html(this, html);
60 | };
61 |
62 | FindView.prototype.clear = function() {
63 | dom.html(this, '');
64 | };
65 |
--------------------------------------------------------------------------------
/src/buffer/prefixtree.js:
--------------------------------------------------------------------------------
1 | // var WORD = /\w+/g;
2 | var WORD = /[a-zA-Z0-9]{1,}/g
3 | var rank = 0;
4 |
5 | module.exports = PrefixTreeNode;
6 |
7 | function PrefixTreeNode() {
8 | this.value = '';
9 | this.rank = 0;
10 | this.children = {};
11 | }
12 |
13 | PrefixTreeNode.prototype.getChildren = function() {
14 | var children = Object
15 | .keys(this.children)
16 | .map((key) => this.children[key]);
17 |
18 | return children.reduce((p, n) => p.concat(n.getChildren()), children);
19 | };
20 |
21 | PrefixTreeNode.prototype.collect = function(key) {
22 | var collection = [];
23 | var node = this.find(key);
24 | if (node) {
25 | collection = node
26 | .getChildren()
27 | .filter((node) => node.value)
28 | .sort((a, b) => {
29 | var res = b.rank - a.rank;
30 | if (res === 0) res = b.value.length - a.value.length;
31 | if (res === 0) res = a.value > b.value;
32 | return res;
33 | });
34 |
35 | if (node.value) collection.push(node);
36 | }
37 | return collection;
38 | };
39 |
40 | PrefixTreeNode.prototype.find = function(key) {
41 | var node = this;
42 | for (var char in key) {
43 | if (key[char] in node.children) {
44 | node = node.children[key[char]];
45 | } else {
46 | return;
47 | }
48 | }
49 | return node;
50 | };
51 |
52 | PrefixTreeNode.prototype.insert = function(s, value) {
53 | var node = this;
54 | var i = 0;
55 | var n = s.length;
56 |
57 | while (i < n) {
58 | if (s[i] in node.children) {
59 | node = node.children[s[i]];
60 | i++;
61 | } else {
62 | break;
63 | }
64 | }
65 |
66 | while (i < n) {
67 | node =
68 | node.children[s[i]] =
69 | node.children[s[i]] || new PrefixTreeNode;
70 | i++;
71 | }
72 |
73 | node.value = s;
74 | node.rank++;
75 | };
76 |
77 | PrefixTreeNode.prototype.index = function(s) {
78 | var word;
79 | while (word = WORD.exec(s)) {
80 | this.insert(word[0]);
81 | }
82 | };
83 |
--------------------------------------------------------------------------------
/lib/box.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = Box;
3 |
4 | function Box(b) {
5 | if (b) {
6 | this.width = b.width;
7 | this.height = b.height;
8 | } else {
9 | this.width = 0;
10 | this.height = 0;
11 | }
12 | }
13 |
14 | Box.prototype.set = function(b) {
15 | this.width = b.width;
16 | this.height = b.height;
17 | };
18 |
19 | Box.prototype['/'] =
20 | Box.prototype.div = function(p) {
21 | return new Box({
22 | width: this.width / (p.x || p.width || 0),
23 | height: this.height / (p.y || p.height || 0)
24 | });
25 | };
26 |
27 | Box.prototype['_/'] =
28 | Box.prototype.floorDiv = function(p) {
29 | return new Box({
30 | width: this.width / (p.x || p.width || 0) | 0,
31 | height: this.height / (p.y || p.height || 0) | 0
32 | });
33 | };
34 |
35 | Box.prototype['^/'] =
36 | Box.prototype.ceildiv = function(p) {
37 | return new Box({
38 | width: Math.ceil(this.width / (p.x || p.width || 0)),
39 | height: Math.ceil(this.height / (p.y || p.height || 0))
40 | });
41 | };
42 |
43 | Box.prototype['*'] =
44 | Box.prototype.mul = function(b) {
45 | return new Box({
46 | width: this.width * (b.width || b.x || 0),
47 | height: this.height * (b.height || b.y || 0)
48 | });
49 | };
50 |
51 | Box.prototype['^*'] =
52 | Box.prototype.mul = function(b) {
53 | return new Box({
54 | width: Math.ceil(this.width * (b.width || b.x || 0)),
55 | height: Math.ceil(this.height * (b.height || b.y || 0))
56 | });
57 | };
58 |
59 | Box.prototype['o*'] =
60 | Box.prototype.mul = function(b) {
61 | return new Box({
62 | width: Math.round(this.width * (b.width || b.x || 0)),
63 | height: Math.round(this.height * (b.height || b.y || 0))
64 | });
65 | };
66 |
67 | Box.prototype['_*'] =
68 | Box.prototype.mul = function(b) {
69 | return new Box({
70 | width: this.width * (b.width || b.x || 0) | 0,
71 | height: this.height * (b.height || b.y || 0) | 0
72 | });
73 | };
74 |
75 | Box.prototype['-'] =
76 | Box.prototype.sub = function(b) {
77 | return new Box({
78 | width: this.width - (b.width || b.x || 0),
79 | height: this.height - (b.height || b.y || 0)
80 | });
81 | };
82 |
--------------------------------------------------------------------------------
/test/assert.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = assert;
3 |
4 | assert.disabled = false;
5 |
6 | function assert(expr, msg) {
7 | if (assert.disabled) return;
8 |
9 | if (!expr) {
10 | throw new Error(msg || 'assertion failed');
11 | }
12 | }
13 |
14 | assert.equal = function(l, r, msg) {
15 | if (assert.disabled) return;
16 |
17 | if (!equal(l, r)) fail(l, r, msg);
18 | };
19 |
20 | assert.throws = function(contains, fn, msg) {
21 | if (assert.disabled) return;
22 |
23 | if (arguments.length <= 2) {
24 | msg = fn;
25 | fn = contains;
26 | contains = null;
27 | }
28 |
29 | var passed = false;
30 | var error;
31 |
32 | try {
33 | fn();
34 | passed = false;
35 | } catch(e) {
36 | error = e;
37 | passed = true;
38 | }
39 |
40 | if (passed) return;
41 | else fail(null, fn.toString(), msg || 'to throw');
42 |
43 | if (contains) {
44 | assert(
45 | e.message.indexOf(contains) > -1,
46 | msg || '"' + e.message + '" does not contain "' + contains + '"'
47 | );
48 | }
49 | };
50 |
51 | function equal(l, r, visited) {
52 | visited = visited || [];
53 |
54 | var lt = typeof l;
55 | if (lt !== 'object' || null === l) {
56 | return l === r;
57 | }
58 |
59 | if (~visited.indexOf(l)) return true;
60 | if (~visited.indexOf(r)) return true;
61 |
62 | visited.push(l, r);
63 |
64 | var lk = Object.keys(l);
65 | var rk = Object.keys(r);
66 |
67 | for (var i = lk.length; i--;) {
68 | var key = lk[i];
69 | if (!equal(l[key], r[key])) return false;
70 | }
71 |
72 | for (var i = rk.length; i--;) {
73 | var key = rk[i];
74 | if (!equal(l[key], r[key])) return false;
75 | }
76 |
77 | return true;
78 | }
79 |
80 | function fail(l, r, msg) {
81 | var ls = '', rs = '';
82 |
83 | try {
84 | ls = l != null ? JSON.stringify(l, null, Array.isArray(l) ? null : ' ') : msg || '';
85 | rs = JSON.stringify(r, null, Array.isArray(r) ? null : ' ');
86 | } catch(_) {
87 | ls = rs = '';
88 | }
89 |
90 | var error = new Error(msg == null ? 'not equal' : msg);
91 | error.meta = { left: ls, right: rs };
92 |
93 | throw error;
94 | }
95 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | .editor {
2 | overflow: scroll;
3 | }
4 |
5 | .editor,
6 | .layer {
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | -webkit-touch-callout: none;
11 | -webkit-user-select: none;
12 | -khtml-user-select: none;
13 | -moz-user-select: none;
14 | -ms-user-select: none;
15 | user-select: none;
16 | }
17 |
18 | .rows,
19 | .mark,
20 | .code,
21 | mark,
22 | p,
23 | t,
24 | k,
25 | d,
26 | n,
27 | o,
28 | e,
29 | m,
30 | f,
31 | r,
32 | c,
33 | s,
34 | l,
35 | x {
36 | white-space: pre;
37 | }
38 |
39 | .mark,
40 | .code {
41 | position: absolute;
42 | cursor: text;
43 | padding-right: 8px;
44 | overflow: hidden;
45 | }
46 |
47 | .caret {
48 | position: absolute;
49 | cursor: text;
50 | top: 0;
51 | left: 0;
52 | width: 1px;
53 | background: #fff;
54 | z-index: 90;
55 | }
56 |
57 | @keyframes caret-blink-smooth {
58 | 0% { opacity: 1 }
59 | 15% { opacity: 0 }
60 | 30% { opacity: 0 }
61 | 70% { opacity: 1 }
62 | 100% { opacity: 1 }
63 | }
64 |
65 | .caret.blink-smooth {
66 | animation-name: caret-blink-smooth;
67 | animation-duration: 776.666ms;
68 | animation-timing-function: ease-in-out;
69 | animation-iteration-count: infinite;
70 | }
71 |
72 | .gutter {
73 | box-sizing: content-box;
74 | position: absolute;
75 | padding: 0 15px;
76 | left: 0;
77 | top: 0;
78 | text-align: right;
79 | cursor: default;
80 | color: #9e9e96;
81 | }
82 |
83 | .ruler {
84 | position: absolute;
85 | cursor: text;
86 | left: 0;
87 | top: 0;
88 | right: 0;
89 | opacity: 0;
90 | }
91 |
92 | .gutter.above {
93 | box-shadow: 0px 0px 8px 6px #222;
94 | }
95 |
96 | .rows {
97 | position: absolute;
98 | text-align: right;
99 | cursor: default;
100 | color: #8e8e86;
101 | z-index: 5;
102 | }
103 |
104 | .mark {
105 | color: transparent;
106 | background: transparent;
107 | z-index: 1;
108 | }
109 |
110 | .find > i {
111 | position: absolute;
112 | border: 1px solid #ff1;
113 | border-radius: 4px;
114 | z-index: 50;
115 | }
116 |
117 | .block > i {
118 | position: absolute;
119 | border-bottom: 1px solid #aaa;
120 | z-index: 60;
121 | }
122 |
--------------------------------------------------------------------------------
/theme/theme.js:
--------------------------------------------------------------------------------
1 |
2 | var picker = [];
3 |
4 | for (var i = 0; i < 9; i++) {
5 | picker[i] = new simpleColorPicker({
6 | color: '#566',
7 | background: '#000',
8 | el: pickers,
9 | width: 140,
10 | height: 100
11 | });
12 | picker[i].on('update', updateStyle);
13 | }
14 |
15 |
16 | function updateStyle() {
17 | var s = '';
18 | var t = '';
19 | s += 'body { background: ' + picker[0].getHexString() + '; color: '+ picker[1].getHexString() +'}';
20 | t += '\nbackground: ' + picker[0].getHexString();
21 | t += '\ncolor: ' + picker[1].getHexString();
22 | s += 'operator,operator2,keyword { color: ' + picker[2].getHexString() + '}';
23 | t += '\nkeyword: ' + picker[2].getHexString();
24 | s += 'function { color: ' + picker[3].getHexString() + '}';
25 | t += '\nfunction: ' + picker[3].getHexString();
26 | s += 'declare,builtin { color: ' + picker[4].getHexString() + '}';
27 | t += '\ndeclare: ' + picker[4].getHexString();
28 | s += 'boolean,number { color: ' + picker[5].getHexString() + '}';
29 | t += '\nnumber: ' + picker[5].getHexString();
30 | s += 'params,regexp { color: ' + picker[6].getHexString() + '}';
31 | t += '\nparams: ' + picker[6].getHexString();
32 | s += 'comment { color: ' + picker[7].getHexString() + '}';
33 | t += '\ncomment: ' + picker[7].getHexString();
34 | s += 'string { color: ' + picker[8].getHexString() + '}';
35 | t += '\nstring: ' + picker[8].getHexString();
36 |
37 | style.textContent = s;
38 | colors.textContent = t;
39 | }
40 |
41 | randomize.onclick = randomizeColors;
42 |
43 | function randomizeColors() {
44 | picker.forEach(function(p, i) {
45 | if (0 === i) {
46 | p.setColor('#' +
47 | (Math.random() * 0xf | 0).toString(16) + (Math.random() * 0xf | 0).toString(16) +
48 | (Math.random() * 0xf | 0).toString(16) + (Math.random() * 0xf | 0).toString(16) +
49 | (Math.random() * 0xf | 0).toString(16) + (Math.random() * 0xf | 0).toString(16)
50 | )
51 | } else {
52 | p.setColor('#' +
53 | zeroPad((Math.random() * 0xff | 0).toString(16)) +
54 | zeroPad((Math.random() * 0xff | 0).toString(16)) +
55 | zeroPad((Math.random() * 0xff | 0).toString(16))
56 | );
57 | }
58 | });
59 | updateStyle();
60 | }
61 |
62 |
63 | var style = document.createElement('style');
64 | document.body.appendChild(style);
65 |
66 | function zeroPad(s) {
67 | if (s.length === 1) return '0' + s;
68 | else return s;
69 | }
--------------------------------------------------------------------------------
/src/buffer/prefixtree.test.js:
--------------------------------------------------------------------------------
1 |
2 | var assert = require('assert');
3 |
4 | module.exports = function(t, PTNode) {
5 | var words = [
6 | 'foo',
7 | 'fo',
8 | 'ba',
9 | 'bar'
10 | ];
11 |
12 | t('insert', function() {
13 | var node = new PTNode;
14 | words.forEach((word) => node.insert(word));
15 | assert.equal('', node.value);
16 | assert.equal('', node.children.f.value);
17 | assert.equal('fo', node.children.f.children.o.value);
18 | assert.equal('foo', node.children.f.children.o.children.o.value);
19 | assert.equal('ba', node.children.b.children.a.value);
20 | assert.equal('bar', node.children.b.children.a.children.r.value);
21 | })
22 |
23 | t('find', function() {
24 | var node = new PTNode;
25 | words.forEach((word) => node.insert(word));
26 | assert.equal(undefined, node.find('x'));
27 | assert.equal('', node.find('f').value);
28 | assert.equal('fo', node.find('fo').value);
29 | assert.equal('foo', node.find('foo').value);
30 | assert.equal('', node.find('b').value);
31 | assert.equal('ba', node.find('ba').value);
32 | assert.equal('bar', node.find('bar').value);
33 | })
34 |
35 | // t('getChildren', function() {
36 | // var node = new PTNode;
37 | // words.forEach((word) => node.insert(word));
38 | // assert.equal(
39 | // ['ba', 'fo', 'bar', 'foo'],
40 | // node.getChildren().map((node) => node.value)
41 | // );
42 | // node.children.f.children.o.incrementRank();
43 | // assert.equal(
44 | // ['fo', 'ba', 'bar', 'foo'],
45 | // node.getChildren().map((node) => node.value)
46 | // );
47 | // })
48 |
49 | t('collect', function() {
50 | var node = new PTNode;
51 | words.forEach((word) => node.insert(word));
52 | assert.equal(
53 | ['bar', 'ba'],
54 | node.collect('b').map((node) => node.value)
55 | );
56 | assert.equal(
57 | ['foo', 'fo'],
58 | node.collect('f').map((node) => node.value)
59 | );
60 | assert.equal(
61 | ['foo'],
62 | node.collect('foo').map((node) => node.value)
63 | );
64 | assert.equal(
65 | [],
66 | node.collect('x')
67 | );
68 | })
69 |
70 | t('index', function() {
71 | var node = new PTNode;
72 | var s = 'foo bar fo ba';
73 | node.index(s);
74 | assert.equal(
75 | ['', '', 'fo', 'foo', 'ba', 'bar'],
76 | node.getChildren().map((node) => node.value)
77 | );
78 | })
79 | };
80 |
81 | function repeat(times, fn) {
82 | while (times--) fn();
83 | }
84 |
--------------------------------------------------------------------------------
/lib/dialog/index.js:
--------------------------------------------------------------------------------
1 | var dom = require('../dom');
2 | var Event = require('../event');
3 | var css = require('./style.css');
4 |
5 | module.exports = Dialog;
6 |
7 | function Dialog(label, keymap) {
8 | this.node = dom(css.dialog, [
9 | `