Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

[CodeMirror] UI-Refresh, unit tests fix, and 3.02 update #412

Merged
merged 6 commits into from

3 participants

@douglasduteil

Hi @ProLoser

Inspired by your last commits #316 I fixed my commented unit tests, added your ui-refresh and updated the CodeMirror version to 3.02.

@ProLoser
Owner

Very cool. So this is an alternative to #316?

@ProLoser ProLoser commented on the diff
modules/directives/codemirror/test/codemirrorSpec.js
((109 lines not shown))
+ scope.$apply("foo = 'bar'");
+ $timeout.flush();
+ var value = 'baz';
+ codemirror.setValue(value);
+ expect(scope.foo).toBe(value);
+ });
+ });
+ });
+
+ describe('when the model changes', function () {
+ it('should update the IDE', function () {
+ var element = $compile('<textarea ui-codemirror ng-model="foo"></textarea>')(scope);
+ scope.foo = 'bar';
+ scope.$apply();
+ $timeout.flush();
+ expect($.trim(element.siblings().text())).toBe(scope.foo);
@ProLoser Owner
ProLoser added a note

When I test locally this seems to be failing :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ProLoser
Owner

Looks like travis only ran 1 test (need to open a bug on testacular about this).

Go to travis, login with your github account, and in the dropdown menu of the build choose 'restart build'. Then scroll to the bottom of the build when it's done and confirm all tests ran. I don't get why when testacular does this and it drives me fucking nuts.

@ProLoser ProLoser referenced this pull request in karma-runner/karma
Closed

Add option to disable caching #344

@douglasduteil

K
I fixed some minor bug and now Travis do all the tests :)

It was there since the 90th travis build (the one that changes the Testacular version from "~0.2.0" to "~0.4.0").

@ProLoser
Owner

There are merge conflicts with master. Additionally, I am getting lots of wonderful problems with my own tests so I may have to let someone else handle this.

Can you pull the latest ui/master version and push the conflict resolution for me? Perhaps I'm resolving things incorrectly.

Also, I know there was an (unrelated) discussion about using elm.type vs elm.tagName.toLowerCase() but since type is always available for textarea and since this directive will never be used with any other DOM element, can you revert it back to using 'type'?

Sorry for all the hassle lol. We are ALMOST THERE!

@douglasduteil

I tried to rebase my repo on angular-ui/angular-ui remote master branch . Is it better ?

@ProLoser ProLoser merged commit cbcfbaa into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 10, 2013
  1. @douglasduteil

    [FIX] Unit tests

    douglasduteil authored
  2. @doogd @douglasduteil

    [FT] Minor changes

    doogd authored douglasduteil committed
  3. @doogd @douglasduteil

    [FT] Simplify options tests

    doogd authored douglasduteil committed
  4. @douglasduteil

    [FT] Add @ProLoser uiRefresh

    douglasduteil authored
  5. @douglasduteil
  6. @douglasduteil
This page is out of date. Refresh to see the latest.
View
4 .travis.yml
@@ -2,9 +2,9 @@
node_js:
- "0.8"
- before_script:
+ before_install:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- - npm install -g grunt@0.3.x testacular@0.2.x
+ - npm install -g grunt@0.3.x testacular@0.4.x
script: "grunt"
View
2  grunt.js
@@ -105,7 +105,7 @@ module.exports = function (grunt) {
var done = this.async();
grunt.utils.spawn({
cmd: process.platform === 'win32' ? 'testacular.cmd' : 'testacular',
- args: process.env.TRAVIS ? ['start', 'test/test-config.js', '--single-run', '--no-auto-watch', '--reporter=dots', '--browsers=Firefox'] : ['run']
+ args: process.env.TRAVIS ? ['start', 'test/test-config.js', '--single-run', '--no-auto-watch', '--reporters=dots', '--browsers=Firefox'] : ['run']
}, function (error, result, code) {
if (error) {
grunt.warn("Make sure the testacular server is online: run `grunt server`.\n" +
View
8 modules/directives/codemirror/codemirror.js
@@ -60,6 +60,14 @@ angular.module('ui.directives').directive('uiCodemirror', ['ui.config', '$timeou
codeMirror.setValue(ngModel.$viewValue);
};
+ // Watch ui-refresh and refresh the directive
+ if (attrs.uiRefresh) {
+ scope.$watch(attrs.uiRefresh, function(newVal, oldVal){
+ // Skip the initial watch firing
+ if (newVal !== oldVal)
+ $timeout(codeMirror.refresh);
+ });
+ }
};
$timeout(deferCodeMirror);
View
250 modules/directives/codemirror/test/codemirrorSpec.js
@@ -2,135 +2,157 @@
/**
* TODO Test all the CodeMirror events : cursorActivity viewportChange gutterClick focus blur scroll update.
* with <textarea ui-codemirror="{onChange: doChange ,onCursorActivity: doSomething}" ng-model="foo">
- *
+ *
*/
describe('uiCodemirror', function () {
- 'use strict';
+ 'use strict';
// declare these up here to be global to all tests
- var $rootScope, $compile, $timeout, uiConfig = angular.module('ui.config');
+ var scope, $compile, $timeout, uiConfig = angular.module('ui.config');
beforeEach(module('ui.directives'));
+ beforeEach(function () {
+ uiConfig.value('ui.config', {codemirror: {bar: 'baz'}});
+ });
// inject in angular constructs. Injector knows about leading/trailing underscores and does the right thing
// otherwise, you would need to inject these into each test
beforeEach(inject(function (_$rootScope_, _$compile_, _$timeout_) {
- $rootScope = _$rootScope_.$new();
+ scope = _$rootScope_.$new();
$compile = _$compile_;
$timeout = _$timeout_;
}));
afterEach(function () {
- angular.module('ui.config').value('ui.config', {}); // cleanup
+ uiConfig.value('ui.config', {}); // cleanup
+ });
+
+ describe('compiling this directive', function () {
+ it('should throw an error if used against a non-textarea', function () {
+ function compile() {
+ $compile('<div ui-codemirror ng-model="foo"></div>')(scope);
+ }
+
+ expect(compile).toThrow();
+ });
+
+ it('should not throw an error when used against a textarea', function () {
+ function compile() {
+ $compile('<textarea ui-codemirror ng-model="foo"></textarea>')(scope);
+ }
+
+ expect(compile).not.toThrow();
+ });
+
+ it('should throw an error when no ngModel attribute defined', function () {
+ function compile() {
+ $compile('<textarea ui-codemirror></textarea>')(scope);
+ }
+
+ expect(compile).toThrow();
+ });
+
+ it('should watch the uiCodemirror attribute', function () {
+ spyOn(scope, '$watch');
+ $compile('<textarea ui-codemirror ng-model="foo"></textarea>')(scope);
+ $timeout.flush();
+ expect(scope.$watch).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('while spying on the CodeMirror instance', function () {
+
+ var codemirror;
+
+ beforeEach(function () {
+ var fromTextArea = CodeMirror.fromTextArea;
+ spyOn(CodeMirror, 'fromTextArea').andCallFake(function () {
+ codemirror = fromTextArea.apply(this, arguments);
+ return codemirror;
+ });
+ });
+
+ describe('verify the directive options', function () {
+ it('should include the passed options', function () {
+ $compile('<textarea ui-codemirror="{oof: \'baar\'}" ng-model="foo"></textarea>')(scope);
+ $timeout.flush();
+ expect(CodeMirror.fromTextArea.mostRecentCall.args[1].oof).toEqual("baar");
+ });
+
+ it('should include the default options', function () {
+ $compile('<textarea ui-codemirror ng-model="foo"></textarea>')(scope);
+ $timeout.flush();
+ expect(CodeMirror.fromTextArea.mostRecentCall.args[1].bar).toEqual('baz');
+ });
+ });
+
+ describe('when uiRefresh is added', function () {
+ it('should trigger the CodeMirror.refresh() method', function () {
+ $compile('<textarea ui-codemirror ng-model="foo" ui-refresh="bar"></textarea>')(scope);
+ $timeout.flush();
+ spyOn(codemirror, 'refresh');
+ scope.$apply('bar = true');
+ $timeout.flush();
+ expect(codemirror.refresh).toHaveBeenCalled();
+ });
+ });
+
+
+ describe('when the IDE changes', function () {
+ it('should update the model', function () {
+ $compile('<textarea ui-codemirror ng-model="foo"></textarea>')(scope);
+ scope.$apply("foo = 'bar'");
+ $timeout.flush();
+ var value = 'baz';
+ codemirror.setValue(value);
+ expect(scope.foo).toBe(value);
+ });
+ });
+ });
+
+ describe('when the model changes', function () {
+ it('should update the IDE', function () {
+ var element = $compile('<textarea ui-codemirror ng-model="foo"></textarea>')(scope);
+ scope.foo = 'bar';
+ scope.$apply();
+ $timeout.flush();
+ expect($.trim(element.siblings().text())).toBe(scope.foo);
@ProLoser Owner
ProLoser added a note

When I test locally this seems to be failing :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ });
});
- describe('compiling this directive', function () {
- it('should throw an error if used against a non-textarea', function () {
- function compile() {
- $compile('<div ui-codemirror ng-model="foo"></div>')($rootScope);
- }
-
- expect(compile).toThrow();
- });
-
- it('should not throw an error when used against a textarea', function () {
- function compile() {
- $compile('<textarea ui-codemirror ng-model="foo"></textarea>')($rootScope);
- }
-
- expect(compile).not.toThrow();
- });
-
- it('should throw an error when no ngModel attribute defined', function () {
- function compile() {
- $compile('<textarea ui-codemirror></textarea>')($rootScope);
- }
-
- expect(compile).toThrow();
- });
-
- it('should watch the uiCodemirror attribute', function () {
- spyOn($rootScope, '$watch');
- $compile('<textarea ui-codemirror ng-model="foo"></textarea>')($rootScope);
- $timeout.flush();
- expect($rootScope.$watch).toHaveBeenCalled();
- });
-
- // Sorry I'm not enough familiar Jasmine to fix this...
- /*
- it('should include the passed options', function () {
- spyOn(CodeMirror, 'fromTextArea');
- $compile('<textarea ui-codemirror="{foo: \'bar\'}" ng-model="foo"></textarea>')($rootScope);
- expect(CodeMirror.fromTextArea.mostRecentCall.args[1].foo).toEqual('bar');
- });
-
- it('should include the default options', function () {
- spyOn(CodeMirror, 'fromTextArea');
- $compile('<textarea ui-codemirror ng-model="foo"></textarea>')($rootScope);
- expect(CodeMirror.fromTextArea.mostRecentCall.args[1].bar).toEqual('baz');
- });
- */
- });
-
- describe('when the model changes', function () {
- it('should update the IDE', function () {
- var element = $compile('<textarea ui-codemirror ng-model="foo"></textarea>')($rootScope);
- $rootScope.foo = 'bar';
- $rootScope.$apply();
- $timeout.flush();
- expect(element[0].nextSibling.CodeMirror.getValue()).toBe($rootScope.foo);
- });
- });
-
- describe('when the IDE changes', function () {
- it('should update the model', function () {
- var codemirror,
- fromTextArea = CodeMirror.fromTextArea;
- spyOn(CodeMirror, 'fromTextArea').andCallFake(function () {
- codemirror = fromTextArea.apply(this, arguments);
- return codemirror;
- });
- $compile('<textarea ui-codemirror ng-model="foo"></textarea>')($rootScope);
- $rootScope.foo = 'bar';
- $rootScope.$apply();
- $timeout.flush();
- var value = 'baz';
- codemirror.setValue(value);
- expect($rootScope.foo).toBe(value);
- });
- });
-
- describe('when the model is undefined/null', function () {
- it('should update the IDE with an empty string', function () {
- var element = $compile('<textarea ui-codemirror ng-model="foo"></textarea>')($rootScope);
- $rootScope.$apply();
- expect($rootScope.foo).toBe(undefined);
- expect($.trim(element.siblings().text())).toBe('');
- $rootScope.foo = null;
- $rootScope.$apply();
- expect($rootScope.foo).toBe(null);
- expect($.trim(element.siblings().text())).toBe('');
- });
- });
-
- describe('when the model is an object or an array', function () {
- it('should throw an error', function () {
- function compileWithObject() {
- $compile('<textarea ui-codemirror ng-model="foo"></textarea>')($rootScope);
- $timeout.flush();
- $rootScope.foo = {};
- $rootScope.$apply();
- }
-
- function compileWithArray() {
- $compile('<textarea ui-codemirror ng-model="foo"></textarea>')($rootScope);
- $timeout.flush();
- $rootScope.foo = [];
- $rootScope.$apply();
- }
-
- expect(compileWithObject).toThrow();
- expect(compileWithArray).toThrow();
- });
- });
+
+ describe('when the model is undefined/null', function () {
+ it('should update the IDE with an empty string', function () {
+ var element = $compile('<textarea ui-codemirror ng-model="foo"></textarea>')(scope);
+ scope.$apply();
+ expect(scope.foo).toBe(undefined);
+ expect($.trim(element.siblings().text())).toBe('');
+ scope.foo = null;
+ scope.$apply();
+ expect(scope.foo).toBe(null);
+ expect($.trim(element.siblings().text())).toBe('');
+ });
+ });
+
+ describe('when the model is an object or an array', function () {
+ it('should throw an error', function () {
+ function compileWithObject() {
+ $compile('<textarea ui-codemirror ng-model="foo"></textarea>')(scope);
+ $timeout.flush();
+ scope.foo = {};
+ scope.$apply();
+ }
+
+ function compileWithArray() {
+ $compile('<textarea ui-codemirror ng-model="foo"></textarea>')(scope);
+ $timeout.flush();
+ scope.foo = [];
+ scope.$apply();
+ }
+
+ expect(compileWithObject).toThrow();
+ expect(compileWithArray).toThrow();
+ });
+ });
});
View
2  package.json
@@ -15,6 +15,6 @@
"devDependencies": {
"grunt-recess": "~0.1.3",
"async": "0.1.x",
- "testacular": "~0.4.0"
+ "testacular": "~0.4.x"
}
}
View
537 test/lib/codemirror/codemirror.js
@@ -1,4 +1,4 @@
-// CodeMirror version 3.0
+// CodeMirror version 3.02
//
// CodeMirror is the only global var we claim
window.CodeMirror = (function() {
@@ -10,8 +10,8 @@ window.CodeMirror = (function() {
// bugs and behavior differences.
var gecko = /gecko\/\d/i.test(navigator.userAgent);
var ie = /MSIE \d/.test(navigator.userAgent);
- var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
- var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
+ var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
+ var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
var webkit = /WebKit\//.test(navigator.userAgent);
var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
var chrome = /Chrome\//.test(navigator.userAgent);
@@ -24,8 +24,14 @@ window.CodeMirror = (function() {
var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
// This is woefully incomplete. Suggestions for alternative methods welcome.
- var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent);
+ var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
var mac = ios || /Mac/.test(navigator.platform);
+ var windows = /windows/i.test(navigator.platform);
+
+ var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
+ if (opera_version) opera_version = Number(opera_version[1]);
+ // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
+ var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
// Optimize some code when these features are not used
var sawReadOnlySpans = false, sawCollapsedSpans = false;
@@ -80,7 +86,9 @@ window.CodeMirror = (function() {
function makeDisplay(place) {
var d = {};
var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;");
- input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
+ if (webkit) input.style.width = "1000px";
+ else input.setAttribute("wrap", "off");
+ input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
// Wraps and hides input textarea
d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
// The actual fake scrollbars.
@@ -91,9 +99,9 @@ window.CodeMirror = (function() {
d.lineDiv = elt("div");
d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
// Blinky cursor, and element used to ensure cursor fits at the end of a line
- d.cursor = elt("pre", "\u00a0", "CodeMirror-cursor");
+ d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
// Secondary cursor, shown when on a 'jump' in bi-directional text
- d.otherCursor = elt("pre", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
+ d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
// Used to measure text size
d.measure = elt("div", null, "CodeMirror-measure");
// Wraps everything that needs to exist inside the vertically-padded coordinate system
@@ -160,6 +168,9 @@ window.CodeMirror = (function() {
// detected
d.pasteIncoming = false;
+ // Used for measuring wheel scrolling granularity
+ d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
+
return d;
}
@@ -183,7 +194,9 @@ window.CodeMirror = (function() {
suppressEdits: false,
goalColumn: null,
cantEdit: false,
- keyMaps: []
+ keyMaps: [],
+ overlays: [],
+ modeGen: 0
};
}
@@ -194,9 +207,14 @@ window.CodeMirror = (function() {
function loadMode(cm) {
var doc = cm.view.doc;
cm.view.mode = CodeMirror.getMode(cm.options, cm.options.mode);
- doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
+ doc.iter(0, doc.size, function(line) {
+ if (line.stateAfter) line.stateAfter = null;
+ if (line.styles) line.styles = null;
+ });
cm.view.frontier = 0;
startWorker(cm, 100);
+ cm.view.modeGen++;
+ if (cm.curOp) regChange(cm, 0, doc.size);
}
function wrappingChanged(cm) {
@@ -342,13 +360,14 @@ window.CodeMirror = (function() {
function alignHorizontally(cm) {
var display = cm.display;
- if (!display.alignWidgets && !display.gutters.firstChild) return;
+ if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.view.scrollLeft;
var gutterW = display.gutters.offsetWidth, l = comp + "px";
for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
}
- display.gutters.style.left = (comp + gutterW) + "px";
+ if (cm.options.fixedGutter)
+ display.gutters.style.left = (comp + gutterW) + "px";
}
function maybeUpdateLineNumberWidth(cm) {
@@ -412,7 +431,8 @@ window.CodeMirror = (function() {
if (changes && maybeUpdateLineNumberWidth(cm))
changes = true;
- display.sizer.style.marginLeft = display.scrollbarH.style.left = display.gutters.offsetWidth + "px";
+ var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
+ display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
// When merged lines are present, the line that needs to be
// redrawn might not be the one that was changed.
@@ -458,9 +478,11 @@ window.CodeMirror = (function() {
return;
intact.sort(function(a, b) {return a.from - b.from;});
+ var focused = document.activeElement;
if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
patchDisplay(cm, from, to, intact, positionsChangedFrom);
display.lineDiv.style.display = "";
+ if (document.activeElement != focused && focused.offsetHeight) focused.focus();
var different = from != display.showingFrom || to != display.showingTo ||
display.lastSizeC != display.wrapper.clientHeight;
@@ -482,12 +504,19 @@ window.CodeMirror = (function() {
}
var diff = node.lineObj.height - height;
if (height < 2) height = textHeight(display);
- if (diff > .001 || diff < -.001)
+ if (diff > .001 || diff < -.001) {
updateLineHeight(node.lineObj, height);
+ var widgets = node.lineObj.widgets;
+ if (widgets) for (var i = 0; i < widgets.length; ++i)
+ widgets[i].height = widgets[i].node.offsetHeight;
+ }
}
display.viewOffset = heightAtLine(cm, getLine(doc, from));
// Position the mover div to align with the current virtual scroll position
display.mover.style.top = display.viewOffset + "px";
+
+ if (visibleLines(display, doc, viewPort).to >= to)
+ updateDisplayInner(cm, [], viewPort);
return true;
}
@@ -528,9 +557,7 @@ window.CodeMirror = (function() {
function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
var dims = getDimensions(cm);
var display = cm.display, lineNumbers = cm.options.lineNumbers;
- // IE does bad things to nodes when .innerHTML = "" is used on a parent
- // we still need widgets and markers intact to add back to the new content later
- if (!intact.length && !ie && (!webkit || !cm.display.currentWheelTarget))
+ if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
removeChildren(display.lineDiv);
var container = display.lineDiv, cur = container.firstChild;
@@ -540,7 +567,7 @@ window.CodeMirror = (function() {
node.style.display = "none";
node.lineObj = null;
} else {
- container.removeChild(node);
+ node.parentNode.removeChild(node);
}
return next;
}
@@ -550,6 +577,17 @@ window.CodeMirror = (function() {
if (nextIntact && nextIntact.to == lineNo) nextIntact = intact.shift();
if (lineIsHidden(line)) {
if (line.height != 0) updateLineHeight(line, 0);
+ if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i)
+ if (line.widgets[i].showIfHidden) {
+ var prev = cur.previousSibling;
+ if (prev.nodeType == "pre") {
+ var wrap = elt("div", null, null, "position: relative");
+ prev.parentNode.replaceChild(wrap, prev);
+ wrap.appendChild(prev);
+ prev = wrap;
+ }
+ prev.appendChild(buildLineWidget(line.widgets[i], prev, dims));
+ }
} else if (nextIntact && nextIntact.from <= lineNo && nextIntact.to > lineNo) {
// This line is intact. Skip to the actual node. Update its
// line number if needed.
@@ -582,8 +620,8 @@ window.CodeMirror = (function() {
var wrap = elt("div", null, line.wrapClass, "position: relative");
if (cm.options.lineNumbers || markers) {
var gutterWrap = wrap.appendChild(elt("div", null, null, "position: absolute; left: " +
- dims.fixedPos + "px"));
- wrap.alignable = [gutterWrap];
+ (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"));
+ if (cm.options.fixedGutter) wrap.alignable = [gutterWrap];
if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
wrap.lineNumber = gutterWrap.appendChild(
elt("div", lineNumberFor(cm.options, lineNo),
@@ -602,35 +640,38 @@ window.CodeMirror = (function() {
if (line.bgClass)
wrap.appendChild(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground"));
wrap.appendChild(lineElement);
- if (line.widgets)
- for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
- var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
- node.widget = widget;
- if (widget.noHScroll) {
- (wrap.alignable || (wrap.alignable = [])).push(node);
- var width = dims.wrapperWidth;
- node.style.left = dims.fixedPos + "px";
- if (!widget.coverGutter) {
- width -= dims.gutterTotalWidth;
- node.style.paddingLeft = dims.gutterTotalWidth + "px";
- }
- node.style.width = width + "px";
- }
- if (widget.coverGutter) {
- node.style.zIndex = 5;
- node.style.position = "relative";
- if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
- }
- if (widget.above)
- wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
- else
- wrap.appendChild(node);
- }
-
+ if (line.widgets) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
+ var widget = ws[i], node = buildLineWidget(widget, wrap, dims);
+ if (widget.above)
+ wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
+ else
+ wrap.appendChild(node);
+ }
if (ie_lt8) wrap.style.zIndex = 2;
return wrap;
}
+ function buildLineWidget(widget, wrap, dims) {
+ var node = elt("div", [widget.node], "CodeMirror-linewidget");
+ node.widget = widget;
+ if (widget.noHScroll) {
+ (wrap.alignable || (wrap.alignable = [])).push(node);
+ var width = dims.wrapperWidth;
+ node.style.left = dims.fixedPos + "px";
+ if (!widget.coverGutter) {
+ width -= dims.gutterTotalWidth;
+ node.style.paddingLeft = dims.gutterTotalWidth + "px";
+ }
+ node.style.width = width + "px";
+ }
+ if (widget.coverGutter) {
+ node.style.zIndex = 5;
+ node.style.position = "relative";
+ if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
+ }
+ return node;
+ }
+
// SELECTION / CURSOR
function updateSelection(cm) {
@@ -765,7 +806,7 @@ window.CodeMirror = (function() {
// HIGHLIGHT WORKER
function startWorker(cm, time) {
- if (cm.view.frontier < cm.display.showingTo)
+ if (cm.view.mode.startState && cm.view.frontier < cm.display.showingTo)
cm.view.highlight.set(time, bind(highlightWorker, cm));
}
@@ -777,7 +818,12 @@ window.CodeMirror = (function() {
var changed = [], prevChange;
doc.iter(view.frontier, Math.min(doc.size, cm.display.showingTo + 500), function(line) {
if (view.frontier >= cm.display.showingFrom) { // Visible
- if (highlightLine(cm, line, state) && view.frontier >= cm.display.showingFrom) {
+ var oldStyles = line.styles;
+ line.styles = highlightLine(cm, line, state);
+ var ischange = !oldStyles || oldStyles.length != line.styles.length;
+ for (var i = 0; !ischange && i < oldStyles.length; ++i)
+ ischange = oldStyles[i] != line.styles[i];
+ if (ischange) {
if (prevChange && prevChange.end == view.frontier) prevChange.end++;
else changed.push(prevChange = {start: view.frontier, end: view.frontier + 1});
}
@@ -821,6 +867,7 @@ window.CodeMirror = (function() {
function getStateBefore(cm, n) {
var view = cm.view;
+ if (!view.mode.startState) return true;
var pos = findStartLine(cm, n), state = pos && getLine(view.doc, pos-1).stateAfter;
if (!state) state = startState(view.mode);
else state = copyState(view.mode, state);
@@ -842,7 +889,9 @@ window.CodeMirror = (function() {
}
function measureChar(cm, line, ch, data) {
- var data = data || measureLine(cm, line), dir = -1;
+ var dir = -1;
+ data = data || measureLine(cm, line);
+
for (var pos = ch;; pos += dir) {
var r = data[pos];
if (r) break;
@@ -938,7 +987,7 @@ window.CodeMirror = (function() {
// Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
function intoCoordSystem(cm, lineObj, rect, context) {
if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
- var size = lineObj.widgets[i].node.offsetHeight;
+ var size = widgetHeight(lineObj.widgets[i]);
rect.top += size; rect.bottom += size;
}
if (context == "line") return rect;
@@ -1010,8 +1059,9 @@ window.CodeMirror = (function() {
var lineObj = getLine(doc, lineNo);
var found = coordsCharInner(cm, lineObj, lineNo, x, y);
var merged = collapsedSpanAtEnd(lineObj);
- if (merged && found.ch == lineRight(lineObj))
- lineNo = merged.find().to.line;
+ var mergedPos = merged && merged.find();
+ if (merged && found.ch >= mergedPos.from.ch)
+ lineNo = mergedPos.to.line;
else
return found;
}
@@ -1121,6 +1171,9 @@ window.CodeMirror = (function() {
var width = measureChar(cm, view.maxLine, view.maxLine.text.length).right;
display.sizer.style.minWidth = (width + 3 + scrollerCutOff) + "px";
view.maxLineChanged = false;
+ var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
+ if (maxScrollLeft < view.scrollLeft)
+ setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
}
var newScrollPos, updated;
if (op.selectionChanged) {
@@ -1237,7 +1290,7 @@ window.CodeMirror = (function() {
on(d.scroller, "mousedown", operation(cm, onMouseDown));
on(d.scroller, "dblclick", operation(cm, e_preventDefault));
on(d.lineSpace, "selectstart", function(e) {
- if (!mouseEventInWidget(d, e)) e_preventDefault(e);
+ if (!eventInWidget(d, e)) e_preventDefault(e);
});
// Gecko browsers fire contextmenu *after* opening the menu, at
// which point we can't mess with it anymore. Context menu is
@@ -1264,13 +1317,24 @@ window.CodeMirror = (function() {
on(d.scrollbarV, "mousedown", reFocus);
// Prevent wrapper from ever scrolling
on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
- on(window, "resize", function resizeHandler() {
+
+ if (!window.registered) window.registered = 0;
+ ++window.registered;
+ function onResize() {
// Might be a text scaling operation, clear size caches.
d.cachedCharWidth = d.cachedTextHeight = null;
clearCaches(cm);
- if (d.wrapper.parentNode) updateDisplay(cm, true);
- else off(window, "resize", resizeHandler);
- });
+ updateDisplay(cm, true);
+ }
+ on(window, "resize", onResize);
+ // Above handler holds on to the editor and its data structures.
+ // Here we poll to unregister it when the editor is no longer in
+ // the document, so that it can be garbage-collected.
+ setTimeout(function unregister() {
+ for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
+ if (p) setTimeout(unregister, 5000);
+ else {--window.registered; off(window, "resize", onResize);}
+ }, 5000);
on(d.input, "keyup", operation(cm, function(e) {
if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
@@ -1292,7 +1356,11 @@ window.CodeMirror = (function() {
on(d.scroller, "dragover", drag_);
on(d.scroller, "drop", operation(cm, onDrop));
}
- on(d.scroller, "paste", function(){focusInput(cm); fastPoll(cm);});
+ on(d.scroller, "paste", function(e){
+ if (eventInWidget(d, e)) return;
+ focusInput(cm);
+ fastPoll(cm);
+ });
on(d.input, "paste", function() {
d.pasteIncoming = true;
fastPoll(cm);
@@ -1316,10 +1384,12 @@ window.CodeMirror = (function() {
});
}
- function mouseEventInWidget(display, e) {
- for (var n = e_target(e); n != display.wrapper; n = n.parentNode)
+ function eventInWidget(display, e) {
+ for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
+ if (!n) return true;
if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) ||
n.parentNode == display.sizer && n != display.mover) return true;
+ }
}
function posFromMouse(cm, e, liberal) {
@@ -1341,7 +1411,7 @@ window.CodeMirror = (function() {
var cm = this, display = cm.display, view = cm.view, sel = view.sel, doc = view.doc;
sel.shift = e_prop(e, "shiftKey");
- if (mouseEventInWidget(display, e)) {
+ if (eventInWidget(display, e)) {
if (!webkit) {
display.scroller.draggable = false;
setTimeout(function(){display.scroller.draggable = true;}, 100);
@@ -1477,7 +1547,8 @@ window.CodeMirror = (function() {
function onDrop(e) {
var cm = this;
- if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
+ if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
+ return;
e_preventDefault(e);
var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
if (!pos || isReadOnly(cm)) return;
@@ -1502,7 +1573,8 @@ window.CodeMirror = (function() {
// Don't do a replace if the drop happened inside of the selected text.
if (cm.view.draggingText && !(posLess(pos, cm.view.sel.from) || posLess(cm.view.sel.to, pos))) {
cm.view.draggingText(e);
- if (ie) setTimeout(bind(focusInput, cm), 50);
+ // Ensure the editor is re-focused
+ setTimeout(bind(focusInput, cm), 20);
return;
}
try {
@@ -1546,13 +1618,24 @@ window.CodeMirror = (function() {
}
function onDragStart(cm, e) {
+ if (eventInWidget(cm.display, e)) return;
+
var txt = cm.getSelection();
e.dataTransfer.setData("Text", txt);
// Use dummy image instead of default browsers image.
// Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
- if (e.dataTransfer.setDragImage && !safari)
- e.dataTransfer.setDragImage(elt('img'), 0, 0);
+ if (e.dataTransfer.setDragImage && !safari) {
+ var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
+ if (opera) {
+ img.width = img.height = 1;
+ cm.display.wrapper.appendChild(img);
+ // Force a relayout, or Opera won't use our image for some obscure reason
+ img._top = img.offsetTop;
+ }
+ e.dataTransfer.setDragImage(img, 0, 0);
+ if (opera) img.parentNode.removeChild(img);
+ }
}
function setScrollTop(cm, val) {
@@ -1565,6 +1648,7 @@ window.CodeMirror = (function() {
}
function setScrollLeft(cm, val, isScroller) {
if (isScroller ? val == cm.view.scrollLeft : Math.abs(cm.view.scrollLeft - val) < 2) return;
+ val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
cm.view.scrollLeft = val;
alignHorizontally(cm);
if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
@@ -1582,7 +1666,7 @@ window.CodeMirror = (function() {
// is that it gives us a chance to update the display before the
// actual scrolling happens, reducing flickering.
- var wheelSamples = 0, wheelDX, wheelDY, wheelStartX, wheelStartY, wheelPixelsPerUnit = null;
+ var wheelSamples = 0, wheelPixelsPerUnit = null;
// Fill in a browser-detected starting value on browsers where we
// know one. These don't have to be accurate -- the result of them
// being wrong would just be a slight flicker on the first wheel
@@ -1611,7 +1695,7 @@ window.CodeMirror = (function() {
}
}
- var scroll = cm.display.scroller;
+ var display = cm.display, scroll = display.scroller;
// On some browsers, horizontal scrolling will cause redraws to
// happen before the gutter has been realigned, causing it to
// wriggle around in a most unseemly way. When we have an
@@ -1623,35 +1707,35 @@ window.CodeMirror = (function() {
setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
e_preventDefault(e);
- wheelStartX = null; // Abort measurement, if in progress
+ display.wheelStartX = null; // Abort measurement, if in progress
return;
}
if (dy && wheelPixelsPerUnit != null) {
var pixels = dy * wheelPixelsPerUnit;
- var top = cm.view.scrollTop, bot = top + cm.display.wrapper.clientHeight;
+ var top = cm.view.scrollTop, bot = top + display.wrapper.clientHeight;
if (pixels < 0) top = Math.max(0, top + pixels - 50);
else bot = Math.min(cm.view.doc.height, bot + pixels + 50);
updateDisplay(cm, [], {top: top, bottom: bot});
}
if (wheelSamples < 20) {
- if (wheelStartX == null) {
- wheelStartX = scroll.scrollLeft; wheelStartY = scroll.scrollTop;
- wheelDX = dx; wheelDY = dy;
+ if (display.wheelStartX == null) {
+ display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
+ display.wheelDX = dx; display.wheelDY = dy;
setTimeout(function() {
- if (wheelStartX == null) return;
- var movedX = scroll.scrollLeft - wheelStartX;
- var movedY = scroll.scrollTop - wheelStartY;
- var sample = (movedY && wheelDY && movedY / wheelDY) ||
- (movedX && wheelDX && movedX / wheelDX);
- wheelStartX = wheelStartY = null;
+ if (display.wheelStartX == null) return;
+ var movedX = scroll.scrollLeft - display.wheelStartX;
+ var movedY = scroll.scrollTop - display.wheelStartY;
+ var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
+ (movedX && display.wheelDX && movedX / display.wheelDX);
+ display.wheelStartX = display.wheelStartY = null;
if (!sample) return;
wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
++wheelSamples;
}, 200);
} else {
- wheelDX += dx; wheelDY += dy;
+ display.wheelDX += dx; display.wheelDY += dy;
}
}
}
@@ -1697,7 +1781,6 @@ window.CodeMirror = (function() {
}, 50);
var name = keyNames[e_prop(e, "keyCode")], handled = false;
- var flipCtrlCmd = mac && (opera || qtwebkit);
if (name == null || e.altGraphKey) return false;
if (e_prop(e, "altKey")) name = "Alt-" + name;
if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name;
@@ -1794,7 +1877,10 @@ window.CodeMirror = (function() {
var detectingSelectAll;
function onContextMenu(cm, e) {
- var display = cm.display, sel = cm.view.sel;
+ var display = cm.display;
+ if (eventInWidget(display, e)) return;
+
+ var sel = cm.view.sel;
var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
if (!pos || opera) return; // Opera is difficult.
if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
@@ -1904,7 +1990,7 @@ window.CodeMirror = (function() {
if (!cm.options.lineWrapping) {
checkWidthStart = lineNo(visualLine(doc, firstLine));
doc.iter(checkWidthStart, to.line + 1, function(line) {
- if (lineLength(doc, line) == view.maxLineLength) {
+ if (line == view.maxLine) {
recomputeMaxLength = true;
return true;
}
@@ -2351,6 +2437,25 @@ window.CodeMirror = (function() {
}
},
+ addOverlay: operation(null, function(spec, options) {
+ var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
+ if (mode.startState) throw new Error("Overlays may not be stateful.");
+ this.view.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
+ this.view.modeGen++;
+ regChange(this, 0, this.view.doc.size);
+ }),
+ removeOverlay: operation(null, function(spec) {
+ var overlays = this.view.overlays;
+ for (var i = 0; i < overlays.length; ++i) {
+ if (overlays[i].modeSpec == spec) {
+ overlays.splice(i, 1);
+ this.view.modeGen++;
+ regChange(this, 0, this.view.doc.size);
+ return;
+ }
+ }
+ }),
+
undo: operation(null, function() {unredoHelper(this, "undo");}),
redo: operation(null, function() {unredoHelper(this, "redo");}),
@@ -2522,23 +2627,10 @@ window.CodeMirror = (function() {
}),
addLineWidget: operation(null, function(handle, node, options) {
- var widget = options || {};
- widget.node = node;
- if (widget.noHScroll) this.display.alignWidgets = true;
- changeLine(this, handle, function(line) {
- (line.widgets || (line.widgets = [])).push(widget);
- widget.line = line;
- return true;
- });
- return widget;
+ return addLineWidget(this, handle, node, options);
}),
- removeLineWidget: operation(null, function(widget) {
- var ws = widget.line.widgets, no = lineNo(widget.line);
- if (no == null) return;
- for (var i = 0; i < ws.length; ++i) if (ws[i] == widget) ws.splice(i--, 1);
- regChange(this, no, no + 1);
- }),
+ removeLineWidget: function(widget) { widget.clear(); },
lineInfo: function(line) {
if (typeof line == "number") {
@@ -2663,7 +2755,8 @@ window.CodeMirror = (function() {
// Stuff used by commands, probably not much use to outside code.
moveH: operation(null, function(dir, unit) {
var sel = this.view.sel, pos = dir < 0 ? sel.from : sel.to;
- if (sel.shift || sel.extend || posEq(sel.from, sel.to)) pos = findPosH(this, dir, unit, true);
+ if (sel.shift || sel.extend || posEq(sel.from, sel.to))
+ pos = findPosH(this, dir, unit, this.options.rtlMoveVisually);
extendSelection(this, pos, pos, dir);
}),
@@ -2713,7 +2806,7 @@ window.CodeMirror = (function() {
return clipPos(doc, {line: lineNo, ch: ch});
},
indexFromPos: function (coords) {
- if (coords.line < 0 || coords.ch < 0) return 0;
+ coords = clipPos(this.view.doc, coords);
var index = coords.ch;
this.view.doc.iter(0, coords.line, function (line) {
index += line.text.length + 1;
@@ -2735,8 +2828,12 @@ window.CodeMirror = (function() {
scrollIntoView: function(pos) {
if (typeof pos == "number") pos = {line: pos, ch: 0};
- pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head;
- scrollPosIntoView(this, pos);
+ if (!pos || pos.line != null) {
+ pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head;
+ scrollPosIntoView(this, pos);
+ } else {
+ scrollIntoView(this, pos.left, pos.top, pos.right, pos.bottom);
+ }
},
setSize: function(width, height) {
@@ -2755,8 +2852,11 @@ window.CodeMirror = (function() {
refresh: function() {
clearCaches(this);
- if (this.display.scroller.scrollHeight > this.view.scrollTop)
- this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = this.view.scrollTop;
+ var sTop = this.view.scrollTop, sLeft = this.view.scrollLeft;
+ if (this.display.scroller.scrollHeight > sTop)
+ this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = sTop;
+ if (this.display.scroller.scrollWidth > sLeft)
+ this.display.scrollbarH.scrollLeft = this.display.scroller.scrollLeft = sLeft;
updateDisplay(this, true);
},
@@ -2795,6 +2895,7 @@ window.CodeMirror = (function() {
updateDisplay(cm, true);
}, true);
option("electricChars", true);
+ option("rtlMoveVisually", !windows);
option("theme", "default", function(cm) {
themeChanged(cm);
@@ -2811,6 +2912,10 @@ window.CodeMirror = (function() {
setGuttersForLineNumbers(cm.options);
guttersChanged(cm);
}, true);
+ option("fixedGutter", true, function(cm, val) {
+ cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
+ cm.refresh();
+ }, true);
option("lineNumbers", false, function(cm) {
setGuttersForLineNumbers(cm.options);
guttersChanged(cm);
@@ -2867,7 +2972,7 @@ window.CodeMirror = (function() {
};
CodeMirror.getMode = function(options, spec) {
- var spec = CodeMirror.resolveMode(spec);
+ spec = CodeMirror.resolveMode(spec);
var mfactory = modes[spec.name];
if (!mfactory) return CodeMirror.getMode(options, "text/plain");
var modeObj = mfactory(options, spec);
@@ -3204,11 +3309,12 @@ window.CodeMirror = (function() {
this.type = type;
this.cm = cm;
}
+ CodeMirror.TextMarker = TextMarker;
TextMarker.prototype.clear = function() {
if (this.explicitlyCleared) return;
startOperation(this.cm);
- var min = null, max = null;
+ var view = this.cm.view, min = null, max = null;
for (var i = 0; i < this.lines.length; ++i) {
var line = this.lines[i];
var span = getMarkedSpanFor(line.markedSpans, this);
@@ -3219,6 +3325,15 @@ window.CodeMirror = (function() {
else if (this.collapsed && !lineIsHidden(line))
updateLineHeight(line, textHeight(this.cm.display));
}
+ if (this.collapsed && !this.cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
+ var visual = visualLine(view.doc, this.lines[i]), len = lineLength(view.doc, visual);
+ if (len > view.maxLineLength) {
+ view.maxLine = visual;
+ view.maxLineLength = len;
+ view.maxLineChanged = true;
+ }
+ }
+
if (min != null) regChange(this.cm, min, max + 1);
this.lines.length = 0;
this.explicitlyCleared = true;
@@ -3245,6 +3360,18 @@ window.CodeMirror = (function() {
return from && {from: from, to: to};
};
+ TextMarker.prototype.getOptions = function(copyWidget) {
+ var repl = this.replacedWith;
+ return {className: this.className,
+ inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight,
+ atomic: this.atomic,
+ collapsed: this.collapsed,
+ clearOnEnter: this.clearOnEnter,
+ replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl,
+ readOnly: this.readOnly,
+ startStyle: this.startStyle, endStyle: this.endStyle};
+ };
+
function markText(cm, from, to, options, type) {
var doc = cm.view.doc;
var marker = new TextMarker(cm, type);
@@ -3259,6 +3386,8 @@ window.CodeMirror = (function() {
var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd;
doc.iter(curLine, to.line + 1, function(line) {
+ if (marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.view.maxLine)
+ cm.curOp.updateMaxLine = true;
var span = {from: null, to: null, marker: marker};
size += line.text.length;
if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
@@ -3269,10 +3398,11 @@ window.CodeMirror = (function() {
else updateLineHeight(line, 0);
}
addMarkedSpan(line, span);
- if (marker.collapsed && curLine == from.line && lineIsHidden(line))
- updateLineHeight(line, 0);
++curLine;
});
+ if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
+ if (lineIsHidden(line)) updateLineHeight(line, 0);
+ });
if (marker.readOnly) {
sawReadOnlySpans = true;
@@ -3447,9 +3577,12 @@ window.CodeMirror = (function() {
return true;
}
}
- window.lineIsHidden = lineIsHidden;
function lineIsHiddenInner(line, span) {
- if (span.to == null || span.marker.inclusiveRight && span.to == line.text.length)
+ if (span.to == null) {
+ var end = span.marker.find().to, endLine = getLine(lineDoc(line), end.line);
+ return lineIsHiddenInner(endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));
+ }
+ if (span.marker.inclusiveRight && span.to == line.text.length)
return true;
for (var sp, i = 0; i < line.markedSpans.length; ++i) {
sp = line.markedSpans[i];
@@ -3491,6 +3624,63 @@ window.CodeMirror = (function() {
line.markedSpans = spans;
}
+ // LINE WIDGETS
+
+ var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
+ for (var opt in options) if (options.hasOwnProperty(opt))
+ this[opt] = options[opt];
+ this.cm = cm;
+ this.node = node;
+ };
+ function widgetOperation(f) {
+ return function() {
+ startOperation(this.cm);
+ try {var result = f.apply(this, arguments);}
+ finally {endOperation(this.cm);}
+ return result;
+ };
+ }
+ LineWidget.prototype.clear = widgetOperation(function() {
+ var ws = this.line.widgets, no = lineNo(this.line);
+ if (no == null || !ws) return;
+ for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
+ updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
+ regChange(this.cm, no, no + 1);
+ });
+ LineWidget.prototype.changed = widgetOperation(function() {
+ var oldH = this.height;
+ this.height = null;
+ var diff = widgetHeight(this) - oldH;
+ if (!diff) return;
+ updateLineHeight(this.line, this.line.height + diff);
+ var no = lineNo(this.line);
+ regChange(this.cm, no, no + 1);
+ });
+
+ function widgetHeight(widget) {
+ if (widget.height != null) return widget.height;
+ if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1)
+ removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative"));
+ return widget.height = widget.node.offsetHeight;
+ }
+
+ function addLineWidget(cm, handle, node, options) {
+ var widget = new LineWidget(cm, node, options);
+ if (widget.noHScroll) cm.display.alignWidgets = true;
+ changeLine(cm, handle, function(line) {
+ (line.widgets || (line.widgets = [])).push(widget);
+ widget.line = line;
+ if (!lineIsHidden(line) || widget.showIfHidden) {
+ var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop;
+ updateLineHeight(line, line.height + widgetHeight(widget));
+ if (aboveVisible)
+ setTimeout(function() {cm.display.scroller.scrollTop += widget.height;});
+ }
+ return true;
+ });
+ return widget;
+ }
+
// LINE DATA STRUCTURE
// Line objects. These hold state related to a line, including
@@ -3504,7 +3694,8 @@ window.CodeMirror = (function() {
function updateLine(cm, line, text, markedSpans) {
line.text = text;
- line.stateAfter = line.styles = null;
+ if (line.stateAfter) line.stateAfter = null;
+ if (line.styles) line.styles = null;
if (line.order != null) line.order = null;
detachMarkedSpans(line);
attachMarkedSpans(line, markedSpans);
@@ -3521,31 +3712,72 @@ window.CodeMirror = (function() {
// Run the given mode's parser over a line, update the styles
// array, which contains alternating fragments of text and CSS
// classes.
- function highlightLine(cm, line, state) {
- var mode = cm.view.mode, flattenSpans = cm.options.flattenSpans;
- var changed = !line.styles, pos = 0, curText = "", curStyle = null;
- var stream = new StringStream(line.text, cm.options.tabSize), st = line.styles || (line.styles = []);
- if (line.text == "" && mode.blankLine) mode.blankLine(state);
+ function runMode(cm, text, mode, state, f) {
+ var flattenSpans = cm.options.flattenSpans;
+ var curText = "", curStyle = null;
+ var stream = new StringStream(text, cm.options.tabSize);
+ if (text == "" && mode.blankLine) mode.blankLine(state);
while (!stream.eol()) {
- var style = mode.token(stream, state), substr = stream.current();
+ var style = mode.token(stream, state);
+ if (stream.pos > 5000) {
+ flattenSpans = false;
+ // Webkit seems to refuse to render text nodes longer than 57444 characters
+ stream.pos = Math.min(text.length, stream.start + 50000);
+ style = null;
+ }
+ var substr = stream.current();
stream.start = stream.pos;
if (!flattenSpans || curStyle != style) {
- if (curText) {
- changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1];
- st[pos++] = curText; st[pos++] = curStyle;
- }
+ if (curText) f(curText, curStyle);
curText = substr; curStyle = style;
} else curText = curText + substr;
- // Give up when line is ridiculously long
- if (stream.pos > 5000) break;
}
- if (curText) {
- changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1];
- st[pos++] = curText; st[pos++] = curStyle;
+ if (curText) f(curText, curStyle);
+ }
+
+ function highlightLine(cm, line, state) {
+ // A styles array always starts with a number identifying the
+ // mode/overlays that it is based on (for easy invalidation).
+ var st = [cm.view.modeGen];
+ // Compute the base array of styles
+ runMode(cm, line.text, cm.view.mode, state, function(txt, style) {st.push(txt, style);});
+
+ // Run overlays, adjust style array.
+ for (var o = 0; o < cm.view.overlays.length; ++o) {
+ var overlay = cm.view.overlays[o], i = 1;
+ runMode(cm, line.text, overlay.mode, true, function(txt, style) {
+ var start = i, len = txt.length;
+ // Ensure there's a token end at the current position, and that i points at it
+ while (len) {
+ var cur = st[i], len_ = cur.length;
+ if (len_ <= len) {
+ len -= len_;
+ } else {
+ st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len));
+ len = 0;
+ }
+ i += 2;
+ }
+ if (!style) return;
+ if (overlay.opaque) {
+ st.splice(start, i - start, txt, style);
+ i = start + 2;
+ } else {
+ for (; start < i; start += 2) {
+ var cur = st[start+1];
+ st[start+1] = cur ? cur + " " + style : style;
+ }
+ }
+ });
}
- if (stream.pos > 5000) { st[pos++] = line.text.slice(stream.pos); st[pos++] = null; }
- if (pos != st.length) { st.length = pos; changed = true; }
- return changed;
+
+ return st;
+ }
+
+ function getLineStyles(cm, line) {
+ if (!line.styles || line.styles[0] != cm.view.modeGen)
+ line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
+ return line.styles;
}
// Lightweight form of highlight -- proceed over this line and
@@ -3580,8 +3812,6 @@ window.CodeMirror = (function() {
if (line.textClass) builder.pre.className = line.textClass;
do {
- if (!line.styles)
- highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
builder.measure = line == realLine && measure;
builder.pos = 0;
builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
@@ -3589,7 +3819,7 @@ window.CodeMirror = (function() {
measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
builder.addedOne = true;
}
- var next = insertLineContent(line, builder);
+ var next = insertLineContent(line, builder, getLineStyles(cm, line));
sawBefore = line == lineBefore;
if (next) {
line = getLine(cm.view.doc, next.to.line);
@@ -3646,7 +3876,7 @@ window.CodeMirror = (function() {
function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
for (var i = 0; i < text.length; ++i) {
- if (i && i < text.length - 1 &&
+ if (i && i < text.length &&
builder.cm.options.lineWrapping &&
spanAffectsWrapping.test(text.slice(i - 1, i + 1)))
builder.pre.appendChild(elt("wbr"));
@@ -3671,16 +3901,16 @@ window.CodeMirror = (function() {
// Outputs a number of spans to make up a line, taking highlighting
// and marked text into account.
- function insertLineContent(line, builder) {
- var st = line.styles, spans = line.markedSpans;
+ function insertLineContent(line, builder, styles) {
+ var spans = line.markedSpans;
if (!spans) {
- for (var i = 0; i < st.length; i+=2)
- builder.addToken(builder, st[i], styleToClass(st[i+1]));
+ for (var i = 1; i < styles.length; i+=2)
+ builder.addToken(builder, styles[i], styleToClass(styles[i+1]));
return;
}
var allText = line.text, len = allText.length;
- var pos = 0, i = 0, text = "", style;
+ var pos = 0, i = 1, text = "", style;
var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed;
for (;;) {
if (nextChange == pos) { // Update current marker set
@@ -3724,7 +3954,7 @@ window.CodeMirror = (function() {
pos = end;
spanStartStyle = "";
}
- text = st[i++]; style = styleToClass(st[i++]);
+ text = styles[i++]; style = styleToClass(styles[i++]);
}
}
}
@@ -3896,6 +4126,11 @@ window.CodeMirror = (function() {
return no;
}
+ function lineDoc(line) {
+ for (var d = line.parent; d.parent; d = d.parent) {}
+ return d;
+ }
+
function lineAtHeight(chunk, h) {
var n = 0;
outer: do {
@@ -4177,7 +4412,9 @@ window.CodeMirror = (function() {
}
function removeChildren(e) {
- e.innerHTML = "";
+ // IE will break all parent-child relations in subnodes when setting innerHTML
+ if (!ie) e.innerHTML = "";
+ else while (e.firstChild) e.removeChild(e.firstChild);
return e;
}
@@ -4300,7 +4537,7 @@ window.CodeMirror = (function() {
if (!order) return f(from, to, "ltr");
for (var i = 0; i < order.length; ++i) {
var part = order[i];
- if (part.from < to && part.to > from)
+ if (part.from < to && part.to > from || from == to && part.to == from)
f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
}
}
@@ -4418,24 +4655,20 @@ window.CodeMirror = (function() {
var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
+ // Browsers seem to always treat the boundaries of block elements as being L.
+ var outerType = "L";
return function charOrdering(str) {
if (!bidiRE.test(str)) return false;
- var len = str.length, types = [], startType = null;
- for (var i = 0, type; i < len; ++i) {
+ var len = str.length, types = [];
+ for (var i = 0, type; i < len; ++i)
types.push(type = charType(str.charCodeAt(i)));
- if (startType == null) {
- if (type == "L") startType = "L";
- else if (type == "R" || type == "r") startType = "R";
- }
- }
- if (startType == null) startType = "L";
// W1. Examine each non-spacing mark (NSM) in the level run, and
// change the type of the NSM to the type of the previous
// character. If the NSM is at the start of the level run, it will
// get the type of sor.
- for (var i = 0, prev = startType; i < len; ++i) {
+ for (var i = 0, prev = outerType; i < len; ++i) {
var type = types[i];
if (type == "m") types[i] = prev;
else prev = type;
@@ -4446,7 +4679,7 @@ window.CodeMirror = (function() {
// AL is found, change the type of the European number to Arabic
// number.
// W3. Change all ALs to R.
- for (var i = 0, cur = startType; i < len; ++i) {
+ for (var i = 0, cur = outerType; i < len; ++i) {
var type = types[i];
if (type == "1" && cur == "r") types[i] = "n";
else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
@@ -4481,7 +4714,7 @@ window.CodeMirror = (function() {
// W7. Search backwards from each instance of a European number
// until the first strong type (R, L, or sor) is found. If an L is
// found, then change the type of the European number to L.
- for (var i = 0, cur = startType; i < len; ++i) {
+ for (var i = 0, cur = outerType; i < len; ++i) {
var type = types[i];
if (cur == "L" && type == "1") types[i] = "L";
else if (isStrong.test(type)) cur = type;
@@ -4496,8 +4729,8 @@ window.CodeMirror = (function() {
for (var i = 0; i < len; ++i) {
if (isNeutral.test(types[i])) {
for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
- var before = (i ? types[i-1] : startType) == "L";
- var after = (end < len - 1 ? types[end] : startType) == "L";
+ var before = (i ? types[i-1] : outerType) == "L";
+ var after = (end < len - 1 ? types[end] : outerType) == "L";
var replace = before || after ? "L" : "R";
for (var j = i; j < end; ++j) types[j] = replace;
i = end - 1;
@@ -4547,7 +4780,7 @@ window.CodeMirror = (function() {
// THE END
- CodeMirror.version = "3.0";
+ CodeMirror.version = "3.02";
return CodeMirror;
})();
Something went wrong with that request. Please try again.