From 8db5f53579e05bd3541578ff87c3817333978729 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 19 Nov 2013 15:17:40 -0800 Subject: [PATCH 01/27] Bug 922812 - Add an 'eval current top level function' command to scratchpad; r=past --- browser/devtools/scratchpad/scratchpad.js | 166 ++++++++++++++++++ browser/devtools/scratchpad/scratchpad.xul | 15 ++ browser/devtools/scratchpad/test/browser.ini | 1 + .../test/browser_scratchpad_eval_func.js | 86 +++++++++ browser/devtools/sourceeditor/editor.js | 1 + .../chrome/browser/devtools/scratchpad.dtd | 7 + browser/themes/shared/devtools/dark-theme.css | 5 + .../themes/shared/devtools/light-theme.css | 5 + 8 files changed, 286 insertions(+) create mode 100644 browser/devtools/scratchpad/test/browser_scratchpad_eval_func.js diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js index 5e786bcf3b7a..0cc646e32d6a 100644 --- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -24,6 +24,7 @@ const BUTTON_POSITION_SAVE = 0; const BUTTON_POSITION_CANCEL = 1; const BUTTON_POSITION_DONT_SAVE = 2; const BUTTON_POSITION_REVERT = 0; +const EVAL_FUNCTION_TIMEOUT = 1000; // milliseconds const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties"; const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; @@ -75,6 +76,9 @@ XPCOMUtils.defineLazyGetter(this, "REMOTE_TIMEOUT", () => XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils", "resource://gre/modules/ShortcutUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Reflect", + "resource://gre/modules/reflect.jsm"); + // Because we have no constructor / destructor where we can log metrics we need // to do so here. let telemetry = new Telemetry(); @@ -567,6 +571,168 @@ var Scratchpad = { return deferred.promise; }, + /** + * Parse the text and return an AST. If we can't parse it, write an error + * comment and return false. + */ + _parseText: function SP__parseText(aText) { + try { + return Reflect.parse(aText); + } catch (e) { + this.writeAsErrorComment(DevToolsUtils.safeErrorString(e)); + return false; + } + }, + + /** + * Determine if the given AST node location contains the given cursor + * position. + * + * @returns Boolean + */ + _containsCursor: function (aLoc, aCursorPos) { + // Our line numbers are 1-based, while CodeMirror's are 0-based. + const lineNumber = aCursorPos.line + 1; + const columnNumber = aCursorPos.ch; + + if (aLoc.start.line <= lineNumber && aLoc.end.line >= lineNumber) { + if (aLoc.start.line === aLoc.end.line) { + return aLoc.start.column <= columnNumber + && aLoc.end.column >= columnNumber; + } + + if (aLoc.start.line == lineNumber) { + return columnNumber >= aLoc.start.column; + } + + if (aLoc.end.line == lineNumber) { + return columnNumber <= aLoc.end.column; + } + + return true; + } + + return false; + }, + + /** + * Find the top level function AST node that the cursor is within. + * + * @returns Object|null + */ + _findTopLevelFunction: function SP__findTopLevelFunction(aAst, aCursorPos) { + for (let statement of aAst.body) { + switch (statement.type) { + case "FunctionDeclaration": + if (this._containsCursor(statement.loc, aCursorPos)) { + return statement; + } + break; + + case "VariableDeclaration": + for (let decl of statement.declarations) { + if (!decl.init) { + continue; + } + if ((decl.init.type == "FunctionExpression" + || decl.init.type == "ArrowExpression") + && this._containsCursor(decl.loc, aCursorPos)) { + return decl; + } + } + break; + } + } + + return null; + }, + + /** + * Get the source text associated with the given function statement. + * + * @param Object aFunction + * @param String aFullText + * @returns String + */ + _getFunctionText: function SP__getFunctionText(aFunction, aFullText) { + let functionText = ""; + // Initially set to 0, but incremented first thing in the loop below because + // line numbers are 1 based, not 0 based. + let lineNumber = 0; + const { start, end } = aFunction.loc; + const singleLine = start.line === end.line; + + for (let line of aFullText.split(/\n/g)) { + lineNumber++; + + if (singleLine && start.line === lineNumber) { + functionText = line.slice(start.column, end.column); + break; + } + + if (start.line === lineNumber) { + functionText += line.slice(start.column) + "\n"; + continue; + } + + if (end.line === lineNumber) { + functionText += line.slice(0, end.column); + break; + } + + if (start.line < lineNumber && end.line > lineNumber) { + functionText += line + "\n"; + } + } + + return functionText; + }, + + /** + * Evaluate the top level function that the cursor is resting in. + * + * @returns Promise [text, error, result] + */ + evalTopLevelFunction: function SP_evalTopLevelFunction() { + const text = this.getText(); + const ast = this._parseText(text); + if (!ast) { + return promise.resolve([text, undefined, undefined]); + } + + const cursorPos = this.editor.getCursor(); + const funcStatement = this._findTopLevelFunction(ast, cursorPos); + if (!funcStatement) { + return promise.resolve([text, undefined, undefined]); + } + + let functionText = this._getFunctionText(funcStatement, text); + + // TODO: This is a work around for bug 940086. It should be removed when + // that is fixed. + if (funcStatement.type == "FunctionDeclaration" + && !functionText.startsWith("function ")) { + functionText = "function " + functionText; + funcStatement.loc.start.column -= 9; + } + + // The decrement by one is because our line numbers are 1-based, while + // CodeMirror's are 0-based. + const from = { + line: funcStatement.loc.start.line - 1, + ch: funcStatement.loc.start.column + }; + const to = { + line: funcStatement.loc.end.line - 1, + ch: funcStatement.loc.end.column + }; + + const marker = this.editor.markText(from, to, { className: "eval-text" }); + setTimeout(() => marker.clear(), EVAL_FUNCTION_TIMEOUT); + + return this.evaluate(functionText); + }, + /** * Writes out a primitive value as a comment. This handles values which are * to be printed directly (number, string) as well as grips to values diff --git a/browser/devtools/scratchpad/scratchpad.xul b/browser/devtools/scratchpad/scratchpad.xul index 5c8f780a9aa7..afffbba93793 100644 --- a/browser/devtools/scratchpad/scratchpad.xul +++ b/browser/devtools/scratchpad/scratchpad.xul @@ -63,6 +63,7 @@ + @@ -108,6 +109,10 @@ key="&reloadAndRun.key;" command="sp-cmd-reloadAndRun" modifiers="accel,shift"/> + + @@ -314,6 +324,11 @@ accesskey="&display.accesskey;" key="sp-key-display" command="sp-cmd-display"/> + diff --git a/browser/devtools/scratchpad/test/browser.ini b/browser/devtools/scratchpad/test/browser.ini index 82ee710d3223..9c0c98764712 100644 --- a/browser/devtools/scratchpad/test/browser.ini +++ b/browser/devtools/scratchpad/test/browser.ini @@ -4,6 +4,7 @@ support-files = head.js [browser_scratchpad_browser_last_window_closing.js] [browser_scratchpad_reset_undo.js] [browser_scratchpad_display_outputs_errors.js] +[browser_scratchpad_eval_func.js] [browser_scratchpad_goto_line_ui.js] [browser_scratchpad_reload_and_run.js] [browser_scratchpad_display_non_error_exceptions.js] diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_eval_func.js b/browser/devtools/scratchpad/test/browser_scratchpad_eval_func.js new file mode 100644 index 000000000000..1dbc5fdfc206 --- /dev/null +++ b/browser/devtools/scratchpad/test/browser_scratchpad_eval_func.js @@ -0,0 +1,86 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8,test Scratchpad eval function."; +} + +function reportErrorAndQuit(error) { + DevToolsUtils.reportException("browser_scratchpad_eval_func.js", error); + ok(false); + finish(); +} + +function runTests(sw) +{ + const sp = sw.Scratchpad; + + let foo = "" + function main() { console.log(1); }; + let bar = "var bar = " + (() => { console.log(2); }); + + const fullText = + foo + "\n" + + "\n" + + bar + "\n" + + sp.setText(fullText); + + // On the function declaration. + sp.editor.setCursor({ line: 0, ch: 18 }); + sp.evalTopLevelFunction() + .then(([text, error, result]) => { + is(text, foo, "Should re-eval foo."); + ok(!error, "Should not have got an error."); + ok(result, "Should have got a result."); + }) + + // On the arrow function. + .then(() => { + sp.editor.setCursor({ line: 2, ch: 18 }); + return sp.evalTopLevelFunction(); + }) + .then(([text, error, result]) => { + is(text, bar.replace("var ", ""), "Should re-eval bar."); + ok(!error, "Should not have got an error."); + ok(result, "Should have got a result."); + }) + + // On the empty line. + .then(() => { + sp.editor.setCursor({ line: 1, ch: 0 }); + return sp.evalTopLevelFunction(); + }) + .then(([text, error, result]) => { + is(text, fullText, + "Should get full text back since we didn't find a specific function."); + ok(!error, "Should not have got an error."); + ok(!result, "Should not have got a result."); + }) + + // Syntax error. + .then(() => { + sp.setText("function {}"); + sp.editor.setCursor({ line: 0, ch: 9 }); + return sp.evalTopLevelFunction(); + }) + .then(([text, error, result]) => { + is(text, "function {}", + "Should get the full text back since there was a parse error."); + ok(!error, "Should not have got an error"); + ok(!result, "Should not have got a result"); + ok(sp.getText().contains("SyntaxError"), + "We should have written the syntax error to the scratchpad."); + }) + + .then(finish, reportErrorAndQuit); +} diff --git a/browser/devtools/sourceeditor/editor.js b/browser/devtools/sourceeditor/editor.js index 25b0706aea79..895eb4cee292 100644 --- a/browser/devtools/sourceeditor/editor.js +++ b/browser/devtools/sourceeditor/editor.js @@ -78,6 +78,7 @@ const CM_MAPPING = [ "clearHistory", "openDialog", "cursorCoords", + "markText", "refresh" ]; diff --git a/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd b/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd index e1d5d2a6c79e..e1a2c776c43b 100644 --- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd +++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd @@ -120,3 +120,10 @@ - the same name in browser.dtd. --> + + + + + diff --git a/browser/themes/shared/devtools/dark-theme.css b/browser/themes/shared/devtools/dark-theme.css index 3bf69fa15d59..46c6c7609733 100644 --- a/browser/themes/shared/devtools/dark-theme.css +++ b/browser/themes/shared/devtools/dark-theme.css @@ -189,6 +189,11 @@ div.cm-s-mozilla span.CodeMirror-matchingbracket { /* highlight brackets */ color: white; } +/* Highlight for evaluating current statement. */ +div.CodeMirror span.eval-text { + background-color: #556; +} + .cm-s-mozilla .CodeMirror-linenumber { /* line number text */ color: #5f7387; } diff --git a/browser/themes/shared/devtools/light-theme.css b/browser/themes/shared/devtools/light-theme.css index acbbf6d090ae..3ae8aa38e3d4 100644 --- a/browser/themes/shared/devtools/light-theme.css +++ b/browser/themes/shared/devtools/light-theme.css @@ -188,6 +188,11 @@ div.cm-s-mozilla span.CodeMirror-matchingbracket { /* highlight brackets */ color: black; } +/* Highlight for evaluating current statement. */ +div.CodeMirror span.eval-text { + background-color: #ccd; +} + .cm-s-mozilla .CodeMirror-linenumber { /* line number text */ color: #667380; } From 05ae73ac5895ce80cfeb0938bb1765b85b2aa930 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Mon, 28 Oct 2013 10:09:04 +0100 Subject: [PATCH 02/27] Bug 939784 - Provide a notification when the applicable state of a style sheet changes r=bz From 0d69149ae5e9809d42bb7fbe9c60b760b931ec7e Mon Sep 17 00:00:00 2001 --- content/base/src/nsDocument.cpp | 20 ++++++++++++++++++++ content/base/src/nsDocument.h | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 6b5feb2e73ff..89fbdb9c40c5 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -3989,6 +3989,26 @@ nsDocument::SetStyleSheetApplicableState(nsIStyleSheet* aSheet, "StyleSheetApplicableStateChanged", aApplicable); } + + if (!mSSApplicableStateNotificationPending) { + nsRefPtr notification = NS_NewRunnableMethod(this, + &nsDocument::NotifyStyleSheetApplicableStateChanged); + mSSApplicableStateNotificationPending = + NS_SUCCEEDED(NS_DispatchToCurrentThread(notification)); + } +} + +void +nsDocument::NotifyStyleSheetApplicableStateChanged() +{ + mSSApplicableStateNotificationPending = false; + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(static_cast(this), + "style-sheet-applicable-state-changed", + nullptr); + } } // These three functions are a lot like the implementation of the diff --git a/content/base/src/nsDocument.h b/content/base/src/nsDocument.h index 57f2fcf958c4..730923035630 100644 --- a/content/base/src/nsDocument.h +++ b/content/base/src/nsDocument.h @@ -1156,6 +1156,8 @@ protected: void EnsureOnloadBlocker(); + void NotifyStyleSheetApplicableStateChanged(); + nsTArray mCharSetObservers; PLDHashTable *mSubDocuments; @@ -1279,6 +1281,10 @@ protected: bool mAsyncFullscreenPending:1; + // Keeps track of whether we have a pending + // 'style-sheet-applicable-state-changed' notification. + bool mSSApplicableStateNotificationPending:1; + uint32_t mCancelledPointerLockRequests; uint8_t mXMLDeclarationBits; From a28902649f4d9c467846b87b29d82b27aecc7026 Mon Sep 17 00:00:00 2001 From: Brad Lassey Date: Wed, 23 Oct 2013 18:37:14 +0200 Subject: [PATCH 03/27] bug 930072 - Provide example app using GeckoView library in the tree r=mfinkle, nalexander --- .../geckoview_example/AndroidManifest.xml | 22 ++++++++++++ .../geckoview_example/GeckoViewExample.java | 14 ++++++++ .../android/geckoview_example/Makefile.in | 34 +++++++++++++++++++ embedding/android/geckoview_example/main.xml | 12 +++++++ embedding/android/geckoview_example/moz.build | 0 embedding/moz.build | 4 +++ 6 files changed, 86 insertions(+) create mode 100644 embedding/android/geckoview_example/AndroidManifest.xml create mode 100644 embedding/android/geckoview_example/GeckoViewExample.java create mode 100644 embedding/android/geckoview_example/Makefile.in create mode 100644 embedding/android/geckoview_example/main.xml create mode 100644 embedding/android/geckoview_example/moz.build diff --git a/embedding/android/geckoview_example/AndroidManifest.xml b/embedding/android/geckoview_example/AndroidManifest.xml new file mode 100644 index 000000000000..6ced8a2b56bf --- /dev/null +++ b/embedding/android/geckoview_example/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/embedding/android/geckoview_example/GeckoViewExample.java b/embedding/android/geckoview_example/GeckoViewExample.java new file mode 100644 index 000000000000..dafe70e861cf --- /dev/null +++ b/embedding/android/geckoview_example/GeckoViewExample.java @@ -0,0 +1,14 @@ +package org.mozilla.geckoviewexample; + +import android.app.Activity; +import android.os.Bundle; +import android.util.AttributeSet; + +public class GeckoViewExample extends Activity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } +} diff --git a/embedding/android/geckoview_example/Makefile.in b/embedding/android/geckoview_example/Makefile.in new file mode 100644 index 000000000000..005c714067ad --- /dev/null +++ b/embedding/android/geckoview_example/Makefile.in @@ -0,0 +1,34 @@ +include $(topsrcdir)/config/rules.mk + +GARBAGE = \ + AndroidManifest.xml \ + proguard-project.txt \ + project.properties \ + ant.properties \ + build.xml \ + local.properties \ + $(NULL) + +GARBAGE_DIRS = \ + assets \ + geckoview_library \ + gen \ + bin \ + libs \ + res \ + src \ + $(NULL) + +ANDROID=$(ANDROID_SDK)/../../tools/android + +build.xml: + $(ANDROID) create project --name GeckoViewExample --target android-18 --path $(CURDIR) --activity GeckoViewExample --package org.mozilla.geckoviewexample + $(ANDROID) update project --target android-18 --path $(CURDIR) --library $(DEPTH)/mobile/android/geckoview_library + $(UNZIP) -o $(DIST)/geckoview_library/geckoview_assets.zip + $(NSINSTALL) $(srcdir)/main.xml res/layout/ + $(NSINSTALL) $(srcdir)/AndroidManifest.xml . + $(NSINSTALL) $(srcdir)/GeckoViewExample.java src/org/mozilla/geckoviewexample/ + echo jar.libs.dir=libs >> project.properties + +package: build.xml + ant debug diff --git a/embedding/android/geckoview_example/main.xml b/embedding/android/geckoview_example/main.xml new file mode 100644 index 000000000000..4f1c80e13dff --- /dev/null +++ b/embedding/android/geckoview_example/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/embedding/android/geckoview_example/moz.build b/embedding/android/geckoview_example/moz.build new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/embedding/moz.build b/embedding/moz.build index 6b326a85cf7d..35f6bde5e017 100644 --- a/embedding/moz.build +++ b/embedding/moz.build @@ -5,6 +5,10 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DIRS += ['base', 'components', 'browser'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': + DIRS += ['android/geckoview_example'] + TEST_DIRS += ['test'] if CONFIG['ENABLE_TESTS']: From 618ec264b40ec64198e05c3536ad7c8ccb59efc3 Mon Sep 17 00:00:00 2001 From: Anton Kovalyov Date: Tue, 19 Nov 2013 15:53:13 -0800 Subject: [PATCH 04/27] Bug 929766 - Remove Orion from the SourceEditor component. r=msucan --- browser/app/profile/firefox.js | 8 - browser/devtools/jar.mn | 2 - browser/devtools/markupview/html-editor.js | 3 +- browser/devtools/scratchpad/scratchpad.js | 2 +- .../browser_scratchpad_edit_ui_updates.js | 4 - .../sourceeditor/codemirror/mozilla.css | 10 +- browser/devtools/sourceeditor/editor.js | 19 +- browser/devtools/sourceeditor/moz.build | 5 +- browser/devtools/sourceeditor/orion/LICENSE | 29 - .../sourceeditor/orion/Makefile.dryice.js | 56 - browser/devtools/sourceeditor/orion/README | 43 - browser/devtools/sourceeditor/orion/UPGRADE | 20 - browser/devtools/sourceeditor/orion/orion.css | 277 - browser/devtools/sourceeditor/orion/orion.js | 12303 ---------------- .../sourceeditor/source-editor-orion.jsm | 2131 --- .../sourceeditor/source-editor-overlay.xul | 204 - .../sourceeditor/source-editor-ui.jsm | 332 - .../devtools/sourceeditor/source-editor.jsm | 455 - .../devtools/sourceeditor/test/browser.ini | 26 +- .../test/browser_bug650345_find.js | 149 - .../test/browser_bug684546_reset_undo.js | 72 - .../test/browser_bug684862_paste_html.js | 119 - .../test/browser_bug687160_line_api.js | 90 - .../test/browser_bug687568_pagescroll.js | 89 - .../test/browser_bug687573_vscroll.js | 133 - .../test/browser_bug687580_drag_and_drop.js | 162 - .../browser_bug695035_middle_click_paste.js | 100 - .../test/browser_bug700893_dirty_state.js | 94 - .../test/browser_bug703692_focus_blur.js | 71 - .../browser_bug707987_debugger_breakpoints.js | 169 - .../browser_bug712982_line_ruler_click.js | 74 - .../test/browser_bug725388_mouse_events.js | 107 - ...wser_bug725392_mouse_coords_char_offset.js | 160 - .../browser_bug725430_comment_uncomment.js | 151 - .../browser_bug725618_moveLines_shortcut.js | 117 - .../browser_bug729480_line_vertical_align.js | 99 - .../browser_bug729960_block_bracket_jump.js | 164 - .../browser_bug731721_debugger_stepping.js | 59 - ...rowser_bug744021_next_prev_bracket_jump.js | 104 - .../sourceeditor/test/browser_editor_basic.js | 51 + .../test/browser_editor_cursor.js | 36 + .../test/browser_editor_history.js | 32 + .../test/browser_editor_markers.js | 39 + .../browser_sourceeditor_initialization.js | 499 - browser/devtools/sourceeditor/test/head.js | 202 +- .../devtools/styleeditor/StyleSheetEditor.jsm | 7 +- browser/devtools/styleeditor/styleeditor.xul | 45 +- .../test/browser_styleeditor_sv_resize.js | 2 +- ...n-breakpoint.png => editor-breakpoint.png} | Bin ...location.png => editor-debug-location.png} | Bin .../{orion-error.png => editor-error.png} | Bin .../themes/linux/devtools/orion-container.css | 39 - browser/themes/linux/devtools/orion-task.png | Bin 413 -> 0 bytes browser/themes/linux/devtools/orion.css | 199 - browser/themes/linux/jar.mn | 9 +- ...n-breakpoint.png => editor-breakpoint.png} | Bin ...location.png => editor-debug-location.png} | Bin .../{orion-error.png => editor-error.png} | Bin .../themes/osx/devtools/orion-container.css | 39 - browser/themes/osx/devtools/orion-task.png | Bin 413 -> 0 bytes browser/themes/osx/devtools/orion.css | 199 - browser/themes/osx/jar.mn | 9 +- ...n-breakpoint.png => editor-breakpoint.png} | Bin ...location.png => editor-debug-location.png} | Bin .../{orion-error.png => editor-error.png} | Bin .../windows/devtools/orion-container.css | 39 - .../themes/windows/devtools/orion-task.png | Bin 413 -> 0 bytes browser/themes/windows/devtools/orion.css | 199 - browser/themes/windows/jar.mn | 18 +- 69 files changed, 268 insertions(+), 19607 deletions(-) delete mode 100644 browser/devtools/sourceeditor/orion/LICENSE delete mode 100644 browser/devtools/sourceeditor/orion/Makefile.dryice.js delete mode 100644 browser/devtools/sourceeditor/orion/README delete mode 100644 browser/devtools/sourceeditor/orion/UPGRADE delete mode 100644 browser/devtools/sourceeditor/orion/orion.css delete mode 100644 browser/devtools/sourceeditor/orion/orion.js delete mode 100644 browser/devtools/sourceeditor/source-editor-orion.jsm delete mode 100644 browser/devtools/sourceeditor/source-editor-overlay.xul delete mode 100644 browser/devtools/sourceeditor/source-editor-ui.jsm delete mode 100644 browser/devtools/sourceeditor/source-editor.jsm delete mode 100644 browser/devtools/sourceeditor/test/browser_bug650345_find.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug684546_reset_undo.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug684862_paste_html.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug687160_line_api.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug687568_pagescroll.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug687573_vscroll.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug687580_drag_and_drop.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug695035_middle_click_paste.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug700893_dirty_state.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug703692_focus_blur.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug707987_debugger_breakpoints.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug712982_line_ruler_click.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug725388_mouse_events.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug725392_mouse_coords_char_offset.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug725430_comment_uncomment.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug725618_moveLines_shortcut.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug729480_line_vertical_align.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug729960_block_bracket_jump.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug731721_debugger_stepping.js delete mode 100644 browser/devtools/sourceeditor/test/browser_bug744021_next_prev_bracket_jump.js create mode 100644 browser/devtools/sourceeditor/test/browser_editor_basic.js create mode 100644 browser/devtools/sourceeditor/test/browser_editor_cursor.js create mode 100644 browser/devtools/sourceeditor/test/browser_editor_history.js create mode 100644 browser/devtools/sourceeditor/test/browser_editor_markers.js delete mode 100644 browser/devtools/sourceeditor/test/browser_sourceeditor_initialization.js rename browser/themes/linux/devtools/{orion-breakpoint.png => editor-breakpoint.png} (100%) rename browser/themes/linux/devtools/{orion-debug-location.png => editor-debug-location.png} (100%) rename browser/themes/linux/devtools/{orion-error.png => editor-error.png} (100%) delete mode 100644 browser/themes/linux/devtools/orion-container.css delete mode 100644 browser/themes/linux/devtools/orion-task.png delete mode 100644 browser/themes/linux/devtools/orion.css rename browser/themes/osx/devtools/{orion-breakpoint.png => editor-breakpoint.png} (100%) rename browser/themes/osx/devtools/{orion-debug-location.png => editor-debug-location.png} (100%) rename browser/themes/osx/devtools/{orion-error.png => editor-error.png} (100%) delete mode 100644 browser/themes/osx/devtools/orion-container.css delete mode 100644 browser/themes/osx/devtools/orion-task.png delete mode 100644 browser/themes/osx/devtools/orion.css rename browser/themes/windows/devtools/{orion-breakpoint.png => editor-breakpoint.png} (100%) rename browser/themes/windows/devtools/{orion-debug-location.png => editor-debug-location.png} (100%) rename browser/themes/windows/devtools/{orion-error.png => editor-error.png} (100%) delete mode 100644 browser/themes/windows/devtools/orion-container.css delete mode 100644 browser/themes/windows/devtools/orion-task.png delete mode 100644 browser/themes/windows/devtools/orion.css diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 88dc128fb812..0bebde252b52 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1227,14 +1227,6 @@ pref("devtools.hud.loglimit.console", 200); pref("devtools.editor.tabsize", 4); pref("devtools.editor.expandtab", true); -// Tells which component you want to use for source editing in developer tools. -// -// Available components: -// "orion" - this is the Orion source code editor from the Eclipse project. It -// provides programmer-specific editor features such as syntax highlighting, -// indenting and bracket recognition. -pref("devtools.editor.component", "orion"); - // Enable the Font Inspector pref("devtools.fontinspector.enabled", true); diff --git a/browser/devtools/jar.mn b/browser/devtools/jar.mn index e4e784b3ec72..8f23afb5420b 100644 --- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -28,7 +28,6 @@ browser.jar: content/browser/devtools/fontinspector/font-inspector.js (fontinspector/font-inspector.js) content/browser/devtools/fontinspector/font-inspector.xhtml (fontinspector/font-inspector.xhtml) content/browser/devtools/fontinspector/font-inspector.css (fontinspector/font-inspector.css) - content/browser/devtools/orion.js (sourceeditor/orion/orion.js) content/browser/devtools/codemirror/codemirror.js (sourceeditor/codemirror/codemirror.js) content/browser/devtools/codemirror/codemirror.css (sourceeditor/codemirror/codemirror.css) content/browser/devtools/codemirror/javascript.js (sourceeditor/codemirror/javascript.js) @@ -45,7 +44,6 @@ browser.jar: content/browser/devtools/codemirror/dialog.js (sourceeditor/codemirror/dialog/dialog.js) content/browser/devtools/codemirror/dialog.css (sourceeditor/codemirror/dialog/dialog.css) content/browser/devtools/codemirror/mozilla.css (sourceeditor/codemirror/mozilla.css) -* content/browser/devtools/source-editor-overlay.xul (sourceeditor/source-editor-overlay.xul) content/browser/devtools/debugger.xul (debugger/debugger.xul) content/browser/devtools/debugger.css (debugger/debugger.css) content/browser/devtools/debugger-controller.js (debugger/debugger-controller.js) diff --git a/browser/devtools/markupview/html-editor.js b/browser/devtools/markupview/html-editor.js index d11e39a928b0..32517147c987 100644 --- a/browser/devtools/markupview/html-editor.js +++ b/browser/devtools/markupview/html-editor.js @@ -67,7 +67,8 @@ function HTMLEditor(htmlDocument) this.editorInner.addEventListener("click", stopPropagation, false); this.editor = new Editor(config); - this.editor.appendTo(this.editorInner).then(() => { + let iframe = this.editorInner.ownerDocument.createElement("iframe"); + this.editor.appendTo(this.editorInner, iframe).then(() => { this.hide(false); }).then(null, (err) => console.log(err.message)); } diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js index 0cc646e32d6a..b8a8e3b7f8eb 100644 --- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -1682,7 +1682,7 @@ var Scratchpad = { * Add an observer for Scratchpad events. * * The observer implements IScratchpadObserver := { - * onReady: Called when the Scratchpad and its SourceEditor are ready. + * onReady: Called when the Scratchpad and its Editor are ready. * Arguments: (Scratchpad aScratchpad) * } * diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_edit_ui_updates.js b/browser/devtools/scratchpad/test/browser_scratchpad_edit_ui_updates.js index 70e9e2248e5e..f864d5973e85 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_edit_ui_updates.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_edit_ui_updates.js @@ -5,10 +5,6 @@ "use strict"; -let tempScope = {}; -Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope); -let SourceEditor = tempScope.SourceEditor; - function test() { waitForExplicitFinish(); diff --git a/browser/devtools/sourceeditor/codemirror/mozilla.css b/browser/devtools/sourceeditor/codemirror/mozilla.css index 1f79b470f392..ccda017122d9 100644 --- a/browser/devtools/sourceeditor/codemirror/mozilla.css +++ b/browser/devtools/sourceeditor/codemirror/mozilla.css @@ -18,21 +18,21 @@ } .error { - background-image: url("chrome://browser/skin/devtools/orion-error.png"); + background-image: url("chrome://browser/skin/devtools/editor-error.png"); opacity: 0.75; } .breakpoint { - background-image: url("chrome://browser/skin/devtools/orion-breakpoint.png"); + background-image: url("chrome://browser/skin/devtools/editor-breakpoint.png"); } .debugLocation { - background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"); + background-image: url("chrome://browser/skin/devtools/editor-debug-location.png"); } .breakpoint.debugLocation { - background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"), - url("chrome://browser/skin/devtools/orion-breakpoint.png"); + background-image: url("chrome://browser/skin/devtools/editor-debug-location.png"), + url("chrome://browser/skin/devtools/editor-breakpoint.png"); } .error-line { diff --git a/browser/devtools/sourceeditor/editor.js b/browser/devtools/sourceeditor/editor.js index 895eb4cee292..7b942d47dd8d 100644 --- a/browser/devtools/sourceeditor/editor.js +++ b/browser/devtools/sourceeditor/editor.js @@ -84,7 +84,7 @@ const CM_MAPPING = [ const CM_JUMP_DIALOG = [ L10N.GetStringFromName("gotoLineCmd.promptTitle") - + " " + + " " ]; const { cssProperties, cssValues, cssColors } = getCSSKeywords(); @@ -186,15 +186,19 @@ Editor.prototype = { /** * Appends the current Editor instance to the element specified by - * the only argument 'el'. This method actually creates and loads - * CodeMirror and all its dependencies. + * 'el'. You can also provide your won iframe to host the editor as + * an optional second parameter. This method actually creates and + * loads CodeMirror and all its dependencies. * * This method is asynchronous and returns a promise. */ - appendTo: function (el) { + appendTo: function (el, env) { let def = promise.defer(); let cm = editors.get(this); - let env = el.ownerDocument.createElement("iframe"); + + if (!env) + env = el.ownerDocument.createElementNS(XUL_NS, "iframe"); + env.flex = 1; if (cm) @@ -503,10 +507,11 @@ Editor.prototype = { hasLineClass: function (line, className) { let cm = editors.get(this); let info = cm.lineInfo(line); - if (!info) + + if (!info || !info.wrapClass) return false; - return info.wrapClass == className; + return info.wrapClass.split(" ").indexOf(className) != -1; }, /** diff --git a/browser/devtools/sourceeditor/moz.build b/browser/devtools/sourceeditor/moz.build index f12cb40bb3f5..9a586a0f3510 100644 --- a/browser/devtools/sourceeditor/moz.build +++ b/browser/devtools/sourceeditor/moz.build @@ -10,9 +10,6 @@ JS_MODULES_PATH = 'modules/devtools/sourceeditor' EXTRA_JS_MODULES += [ 'debugger.js', - 'editor.js', - 'source-editor-orion.jsm', - 'source-editor-ui.jsm', - 'source-editor.jsm', + 'editor.js' ] diff --git a/browser/devtools/sourceeditor/orion/LICENSE b/browser/devtools/sourceeditor/orion/LICENSE deleted file mode 100644 index 2d907d73a37e..000000000000 --- a/browser/devtools/sourceeditor/orion/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -Eclipse Distribution License - v 1.0 - -Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. -* Neither the name of the Eclipse Foundation, Inc. nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/browser/devtools/sourceeditor/orion/Makefile.dryice.js b/browser/devtools/sourceeditor/orion/Makefile.dryice.js deleted file mode 100644 index 6866ed93771a..000000000000 --- a/browser/devtools/sourceeditor/orion/Makefile.dryice.js +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env node -/* vim:set ts=2 sw=2 sts=2 et tw=80: - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var copy = require('dryice').copy; - -const ORION_EDITOR = "org.eclipse.orion.client.editor/web"; - -var js_src = copy.createDataObject(); - -copy({ - source: [ - ORION_EDITOR + "/orion/textview/global.js", - ORION_EDITOR + "/orion/textview/eventTarget.js", - ORION_EDITOR + "/orion/editor/regex.js", - ORION_EDITOR + "/orion/textview/keyBinding.js", - ORION_EDITOR + "/orion/textview/annotations.js", - ORION_EDITOR + "/orion/textview/rulers.js", - ORION_EDITOR + "/orion/textview/undoStack.js", - ORION_EDITOR + "/orion/textview/textModel.js", - ORION_EDITOR + "/orion/textview/projectionTextModel.js", - ORION_EDITOR + "/orion/textview/tooltip.js", - ORION_EDITOR + "/orion/textview/textView.js", - ORION_EDITOR + "/orion/textview/textDND.js", - ORION_EDITOR + "/orion/editor/htmlGrammar.js", - ORION_EDITOR + "/orion/editor/textMateStyler.js", - ORION_EDITOR + "/examples/textview/textStyler.js", - ], - dest: js_src, -}); - -copy({ - source: js_src, - dest: "orion.js", -}); - -var css_src = copy.createDataObject(); - -copy({ - source: [ - ORION_EDITOR + "/orion/textview/textview.css", - ORION_EDITOR + "/orion/textview/rulers.css", - ORION_EDITOR + "/orion/textview/annotations.css", - ORION_EDITOR + "/examples/textview/textstyler.css", - ORION_EDITOR + "/examples/editor/htmlStyles.css", - ], - dest: css_src, -}); - -copy({ - source: css_src, - dest: "orion.css", -}); - diff --git a/browser/devtools/sourceeditor/orion/README b/browser/devtools/sourceeditor/orion/README deleted file mode 100644 index c7669099cbfb..000000000000 --- a/browser/devtools/sourceeditor/orion/README +++ /dev/null @@ -1,43 +0,0 @@ -# Introduction - -This is the Orion editor packaged for Mozilla. - -The Orion editor web site: http://www.eclipse.org/orion - -# Upgrade - -To upgrade Orion to a newer version see the UPGRADE file. - -Orion version: git clone from 2012-01-26 - commit hash 1d1150131dacecc9f4d9eb3cdda9103ea1819045 - - + patch for Eclipse Bug 370584 - [Firefox] Edit menu items in context menus - http://git.eclipse.org/c/orion/org.eclipse.orion.client.git/commit/?id=137d5a8e9bbc0fa204caae74ebd25a7d9d4729bd - see https://bugs.eclipse.org/bugs/show_bug.cgi?id=370584 - - + patches for Eclipse Bug 370606 - Problems with UndoStack and deletions at - the beginning of the document - http://git.eclipse.org/c/orion/org.eclipse.orion.client.git/commit/?id=cec71bddaf32251c34d3728df5da13c130d14f33 - http://git.eclipse.org/c/orion/org.eclipse.orion.client.git/commit/?id=3ce24b94f1d8103b16b9cf16f2f50a6302d43b18 - http://git.eclipse.org/c/orion/org.eclipse.orion.client.git/commit/?id=27177e9a3dc70c20b4877e3eab3adfff1d56e342 - see https://bugs.eclipse.org/bugs/show_bug.cgi?id=370606 - - + patch for Mozilla Bug 730532 - remove CSS2Properties aliases for MozOpacity - and MozOutline* - see https://bugzilla.mozilla.org/show_bug.cgi?id=730532#c3 - -# License - -The following files are licensed according to the contents in the LICENSE -file: - orion.js - orion.css - -# Theming - -The syntax highlighting and the editor UI are themed using a style sheet. The -default theme file is browser/themes/*/devtools/orion.css - this is based on the -orion.css found in this folder. - -Please note that the orion.css file from this folder is not used. It is kept -here only as reference. diff --git a/browser/devtools/sourceeditor/orion/UPGRADE b/browser/devtools/sourceeditor/orion/UPGRADE deleted file mode 100644 index a2c006efe66e..000000000000 --- a/browser/devtools/sourceeditor/orion/UPGRADE +++ /dev/null @@ -1,20 +0,0 @@ -Upgrade notes: - -1. Get the Orion client source code from: -http://www.eclipse.org/orion - -2. Install Dryice from: -https://github.com/mozilla/dryice - -You also need nodejs for Dryice to run: -http://nodejs.org - -3. Copy Makefile.dryice.js to: -org.eclipse.orion.client/bundles/ - -4. Execute Makefile.dryice.js. You should get orion.js and orion.css. - -5. Copy the two files back here. - -6. Make a new build of Firefox. - diff --git a/browser/devtools/sourceeditor/orion/orion.css b/browser/devtools/sourceeditor/orion/orion.css deleted file mode 100644 index 1e3b003cafe5..000000000000 --- a/browser/devtools/sourceeditor/orion/orion.css +++ /dev/null @@ -1,277 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -.view { - background-color: white; -} - -.viewContainer { - background-color: #eeeeee; - font-family: monospace; - font-size: 10pt; -} -::-webkit-scrollbar-corner { - background-color: #eeeeee; -} - -.viewContent { -}/* Styles for rulers */ -.ruler { - background-color: white; -} -.ruler.annotations { - border-right: 1px solid lightgray; - width: 16px; -} -.ruler.folding { - border-right: 1px solid lightgray; - width: 14px; -} -.ruler.lines { - border-right: 1px solid lightgray; - text-align: right; -} -.ruler.overview { - border-left: 1px solid lightgray; - width: 14px; -} - -/* Styles for the line number ruler */ -.rulerLines { -} -.rulerLines.even -.rulerLines.odd { -}/* Styles for the annotation ruler (all lines) */ -.annotation { -} -.annotation.error, -.annotation.warning -.annotation.task, -.annotation.bookmark, -.annotation.breakpoint, -.annotation.collapsed -.annotation.expanded { -} - -/* Styles for the annotation ruler (first line) */ -.annotationHTML { - cursor: pointer; - width: 16px; - height: 16px; - display: inline-block; - vertical-align: middle; - background-position: center; - background-repeat: no-repeat; -} -.annotationHTML.error { - /* images/error.gif */ - background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAPVvcvWHiPVucvRuc+ttcfV6f91KVN5LU99PV/FZY/JhaM4oN84pONE4Rd1ATfJLWutVYPRgbdxpcsgWKMgZKs4lNfE/UvE/U+artcpdSc5uXveimslHPuBhW/eJhfV5efaCgO2CgP+/v+PExP///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACUALAAAAAAQABAAAAZ+wJJwSCwaScgkySgkjTQZTkYzWhadnE5oE+pwqkSshwQqkzxfa4kkQXxEpA9J9EFI1KQGQQBAigYCBA14ExEWF0gXihETeA0QD3AkD5QQg0NsDnAJmwkOd5gYFSQKpXAFDBhqaxgLBwQBBAapq00YEg0UDRKqTGtKSL7Cw8JBADs="); -} -.annotationHTML.warning { - /* images/warning.gif */ - background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAP7bc//egf/ij/7ijv/jl/7kl//mnv7lnv/uwf7CTP7DTf7DT/7IW//Na/7Na//NbP7QdP/dmbltAIJNAF03AMSAJMSCLKqASa2DS6uBSquCSrGHTq6ETbCHT7WKUrKIUcCVXL+UXMOYX8GWXsSZYMiib6+ETbOIUcOXX86uhd3Muf///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACsALAAAAAAQABAAAAZowJVwSCwaj0ihikRSJYcoBEL0XKlGkcjImQQhJBREKFnyICoThKeE/AAW6AXgdPyUAgrLJBEo0YsbAQyDhAEdRRwDDw8OaA4NDQImRBgFEJdglxAEGEQZKQcHBqOkKRpFF6mqq1WtrUEAOw=="); -} -.annotationHTML.task { - /* images/task.gif */ - background-image: url("data:image/gif;base64,R0lGODlhEAAQAMQAAN7s4uTy6ICvY423c2WdP2ugR3mqWYeza2ejOl6VNVqPM1aJMURsJ2GaOnKlT8PbsbPDqGmmO1OCLk98LEhxKGWfOWKaN0t2KkJoJf///////wAAAAAAAAAAAAAAAAAAACH5BAEAABoALAAAAAAQABAAAAVmoCaOZDk+UaquDxkNcCxHJHLceI6QleD/vkCmQrIYjkiDMGAhJRzQ6NKRICkKgYJ2qVWQFktCmEBYkCSNZSbQaDckpAl5TCZMSBdtAaDXX0gUUYJRFCQMSYgGDCQQGI6PkBAmkyUhADs="); -} -.annotationHTML.bookmark { - /* images/bookmark.gif */ - background-image: url("data:image/gif;base64,R0lGODlhEAAQALMAAP7//+/VNPzZS/vifeumAPrBOOSlHOSuRP///wAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAgALAAAAAAQABAAAARLEMlJq5Xn3EvIrkenfRIhCB5pmKhRdbAJAGhssuc8n6eJoAKdkOaTAIdEQeWoA1oGsiZhYAnIcqiApVPjElyUbkFSgCkn5XElLYkAADs="); -} -.annotationHTML.breakpoint { - /* images/breakpoint.gif */ - background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAFheoFxkoFxnpmt0pmZxpnF7rYyWwmJwpnaFs3aDrWt8rXGBrYycwmZ3mXuNs42cu77F03GIs3aJrYGVu2J5oKCuxeDj6LK/03GLrYieu3aIoIygu6m4zcLN3MTM1m6Rs2aLriRgkSZilXGXtoGcs7LD0QBLhSZikihol3ScubrO2Yaqu5q4xpO0wpm7yabF0ZO9yaXI0r3X3tHj6P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAADQALAAAAAAQABAAAAafQJpwSCwWLYZBIDAwWIw0A+FFpW6aRUPCxe1yE4ahhdCCxWSzmSwGgxGeUceKpUqhUCkVa7UK0wgkJCUjJoUmIyWBBEIEGhoeJ4YmJx6OAUIADQ0QIZIhEJoAQgEUFBUgkiAVpZdRCxIPFx8iIh8XDw4FfhYHDhgZHB0dHBkYEwdwUQoTEc3OEwp+QwYHCBMMDBMIB9JESAJLAk5Q5EVBADs="); -} -.annotationHTML.collapsed { - /* images/collapsed.png */ - width: 14px; - height: 14px; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUeNpi/P//PwMlgImBQkCxASzoAp++fo+6de+Z+fXbD/Jev/nAICoiwKCpqrBBTUlqNR835zJ09YzIYfDxy7eo/cevLmXlYGNQUJAEahZieP3mHcODB08Zfv/4w+BoqR3Nz8O1DKcXzt94HPqXmZlBU1+LgZNfkMHazIOBA0hr6uswgMTP33gYijcMLlx/EMAnLs7w7sc/hg9AG0HgPZB+B8S84hJA+UcBeMPg+at3DJIMnAxZzt5wsUhnXzDdsmIVWB6vAcLCfAys3z4wzN64huEfkJ/uH8IwexOQDQymD2/fgeXxekFLRWHD51evGDhZGRi4WSFSnCwgNjB2Xr1m0AbK4zXAQkdhNdPf3wx3r91g+PruLcOqnasYvn54x3Dv2k0G5r+/GMyB8nijEQTefvoadeH6w9Cbtx8GvH//kUFQkJ9BQ1V+g76m/GphPu5lBA0YenmBYgMAAgwA34GIKjmLxOUAAAAASUVORK5CYII="); -} -.annotationHTML.expanded { - /* images/expanded.png */ - width: 14px; - height: 14px; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAT5JREFUeNrUksFKw0AURW+mTWw67SSEiG209U90r4jddFO34l+5U0HdZCHiFwiCOz9AlMSmGEpMOqk1TWJSFGyFbATR2dyZd+Dw3mOENE3xkyP8PYHrBT3OX7uW43ZefA6FUaw1dJPSyrmu1k8KBYOh37Od4XFZLEPXFdRrFMGIw3U9TKMYqw1tb0VjcxLy9eEF425CCIxWE5JcxSQGxCyNloG87gXhwWIHc4J767lTZQw8ShFGSZbxRyaQmZJxd3NRUJ6ffwQNEi6PzG/L2tjdmvFCgcKqKL2F2Olu43MzggDka+IjPuOFI7Sbujn2fUglYKkkzFIi+R0I/QDrGS8UqDX5QkhiOHYfE84hkhSTkGNgOyDJFCzjhYLTq+vDtrG8r1LZtB6fcHtzB+uhD5VWzLx+lvF/8JV/XfAuwADsrJbMGG4l4AAAAABJRU5ErkJggg=="); -} -.annotationHTML.multiple { - /* images/multiple.gif */ - background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAOdpa+yJiuFYXOFYXeBYXONwded8f+NwdmhwkHB4iPr7/ezx+fP2+2h4kOzy+Wh4iPr8/gCBwTaczjaXyjaYyjaXyTaYyfr8/QCMzQCMzACHxzao2jal2Dak1zag03iAgI/Ckn64fZrHmX+4fZLCianPopPCiarOoqbLlafLlbnXq7nWq6fLlMTcsoCIeJCQcIiIeKCYaJiQcO16ee16evGVlfGWlfahn/ahoPWhn/WhoPe1tP///////wAAAAAAACH5BAEAAD0ALAAAAAAQABAAAAaRwJ5wSCwaj8WYcslcDmObaDTGq1Zjzw4mk+FQIRcFTzaUeTRoj4zHaI+HL0lkLnnxFgsH7zWEWSoTFBMwVlUwQy6JMDCJjYwuQx8tk5MfOzk4OjcfkSssKCkqHzY0MzQ1nEIJJSYkJCcJAQCzAQlDDyIjISMiCQYEAgMGD0MNIMfHDQUHBc3EQgjR0tPSSNY9QQA7"); -} -.annotationHTML.overlay { - /* images/plus.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJEAQvB2JVdrAAAAAdaVRYdENvbW1lbnQAAAAAAENyZWF0ZWQgd2l0aCBHSU1QZC5lBwAAAD1JREFUCNdtjkESADAEAzemf69f66HMqGlOIhYiFRFRtSQBWAY7mzx+EDTL6sSgb1jTk7Q87rxyqe37fXsAa78gLyZnRgEAAAAASUVORK5CYII="); - background-position: right bottom; - position: relative; - top: -16px; -} -.annotationHTML.currentBracket { - /* images/currentBracket.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sLEBULCGQmEKAAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAnklEQVQ4y7VTsRHDIBATJg1HCUzAHEzFBExAzwZsRMkE9gifKhc72ODYibr/+xcnoQdugq0LAujEwmbn0UxQh4OxpjX1XgshwFqLnPM5PQTQGlprWpbl3RhJ/CSQUm7qPYLp7i8cEpRSoJT6ju0lIaVEQgiKMQ4lHHpQayVjzHWCn5jIOcc8z9dMBADvPZxz3SC1tzCI8vgWdvL+VzwB8JSj2GFTyxIAAAAASUVORK5CYII="); -} -.annotationHTML.matchingBracket { - /* images/matchingBracket.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sLEBUMAsuyb3kAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAoklEQVQ4y61TsQ3EIAw80DcI0USKGIApWIsB2IGGKbJPugxBR3VfvfRRCOSTvw7LPuPzGXgI8f0gwAsFu5rXIYMdDiEOIdnKW5YFzjnEGH+bhwA/KKVwmibu0BhRnpEZY1BrHTaVT7fQJZjnGeu63tOAJFNKVEox53yqQZfAWstt27oidgm01ve3UEqBaBjnspG89wgh3LiFgZXHt3Dh23/FGxKViehm0X85AAAAAElFTkSuQmCC"); -} -.annotationHTML.currentLine { - /* images/currentLine.gif */ - background-image: url("data:image/gif;base64,R0lGODlhEAAQAMQAALxe0bNWzbdZzrlb0KpPx61RybBTy6VLxadNxZGctIeUroyYsG92hHyMqIKRq2l9nmyAoHGDonaIpStXj6q80k1aXf///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABYALAAAAAAQABAAAAVCoCWOZGmeKDql5ppOMGXBk/zOoltSNO6XrlXwxIPNYiMGq8SoLC2MaNPygEQkDYdikUg6LQcEoWAICAaA5HPNLoUAADs="); -} - -/* Styles for the overview ruler */ -.annotationOverview { - cursor: pointer; - border-radius: 2px; - left: 2px; - width: 8px; -} -.annotationOverview.task { - background-color: lightgreen; - border: 1px solid green; -} -.annotationOverview.breakpoint { - background-color: lightblue; - border: 1px solid blue; -} -.annotationOverview.bookmark { - background-color: yellow; - border: 1px solid orange; -} -.annotationOverview.error { - background-color: lightcoral; - border: 1px solid darkred; -} -.annotationOverview.warning { - background-color: Gold; - border: 1px solid black; -} -.annotationOverview.currentBracket { - background-color: lightgray; - border: 1px solid red; -} -.annotationOverview.matchingBracket { - background-color: lightgray; - border: 1px solid red; -} -.annotationOverview.currentLine { - background-color: #EAF2FE; - border: 1px solid black; -} - -/* Styles for text range */ -.annotationRange { - background-repeat: repeat-x; - background-position: left bottom; -} -.annotationRange.task { - /* images/squiggly_task.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sLDhEoIrb7JmcAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAGUlEQVQI12NggIH/DGdhDCM45z/DfyiBAADgdQjGhI/4DAAAAABJRU5ErkJggg=="); -} -.annotationRange.breakpoint { - /* images/squiggly_breakpoint.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sLDhEqHTKradgAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAIklEQVQI11XJMQ0AMAzAMGMafwrFlD19+sUKIJTFo9k+B/kQ+Qr2bIVKOgAAAABJRU5ErkJggg=="); -} -.annotationRange.bookmark { - /* images/squiggly_bookmark.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); -} -.annotationRange.error { - /* images/squiggly_error.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg=="); -} -.annotationRange.warning { - /* images/squiggly_warning.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); -} -.annotationRange.currentBracket { -} -.annotationRange.matchingBracket { - outline: 1px solid red; -} - -/* Styles for lines of text */ -.annotationLine { -} -.annotationLine.currentLine { - background-color: #EAF2FE; -} - -.token_singleline_comment { - color: green; -} - -.token_multiline_comment { - color: green; -} - -.token_doc_comment { - color: #00008F; -} - -.token_doc_html_markup { - color: #7F7F9F; -} - -.token_doc_tag { - color: #7F9FBF; -} - -.token_task_tag { - color: #7F9FBF; -} - -.token_string { - color: blue; -} - -.token_keyword { - color: darkred; - font-weight: bold; -} - -.token_space { - /* images/white_space.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAABnRSTlMA/wAAAACkwsAdAAAAIUlEQVR4nGP4z8CAC+GUIEXuABhgkTuABEiRw2cmae4EAH05X7xDolNRAAAAAElFTkSuQmCC"); - background-repeat: no-repeat; - background-position: center center; -} - -.token_tab { - /* images/white_tab.png */ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAJCAIAAACJ2loDAAAABnRSTlMA/wD/AP83WBt9AAAAMklEQVR4nGP4TwRgoK6i52c3bz5w6zMSA6tJn28d2Lx589nnCAYu63AaSLxJRLoJPwAAeNk0aG4opfMAAAAASUVORK5CYII="); - background-repeat: no-repeat; - background-position: left center; -} - -.line_caret { - background-color: #EAF2FE; -} - -/* Styling for html syntax highlighting */ -.entity-name-tag { - color: #3f7f7f; -} - -.entity-other-attribute-name { - color: #7f007f; -} - -.punctuation-definition-comment { - color: #3f5fbf; -} - -.comment { - color: #3f5fbf -} - -.string-quoted { - color: #2a00ff; - font-style: italic; -} - -.invalid { - color: red; - font-weight: bold; -} \ No newline at end of file diff --git a/browser/devtools/sourceeditor/orion/orion.js b/browser/devtools/sourceeditor/orion/orion.js deleted file mode 100644 index 06ad42195a09..000000000000 --- a/browser/devtools/sourceeditor/orion/orion.js +++ /dev/null @@ -1,12303 +0,0 @@ -/******************************************************************************* - * @license - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: - * Felipe Heidrich (IBM Corporation) - initial API and implementation - * Silenio Quarti (IBM Corporation) - initial API and implementation - * Mihai Sucan (Mozilla Foundation) - fix for Bug#364214 - */ - -/*global window */ - -/** - * Evaluates the definition function and mixes in the returned module with - * the module specified by moduleName. - *

- * This function is intented to by used when RequireJS is not available. - *

- * - * @param {String} name The mixin module name. - * @param {String[]} deps The array of dependency names. - * @param {Function} callback The definition function. - */ -if (!window.define) { - window.define = function(name, deps, callback) { - var module = this; - var split = (name || "").split("/"), i, j; - for (i = 0; i < split.length - 1; i++) { - module = module[split[i]] = (module[split[i]] || {}); - } - var depModules = [], depModule; - for (j = 0; j < deps.length; j++) { - depModule = this; - split = deps[j].split("/"); - for (i = 0; i < split.length - 1; i++) { - depModule = depModule[split[i]] = (depModule[split[i]] || {}); - } - depModules.push(depModule); - } - var newModule = callback.apply(this, depModules); - for (var p in newModule) { - if (newModule.hasOwnProperty(p)) { - module[p] = newModule[p]; - } - } - }; -} - -/** - * Require/get the defined modules. - *

- * This function is intented to by used when RequireJS is not available. - *

- * - * @param {String[]|String} deps The array of dependency names. This can also be - * a string, a single dependency name. - * @param {Function} [callback] Optional, the callback function to execute when - * multiple dependencies are required. The callback arguments will have - * references to each module in the same order as the deps array. - * @returns {Object|undefined} If the deps parameter is a string, then this - * function returns the required module definition, otherwise undefined is - * returned. - */ -if (!window.require) { - window.require = function(deps, callback) { - var depsArr = typeof deps === "string" ? [deps] : deps; - var depModules = [], depModule, split, i, j; - for (j = 0; j < depsArr.length; j++) { - depModule = this; - split = depsArr[j].split("/"); - for (i = 0; i < split.length - 1; i++) { - depModule = depModule[split[i]] = (depModule[split[i]] || {}); - } - depModules.push(depModule); - } - if (callback) { - callback.apply(this, depModules); - } - return typeof deps === "string" ? depModules[0] : undefined; - }; -}/******************************************************************************* - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: - * Felipe Heidrich (IBM Corporation) - initial API and implementation - * Silenio Quarti (IBM Corporation) - initial API and implementation - ******************************************************************************/ - -/*global define */ -define("orion/textview/eventTarget", [], function() { - /** - * Constructs a new EventTarget object. - * - * @class - * @name orion.textview.EventTarget - */ - function EventTarget() { - } - /** - * Adds in the event target interface into the specified object. - * - * @param {Object} object The object to add in the event target interface. - */ - EventTarget.addMixin = function(object) { - var proto = EventTarget.prototype; - for (var p in proto) { - if (proto.hasOwnProperty(p)) { - object[p] = proto[p]; - } - } - }; - EventTarget.prototype = /** @lends orion.textview.EventTarget.prototype */ { - /** - * Adds an event listener to this event target. - * - * @param {String} type The event type. - * @param {Function|EventListener} listener The function or the EventListener that will be executed when the event happens. - * @param {Boolean} [useCapture=false] true if the listener should be trigged in the capture phase. - * - * @see #removeEventListener - */ - addEventListener: function(type, listener, useCapture) { - if (!this._eventTypes) { this._eventTypes = {}; } - var state = this._eventTypes[type]; - if (!state) { - state = this._eventTypes[type] = {level: 0, listeners: []}; - } - var listeners = state.listeners; - listeners.push({listener: listener, useCapture: useCapture}); - }, - /** - * Dispatches the given event to the listeners added to this event target. - * @param {Event} evt The event to dispatch. - */ - dispatchEvent: function(evt) { - if (!this._eventTypes) { return; } - var type = evt.type; - var state = this._eventTypes[type]; - if (state) { - var listeners = state.listeners; - try { - state.level++; - if (listeners) { - for (var i=0, len=listeners.length; i < len; i++) { - if (listeners[i]) { - var l = listeners[i].listener; - if (typeof l === "function") { - l.call(this, evt); - } else if (l.handleEvent && typeof l.handleEvent === "function") { - l.handleEvent(evt); - } - } - } - } - } finally { - state.level--; - if (state.compact && state.level === 0) { - for (var j=listeners.length - 1; j >= 0; j--) { - if (!listeners[j]) { - listeners.splice(j, 1); - } - } - if (listeners.length === 0) { - delete this._eventTypes[type]; - } - state.compact = false; - } - } - } - }, - /** - * Returns whether there is a listener for the specified event type. - * - * @param {String} type The event type - * - * @see #addEventListener - * @see #removeEventListener - */ - isListening: function(type) { - if (!this._eventTypes) { return false; } - return this._eventTypes[type] !== undefined; - }, - /** - * Removes an event listener from the event target. - *

- * All the parameters must be the same ones used to add the listener. - *

- * - * @param {String} type The event type - * @param {Function|EventListener} listener The function or the EventListener that will be executed when the event happens. - * @param {Boolean} [useCapture=false] true if the listener should be trigged in the capture phase. - * - * @see #addEventListener - */ - removeEventListener: function(type, listener, useCapture){ - if (!this._eventTypes) { return; } - var state = this._eventTypes[type]; - if (state) { - var listeners = state.listeners; - for (var i=0, len=listeners.length; i < len; i++) { - var l = listeners[i]; - if (l && l.listener === listener && l.useCapture === useCapture) { - if (state.level !== 0) { - listeners[i] = null; - state.compact = true; - } else { - listeners.splice(i, 1); - } - break; - } - } - if (listeners.length === 0) { - delete this._eventTypes[type]; - } - } - } - }; - return {EventTarget: EventTarget}; -}); -/******************************************************************************* - * @license - * Copyright (c) 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -/*global define */ -/*jslint browser:true regexp:false*/ -/** - * @name orion.editor.regex - * @class Utilities for dealing with regular expressions. - * @description Utilities for dealing with regular expressions. - */ -define("orion/editor/regex", [], function() { - /** - * @methodOf orion.editor.regex - * @static - * @description Escapes regex special characters in the input string. - * @param {String} str The string to escape. - * @returns {String} A copy of str with regex special characters escaped. - */ - function escape(str) { - return str.replace(/([\\$\^*\/+?\.\(\)|{}\[\]])/g, "\\$&"); - } - - /** - * @methodOf orion.editor.regex - * @static - * @description Parses a pattern and flags out of a regex literal string. - * @param {String} str The string to parse. Should look something like "/ab+c/" or "/ab+c/i". - * @returns {Object} If str looks like a regex literal, returns an object with properties - *
- *
pattern
{String}
- *
flags
{String}
- *
otherwise returns null. - */ - function parse(str) { - var regexp = /^\s*\/(.+)\/([gim]{0,3})\s*$/.exec(str); - if (regexp) { - return { - pattern : regexp[1], - flags : regexp[2] - }; - } - return null; - } - - return { - escape: escape, - parse: parse - }; -}); -/******************************************************************************* - * @license - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: - * Felipe Heidrich (IBM Corporation) - initial API and implementation - * Silenio Quarti (IBM Corporation) - initial API and implementation - ******************************************************************************/ - -/*global window define */ - -define("orion/textview/keyBinding", [], function() { - var isMac = window.navigator.platform.indexOf("Mac") !== -1; - - /** - * Constructs a new key binding with the given key code and modifiers. - * - * @param {String|Number} keyCode the key code. - * @param {Boolean} mod1 the primary modifier (usually Command on Mac and Control on other platforms). - * @param {Boolean} mod2 the secondary modifier (usually Shift). - * @param {Boolean} mod3 the third modifier (usually Alt). - * @param {Boolean} mod4 the fourth modifier (usually Control on the Mac). - * - * @class A KeyBinding represents of a key code and a modifier state that can be triggered by the user using the keyboard. - * @name orion.textview.KeyBinding - * - * @property {String|Number} keyCode The key code. - * @property {Boolean} mod1 The primary modifier (usually Command on Mac and Control on other platforms). - * @property {Boolean} mod2 The secondary modifier (usually Shift). - * @property {Boolean} mod3 The third modifier (usually Alt). - * @property {Boolean} mod4 The fourth modifier (usually Control on the Mac). - * - * @see orion.textview.TextView#setKeyBinding - */ - function KeyBinding (keyCode, mod1, mod2, mod3, mod4) { - if (typeof(keyCode) === "string") { - this.keyCode = keyCode.toUpperCase().charCodeAt(0); - } else { - this.keyCode = keyCode; - } - this.mod1 = mod1 !== undefined && mod1 !== null ? mod1 : false; - this.mod2 = mod2 !== undefined && mod2 !== null ? mod2 : false; - this.mod3 = mod3 !== undefined && mod3 !== null ? mod3 : false; - this.mod4 = mod4 !== undefined && mod4 !== null ? mod4 : false; - } - KeyBinding.prototype = /** @lends orion.textview.KeyBinding.prototype */ { - /** - * Returns whether this key binding matches the given key event. - * - * @param e the key event. - * @returns {Boolean} true whether the key binding matches the key event. - */ - match: function (e) { - if (this.keyCode === e.keyCode) { - var mod1 = isMac ? e.metaKey : e.ctrlKey; - if (this.mod1 !== mod1) { return false; } - if (this.mod2 !== e.shiftKey) { return false; } - if (this.mod3 !== e.altKey) { return false; } - if (isMac && this.mod4 !== e.ctrlKey) { return false; } - return true; - } - return false; - }, - /** - * Returns whether this key binding is the same as the given parameter. - * - * @param {orion.textview.KeyBinding} kb the key binding to compare with. - * @returns {Boolean} whether or not the parameter and the receiver describe the same key binding. - */ - equals: function(kb) { - if (!kb) { return false; } - if (this.keyCode !== kb.keyCode) { return false; } - if (this.mod1 !== kb.mod1) { return false; } - if (this.mod2 !== kb.mod2) { return false; } - if (this.mod3 !== kb.mod3) { return false; } - if (this.mod4 !== kb.mod4) { return false; } - return true; - } - }; - return {KeyBinding: KeyBinding}; -}); -/******************************************************************************* - * @license - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: - * Felipe Heidrich (IBM Corporation) - initial API and implementation - * Silenio Quarti (IBM Corporation) - initial API and implementation - ******************************************************************************/ - -/*global define */ - -define("orion/textview/annotations", ['orion/textview/eventTarget'], function(mEventTarget) { - /** - * @class This object represents a decoration attached to a range of text. Annotations are added to a - * AnnotationModel which is attached to a TextModel. - *

- * See:
- * {@link orion.textview.AnnotationModel}
- * {@link orion.textview.Ruler}
- *

- * @name orion.textview.Annotation - * - * @property {String} type The annotation type (for example, orion.annotation.error). - * @property {Number} start The start offset of the annotation in the text model. - * @property {Number} end The end offset of the annotation in the text model. - * @property {String} html The HTML displayed for the annotation. - * @property {String} title The text description for the annotation. - * @property {orion.textview.Style} style The style information for the annotation used in the annotations ruler and tooltips. - * @property {orion.textview.Style} overviewStyle The style information for the annotation used in the overview ruler. - * @property {orion.textview.Style} rangeStyle The style information for the annotation used in the text view to decorate a range of text. - * @property {orion.textview.Style} lineStyle The style information for the annotation used in the text view to decorate a line of text. - */ - /** - * Constructs a new folding annotation. - * - * @param {orion.textview.ProjectionTextModel} projectionModel The projection text model. - * @param {String} type The annotation type. - * @param {Number} start The start offset of the annotation in the text model. - * @param {Number} end The end offset of the annotation in the text model. - * @param {String} expandedHTML The HTML displayed for this annotation when it is expanded. - * @param {orion.textview.Style} expandedStyle The style information for the annotation when it is expanded. - * @param {String} collapsedHTML The HTML displayed for this annotation when it is collapsed. - * @param {orion.textview.Style} collapsedStyle The style information for the annotation when it is collapsed. - * - * @class This object represents a folding annotation. - * @name orion.textview.FoldingAnnotation - */ - function FoldingAnnotation (projectionModel, type, start, end, expandedHTML, expandedStyle, collapsedHTML, collapsedStyle) { - this.type = type; - this.start = start; - this.end = end; - this._projectionModel = projectionModel; - this._expandedHTML = this.html = expandedHTML; - this._expandedStyle = this.style = expandedStyle; - this._collapsedHTML = collapsedHTML; - this._collapsedStyle = collapsedStyle; - this.expanded = true; - } - - FoldingAnnotation.prototype = /** @lends orion.textview.FoldingAnnotation.prototype */ { - /** - * Collapses the annotation. - */ - collapse: function () { - if (!this.expanded) { return; } - this.expanded = false; - this.html = this._collapsedHTML; - this.style = this._collapsedStyle; - var projectionModel = this._projectionModel; - var baseModel = projectionModel.getBaseModel(); - this._projection = { - start: baseModel.getLineStart(baseModel.getLineAtOffset(this.start) + 1), - end: baseModel.getLineEnd(baseModel.getLineAtOffset(this.end), true) - }; - projectionModel.addProjection(this._projection); - }, - /** - * Expands the annotation. - */ - expand: function () { - if (this.expanded) { return; } - this.expanded = true; - this.html = this._expandedHTML; - this.style = this._expandedStyle; - this._projectionModel.removeProjection(this._projection); - } - }; - - /** - * Constructs a new AnnotationTypeList object. - * - * @class - * @name orion.textview.AnnotationTypeList - */ - function AnnotationTypeList () { - } - /** - * Adds in the annotation type interface into the specified object. - * - * @param {Object} object The object to add in the annotation type interface. - */ - AnnotationTypeList.addMixin = function(object) { - var proto = AnnotationTypeList.prototype; - for (var p in proto) { - if (proto.hasOwnProperty(p)) { - object[p] = proto[p]; - } - } - }; - AnnotationTypeList.prototype = /** @lends orion.textview.AnnotationTypeList.prototype */ { - /** - * Adds an annotation type to the receiver. - *

- * Only annotations of the specified types will be shown by - * the receiver. - *

- * - * @param {Object} type the annotation type to be shown - * - * @see #removeAnnotationType - * @see #isAnnotationTypeVisible - */ - addAnnotationType: function(type) { - if (!this._annotationTypes) { this._annotationTypes = []; } - this._annotationTypes.push(type); - }, - /** - * Gets the annotation type priority. The priority is determined by the - * order the annotation type is added to the receiver. Annotation types - * added first have higher priority. - *

- * Returns 0 if the annotation type is not added. - *

- * - * @param {Object} type the annotation type - * - * @see #addAnnotationType - * @see #removeAnnotationType - * @see #isAnnotationTypeVisible - */ - getAnnotationTypePriority: function(type) { - if (this._annotationTypes) { - for (var i = 0; i < this._annotationTypes.length; i++) { - if (this._annotationTypes[i] === type) { - return i + 1; - } - } - } - return 0; - }, - /** - * Returns an array of annotations in the specified annotation model for the given range of text sorted by type. - * - * @param {orion.textview.AnnotationModel} annotationModel the annotation model. - * @param {Number} start the start offset of the range. - * @param {Number} end the end offset of the range. - * @return {orion.textview.Annotation[]} an annotation array. - */ - getAnnotationsByType: function(annotationModel, start, end) { - var iter = annotationModel.getAnnotations(start, end); - var annotation, annotations = []; - while (iter.hasNext()) { - annotation = iter.next(); - var priority = this.getAnnotationTypePriority(annotation.type); - if (priority === 0) { continue; } - annotations.push(annotation); - } - var self = this; - annotations.sort(function(a, b) { - return self.getAnnotationTypePriority(a.type) - self.getAnnotationTypePriority(b.type); - }); - return annotations; - }, - /** - * Returns whether the receiver shows annotations of the specified type. - * - * @param {Object} type the annotation type - * @returns {Boolean} whether the specified annotation type is shown - * - * @see #addAnnotationType - * @see #removeAnnotationType - */ - isAnnotationTypeVisible: function(type) { - return this.getAnnotationTypePriority(type) !== 0; - }, - /** - * Removes an annotation type from the receiver. - * - * @param {Object} type the annotation type to be removed - * - * @see #addAnnotationType - * @see #isAnnotationTypeVisible - */ - removeAnnotationType: function(type) { - if (!this._annotationTypes) { return; } - for (var i = 0; i < this._annotationTypes.length; i++) { - if (this._annotationTypes[i] === type) { - this._annotationTypes.splice(i, 1); - break; - } - } - } - }; - - /** - * Constructs an annotation model. - * - * @param {textModel} textModel The text model. - * - * @class This object manages annotations for a TextModel. - *

- * See:
- * {@link orion.textview.Annotation}
- * {@link orion.textview.TextModel}
- *

- * @name orion.textview.AnnotationModel - * @borrows orion.textview.EventTarget#addEventListener as #addEventListener - * @borrows orion.textview.EventTarget#removeEventListener as #removeEventListener - * @borrows orion.textview.EventTarget#dispatchEvent as #dispatchEvent - */ - function AnnotationModel(textModel) { - this._annotations = []; - var self = this; - this._listener = { - onChanged: function(modelChangedEvent) { - self._onChanged(modelChangedEvent); - } - }; - this.setTextModel(textModel); - } - - AnnotationModel.prototype = /** @lends orion.textview.AnnotationModel.prototype */ { - /** - * Adds an annotation to the annotation model. - *

The annotation model listeners are notified of this change.

- * - * @param {orion.textview.Annotation} annotation the annotation to be added. - * - * @see #removeAnnotation - */ - addAnnotation: function(annotation) { - if (!annotation) { return; } - var annotations = this._annotations; - var index = this._binarySearch(annotations, annotation.start); - annotations.splice(index, 0, annotation); - var e = { - type: "Changed", - added: [annotation], - removed: [], - changed: [] - }; - this.onChanged(e); - }, - /** - * Returns the text model. - * - * @return {orion.textview.TextModel} The text model. - * - * @see #setTextModel - */ - getTextModel: function() { - return this._model; - }, - /** - * @class This object represents an annotation iterator. - *

- * See:
- * {@link orion.textview.AnnotationModel#getAnnotations}
- *

- * @name orion.textview.AnnotationIterator - * - * @property {Function} hasNext Determines whether there are more annotations in the iterator. - * @property {Function} next Returns the next annotation in the iterator. - */ - /** - * Returns an iterator of annotations for the given range of text. - * - * @param {Number} start the start offset of the range. - * @param {Number} end the end offset of the range. - * @return {orion.textview.AnnotationIterator} an annotation iterartor. - */ - getAnnotations: function(start, end) { - var annotations = this._annotations, current; - //TODO binary search does not work for range intersection when there are overlaping ranges, need interval search tree for this - var i = 0; - var skip = function() { - while (i < annotations.length) { - var a = annotations[i++]; - if ((start === a.start) || (start > a.start ? start < a.end : a.start < end)) { - return a; - } - if (a.start >= end) { - break; - } - } - return null; - }; - current = skip(); - return { - next: function() { - var result = current; - if (result) { current = skip(); } - return result; - }, - hasNext: function() { - return current !== null; - } - }; - }, - /** - * Notifies the annotation model that the given annotation has been modified. - *

The annotation model listeners are notified of this change.

- * - * @param {orion.textview.Annotation} annotation the modified annotation. - * - * @see #addAnnotation - */ - modifyAnnotation: function(annotation) { - if (!annotation) { return; } - var index = this._getAnnotationIndex(annotation); - if (index < 0) { return; } - var e = { - type: "Changed", - added: [], - removed: [], - changed: [annotation] - }; - this.onChanged(e); - }, - /** - * Notifies all listeners that the annotation model has changed. - * - * @param {orion.textview.Annotation[]} added The list of annotation being added to the model. - * @param {orion.textview.Annotation[]} changed The list of annotation modified in the model. - * @param {orion.textview.Annotation[]} removed The list of annotation being removed from the model. - * @param {ModelChangedEvent} textModelChangedEvent the text model changed event that trigger this change, can be null if the change was trigger by a method call (for example, {@link #addAnnotation}). - */ - onChanged: function(e) { - return this.dispatchEvent(e); - }, - /** - * Removes all annotations of the given type. All annotations - * are removed if the type is not specified. - *

The annotation model listeners are notified of this change. Only one changed event is generated.

- * - * @param {Object} type the type of annotations to be removed. - * - * @see #removeAnnotation - */ - removeAnnotations: function(type) { - var annotations = this._annotations; - var removed, i; - if (type) { - removed = []; - for (i = annotations.length - 1; i >= 0; i--) { - var annotation = annotations[i]; - if (annotation.type === type) { - annotations.splice(i, 1); - } - removed.splice(0, 0, annotation); - } - } else { - removed = annotations; - annotations = []; - } - var e = { - type: "Changed", - removed: removed, - added: [], - changed: [] - }; - this.onChanged(e); - }, - /** - * Removes an annotation from the annotation model. - *

The annotation model listeners are notified of this change.

- * - * @param {orion.textview.Annotation} annotation the annotation to be removed. - * - * @see #addAnnotation - */ - removeAnnotation: function(annotation) { - if (!annotation) { return; } - var index = this._getAnnotationIndex(annotation); - if (index < 0) { return; } - var e = { - type: "Changed", - removed: this._annotations.splice(index, 1), - added: [], - changed: [] - }; - this.onChanged(e); - }, - /** - * Removes and adds the specifed annotations to the annotation model. - *

The annotation model listeners are notified of this change. Only one changed event is generated.

- * - * @param {orion.textview.Annotation} remove the annotations to be removed. - * @param {orion.textview.Annotation} add the annotations to be added. - * - * @see #addAnnotation - * @see #removeAnnotation - */ - replaceAnnotations: function(remove, add) { - var annotations = this._annotations, i, index, annotation, removed = []; - if (remove) { - for (i = remove.length - 1; i >= 0; i--) { - annotation = remove[i]; - index = this._getAnnotationIndex(annotation); - if (index < 0) { continue; } - annotations.splice(index, 1); - removed.splice(0, 0, annotation); - } - } - if (!add) { add = []; } - for (i = 0; i < add.length; i++) { - annotation = add[i]; - index = this._binarySearch(annotations, annotation.start); - annotations.splice(index, 0, annotation); - } - var e = { - type: "Changed", - removed: removed, - added: add, - changed: [] - }; - this.onChanged(e); - }, - /** - * Sets the text model of the annotation model. The annotation - * model listens for changes in the text model to update and remove - * annotations that are affected by the change. - * - * @param {orion.textview.TextModel} textModel the text model. - * - * @see #getTextModel - */ - setTextModel: function(textModel) { - if (this._model) { - this._model.removeEventListener("Changed", this._listener.onChanged); - } - this._model = textModel; - if (this._model) { - this._model.addEventListener("Changed", this._listener.onChanged); - } - }, - /** @ignore */ - _binarySearch: function (array, offset) { - var high = array.length, low = -1, index; - while (high - low > 1) { - index = Math.floor((high + low) / 2); - if (offset <= array[index].start) { - high = index; - } else { - low = index; - } - } - return high; - }, - /** @ignore */ - _getAnnotationIndex: function(annotation) { - var annotations = this._annotations; - var index = this._binarySearch(annotations, annotation.start); - while (index < annotations.length && annotations[index].start === annotation.start) { - if (annotations[index] === annotation) { - return index; - } - index++; - } - return -1; - }, - /** @ignore */ - _onChanged: function(modelChangedEvent) { - var start = modelChangedEvent.start; - var addedCharCount = modelChangedEvent.addedCharCount; - var removedCharCount = modelChangedEvent.removedCharCount; - var annotations = this._annotations, end = start + removedCharCount; - //TODO binary search does not work for range intersection when there are overlaping ranges, need interval search tree for this - var startIndex = 0; - if (!(0 <= startIndex && startIndex < annotations.length)) { return; } - var e = { - type: "Changed", - added: [], - removed: [], - changed: [], - textModelChangedEvent: modelChangedEvent - }; - var changeCount = addedCharCount - removedCharCount, i; - for (i = startIndex; i < annotations.length; i++) { - var annotation = annotations[i]; - if (annotation.start >= end) { - annotation.start += changeCount; - annotation.end += changeCount; - e.changed.push(annotation); - } else if (annotation.end <= start) { - //nothing - } else if (annotation.start < start && end < annotation.end) { - annotation.end += changeCount; - e.changed.push(annotation); - } else { - annotations.splice(i, 1); - e.removed.push(annotation); - i--; - } - } - if (e.added.length > 0 || e.removed.length > 0 || e.changed.length > 0) { - this.onChanged(e); - } - } - }; - mEventTarget.EventTarget.addMixin(AnnotationModel.prototype); - - /** - * Constructs a new styler for annotations. - * - * @param {orion.textview.TextView} view The styler view. - * @param {orion.textview.AnnotationModel} view The styler annotation model. - * - * @class This object represents a styler for annotation attached to a text view. - * @name orion.textview.AnnotationStyler - * @borrows orion.textview.AnnotationTypeList#addAnnotationType as #addAnnotationType - * @borrows orion.textview.AnnotationTypeList#getAnnotationTypePriority as #getAnnotationTypePriority - * @borrows orion.textview.AnnotationTypeList#getAnnotationsByType as #getAnnotationsByType - * @borrows orion.textview.AnnotationTypeList#isAnnotationTypeVisible as #isAnnotationTypeVisible - * @borrows orion.textview.AnnotationTypeList#removeAnnotationType as #removeAnnotationType - */ - function AnnotationStyler (view, annotationModel) { - this._view = view; - this._annotationModel = annotationModel; - var self = this; - this._listener = { - onDestroy: function(e) { - self._onDestroy(e); - }, - onLineStyle: function(e) { - self._onLineStyle(e); - }, - onChanged: function(e) { - self._onAnnotationModelChanged(e); - } - }; - view.addEventListener("Destroy", this._listener.onDestroy); - view.addEventListener("LineStyle", this._listener.onLineStyle); - annotationModel.addEventListener("Changed", this._listener.onChanged); - } - AnnotationStyler.prototype = /** @lends orion.textview.AnnotationStyler.prototype */ { - /** - * Destroys the styler. - *

- * Removes all listeners added by this styler. - *

- */ - destroy: function() { - var view = this._view; - if (view) { - view.removeEventListener("Destroy", this._listener.onDestroy); - view.removeEventListener("LineStyle", this._listener.onLineStyle); - this.view = null; - } - var annotationModel = this._annotationModel; - if (annotationModel) { - annotationModel.removeEventListener("Changed", this._listener.onChanged); - annotationModel = null; - } - }, - _mergeStyle: function(result, style) { - if (style) { - if (!result) { result = {}; } - if (result.styleClass && style.styleClass && result.styleClass !== style.styleClass) { - result.styleClass += " " + style.styleClass; - } else { - result.styleClass = style.styleClass; - } - var prop; - if (style.style) { - if (!result.style) { result.style = {}; } - for (prop in style.style) { - if (!result.style[prop]) { - result.style[prop] = style.style[prop]; - } - } - } - if (style.attributes) { - if (!result.attributes) { result.attributes = {}; } - for (prop in style.attributes) { - if (!result.attributes[prop]) { - result.attributes[prop] = style.attributes[prop]; - } - } - } - } - return result; - }, - _mergeStyleRanges: function(ranges, styleRange) { - if (!ranges) { return; } - for (var i=0; i= range.end) { continue; } - var mergedStyle = this._mergeStyle({}, range.style); - mergedStyle = this._mergeStyle(mergedStyle, styleRange.style); - if (styleRange.start <= range.start && styleRange.end >= range.end) { - ranges[i] = {start: range.start, end: range.end, style: mergedStyle}; - } else if (styleRange.start > range.start && styleRange.end < range.end) { - ranges.splice(i, 1, - {start: range.start, end: styleRange.start, style: range.style}, - {start: styleRange.start, end: styleRange.end, style: mergedStyle}, - {start: styleRange.end, end: range.end, style: range.style}); - i += 2; - } else if (styleRange.start > range.start) { - ranges.splice(i, 1, - {start: range.start, end: styleRange.start, style: range.style}, - {start: styleRange.start, end: range.end, style: mergedStyle}); - i += 1; - } else if (styleRange.end < range.end) { - ranges.splice(i, 1, - {start: range.start, end: styleRange.end, style: mergedStyle}, - {start: styleRange.end, end: range.end, style: range.style}); - i += 1; - } - } - }, - _onAnnotationModelChanged: function(e) { - if (e.textModelChangedEvent) { - return; - } - var view = this._view; - if (!view) { return; } - var self = this; - var model = view.getModel(); - function redraw(changes) { - for (var i = 0; i < changes.length; i++) { - if (!self.isAnnotationTypeVisible(changes[i].type)) { continue; } - var start = changes[i].start; - var end = changes[i].end; - if (model.getBaseModel) { - start = model.mapOffset(start, true); - end = model.mapOffset(end, true); - } - if (start !== -1 && end !== -1) { - view.redrawRange(start, end); - } - } - } - redraw(e.added); - redraw(e.removed); - redraw(e.changed); - }, - _onDestroy: function(e) { - this.destroy(); - }, - _onLineStyle: function (e) { - var annotationModel = this._annotationModel; - var viewModel = this._view.getModel(); - var baseModel = annotationModel.getTextModel(); - var start = e.lineStart; - var end = e.lineStart + e.lineText.length; - if (baseModel !== viewModel) { - start = viewModel.mapOffset(start); - end = viewModel.mapOffset(end); - } - var annotations = annotationModel.getAnnotations(start, end); - while (annotations.hasNext()) { - var annotation = annotations.next(); - if (!this.isAnnotationTypeVisible(annotation.type)) { continue; } - if (annotation.rangeStyle) { - var annotationStart = annotation.start; - var annotationEnd = annotation.end; - if (baseModel !== viewModel) { - annotationStart = viewModel.mapOffset(annotationStart, true); - annotationEnd = viewModel.mapOffset(annotationEnd, true); - } - this._mergeStyleRanges(e.ranges, {start: annotationStart, end: annotationEnd, style: annotation.rangeStyle}); - } - if (annotation.lineStyle) { - e.style = this._mergeStyle({}, e.style); - e.style = this._mergeStyle(e.style, annotation.lineStyle); - } - } - } - }; - AnnotationTypeList.addMixin(AnnotationStyler.prototype); - - return { - FoldingAnnotation: FoldingAnnotation, - AnnotationTypeList: AnnotationTypeList, - AnnotationModel: AnnotationModel, - AnnotationStyler: AnnotationStyler - }; -}); -/******************************************************************************* - * @license - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: IBM Corporation - initial API and implementation - ******************************************************************************/ - -/*global define setTimeout clearTimeout setInterval clearInterval Node */ - -define("orion/textview/rulers", ['orion/textview/annotations', 'orion/textview/tooltip'], function(mAnnotations, mTooltip) { - - /** - * Constructs a new ruler. - *

- * The default implementation does not implement all the methods in the interface - * and is useful only for objects implementing rulers. - *

- * - * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. - * @param {String} [rulerLocation="left"] the location for the ruler. - * @param {String} [rulerOverview="page"] the overview for the ruler. - * @param {orion.textview.Style} [rulerStyle] the style for the ruler. - * - * @class This interface represents a ruler for the text view. - *

- * A Ruler is a graphical element that is placed either on the left or on the right side of - * the view. It can be used to provide the view with per line decoration such as line numbering, - * bookmarks, breakpoints, folding disclosures, etc. - *

- * There are two types of rulers: page and document. A page ruler only shows the content for the lines that are - * visible, while a document ruler always shows the whole content. - *

- * See:
- * {@link orion.textview.LineNumberRuler}
- * {@link orion.textview.AnnotationRuler}
- * {@link orion.textview.OverviewRuler}
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#addRuler} - *

- * @name orion.textview.Ruler - * @borrows orion.textview.AnnotationTypeList#addAnnotationType as #addAnnotationType - * @borrows orion.textview.AnnotationTypeList#getAnnotationTypePriority as #getAnnotationTypePriority - * @borrows orion.textview.AnnotationTypeList#getAnnotationsByType as #getAnnotationsByType - * @borrows orion.textview.AnnotationTypeList#isAnnotationTypeVisible as #isAnnotationTypeVisible - * @borrows orion.textview.AnnotationTypeList#removeAnnotationType as #removeAnnotationType - */ - function Ruler (annotationModel, rulerLocation, rulerOverview, rulerStyle) { - this._location = rulerLocation || "left"; - this._overview = rulerOverview || "page"; - this._rulerStyle = rulerStyle; - this._view = null; - var self = this; - this._listener = { - onTextModelChanged: function(e) { - self._onTextModelChanged(e); - }, - onAnnotationModelChanged: function(e) { - self._onAnnotationModelChanged(e); - } - }; - this.setAnnotationModel(annotationModel); - } - Ruler.prototype = /** @lends orion.textview.Ruler.prototype */ { - /** - * Returns the annotations for a given line range merging multiple - * annotations when necessary. - *

- * This method is called by the text view when the ruler is redrawn. - *

- * - * @param {Number} startLine the start line index - * @param {Number} endLine the end line index - * @return {orion.textview.Annotation[]} the annotations for the line range. The array might be sparse. - */ - getAnnotations: function(startLine, endLine) { - var annotationModel = this._annotationModel; - if (!annotationModel) { return []; } - var model = this._view.getModel(); - var start = model.getLineStart(startLine); - var end = model.getLineEnd(endLine - 1); - var baseModel = model; - if (model.getBaseModel) { - baseModel = model.getBaseModel(); - start = model.mapOffset(start); - end = model.mapOffset(end); - } - var result = []; - var annotations = this.getAnnotationsByType(annotationModel, start, end); - for (var i = 0; i < annotations.length; i++) { - var annotation = annotations[i]; - var annotationLineStart = baseModel.getLineAtOffset(annotation.start); - var annotationLineEnd = baseModel.getLineAtOffset(Math.max(annotation.start, annotation.end - 1)); - for (var lineIndex = annotationLineStart; lineIndex<=annotationLineEnd; lineIndex++) { - var visualLineIndex = lineIndex; - if (model !== baseModel) { - var ls = baseModel.getLineStart(lineIndex); - ls = model.mapOffset(ls, true); - if (ls === -1) { continue; } - visualLineIndex = model.getLineAtOffset(ls); - } - if (!(startLine <= visualLineIndex && visualLineIndex < endLine)) { continue; } - var rulerAnnotation = this._mergeAnnotation(result[visualLineIndex], annotation, lineIndex - annotationLineStart, annotationLineEnd - annotationLineStart + 1); - if (rulerAnnotation) { - result[visualLineIndex] = rulerAnnotation; - } - } - } - if (!this._multiAnnotation && this._multiAnnotationOverlay) { - for (var k in result) { - if (result[k]._multiple) { - result[k].html = result[k].html + this._multiAnnotationOverlay.html; - } - } - } - return result; - }, - /** - * Returns the annotation model. - * - * @returns {orion.textview.AnnotationModel} the ruler annotation model. - * - * @see #setAnnotationModel - */ - getAnnotationModel: function() { - return this._annotationModel; - }, - /** - * Returns the ruler location. - * - * @returns {String} the ruler location, which is either "left" or "right". - * - * @see #getOverview - */ - getLocation: function() { - return this._location; - }, - /** - * Returns the ruler overview type. - * - * @returns {String} the overview type, which is either "page" or "document". - * - * @see #getLocation - */ - getOverview: function() { - return this._overview; - }, - /** - * Returns the style information for the ruler. - * - * @returns {orion.textview.Style} the style information. - */ - getRulerStyle: function() { - return this._rulerStyle; - }, - /** - * Returns the widest annotation which determines the width of the ruler. - *

- * If the ruler does not have a fixed width it should provide the widest - * annotation to avoid the ruler from changing size as the view scrolls. - *

- *

- * This method is called by the text view when the ruler is redrawn. - *

- * - * @returns {orion.textview.Annotation} the widest annotation. - * - * @see #getAnnotations - */ - getWidestAnnotation: function() { - return null; - }, - /** - * Sets the annotation model for the ruler. - * - * @param {orion.textview.AnnotationModel} annotationModel the annotation model. - * - * @see #getAnnotationModel - */ - setAnnotationModel: function (annotationModel) { - if (this._annotationModel) { - this._annotationModel.removEventListener("Changed", this._listener.onAnnotationModelChanged); - } - this._annotationModel = annotationModel; - if (this._annotationModel) { - this._annotationModel.addEventListener("Changed", this._listener.onAnnotationModelChanged); - } - }, - /** - * Sets the annotation that is displayed when a given line contains multiple - * annotations. This annotation is used when there are different types of - * annotations in a given line. - * - * @param {orion.textview.Annotation} annotation the annotation for lines with multiple annotations. - * - * @see #setMultiAnnotationOverlay - */ - setMultiAnnotation: function(annotation) { - this._multiAnnotation = annotation; - }, - /** - * Sets the annotation that overlays a line with multiple annotations. This annotation is displayed on - * top of the computed annotation for a given line when there are multiple annotations of the same type - * in the line. It is also used when the multiple annotation is not set. - * - * @param {orion.textview.Annotation} annotation the annotation overlay for lines with multiple annotations. - * - * @see #setMultiAnnotation - */ - setMultiAnnotationOverlay: function(annotation) { - this._multiAnnotationOverlay = annotation; - }, - /** - * Sets the view for the ruler. - *

- * This method is called by the text view when the ruler - * is added to the view. - *

- * - * @param {orion.textview.TextView} view the text view. - */ - setView: function (view) { - if (this._onTextModelChanged && this._view) { - this._view.removeEventListener("ModelChanged", this._listener.onTextModelChanged); - } - this._view = view; - if (this._onTextModelChanged && this._view) { - this._view.addEventListener("ModelChanged", this._listener.onTextModelChanged); - } - }, - /** - * This event is sent when the user clicks a line annotation. - * - * @event - * @param {Number} lineIndex the line index of the annotation under the pointer. - * @param {DOMEvent} e the click event. - */ - onClick: function(lineIndex, e) { - }, - /** - * This event is sent when the user double clicks a line annotation. - * - * @event - * @param {Number} lineIndex the line index of the annotation under the pointer. - * @param {DOMEvent} e the double click event. - */ - onDblClick: function(lineIndex, e) { - }, - /** - * This event is sent when the user moves the mouse over a line annotation. - * - * @event - * @param {Number} lineIndex the line index of the annotation under the pointer. - * @param {DOMEvent} e the mouse move event. - */ - onMouseMove: function(lineIndex, e) { - var tooltip = mTooltip.Tooltip.getTooltip(this._view); - if (!tooltip) { return; } - if (tooltip.isVisible() && this._tooltipLineIndex === lineIndex) { return; } - this._tooltipLineIndex = lineIndex; - var self = this; - tooltip.setTarget({ - y: e.clientY, - getTooltipInfo: function() { - return self._getTooltipInfo(self._tooltipLineIndex, this.y); - } - }); - }, - /** - * This event is sent when the mouse pointer enters a line annotation. - * - * @event - * @param {Number} lineIndex the line index of the annotation under the pointer. - * @param {DOMEvent} e the mouse over event. - */ - onMouseOver: function(lineIndex, e) { - this.onMouseMove(lineIndex, e); - }, - /** - * This event is sent when the mouse pointer exits a line annotation. - * - * @event - * @param {Number} lineIndex the line index of the annotation under the pointer. - * @param {DOMEvent} e the mouse out event. - */ - onMouseOut: function(lineIndex, e) { - var tooltip = mTooltip.Tooltip.getTooltip(this._view); - if (!tooltip) { return; } - tooltip.setTarget(null); - }, - /** @ignore */ - _getTooltipInfo: function(lineIndex, y) { - if (lineIndex === undefined) { return; } - var view = this._view; - var model = view.getModel(); - var annotationModel = this._annotationModel; - var annotations = []; - if (annotationModel) { - var start = model.getLineStart(lineIndex); - var end = model.getLineEnd(lineIndex); - if (model.getBaseModel) { - start = model.mapOffset(start); - end = model.mapOffset(end); - } - annotations = this.getAnnotationsByType(annotationModel, start, end); - } - var contents = this._getTooltipContents(lineIndex, annotations); - if (!contents) { return null; } - var info = { - contents: contents, - anchor: this.getLocation() - }; - var rect = view.getClientArea(); - if (this.getOverview() === "document") { - rect.y = view.convert({y: y}, "view", "document").y; - } else { - rect.y = view.getLocationAtOffset(model.getLineStart(lineIndex)).y; - } - view.convert(rect, "document", "page"); - info.x = rect.x; - info.y = rect.y; - if (info.anchor === "right") { - info.x += rect.width; - } - info.maxWidth = rect.width; - info.maxHeight = rect.height - (rect.y - view._parent.getBoundingClientRect().top); - return info; - }, - /** @ignore */ - _getTooltipContents: function(lineIndex, annotations) { - return annotations; - }, - /** @ignore */ - _onAnnotationModelChanged: function(e) { - var view = this._view; - if (!view) { return; } - var model = view.getModel(), self = this; - var lineCount = model.getLineCount(); - if (e.textModelChangedEvent) { - var start = e.textModelChangedEvent.start; - if (model.getBaseModel) { start = model.mapOffset(start, true); } - var startLine = model.getLineAtOffset(start); - view.redrawLines(startLine, lineCount, self); - return; - } - function redraw(changes) { - for (var i = 0; i < changes.length; i++) { - if (!self.isAnnotationTypeVisible(changes[i].type)) { continue; } - var start = changes[i].start; - var end = changes[i].end; - if (model.getBaseModel) { - start = model.mapOffset(start, true); - end = model.mapOffset(end, true); - } - if (start !== -1 && end !== -1) { - view.redrawLines(model.getLineAtOffset(start), model.getLineAtOffset(Math.max(start, end - 1)) + 1, self); - } - } - } - redraw(e.added); - redraw(e.removed); - redraw(e.changed); - }, - /** @ignore */ - _mergeAnnotation: function(result, annotation, annotationLineIndex, annotationLineCount) { - if (!result) { result = {}; } - if (annotationLineIndex === 0) { - if (result.html && annotation.html) { - if (annotation.html !== result.html) { - if (!result._multiple && this._multiAnnotation) { - result.html = this._multiAnnotation.html; - } - } - result._multiple = true; - } else { - result.html = annotation.html; - } - } - result.style = this._mergeStyle(result.style, annotation.style); - return result; - }, - /** @ignore */ - _mergeStyle: function(result, style) { - if (style) { - if (!result) { result = {}; } - if (result.styleClass && style.styleClass && result.styleClass !== style.styleClass) { - result.styleClass += " " + style.styleClass; - } else { - result.styleClass = style.styleClass; - } - var prop; - if (style.style) { - if (!result.style) { result.style = {}; } - for (prop in style.style) { - if (!result.style[prop]) { - result.style[prop] = style.style[prop]; - } - } - } - if (style.attributes) { - if (!result.attributes) { result.attributes = {}; } - for (prop in style.attributes) { - if (!result.attributes[prop]) { - result.attributes[prop] = style.attributes[prop]; - } - } - } - } - return result; - } - }; - mAnnotations.AnnotationTypeList.addMixin(Ruler.prototype); - - /** - * Constructs a new line numbering ruler. - * - * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. - * @param {String} [rulerLocation="left"] the location for the ruler. - * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. - * @param {orion.textview.Style} [oddStyle={style: {backgroundColor: "white"}] the style for lines with odd line index. - * @param {orion.textview.Style} [evenStyle={backgroundColor: "white"}] the style for lines with even line index. - * - * @augments orion.textview.Ruler - * @class This objects implements a line numbering ruler. - * - *

See:
- * {@link orion.textview.Ruler} - *

- * @name orion.textview.LineNumberRuler - */ - function LineNumberRuler (annotationModel, rulerLocation, rulerStyle, oddStyle, evenStyle) { - Ruler.call(this, annotationModel, rulerLocation, "page", rulerStyle); - this._oddStyle = oddStyle || {style: {backgroundColor: "white"}}; - this._evenStyle = evenStyle || {style: {backgroundColor: "white"}}; - this._numOfDigits = 0; - } - LineNumberRuler.prototype = new Ruler(); - /** @ignore */ - LineNumberRuler.prototype.getAnnotations = function(startLine, endLine) { - var result = Ruler.prototype.getAnnotations.call(this, startLine, endLine); - var model = this._view.getModel(); - for (var lineIndex = startLine; lineIndex < endLine; lineIndex++) { - var style = lineIndex & 1 ? this._oddStyle : this._evenStyle; - var mapLine = lineIndex; - if (model.getBaseModel) { - var lineStart = model.getLineStart(mapLine); - mapLine = model.getBaseModel().getLineAtOffset(model.mapOffset(lineStart)); - } - if (!result[lineIndex]) { result[lineIndex] = {}; } - result[lineIndex].html = (mapLine + 1) + ""; - if (!result[lineIndex].style) { result[lineIndex].style = style; } - } - return result; - }; - /** @ignore */ - LineNumberRuler.prototype.getWidestAnnotation = function() { - var lineCount = this._view.getModel().getLineCount(); - return this.getAnnotations(lineCount - 1, lineCount)[lineCount - 1]; - }; - /** @ignore */ - LineNumberRuler.prototype._onTextModelChanged = function(e) { - var start = e.start; - var model = this._view.getModel(); - var lineCount = model.getBaseModel ? model.getBaseModel().getLineCount() : model.getLineCount(); - var numOfDigits = (lineCount+"").length; - if (this._numOfDigits !== numOfDigits) { - this._numOfDigits = numOfDigits; - var startLine = model.getLineAtOffset(start); - this._view.redrawLines(startLine, model.getLineCount(), this); - } - }; - - /** - * @class This is class represents an annotation for the AnnotationRuler. - *

- * See:
- * {@link orion.textview.AnnotationRuler} - *

- * - * @name orion.textview.Annotation - * - * @property {String} [html=""] The html content for the annotation, typically contains an image. - * @property {orion.textview.Style} [style] the style for the annotation. - * @property {orion.textview.Style} [overviewStyle] the style for the annotation in the overview ruler. - */ - /** - * Constructs a new annotation ruler. - * - * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. - * @param {String} [rulerLocation="left"] the location for the ruler. - * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. - * @param {orion.textview.Annotation} [defaultAnnotation] the default annotation. - * - * @augments orion.textview.Ruler - * @class This objects implements an annotation ruler. - * - *

See:
- * {@link orion.textview.Ruler}
- * {@link orion.textview.Annotation} - *

- * @name orion.textview.AnnotationRuler - */ - function AnnotationRuler (annotationModel, rulerLocation, rulerStyle) { - Ruler.call(this, annotationModel, rulerLocation, "page", rulerStyle); - } - AnnotationRuler.prototype = new Ruler(); - - /** - * Constructs a new overview ruler. - *

- * The overview ruler is used in conjunction with a AnnotationRuler, for each annotation in the - * AnnotationRuler this ruler displays a mark in the overview. Clicking on the mark causes the - * view to scroll to the annotated line. - *

- * - * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. - * @param {String} [rulerLocation="left"] the location for the ruler. - * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. - * - * @augments orion.textview.Ruler - * @class This objects implements an overview ruler. - * - *

See:
- * {@link orion.textview.AnnotationRuler}
- * {@link orion.textview.Ruler} - *

- * @name orion.textview.OverviewRuler - */ - function OverviewRuler (annotationModel, rulerLocation, rulerStyle) { - Ruler.call(this, annotationModel, rulerLocation, "document", rulerStyle); - } - OverviewRuler.prototype = new Ruler(); - - /** @ignore */ - OverviewRuler.prototype.getRulerStyle = function() { - var result = {style: {lineHeight: "1px", fontSize: "1px"}}; - result = this._mergeStyle(result, this._rulerStyle); - return result; - }; - /** @ignore */ - OverviewRuler.prototype.onClick = function(lineIndex, e) { - if (lineIndex === undefined) { return; } - this._view.setTopIndex(lineIndex); - }; - /** @ignore */ - OverviewRuler.prototype._getTooltipContents = function(lineIndex, annotations) { - if (annotations.length === 0) { - var model = this._view.getModel(); - var mapLine = lineIndex; - if (model.getBaseModel) { - var lineStart = model.getLineStart(mapLine); - mapLine = model.getBaseModel().getLineAtOffset(model.mapOffset(lineStart)); - } - return "Line: " + (mapLine + 1); - } - return Ruler.prototype._getTooltipContents.call(this, lineIndex, annotations); - }; - /** @ignore */ - OverviewRuler.prototype._mergeAnnotation = function(previousAnnotation, annotation, annotationLineIndex, annotationLineCount) { - if (annotationLineIndex !== 0) { return undefined; } - var result = previousAnnotation; - if (!result) { - //TODO annotationLineCount does not work when there are folded lines - var height = 3 * annotationLineCount; - result = {html: " ", style: { style: {height: height + "px"}}}; - result.style = this._mergeStyle(result.style, annotation.overviewStyle); - } - return result; - }; - - /** - * Constructs a new folding ruler. - * - * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. - * @param {String} [rulerLocation="left"] the location for the ruler. - * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. - * - * @augments orion.textview.Ruler - * @class This objects implements an overview ruler. - * - *

See:
- * {@link orion.textview.AnnotationRuler}
- * {@link orion.textview.Ruler} - *

- * @name orion.textview.OverviewRuler - */ - function FoldingRuler (annotationModel, rulerLocation, rulerStyle) { - AnnotationRuler.call(this, annotationModel, rulerLocation, rulerStyle); - } - FoldingRuler.prototype = new AnnotationRuler(); - - /** @ignore */ - FoldingRuler.prototype.onClick = function(lineIndex, e) { - if (lineIndex === undefined) { return; } - var annotationModel = this._annotationModel; - if (!annotationModel) { return; } - var view = this._view; - var model = view.getModel(); - var start = model.getLineStart(lineIndex); - var end = model.getLineEnd(lineIndex, true); - if (model.getBaseModel) { - start = model.mapOffset(start); - end = model.mapOffset(end); - } - var annotation, iter = annotationModel.getAnnotations(start, end); - while (!annotation && iter.hasNext()) { - var a = iter.next(); - if (!this.isAnnotationTypeVisible(a.type)) { continue; } - annotation = a; - } - if (annotation) { - var tooltip = mTooltip.Tooltip.getTooltip(this._view); - if (tooltip) { - tooltip.setTarget(null); - } - if (annotation.expanded) { - annotation.collapse(); - } else { - annotation.expand(); - } - this._annotationModel.modifyAnnotation(annotation); - } - }; - /** @ignore */ - FoldingRuler.prototype._getTooltipContents = function(lineIndex, annotations) { - if (annotations.length === 1) { - if (annotations[0].expanded) { - return null; - } - } - return AnnotationRuler.prototype._getTooltipContents.call(this, lineIndex, annotations); - }; - /** @ignore */ - FoldingRuler.prototype._onAnnotationModelChanged = function(e) { - if (e.textModelChangedEvent) { - AnnotationRuler.prototype._onAnnotationModelChanged.call(this, e); - return; - } - var view = this._view; - if (!view) { return; } - var model = view.getModel(), self = this, i; - var lineCount = model.getLineCount(), lineIndex = lineCount; - function redraw(changes) { - for (i = 0; i < changes.length; i++) { - if (!self.isAnnotationTypeVisible(changes[i].type)) { continue; } - var start = changes[i].start; - if (model.getBaseModel) { - start = model.mapOffset(start, true); - } - if (start !== -1) { - lineIndex = Math.min(lineIndex, model.getLineAtOffset(start)); - } - } - } - redraw(e.added); - redraw(e.removed); - redraw(e.changed); - var rulers = view.getRulers(); - for (i = 0; i < rulers.length; i++) { - view.redrawLines(lineIndex, lineCount, rulers[i]); - } - }; - - return { - Ruler: Ruler, - AnnotationRuler: AnnotationRuler, - LineNumberRuler: LineNumberRuler, - OverviewRuler: OverviewRuler, - FoldingRuler: FoldingRuler - }; -}); -/******************************************************************************* - * @license - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: IBM Corporation - initial API and implementation - ******************************************************************************/ - -/*global define */ - -define("orion/textview/undoStack", [], function() { - - /** - * Constructs a new Change object. - * - * @class - * @name orion.textview.Change - * @private - */ - function Change(offset, text, previousText) { - this.offset = offset; - this.text = text; - this.previousText = previousText; - } - Change.prototype = { - /** @ignore */ - undo: function (view, select) { - this._doUndoRedo(this.offset, this.previousText, this.text, view, select); - }, - /** @ignore */ - redo: function (view, select) { - this._doUndoRedo(this.offset, this.text, this.previousText, view, select); - }, - _doUndoRedo: function(offset, text, previousText, view, select) { - var model = view.getModel(); - /* - * TODO UndoStack should be changing the text in the base model. - * This is code needs to change when modifications in the base - * model are supported properly by the projection model. - */ - if (model.mapOffset && view.annotationModel) { - var mapOffset = model.mapOffset(offset, true); - if (mapOffset < 0) { - var annotationModel = view.annotationModel; - var iter = annotationModel.getAnnotations(offset, offset + 1); - while (iter.hasNext()) { - var annotation = iter.next(); - if (annotation.type === "orion.annotation.folding") { - annotation.expand(); - mapOffset = model.mapOffset(offset, true); - break; - } - } - } - if (mapOffset < 0) { return; } - offset = mapOffset; - } - view.setText(text, offset, offset + previousText.length); - if (select) { - view.setSelection(offset, offset + text.length); - } - } - }; - - /** - * Constructs a new CompoundChange object. - * - * @class - * @name orion.textview.CompoundChange - * @private - */ - function CompoundChange () { - this.changes = []; - } - CompoundChange.prototype = { - /** @ignore */ - add: function (change) { - this.changes.push(change); - }, - /** @ignore */ - end: function (view) { - this.endSelection = view.getSelection(); - this.endCaret = view.getCaretOffset(); - }, - /** @ignore */ - undo: function (view, select) { - for (var i=this.changes.length - 1; i >= 0; i--) { - this.changes[i].undo(view, false); - } - if (select) { - var start = this.startSelection.start; - var end = this.startSelection.end; - view.setSelection(this.startCaret ? start : end, this.startCaret ? end : start); - } - }, - /** @ignore */ - redo: function (view, select) { - for (var i = 0; i < this.changes.length; i++) { - this.changes[i].redo(view, false); - } - if (select) { - var start = this.endSelection.start; - var end = this.endSelection.end; - view.setSelection(this.endCaret ? start : end, this.endCaret ? end : start); - } - }, - /** @ignore */ - start: function (view) { - this.startSelection = view.getSelection(); - this.startCaret = view.getCaretOffset(); - } - }; - - /** - * Constructs a new UndoStack on a text view. - * - * @param {orion.textview.TextView} view the text view for the undo stack. - * @param {Number} [size=100] the size for the undo stack. - * - * @name orion.textview.UndoStack - * @class The UndoStack is used to record the history of a text model associated to an view. Every - * change to the model is added to stack, allowing the application to undo and redo these changes. - * - *

- * See:
- * {@link orion.textview.TextView}
- *

- */ - function UndoStack (view, size) { - this.view = view; - this.size = size !== undefined ? size : 100; - this.reset(); - var model = view.getModel(); - if (model.getBaseModel) { - model = model.getBaseModel(); - } - this.model = model; - var self = this; - this._listener = { - onChanging: function(e) { - self._onChanging(e); - }, - onDestroy: function(e) { - self._onDestroy(e); - } - }; - model.addEventListener("Changing", this._listener.onChanging); - view.addEventListener("Destroy", this._listener.onDestroy); - } - UndoStack.prototype = /** @lends orion.textview.UndoStack.prototype */ { - /** - * Adds a change to the stack. - * - * @param change the change to add. - * @param {Number} change.offset the offset of the change - * @param {String} change.text the new text of the change - * @param {String} change.previousText the previous text of the change - */ - add: function (change) { - if (this.compoundChange) { - this.compoundChange.add(change); - } else { - var length = this.stack.length; - this.stack.splice(this.index, length-this.index, change); - this.index++; - if (this.stack.length > this.size) { - this.stack.shift(); - this.index--; - this.cleanIndex--; - } - } - }, - /** - * Marks the current state of the stack as clean. - * - *

- * This function is typically called when the content of view associated with the stack is saved. - *

- * - * @see #isClean - */ - markClean: function() { - this.endCompoundChange(); - this._commitUndo(); - this.cleanIndex = this.index; - }, - /** - * Returns true if current state of stack is the same - * as the state when markClean() was called. - * - *

- * For example, the application calls markClean(), then calls undo() four times and redo() four times. - * At this point isClean() returns true. - *

- *

- * This function is typically called to determine if the content of the view associated with the stack - * has changed since the last time it was saved. - *

- * - * @return {Boolean} returns if the state is the same as the state when markClean() was called. - * - * @see #markClean - */ - isClean: function() { - return this.cleanIndex === this.getSize().undo; - }, - /** - * Returns true if there is at least one change to undo. - * - * @return {Boolean} returns true if there is at least one change to undo. - * - * @see #canRedo - * @see #undo - */ - canUndo: function() { - return this.getSize().undo > 0; - }, - /** - * Returns true if there is at least one change to redo. - * - * @return {Boolean} returns true if there is at least one change to redo. - * - * @see #canUndo - * @see #redo - */ - canRedo: function() { - return this.getSize().redo > 0; - }, - /** - * Finishes a compound change. - * - * @see #startCompoundChange - */ - endCompoundChange: function() { - if (this.compoundChange) { - this.compoundChange.end(this.view); - } - this.compoundChange = undefined; - }, - /** - * Returns the sizes of the stack. - * - * @return {object} a object where object.undo is the number of changes that can be un-done, - * and object.redo is the number of changes that can be re-done. - * - * @see #canUndo - * @see #canRedo - */ - getSize: function() { - var index = this.index; - var length = this.stack.length; - if (this._undoStart !== undefined) { - index++; - } - return {undo: index, redo: (length - index)}; - }, - /** - * Undo the last change in the stack. - * - * @return {Boolean} returns true if a change was un-done. - * - * @see #redo - * @see #canUndo - */ - undo: function() { - this._commitUndo(); - if (this.index <= 0) { - return false; - } - var change = this.stack[--this.index]; - this._ignoreUndo = true; - change.undo(this.view, true); - this._ignoreUndo = false; - return true; - }, - /** - * Redo the last change in the stack. - * - * @return {Boolean} returns true if a change was re-done. - * - * @see #undo - * @see #canRedo - */ - redo: function() { - this._commitUndo(); - if (this.index >= this.stack.length) { - return false; - } - var change = this.stack[this.index++]; - this._ignoreUndo = true; - change.redo(this.view, true); - this._ignoreUndo = false; - return true; - }, - /** - * Reset the stack to its original state. All changes in the stack are thrown away. - */ - reset: function() { - this.index = this.cleanIndex = 0; - this.stack = []; - this._undoStart = undefined; - this._undoText = ""; - this._undoType = 0; - this._ignoreUndo = false; - this._compoundChange = undefined; - }, - /** - * Starts a compound change. - *

- * All changes added to stack from the time startCompoundChange() is called - * to the time that endCompoundChange() is called are compound on one change that can be un-done or re-done - * with one single call to undo() or redo(). - *

- * - * @see #endCompoundChange - */ - startCompoundChange: function() { - this._commitUndo(); - var change = new CompoundChange(); - this.add(change); - this.compoundChange = change; - this.compoundChange.start(this.view); - }, - _commitUndo: function () { - if (this._undoStart !== undefined) { - if (this._undoType === -1) { - this.add(new Change(this._undoStart, "", this._undoText, "")); - } else { - this.add(new Change(this._undoStart, this._undoText, "")); - } - this._undoStart = undefined; - this._undoText = ""; - this._undoType = 0; - } - }, - _onDestroy: function(evt) { - this.model.removeEventListener("Changing", this._listener.onChanging); - this.view.removeEventListener("Destroy", this._listener.onDestroy); - }, - _onChanging: function(e) { - var newText = e.text; - var start = e.start; - var removedCharCount = e.removedCharCount; - var addedCharCount = e.addedCharCount; - if (this._ignoreUndo) { - return; - } - if (this._undoStart !== undefined && - !((addedCharCount === 1 && removedCharCount === 0 && this._undoType === 1 && start === this._undoStart + this._undoText.length) || - (addedCharCount === 0 && removedCharCount === 1 && this._undoType === -1 && (((start + 1) === this._undoStart) || (start === this._undoStart))))) - { - this._commitUndo(); - } - if (!this.compoundChange) { - if (addedCharCount === 1 && removedCharCount === 0) { - if (this._undoStart === undefined) { - this._undoStart = start; - } - this._undoText = this._undoText + newText; - this._undoType = 1; - return; - } else if (addedCharCount === 0 && removedCharCount === 1) { - var deleting = this._undoText.length > 0 && this._undoStart === start; - this._undoStart = start; - this._undoType = -1; - if (deleting) { - this._undoText = this._undoText + this.model.getText(start, start + removedCharCount); - } else { - this._undoText = this.model.getText(start, start + removedCharCount) + this._undoText; - } - return; - } - } - this.add(new Change(start, newText, this.model.getText(start, start + removedCharCount))); - } - }; - - return { - UndoStack: UndoStack - }; -}); -/******************************************************************************* - * @license - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: - * Felipe Heidrich (IBM Corporation) - initial API and implementation - * Silenio Quarti (IBM Corporation) - initial API and implementation - ******************************************************************************/ - -/*global define window*/ - -define("orion/textview/textModel", ['orion/textview/eventTarget'], function(mEventTarget) { - var isWindows = window.navigator.platform.indexOf("Win") !== -1; - - /** - * Constructs a new TextModel with the given text and default line delimiter. - * - * @param {String} [text=""] the text that the model will store - * @param {String} [lineDelimiter=platform delimiter] the line delimiter used when inserting new lines to the model. - * - * @name orion.textview.TextModel - * @class The TextModel is an interface that provides text for the view. Applications may - * implement the TextModel interface to provide a custom store for the view content. The - * view interacts with its text model in order to access and update the text that is being - * displayed and edited in the view. This is the default implementation. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#setModel} - *

- * @borrows orion.textview.EventTarget#addEventListener as #addEventListener - * @borrows orion.textview.EventTarget#removeEventListener as #removeEventListener - * @borrows orion.textview.EventTarget#dispatchEvent as #dispatchEvent - */ - function TextModel(text, lineDelimiter) { - this._lastLineIndex = -1; - this._text = [""]; - this._lineOffsets = [0]; - this.setText(text); - this.setLineDelimiter(lineDelimiter); - } - - TextModel.prototype = /** @lends orion.textview.TextModel.prototype */ { - /** - * Returns the number of characters in the model. - * - * @returns {Number} the number of characters in the model. - */ - getCharCount: function() { - var count = 0; - for (var i = 0; i - * The valid indices are 0 to line count exclusive. Returns null - * if the index is out of range. - *

- * - * @param {Number} lineIndex the zero based index of the line. - * @param {Boolean} [includeDelimiter=false] whether or not to include the line delimiter. - * @returns {String} the line text or null if out of range. - * - * @see #getLineAtOffset - */ - getLine: function(lineIndex, includeDelimiter) { - var lineCount = this.getLineCount(); - if (!(0 <= lineIndex && lineIndex < lineCount)) { - return null; - } - var start = this._lineOffsets[lineIndex]; - if (lineIndex + 1 < lineCount) { - var text = this.getText(start, this._lineOffsets[lineIndex + 1]); - if (includeDelimiter) { - return text; - } - var end = text.length, c; - while (((c = text.charCodeAt(end - 1)) === 10) || (c === 13)) { - end--; - } - return text.substring(0, end); - } else { - return this.getText(start); - } - }, - /** - * Returns the line index at the given character offset. - *

- * The valid offsets are 0 to char count inclusive. The line index for - * char count is line count - 1. Returns -1 if - * the offset is out of range. - *

- * - * @param {Number} offset a character offset. - * @returns {Number} the zero based line index or -1 if out of range. - */ - getLineAtOffset: function(offset) { - var charCount = this.getCharCount(); - if (!(0 <= offset && offset <= charCount)) { - return -1; - } - var lineCount = this.getLineCount(); - if (offset === charCount) { - return lineCount - 1; - } - var lineStart, lineEnd; - var index = this._lastLineIndex; - if (0 <= index && index < lineCount) { - lineStart = this._lineOffsets[index]; - lineEnd = index + 1 < lineCount ? this._lineOffsets[index + 1] : charCount; - if (lineStart <= offset && offset < lineEnd) { - return index; - } - } - var high = lineCount; - var low = -1; - while (high - low > 1) { - index = Math.floor((high + low) / 2); - lineStart = this._lineOffsets[index]; - lineEnd = index + 1 < lineCount ? this._lineOffsets[index + 1] : charCount; - if (offset <= lineStart) { - high = index; - } else if (offset < lineEnd) { - high = index; - break; - } else { - low = index; - } - } - this._lastLineIndex = high; - return high; - }, - /** - * Returns the number of lines in the model. - *

- * The model always has at least one line. - *

- * - * @returns {Number} the number of lines. - */ - getLineCount: function() { - return this._lineOffsets.length; - }, - /** - * Returns the line delimiter that is used by the view - * when inserting new lines. New lines entered using key strokes - * and paste operations use this line delimiter. - * - * @return {String} the line delimiter that is used by the view when inserting new lines. - */ - getLineDelimiter: function() { - return this._lineDelimiter; - }, - /** - * Returns the end character offset for the given line. - *

- * The end offset is not inclusive. This means that when the line delimiter is included, the - * offset is either the start offset of the next line or char count. When the line delimiter is - * not included, the offset is the offset of the line delimiter. - *

- *

- * The valid indices are 0 to line count exclusive. Returns -1 - * if the index is out of range. - *

- * - * @param {Number} lineIndex the zero based index of the line. - * @param {Boolean} [includeDelimiter=false] whether or not to include the line delimiter. - * @return {Number} the line end offset or -1 if out of range. - * - * @see #getLineStart - */ - getLineEnd: function(lineIndex, includeDelimiter) { - var lineCount = this.getLineCount(); - if (!(0 <= lineIndex && lineIndex < lineCount)) { - return -1; - } - if (lineIndex + 1 < lineCount) { - var end = this._lineOffsets[lineIndex + 1]; - if (includeDelimiter) { - return end; - } - var text = this.getText(Math.max(this._lineOffsets[lineIndex], end - 2), end); - var i = text.length, c; - while (((c = text.charCodeAt(i - 1)) === 10) || (c === 13)) { - i--; - } - return end - (text.length - i); - } else { - return this.getCharCount(); - } - }, - /** - * Returns the start character offset for the given line. - *

- * The valid indices are 0 to line count exclusive. Returns -1 - * if the index is out of range. - *

- * - * @param {Number} lineIndex the zero based index of the line. - * @return {Number} the line start offset or -1 if out of range. - * - * @see #getLineEnd - */ - getLineStart: function(lineIndex) { - if (!(0 <= lineIndex && lineIndex < this.getLineCount())) { - return -1; - } - return this._lineOffsets[lineIndex]; - }, - /** - * Returns the text for the given range. - *

- * The end offset is not inclusive. This means that character at the end offset - * is not included in the returned text. - *

- * - * @param {Number} [start=0] the zero based start offset of text range. - * @param {Number} [end=char count] the zero based end offset of text range. - * - * @see #setText - */ - getText: function(start, end) { - if (start === undefined) { start = 0; } - if (end === undefined) { end = this.getCharCount(); } - if (start === end) { return ""; } - var offset = 0, chunk = 0, length; - while (chunk - * This notification is intended to be used only by the view. Application clients should - * use {@link orion.textview.TextView#event:onModelChanging}. - *

- *

- * NOTE: This method is not meant to called directly by application code. It is called internally by the TextModel - * as part of the implementation of {@link #setText}. This method is included in the public API for documentation - * purposes and to allow integration with other toolkit frameworks. - *

- * - * @param {orion.textview.ModelChangingEvent} modelChangingEvent the changing event - */ - onChanging: function(modelChangingEvent) { - return this.dispatchEvent(modelChangingEvent); - }, - /** - * Notifies all listeners that the text has changed. - *

- * This notification is intended to be used only by the view. Application clients should - * use {@link orion.textview.TextView#event:onModelChanged}. - *

- *

- * NOTE: This method is not meant to called directly by application code. It is called internally by the TextModel - * as part of the implementation of {@link #setText}. This method is included in the public API for documentation - * purposes and to allow integration with other toolkit frameworks. - *

- * - * @param {orion.textview.ModelChangedEvent} modelChangedEvent the changed event - */ - onChanged: function(modelChangedEvent) { - return this.dispatchEvent(modelChangedEvent); - }, - /** - * Sets the line delimiter that is used by the view - * when new lines are inserted in the model due to key - * strokes and paste operations. - *

- * If lineDelimiter is "auto", the delimiter is computed to be - * the first delimiter found the in the current text. If lineDelimiter - * is undefined or if there are no delimiters in the current text, the - * platform delimiter is used. - *

- * - * @param {String} lineDelimiter the line delimiter that is used by the view when inserting new lines. - */ - setLineDelimiter: function(lineDelimiter) { - if (lineDelimiter === "auto") { - lineDelimiter = undefined; - if (this.getLineCount() > 1) { - lineDelimiter = this.getText(this.getLineEnd(0), this.getLineEnd(0, true)); - } - } - this._lineDelimiter = lineDelimiter ? lineDelimiter : (isWindows ? "\r\n" : "\n"); - }, - /** - * Replaces the text in the given range with the given text. - *

- * The end offset is not inclusive. This means that the character at the - * end offset is not replaced. - *

- *

- * The text model must notify the listeners before and after the - * the text is changed by calling {@link #onChanging} and {@link #onChanged} - * respectively. - *

- * - * @param {String} [text=""] the new text. - * @param {Number} [start=0] the zero based start offset of text range. - * @param {Number} [end=char count] the zero based end offset of text range. - * - * @see #getText - */ - setText: function(text, start, end) { - if (text === undefined) { text = ""; } - if (start === undefined) { start = 0; } - if (end === undefined) { end = this.getCharCount(); } - if (start === end && text === "") { return; } - var startLine = this.getLineAtOffset(start); - var endLine = this.getLineAtOffset(end); - var eventStart = start; - var removedCharCount = end - start; - var removedLineCount = endLine - startLine; - var addedCharCount = text.length; - var addedLineCount = 0; - var lineCount = this.getLineCount(); - - var cr = 0, lf = 0, index = 0; - var newLineOffsets = []; - while (true) { - if (cr !== -1 && cr <= index) { cr = text.indexOf("\r", index); } - if (lf !== -1 && lf <= index) { lf = text.indexOf("\n", index); } - if (lf === -1 && cr === -1) { break; } - if (cr !== -1 && lf !== -1) { - if (cr + 1 === lf) { - index = lf + 1; - } else { - index = (cr < lf ? cr : lf) + 1; - } - } else if (cr !== -1) { - index = cr + 1; - } else { - index = lf + 1; - } - newLineOffsets.push(start + index); - addedLineCount++; - } - - var modelChangingEvent = { - type: "Changing", - text: text, - start: eventStart, - removedCharCount: removedCharCount, - addedCharCount: addedCharCount, - removedLineCount: removedLineCount, - addedLineCount: addedLineCount - }; - this.onChanging(modelChangingEvent); - - //TODO this should be done the loops below to avoid getText() - if (newLineOffsets.length === 0) { - var startLineOffset = this.getLineStart(startLine), endLineOffset; - if (endLine + 1 < lineCount) { - endLineOffset = this.getLineStart(endLine + 1); - } else { - endLineOffset = this.getCharCount(); - } - if (start !== startLineOffset) { - text = this.getText(startLineOffset, start) + text; - start = startLineOffset; - } - if (end !== endLineOffset) { - text = text + this.getText(end, endLineOffset); - end = endLineOffset; - } - } - - var changeCount = addedCharCount - removedCharCount; - for (var j = startLine + removedLineCount + 1; j < lineCount; j++) { - this._lineOffsets[j] += changeCount; - } - var args = [startLine + 1, removedLineCount].concat(newLineOffsets); - Array.prototype.splice.apply(this._lineOffsets, args); - - var offset = 0, chunk = 0, length; - while (chunk - * See:
- * {@link orion.textview.ProjectionTextModel}
- * {@link orion.textview.ProjectionTextModel#addProjection}
- *

- * @name orion.textview.Projection - * - * @property {Number} start The start offset of the projection range. - * @property {Number} end The end offset of the projection range. This offset is exclusive. - * @property {String|orion.textview.TextModel} [text=""] The projection text to be inserted - */ - /** - * Constructs a new ProjectionTextModel based on the specified TextModel. - * - * @param {orion.textview.TextModel} baseModel The base text model. - * - * @name orion.textview.ProjectionTextModel - * @class The ProjectionTextModel represents a projection of its base text - * model. Projection ranges can be added to the projection text model to hide and/or insert - * ranges to the base text model. - *

- * The contents of the projection text model is modified when changes occur in the base model, - * projection model or by calls to {@link #addProjection} and {@link #removeProjection}. - *

- *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextModel} - * {@link orion.textview.TextView#setModel} - *

- * @borrows orion.textview.EventTarget#addEventListener as #addEventListener - * @borrows orion.textview.EventTarget#removeEventListener as #removeEventListener - * @borrows orion.textview.EventTarget#dispatchEvent as #dispatchEvent - */ - function ProjectionTextModel(baseModel) { - this._model = baseModel; /* Base Model */ - this._projections = []; - } - - ProjectionTextModel.prototype = /** @lends orion.textview.ProjectionTextModel.prototype */ { - /** - * Adds a projection range to the model. - *

- * The model must notify the listeners before and after the the text is - * changed by calling {@link #onChanging} and {@link #onChanged} respectively. - *

- * @param {orion.textview.Projection} projection The projection range to be added. - * - * @see #removeProjection - */ - addProjection: function(projection) { - if (!projection) {return;} - //start and end can't overlap any exist projection - var model = this._model, projections = this._projections; - projection._lineIndex = model.getLineAtOffset(projection.start); - projection._lineCount = model.getLineAtOffset(projection.end) - projection._lineIndex; - var text = projection.text; - if (!text) { text = ""; } - if (typeof text === "string") { - projection._model = new mTextModel.TextModel(text, model.getLineDelimiter()); - } else { - projection._model = text; - } - var eventStart = this.mapOffset(projection.start, true); - var removedCharCount = projection.end - projection.start; - var removedLineCount = projection._lineCount; - var addedCharCount = projection._model.getCharCount(); - var addedLineCount = projection._model.getLineCount() - 1; - var modelChangingEvent = { - type: "Changing", - text: projection._model.getText(), - start: eventStart, - removedCharCount: removedCharCount, - addedCharCount: addedCharCount, - removedLineCount: removedLineCount, - addedLineCount: addedLineCount - }; - this.onChanging(modelChangingEvent); - var index = this._binarySearch(projections, projection.start); - projections.splice(index, 0, projection); - var modelChangedEvent = { - type: "Changed", - start: eventStart, - removedCharCount: removedCharCount, - addedCharCount: addedCharCount, - removedLineCount: removedLineCount, - addedLineCount: addedLineCount - }; - this.onChanged(modelChangedEvent); - }, - /** - * Returns all projection ranges of this model. - * - * @return {orion.textview.Projection[]} The projection ranges. - * - * @see #addProjection - */ - getProjections: function() { - return this._projections.slice(0); - }, - /** - * Gets the base text model. - * - * @return {orion.textview.TextModel} The base text model. - */ - getBaseModel: function() { - return this._model; - }, - /** - * Maps offsets between the projection model and its base model. - * - * @param {Number} offset The offset to be mapped. - * @param {Boolean} [baseOffset=false] true if offset is in base model and - * should be mapped to the projection model. - * @return {Number} The mapped offset - */ - mapOffset: function(offset, baseOffset) { - var projections = this._projections, delta = 0, i, projection; - if (baseOffset) { - for (i = 0; i < projections.length; i++) { - projection = projections[i]; - if (projection.start > offset) { break; } - if (projection.end > offset) { return -1; } - delta += projection._model.getCharCount() - (projection.end - projection.start); - } - return offset + delta; - } - for (i = 0; i < projections.length; i++) { - projection = projections[i]; - if (projection.start > offset - delta) { break; } - var charCount = projection._model.getCharCount(); - if (projection.start + charCount > offset - delta) { - return -1; - } - delta += charCount - (projection.end - projection.start); - } - return offset - delta; - }, - /** - * Removes a projection range from the model. - *

- * The model must notify the listeners before and after the the text is - * changed by calling {@link #onChanging} and {@link #onChanged} respectively. - *

- * - * @param {orion.textview.Projection} projection The projection range to be removed. - * - * @see #addProjection - */ - removeProjection: function(projection) { - //TODO remove listeners from model - var i, delta = 0; - for (i = 0; i < this._projections.length; i++) { - var p = this._projections[i]; - if (p === projection) { - projection = p; - break; - } - delta += p._model.getCharCount() - (p.end - p.start); - } - if (i < this._projections.length) { - var model = this._model; - var eventStart = projection.start + delta; - var addedCharCount = projection.end - projection.start; - var addedLineCount = projection._lineCount; - var removedCharCount = projection._model.getCharCount(); - var removedLineCount = projection._model.getLineCount() - 1; - var modelChangingEvent = { - type: "Changing", - text: model.getText(projection.start, projection.end), - start: eventStart, - removedCharCount: removedCharCount, - addedCharCount: addedCharCount, - removedLineCount: removedLineCount, - addedLineCount: addedLineCount - }; - this.onChanging(modelChangingEvent); - this._projections.splice(i, 1); - var modelChangedEvent = { - type: "Changed", - start: eventStart, - removedCharCount: removedCharCount, - addedCharCount: addedCharCount, - removedLineCount: removedLineCount, - addedLineCount: addedLineCount - }; - this.onChanged(modelChangedEvent); - } - }, - /** @ignore */ - _binarySearch: function (array, offset) { - var high = array.length, low = -1, index; - while (high - low > 1) { - index = Math.floor((high + low) / 2); - if (offset <= array[index].start) { - high = index; - } else { - low = index; - } - } - return high; - }, - /** - * @see orion.textview.TextModel#getCharCount - */ - getCharCount: function() { - var count = this._model.getCharCount(), projections = this._projections; - for (var i = 0; i < projections.length; i++) { - var projection = projections[i]; - count += projection._model.getCharCount() - (projection.end - projection.start); - } - return count; - }, - /** - * @see orion.textview.TextModel#getLine - */ - getLine: function(lineIndex, includeDelimiter) { - if (lineIndex < 0) { return null; } - var model = this._model, projections = this._projections; - var delta = 0, result = [], offset = 0, i, lineCount, projection; - for (i = 0; i < projections.length; i++) { - projection = projections[i]; - if (projection._lineIndex >= lineIndex - delta) { break; } - lineCount = projection._model.getLineCount() - 1; - if (projection._lineIndex + lineCount >= lineIndex - delta) { - var projectionLineIndex = lineIndex - (projection._lineIndex + delta); - if (projectionLineIndex < lineCount) { - return projection._model.getLine(projectionLineIndex, includeDelimiter); - } else { - result.push(projection._model.getLine(lineCount)); - } - } - offset = projection.end; - delta += lineCount - projection._lineCount; - } - offset = Math.max(offset, model.getLineStart(lineIndex - delta)); - for (; i < projections.length; i++) { - projection = projections[i]; - if (projection._lineIndex > lineIndex - delta) { break; } - result.push(model.getText(offset, projection.start)); - lineCount = projection._model.getLineCount() - 1; - if (projection._lineIndex + lineCount > lineIndex - delta) { - result.push(projection._model.getLine(0, includeDelimiter)); - return result.join(""); - } - result.push(projection._model.getText()); - offset = projection.end; - delta += lineCount - projection._lineCount; - } - var end = model.getLineEnd(lineIndex - delta, includeDelimiter); - if (offset < end) { - result.push(model.getText(offset, end)); - } - return result.join(""); - }, - /** - * @see orion.textview.TextModel#getLineAtOffset - */ - getLineAtOffset: function(offset) { - var model = this._model, projections = this._projections; - var delta = 0, lineDelta = 0; - for (var i = 0; i < projections.length; i++) { - var projection = projections[i]; - if (projection.start > offset - delta) { break; } - var charCount = projection._model.getCharCount(); - if (projection.start + charCount > offset - delta) { - var projectionOffset = offset - (projection.start + delta); - lineDelta += projection._model.getLineAtOffset(projectionOffset); - delta += projectionOffset; - break; - } - lineDelta += projection._model.getLineCount() - 1 - projection._lineCount; - delta += charCount - (projection.end - projection.start); - } - return model.getLineAtOffset(offset - delta) + lineDelta; - }, - /** - * @see orion.textview.TextModel#getLineCount - */ - getLineCount: function() { - var model = this._model, projections = this._projections; - var count = model.getLineCount(); - for (var i = 0; i < projections.length; i++) { - var projection = projections[i]; - count += projection._model.getLineCount() - 1 - projection._lineCount; - } - return count; - }, - /** - * @see orion.textview.TextModel#getLineDelimiter - */ - getLineDelimiter: function() { - return this._model.getLineDelimiter(); - }, - /** - * @see orion.textview.TextModel#getLineEnd - */ - getLineEnd: function(lineIndex, includeDelimiter) { - if (lineIndex < 0) { return -1; } - var model = this._model, projections = this._projections; - var delta = 0, offsetDelta = 0; - for (var i = 0; i < projections.length; i++) { - var projection = projections[i]; - if (projection._lineIndex > lineIndex - delta) { break; } - var lineCount = projection._model.getLineCount() - 1; - if (projection._lineIndex + lineCount > lineIndex - delta) { - var projectionLineIndex = lineIndex - (projection._lineIndex + delta); - return projection._model.getLineEnd (projectionLineIndex, includeDelimiter) + projection.start + offsetDelta; - } - offsetDelta += projection._model.getCharCount() - (projection.end - projection.start); - delta += lineCount - projection._lineCount; - } - return model.getLineEnd(lineIndex - delta, includeDelimiter) + offsetDelta; - }, - /** - * @see orion.textview.TextModel#getLineStart - */ - getLineStart: function(lineIndex) { - if (lineIndex < 0) { return -1; } - var model = this._model, projections = this._projections; - var delta = 0, offsetDelta = 0; - for (var i = 0; i < projections.length; i++) { - var projection = projections[i]; - if (projection._lineIndex >= lineIndex - delta) { break; } - var lineCount = projection._model.getLineCount() - 1; - if (projection._lineIndex + lineCount >= lineIndex - delta) { - var projectionLineIndex = lineIndex - (projection._lineIndex + delta); - return projection._model.getLineStart (projectionLineIndex) + projection.start + offsetDelta; - } - offsetDelta += projection._model.getCharCount() - (projection.end - projection.start); - delta += lineCount - projection._lineCount; - } - return model.getLineStart(lineIndex - delta) + offsetDelta; - }, - /** - * @see orion.textview.TextModel#getText - */ - getText: function(start, end) { - if (start === undefined) { start = 0; } - var model = this._model, projections = this._projections; - var delta = 0, result = [], i, projection, charCount; - for (i = 0; i < projections.length; i++) { - projection = projections[i]; - if (projection.start > start - delta) { break; } - charCount = projection._model.getCharCount(); - if (projection.start + charCount > start - delta) { - if (end !== undefined && projection.start + charCount > end - delta) { - return projection._model.getText(start - (projection.start + delta), end - (projection.start + delta)); - } else { - result.push(projection._model.getText(start - (projection.start + delta))); - start = projection.end + delta + charCount - (projection.end - projection.start); - } - } - delta += charCount - (projection.end - projection.start); - } - var offset = start - delta; - if (end !== undefined) { - for (; i < projections.length; i++) { - projection = projections[i]; - if (projection.start > end - delta) { break; } - result.push(model.getText(offset, projection.start)); - charCount = projection._model.getCharCount(); - if (projection.start + charCount > end - delta) { - result.push(projection._model.getText(0, end - (projection.start + delta))); - return result.join(""); - } - result.push(projection._model.getText()); - offset = projection.end; - delta += charCount - (projection.end - projection.start); - } - result.push(model.getText(offset, end - delta)); - } else { - for (; i < projections.length; i++) { - projection = projections[i]; - result.push(model.getText(offset, projection.start)); - result.push(projection._model.getText()); - offset = projection.end; - } - result.push(model.getText(offset)); - } - return result.join(""); - }, - /** @ignore */ - _onChanging: function(text, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) { - var model = this._model, projections = this._projections, i, projection, delta = 0, lineDelta; - var end = start + removedCharCount; - for (; i < projections.length; i++) { - projection = projections[i]; - if (projection.start > start) { break; } - delta += projection._model.getCharCount() - (projection.end - projection.start); - } - /*TODO add stuff saved by setText*/ - var mapStart = start + delta, rangeStart = i; - for (; i < projections.length; i++) { - projection = projections[i]; - if (projection.start > end) { break; } - delta += projection._model.getCharCount() - (projection.end - projection.start); - lineDelta += projection._model.getLineCount() - 1 - projection._lineCount; - } - /*TODO add stuff saved by setText*/ - var mapEnd = end + delta, rangeEnd = i; - this.onChanging(mapStart, mapEnd - mapStart, addedCharCount/*TODO add stuff saved by setText*/, removedLineCount + lineDelta/*TODO add stuff saved by setText*/, addedLineCount/*TODO add stuff saved by setText*/); - projections.splice(projections, rangeEnd - rangeStart); - var count = text.length - (mapEnd - mapStart); - for (; i < projections.length; i++) { - projection = projections[i]; - projection.start += count; - projection.end += count; - projection._lineIndex = model.getLineAtOffset(projection.start); - } - }, - /** - * @see orion.textview.TextModel#onChanging - */ - onChanging: function(modelChangingEvent) { - return this.dispatchEvent(modelChangingEvent); - }, - /** - * @see orion.textview.TextModel#onChanged - */ - onChanged: function(modelChangedEvent) { - return this.dispatchEvent(modelChangedEvent); - }, - /** - * @see orion.textview.TextModel#setLineDelimiter - */ - setLineDelimiter: function(lineDelimiter) { - this._model.setLineDelimiter(lineDelimiter); - }, - /** - * @see orion.textview.TextModel#setText - */ - setText: function(text, start, end) { - if (text === undefined) { text = ""; } - if (start === undefined) { start = 0; } - var eventStart = start, eventEnd = end; - var model = this._model, projections = this._projections; - var delta = 0, lineDelta = 0, i, projection, charCount, startProjection, endProjection, startLineDelta = 0; - for (i = 0; i < projections.length; i++) { - projection = projections[i]; - if (projection.start > start - delta) { break; } - charCount = projection._model.getCharCount(); - if (projection.start + charCount > start - delta) { - if (end !== undefined && projection.start + charCount > end - delta) { - projection._model.setText(text, start - (projection.start + delta), end - (projection.start + delta)); - //TODO events - special case - return; - } else { - startLineDelta = projection._model.getLineCount() - 1 - projection._model.getLineAtOffset(start - (projection.start + delta)); - startProjection = { - projection: projection, - start: start - (projection.start + delta) - }; - start = projection.end + delta + charCount - (projection.end - projection.start); - } - } - lineDelta += projection._model.getLineCount() - 1 - projection._lineCount; - delta += charCount - (projection.end - projection.start); - } - var mapStart = start - delta, rangeStart = i, startLine = model.getLineAtOffset(mapStart) + lineDelta - startLineDelta; - if (end !== undefined) { - for (; i < projections.length; i++) { - projection = projections[i]; - if (projection.start > end - delta) { break; } - charCount = projection._model.getCharCount(); - if (projection.start + charCount > end - delta) { - lineDelta += projection._model.getLineAtOffset(end - (projection.start + delta)); - charCount = end - (projection.start + delta); - end = projection.end + delta; - endProjection = { - projection: projection, - end: charCount - }; - break; - } - lineDelta += projection._model.getLineCount() - 1 - projection._lineCount; - delta += charCount - (projection.end - projection.start); - } - } else { - for (; i < projections.length; i++) { - projection = projections[i]; - lineDelta += projection._model.getLineCount() - 1 - projection._lineCount; - delta += projection._model.getCharCount() - (projection.end - projection.start); - } - end = eventEnd = model.getCharCount() + delta; - } - var mapEnd = end - delta, rangeEnd = i, endLine = model.getLineAtOffset(mapEnd) + lineDelta; - - //events - var removedCharCount = eventEnd - eventStart; - var removedLineCount = endLine - startLine; - var addedCharCount = text.length; - var addedLineCount = 0; - var cr = 0, lf = 0, index = 0; - while (true) { - if (cr !== -1 && cr <= index) { cr = text.indexOf("\r", index); } - if (lf !== -1 && lf <= index) { lf = text.indexOf("\n", index); } - if (lf === -1 && cr === -1) { break; } - if (cr !== -1 && lf !== -1) { - if (cr + 1 === lf) { - index = lf + 1; - } else { - index = (cr < lf ? cr : lf) + 1; - } - } else if (cr !== -1) { - index = cr + 1; - } else { - index = lf + 1; - } - addedLineCount++; - } - - var modelChangingEvent = { - type: "Changing", - text: text, - start: eventStart, - removedCharCount: removedCharCount, - addedCharCount: addedCharCount, - removedLineCount: removedLineCount, - addedLineCount: addedLineCount - }; - this.onChanging(modelChangingEvent); - -// var changeLineCount = model.getLineAtOffset(mapEnd) - model.getLineAtOffset(mapStart) + addedLineCount; - model.setText(text, mapStart, mapEnd); - if (startProjection) { - projection = startProjection.projection; - projection._model.setText("", startProjection.start); - } - if (endProjection) { - projection = endProjection.projection; - projection._model.setText("", 0, endProjection.end); - projection.start = projection.end; - projection._lineCount = 0; - } - projections.splice(rangeStart, rangeEnd - rangeStart); - var changeCount = text.length - (mapEnd - mapStart); - for (i = rangeEnd; i < projections.length; i++) { - projection = projections[i]; - projection.start += changeCount; - projection.end += changeCount; -// if (projection._lineIndex + changeLineCount !== model.getLineAtOffset(projection.start)) { -// log("here"); -// } - projection._lineIndex = model.getLineAtOffset(projection.start); -// projection._lineIndex += changeLineCount; - } - - var modelChangedEvent = { - type: "Changed", - start: eventStart, - removedCharCount: removedCharCount, - addedCharCount: addedCharCount, - removedLineCount: removedLineCount, - addedLineCount: addedLineCount - }; - this.onChanged(modelChangedEvent); - } - }; - mEventTarget.EventTarget.addMixin(ProjectionTextModel.prototype); - - return {ProjectionTextModel: ProjectionTextModel}; -}); -/******************************************************************************* - * @license - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: IBM Corporation - initial API and implementation - ******************************************************************************/ - -/*global define setTimeout clearTimeout setInterval clearInterval Node */ - -define("orion/textview/tooltip", ['orion/textview/textView', 'orion/textview/textModel', 'orion/textview/projectionTextModel'], function(mTextView, mTextModel, mProjectionTextModel) { - - /** @private */ - function Tooltip (view) { - this._view = view; - //TODO add API to get the parent of the view - this._create(view._parent.ownerDocument); - view.addEventListener("Destroy", this, this.destroy); - } - Tooltip.getTooltip = function(view) { - if (!view._tooltip) { - view._tooltip = new Tooltip(view); - } - return view._tooltip; - }; - Tooltip.prototype = /** @lends orion.textview.Tooltip.prototype */ { - _create: function(document) { - if (this._domNode) { return; } - this._document = document; - var domNode = this._domNode = document.createElement("DIV"); - domNode.className = "viewTooltip"; - var viewParent = this._viewParent = document.createElement("DIV"); - domNode.appendChild(viewParent); - var htmlParent = this._htmlParent = document.createElement("DIV"); - domNode.appendChild(htmlParent); - document.body.appendChild(domNode); - this.hide(); - }, - destroy: function() { - if (!this._domNode) { return; } - if (this._contentsView) { - this._contentsView.destroy(); - this._contentsView = null; - this._emptyModel = null; - } - var parent = this._domNode.parentNode; - if (parent) { parent.removeChild(this._domNode); } - this._domNode = null; - }, - hide: function() { - if (this._contentsView) { - this._contentsView.setModel(this._emptyModel); - } - if (this._viewParent) { - this._viewParent.style.left = "-10000px"; - this._viewParent.style.position = "fixed"; - this._viewParent.style.visibility = "hidden"; - } - if (this._htmlParent) { - this._htmlParent.style.left = "-10000px"; - this._htmlParent.style.position = "fixed"; - this._htmlParent.style.visibility = "hidden"; - this._htmlParent.innerHTML = ""; - } - if (this._domNode) { - this._domNode.style.visibility = "hidden"; - } - if (this._showTimeout) { - clearTimeout(this._showTimeout); - this._showTimeout = null; - } - if (this._hideTimeout) { - clearTimeout(this._hideTimeout); - this._hideTimeout = null; - } - if (this._fadeTimeout) { - clearInterval(this._fadeTimeout); - this._fadeTimeout = null; - } - }, - isVisible: function() { - return this._domNode && this._domNode.style.visibility === "visible"; - }, - setTarget: function(target) { - if (this.target === target) { return; } - this._target = target; - this.hide(); - if (target) { - var self = this; - self._showTimeout = setTimeout(function() { - self.show(true); - }, 1000); - } - }, - show: function(autoHide) { - if (!this._target) { return; } - var info = this._target.getTooltipInfo(); - if (!info) { return; } - var domNode = this._domNode; - domNode.style.left = domNode.style.right = domNode.style.width = domNode.style.height = "auto"; - var contents = info.contents, contentsDiv; - if (contents instanceof Array) { - contents = this._getAnnotationContents(contents); - } - if (typeof contents === "string") { - (contentsDiv = this._htmlParent).innerHTML = contents; - } else if (contents instanceof Node) { - (contentsDiv = this._htmlParent).appendChild(contents); - } else if (contents instanceof mProjectionTextModel.ProjectionTextModel) { - if (!this._contentsView) { - this._emptyModel = new mTextModel.TextModel(""); - //TODO need hook into setup.js (or editor.js) to create a text view (and styler) - var newView = this._contentsView = new mTextView.TextView({ - model: this._emptyModel, - parent: this._viewParent, - tabSize: 4, - sync: true, - stylesheet: ["/orion/textview/tooltip.css", "/orion/textview/rulers.css", - "/examples/textview/textstyler.css", "/css/default-theme.css"] - }); - //TODO this is need to avoid IE from getting focus - newView._clientDiv.contentEditable = false; - //TODO need to find a better way of sharing the styler for multiple views - var view = this._view; - var listener = { - onLineStyle: function(e) { - view.onLineStyle(e); - } - }; - newView.addEventListener("LineStyle", listener.onLineStyle); - } - var contentsView = this._contentsView; - contentsView.setModel(contents); - var size = contentsView.computeSize(); - contentsDiv = this._viewParent; - //TODO always make the width larger than the size of the scrollbar to avoid bug in updatePage - contentsDiv.style.width = (size.width + 20) + "px"; - contentsDiv.style.height = size.height + "px"; - } else { - return; - } - contentsDiv.style.left = "auto"; - contentsDiv.style.position = "static"; - contentsDiv.style.visibility = "visible"; - var left = parseInt(this._getNodeStyle(domNode, "padding-left", "0"), 10); - left += parseInt(this._getNodeStyle(domNode, "border-left-width", "0"), 10); - if (info.anchor === "right") { - var right = parseInt(this._getNodeStyle(domNode, "padding-right", "0"), 10); - right += parseInt(this._getNodeStyle(domNode, "border-right-width", "0"), 10); - domNode.style.right = (domNode.ownerDocument.body.getBoundingClientRect().right - info.x + left + right) + "px"; - } else { - domNode.style.left = (info.x - left) + "px"; - } - var top = parseInt(this._getNodeStyle(domNode, "padding-top", "0"), 10); - top += parseInt(this._getNodeStyle(domNode, "border-top-width", "0"), 10); - domNode.style.top = (info.y - top) + "px"; - domNode.style.maxWidth = info.maxWidth + "px"; - domNode.style.maxHeight = info.maxHeight + "px"; - domNode.style.opacity = "1"; - domNode.style.visibility = "visible"; - if (autoHide) { - var self = this; - self._hideTimeout = setTimeout(function() { - var opacity = parseFloat(self._getNodeStyle(domNode, "opacity", "1")); - self._fadeTimeout = setInterval(function() { - if (domNode.style.visibility === "visible" && opacity > 0) { - opacity -= 0.1; - domNode.style.opacity = opacity; - return; - } - self.hide(); - }, 50); - }, 5000); - } - }, - _getAnnotationContents: function(annotations) { - if (annotations.length === 0) { - return null; - } - var model = this._view.getModel(), annotation; - var baseModel = model.getBaseModel ? model.getBaseModel() : model; - function getText(start, end) { - var textStart = baseModel.getLineStart(baseModel.getLineAtOffset(start)); - var textEnd = baseModel.getLineEnd(baseModel.getLineAtOffset(end), true); - return baseModel.getText(textStart, textEnd); - } - var title; - if (annotations.length === 1) { - annotation = annotations[0]; - if (annotation.title) { - title = annotation.title.replace(//g, ">"); - return "
" + annotation.html + " " + title + "
"; - } else { - var newModel = new mProjectionTextModel.ProjectionTextModel(baseModel); - var lineStart = baseModel.getLineStart(baseModel.getLineAtOffset(annotation.start)); - newModel.addProjection({start: annotation.end, end: newModel.getCharCount()}); - newModel.addProjection({start: 0, end: lineStart}); - return newModel; - } - } else { - var tooltipHTML = "
Multiple annotations:
"; - for (var i = 0; i < annotations.length; i++) { - annotation = annotations[i]; - title = annotation.title; - if (!title) { - title = getText(annotation.start, annotation.end); - } - title = title.replace(//g, ">"); - tooltipHTML += "
" + annotation.html + " " + title + "
"; - } - return tooltipHTML; - } - }, - _getNodeStyle: function(node, prop, defaultValue) { - var value; - if (node) { - value = node.style[prop]; - if (!value) { - if (node.currentStyle) { - var index = 0, p = prop; - while ((index = p.indexOf("-", index)) !== -1) { - p = p.substring(0, index) + p.substring(index + 1, index + 2).toUpperCase() + p.substring(index + 2); - } - value = node.currentStyle[p]; - } else { - var css = node.ownerDocument.defaultView.getComputedStyle(node, null); - value = css ? css.getPropertyValue(prop) : null; - } - } - } - return value || defaultValue; - } - }; - return {Tooltip: Tooltip}; -}); -/******************************************************************************* - * @license - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: - * Felipe Heidrich (IBM Corporation) - initial API and implementation - * Silenio Quarti (IBM Corporation) - initial API and implementation - * Mihai Sucan (Mozilla Foundation) - fix for Bug#334583 Bug#348471 Bug#349485 Bug#350595 Bug#360726 Bug#361180 Bug#362835 Bug#362428 Bug#362286 Bug#354270 Bug#361474 Bug#363945 Bug#366312 Bug#370584 - ******************************************************************************/ - -/*global window document navigator setTimeout clearTimeout XMLHttpRequest define DOMException */ - -define("orion/textview/textView", ['orion/textview/textModel', 'orion/textview/keyBinding', 'orion/textview/eventTarget'], function(mTextModel, mKeyBinding, mEventTarget) { - - /** @private */ - function addHandler(node, type, handler, capture) { - if (typeof node.addEventListener === "function") { - node.addEventListener(type, handler, capture === true); - } else { - node.attachEvent("on" + type, handler); - } - } - /** @private */ - function removeHandler(node, type, handler, capture) { - if (typeof node.removeEventListener === "function") { - node.removeEventListener(type, handler, capture === true); - } else { - node.detachEvent("on" + type, handler); - } - } - var userAgent = navigator.userAgent; - var isIE; - if (document.selection && window.ActiveXObject && /MSIE/.test(userAgent)) { - isIE = document.documentMode ? document.documentMode : 7; - } - var isFirefox = parseFloat(userAgent.split("Firefox/")[1] || userAgent.split("Minefield/")[1]) || undefined; - var isOpera = userAgent.indexOf("Opera") !== -1; - var isChrome = userAgent.indexOf("Chrome") !== -1; - var isSafari = userAgent.indexOf("Safari") !== -1 && !isChrome; - var isWebkit = userAgent.indexOf("WebKit") !== -1; - var isPad = userAgent.indexOf("iPad") !== -1; - var isMac = navigator.platform.indexOf("Mac") !== -1; - var isWindows = navigator.platform.indexOf("Win") !== -1; - var isLinux = navigator.platform.indexOf("Linux") !== -1; - var isW3CEvents = typeof window.document.documentElement.addEventListener === "function"; - var isRangeRects = (!isIE || isIE >= 9) && typeof window.document.createRange().getBoundingClientRect === "function"; - var platformDelimiter = isWindows ? "\r\n" : "\n"; - - /** - * Constructs a new Selection object. - * - * @class A Selection represents a range of selected text in the view. - * @name orion.textview.Selection - */ - function Selection (start, end, caret) { - /** - * The selection start offset. - * - * @name orion.textview.Selection#start - */ - this.start = start; - /** - * The selection end offset. - * - * @name orion.textview.Selection#end - */ - this.end = end; - /** @private */ - this.caret = caret; //true if the start, false if the caret is at end - } - Selection.prototype = /** @lends orion.textview.Selection.prototype */ { - /** @private */ - clone: function() { - return new Selection(this.start, this.end, this.caret); - }, - /** @private */ - collapse: function() { - if (this.caret) { - this.end = this.start; - } else { - this.start = this.end; - } - }, - /** @private */ - extend: function (offset) { - if (this.caret) { - this.start = offset; - } else { - this.end = offset; - } - if (this.start > this.end) { - var tmp = this.start; - this.start = this.end; - this.end = tmp; - this.caret = !this.caret; - } - }, - /** @private */ - setCaret: function(offset) { - this.start = offset; - this.end = offset; - this.caret = false; - }, - /** @private */ - getCaret: function() { - return this.caret ? this.start : this.end; - }, - /** @private */ - toString: function() { - return "start=" + this.start + " end=" + this.end + (this.caret ? " caret is at start" : " caret is at end"); - }, - /** @private */ - isEmpty: function() { - return this.start === this.end; - }, - /** @private */ - equals: function(object) { - return this.caret === object.caret && this.start === object.start && this.end === object.end; - } - }; - /** - * @class This object describes the options for the text view. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#setOptions} - * {@link orion.textview.TextView#getOptions} - *

- * @name orion.textview.TextViewOptions - * - * @property {String|DOMElement} parent the parent element for the view, it can be either a DOM element or an ID for a DOM element. - * @property {orion.textview.TextModel} [model] the text model for the view. If it is not set the view creates an empty {@link orion.textview.TextModel}. - * @property {Boolean} [readonly=false] whether or not the view is read-only. - * @property {Boolean} [fullSelection=true] whether or not the view is in full selection mode. - * @property {Boolean} [sync=false] whether or not the view creation should be synchronous (if possible). - * @property {Boolean} [expandTab=false] whether or not the tab key inserts white spaces. - * @property {String|String[]} [stylesheet] one or more stylesheet for the view. Each stylesheet can be either a URI or a string containing the CSS rules. - * @property {String} [themeClass] the CSS class for the view theming. - * @property {Number} [tabSize] The number of spaces in a tab. - */ - /** - * Constructs a new text view. - * - * @param {orion.textview.TextViewOptions} options the view options. - * - * @class A TextView is a user interface for editing text. - * @name orion.textview.TextView - * @borrows orion.textview.EventTarget#addEventListener as #addEventListener - * @borrows orion.textview.EventTarget#removeEventListener as #removeEventListener - * @borrows orion.textview.EventTarget#dispatchEvent as #dispatchEvent - */ - function TextView (options) { - this._init(options); - } - - TextView.prototype = /** @lends orion.textview.TextView.prototype */ { - /** - * Adds a ruler to the text view. - * - * @param {orion.textview.Ruler} ruler the ruler. - */ - addRuler: function (ruler) { - this._rulers.push(ruler); - ruler.setView(this); - this._createRuler(ruler); - this._updatePage(); - }, - computeSize: function() { - var w = 0, h = 0; - var model = this._model, clientDiv = this._clientDiv; - if (!clientDiv) { return {width: w, height: h}; } - var clientWidth = clientDiv.style.width; - /* - * Feature in WekKit. Webkit limits the width of the lines - * computed below to the width of the client div. This causes - * the lines to be wrapped even though "pre" is set. The fix - * is to set the width of the client div to a larger number - * before computing the lines width. Note that this value is - * reset to the appropriate value further down. - */ - if (isWebkit) { - clientDiv.style.width = (0x7FFFF).toString() + "px"; - } - var lineCount = model.getLineCount(); - var document = this._frameDocument; - for (var lineIndex=0; lineIndexThe supported coordinate spaces are: - *
    - *
  • "document" - relative to document, the origin is the top-left corner of first line
  • - *
  • "page" - relative to html page that contains the text view
  • - *
  • "view" - relative to text view, the origin is the top-left corner of the view container
  • - *
- *

- *

All methods in the view that take or return a position are in the document coordinate space.

- * - * @param rect the rectangle to convert. - * @param rect.x the x of the rectangle. - * @param rect.y the y of the rectangle. - * @param rect.width the width of the rectangle. - * @param rect.height the height of the rectangle. - * @param {String} from the source coordinate space. - * @param {String} to the destination coordinate space. - * - * @see #getLocationAtOffset - * @see #getOffsetAtLocation - * @see #getTopPixel - * @see #setTopPixel - */ - convert: function(rect, from, to) { - if (!this._clientDiv) { return; } - var scroll = this._getScroll(); - var viewPad = this._getViewPadding(); - var frame = this._frame.getBoundingClientRect(); - var viewRect = this._viewDiv.getBoundingClientRect(); - switch(from) { - case "document": - if (rect.x !== undefined) { - rect.x += - scroll.x + viewRect.left + viewPad.left; - } - if (rect.y !== undefined) { - rect.y += - scroll.y + viewRect.top + viewPad.top; - } - break; - case "page": - if (rect.x !== undefined) { - rect.x += - frame.left; - } - if (rect.y !== undefined) { - rect.y += - frame.top; - } - break; - } - //At this point rect is in the widget coordinate space - switch (to) { - case "document": - if (rect.x !== undefined) { - rect.x += scroll.x - viewRect.left - viewPad.left; - } - if (rect.y !== undefined) { - rect.y += scroll.y - viewRect.top - viewPad.top; - } - break; - case "page": - if (rect.x !== undefined) { - rect.x += frame.left; - } - if (rect.y !== undefined) { - rect.y += frame.top; - } - break; - } - return rect; - }, - /** - * Destroys the text view. - *

- * Removes the view from the page and frees all resources created by the view. - * Calling this function causes the "Destroy" event to be fire so that all components - * attached to view can release their references. - *

- * - * @see #onDestroy - */ - destroy: function() { - /* Destroy rulers*/ - for (var i=0; i< this._rulers.length; i++) { - this._rulers[i].setView(null); - } - this.rulers = null; - - /* - * Note that when the frame is removed, the unload event is trigged - * and the view contents and handlers is released properly by - * destroyView(). - */ - this._destroyFrame(); - - var e = {type: "Destroy"}; - this.onDestroy(e); - - this._parent = null; - this._parentDocument = null; - this._model = null; - this._selection = null; - this._doubleClickSelection = null; - this._keyBindings = null; - this._actions = null; - }, - /** - * Gives focus to the text view. - */ - focus: function() { - if (!this._clientDiv) { return; } - /* - * Feature in Chrome. When focus is called in the clientDiv without - * setting selection the browser will set the selection to the first dom - * element, which can be above the client area. When this happen the - * browser also scrolls the window to show that element. - * The fix is to call _updateDOMSelection() before calling focus(). - */ - this._updateDOMSelection(); - if (isPad) { - this._textArea.focus(); - } else { - if (isOpera) { this._clientDiv.blur(); } - this._clientDiv.focus(); - } - /* - * Feature in Safari. When focus is called the browser selects the clientDiv - * itself. The fix is to call _updateDOMSelection() after calling focus(). - */ - this._updateDOMSelection(); - }, - /** - * Check if the text view has focus. - * - * @returns {Boolean} true if the text view has focus, otherwise false. - */ - hasFocus: function() { - return this._hasFocus; - }, - /** - * Returns all action names defined in the text view. - *

- * There are two types of actions, the predefined actions of the view - * and the actions added by application code. - *

- *

- * The predefined actions are: - *

    - *
  • Navigation actions. These actions move the caret collapsing the selection.
  • - *
      - *
    • "lineUp" - moves the caret up by one line
    • - *
    • "lineDown" - moves the caret down by one line
    • - *
    • "lineStart" - moves the caret to beginning of the current line
    • - *
    • "lineEnd" - moves the caret to end of the current line
    • - *
    • "charPrevious" - moves the caret to the previous character
    • - *
    • "charNext" - moves the caret to the next character
    • - *
    • "pageUp" - moves the caret up by one page
    • - *
    • "pageDown" - moves the caret down by one page
    • - *
    • "wordPrevious" - moves the caret to the previous word
    • - *
    • "wordNext" - moves the caret to the next word
    • - *
    • "textStart" - moves the caret to the beginning of the document
    • - *
    • "textEnd" - moves the caret to the end of the document
    • - *
    - *
  • Selection actions. These actions move the caret extending the selection.
  • - *
      - *
    • "selectLineUp" - moves the caret up by one line
    • - *
    • "selectLineDown" - moves the caret down by one line
    • - *
    • "selectLineStart" - moves the caret to beginning of the current line
    • - *
    • "selectLineEnd" - moves the caret to end of the current line
    • - *
    • "selectCharPrevious" - moves the caret to the previous character
    • - *
    • "selectCharNext" - moves the caret to the next character
    • - *
    • "selectPageUp" - moves the caret up by one page
    • - *
    • "selectPageDown" - moves the caret down by one page
    • - *
    • "selectWordPrevious" - moves the caret to the previous word
    • - *
    • "selectWordNext" - moves the caret to the next word
    • - *
    • "selectTextStart" - moves the caret to the beginning of the document
    • - *
    • "selectTextEnd" - moves the caret to the end of the document
    • - *
    • "selectAll" - selects the entire document
    • - *
    - *
  • Edit actions. These actions modify the text view text
  • - *
      - *
    • "deletePrevious" - deletes the character preceding the caret
    • - *
    • "deleteNext" - deletes the charecter following the caret
    • - *
    • "deleteWordPrevious" - deletes the word preceding the caret
    • - *
    • "deleteWordNext" - deletes the word following the caret
    • - *
    • "tab" - inserts a tab character at the caret
    • - *
    • "enter" - inserts a line delimiter at the caret
    • - *
    - *
  • Clipboard actions.
  • - *
      - *
    • "copy" - copies the selected text to the clipboard
    • - *
    • "cut" - copies the selected text to the clipboard and deletes the selection
    • - *
    • "paste" - replaces the selected text with the clipboard contents
    • - *
    - *
- *

- * - * @param {Boolean} [defaultAction=false] whether or not the predefined actions are included. - * @returns {String[]} an array of action names defined in the text view. - * - * @see #invokeAction - * @see #setAction - * @see #setKeyBinding - * @see #getKeyBindings - */ - getActions: function (defaultAction) { - var result = []; - var actions = this._actions; - for (var i = 0; i < actions.length; i++) { - if (!defaultAction && actions[i].defaultHandler) { continue; } - result.push(actions[i].name); - } - return result; - }, - /** - * Returns the bottom index. - *

- * The bottom index is the line that is currently at the bottom of the view. This - * line may be partially visible depending on the vertical scroll of the view. The parameter - * fullyVisible determines whether to return only fully visible lines. - *

- * - * @param {Boolean} [fullyVisible=false] if true, returns the index of the last fully visible line. This - * parameter is ignored if the view is not big enough to show one line. - * @returns {Number} the index of the bottom line. - * - * @see #getTopIndex - * @see #setTopIndex - */ - getBottomIndex: function(fullyVisible) { - if (!this._clientDiv) { return 0; } - return this._getBottomIndex(fullyVisible); - }, - /** - * Returns the bottom pixel. - *

- * The bottom pixel is the pixel position that is currently at - * the bottom edge of the view. This position is relative to the - * beginning of the document. - *

- * - * @returns {Number} the bottom pixel. - * - * @see #getTopPixel - * @see #setTopPixel - * @see #convert - */ - getBottomPixel: function() { - if (!this._clientDiv) { return 0; } - return this._getScroll().y + this._getClientHeight(); - }, - /** - * Returns the caret offset relative to the start of the document. - * - * @returns the caret offset relative to the start of the document. - * - * @see #setCaretOffset - * @see #setSelection - * @see #getSelection - */ - getCaretOffset: function () { - var s = this._getSelection(); - return s.getCaret(); - }, - /** - * Returns the client area. - *

- * The client area is the portion in pixels of the document that is visible. The - * client area position is relative to the beginning of the document. - *

- * - * @returns the client area rectangle {x, y, width, height}. - * - * @see #getTopPixel - * @see #getBottomPixel - * @see #getHorizontalPixel - * @see #convert - */ - getClientArea: function() { - if (!this._clientDiv) { return {x: 0, y: 0, width: 0, height: 0}; } - var scroll = this._getScroll(); - return {x: scroll.x, y: scroll.y, width: this._getClientWidth(), height: this._getClientHeight()}; - }, - /** - * Returns the horizontal pixel. - *

- * The horizontal pixel is the pixel position that is currently at - * the left edge of the view. This position is relative to the - * beginning of the document. - *

- * - * @returns {Number} the horizontal pixel. - * - * @see #setHorizontalPixel - * @see #convert - */ - getHorizontalPixel: function() { - if (!this._clientDiv) { return 0; } - return this._getScroll().x; - }, - /** - * Returns all the key bindings associated to the given action name. - * - * @param {String} name the action name. - * @returns {orion.textview.KeyBinding[]} the array of key bindings associated to the given action name. - * - * @see #setKeyBinding - * @see #setAction - */ - getKeyBindings: function (name) { - var result = []; - var keyBindings = this._keyBindings; - for (var i = 0; i < keyBindings.length; i++) { - if (keyBindings[i].name === name) { - result.push(keyBindings[i].keyBinding); - } - } - return result; - }, - /** - * Returns the line height for a given line index. Returns the default line - * height if the line index is not specified. - * - * @param {Number} [lineIndex] the line index. - * @returns {Number} the height of the line in pixels. - * - * @see #getLinePixel - */ - getLineHeight: function(lineIndex) { - if (!this._clientDiv) { return 0; } - return this._getLineHeight(); - }, - /** - * Returns the top pixel position of a given line index relative to the beginning - * of the document. - *

- * Clamps out of range indices. - *

- * - * @param {Number} lineIndex the line index. - * @returns {Number} the pixel position of the line. - * - * @see #setTopPixel - * @see #convert - */ - getLinePixel: function(lineIndex) { - if (!this._clientDiv) { return 0; } - lineIndex = Math.min(Math.max(0, lineIndex), this._model.getLineCount()); - var lineHeight = this._getLineHeight(); - return lineHeight * lineIndex; - }, - /** - * Returns the {x, y} pixel location of the top-left corner of the character - * bounding box at the specified offset in the document. The pixel location - * is relative to the document. - *

- * Clamps out of range offsets. - *

- * - * @param {Number} offset the character offset - * @returns the {x, y} pixel location of the given offset. - * - * @see #getOffsetAtLocation - * @see #convert - */ - getLocationAtOffset: function(offset) { - if (!this._clientDiv) { return {x: 0, y: 0}; } - var model = this._model; - offset = Math.min(Math.max(0, offset), model.getCharCount()); - var lineIndex = model.getLineAtOffset(offset); - var scroll = this._getScroll(); - var viewRect = this._viewDiv.getBoundingClientRect(); - var viewPad = this._getViewPadding(); - var x = this._getOffsetToX(offset) + scroll.x - viewRect.left - viewPad.left; - var y = this.getLinePixel(lineIndex); - return {x: x, y: y}; - }, - /** - * Returns the specified view options. - *

- * The returned value is either a orion.textview.TextViewOptions or an option value. An option value is returned when only one string paremeter - * is specified. A orion.textview.TextViewOptions is returned when there are no paremeters, or the parameters are a list of options names or a - * orion.textview.TextViewOptions. All view options are returned when there no paremeters. - *

- * - * @param {String|orion.textview.TextViewOptions} [options] The options to return. - * @return {Object|orion.textview.TextViewOptions} The requested options or an option value. - * - * @see #setOptions - */ - getOptions: function() { - var options; - if (arguments.length === 0) { - options = this._defaultOptions(); - } else if (arguments.length === 1) { - var arg = arguments[0]; - if (typeof arg === "string") { - return this._clone(this["_" + arg]); - } - options = arg; - } else { - options = {}; - for (var index in arguments) { - if (arguments.hasOwnProperty(index)) { - options[arguments[index]] = undefined; - } - } - } - for (var option in options) { - if (options.hasOwnProperty(option)) { - options[option] = this._clone(this["_" + option]); - } - } - return options; - }, - /** - * Returns the text model of the text view. - * - * @returns {orion.textview.TextModel} the text model of the view. - */ - getModel: function() { - return this._model; - }, - /** - * Returns the character offset nearest to the given pixel location. The - * pixel location is relative to the document. - * - * @param x the x of the location - * @param y the y of the location - * @returns the character offset at the given location. - * - * @see #getLocationAtOffset - */ - getOffsetAtLocation: function(x, y) { - if (!this._clientDiv) { return 0; } - var scroll = this._getScroll(); - var viewRect = this._viewDiv.getBoundingClientRect(); - var viewPad = this._getViewPadding(); - var lineIndex = this._getYToLine(y - scroll.y); - x += -scroll.x + viewRect.left + viewPad.left; - var offset = this._getXToOffset(lineIndex, x); - return offset; - }, - /** - * Get the view rulers. - * - * @returns the view rulers - * - * @see #addRuler - */ - getRulers: function() { - return this._rulers.slice(0); - }, - /** - * Returns the text view selection. - *

- * The selection is defined by a start and end character offset relative to the - * document. The character at end offset is not included in the selection. - *

- * - * @returns {orion.textview.Selection} the view selection - * - * @see #setSelection - */ - getSelection: function () { - var s = this._getSelection(); - return {start: s.start, end: s.end}; - }, - /** - * Returns the text for the given range. - *

- * The text does not include the character at the end offset. - *

- * - * @param {Number} [start=0] the start offset of text range. - * @param {Number} [end=char count] the end offset of text range. - * - * @see #setText - */ - getText: function(start, end) { - var model = this._model; - return model.getText(start, end); - }, - /** - * Returns the top index. - *

- * The top index is the line that is currently at the top of the view. This - * line may be partially visible depending on the vertical scroll of the view. The parameter - * fullyVisible determines whether to return only fully visible lines. - *

- * - * @param {Boolean} [fullyVisible=false] if true, returns the index of the first fully visible line. This - * parameter is ignored if the view is not big enough to show one line. - * @returns {Number} the index of the top line. - * - * @see #getBottomIndex - * @see #setTopIndex - */ - getTopIndex: function(fullyVisible) { - if (!this._clientDiv) { return 0; } - return this._getTopIndex(fullyVisible); - }, - /** - * Returns the top pixel. - *

- * The top pixel is the pixel position that is currently at - * the top edge of the view. This position is relative to the - * beginning of the document. - *

- * - * @returns {Number} the top pixel. - * - * @see #getBottomPixel - * @see #setTopPixel - * @see #convert - */ - getTopPixel: function() { - if (!this._clientDiv) { return 0; } - return this._getScroll().y; - }, - /** - * Executes the action handler associated with the given name. - *

- * The application defined action takes precedence over predefined actions unless - * the defaultAction paramater is true. - *

- *

- * If the application defined action returns false, the text view predefined - * action is executed if present. - *

- * - * @param {String} name the action name. - * @param {Boolean} [defaultAction] whether to always execute the predefined action. - * @returns {Boolean} true if the action was executed. - * - * @see #setAction - * @see #getActions - */ - invokeAction: function (name, defaultAction) { - if (!this._clientDiv) { return; } - var actions = this._actions; - for (var i = 0; i < actions.length; i++) { - var a = actions[i]; - if (a.name && a.name === name) { - if (!defaultAction && a.userHandler) { - if (a.userHandler()) { return; } - } - if (a.defaultHandler) { return a.defaultHandler(); } - return false; - } - } - return false; - }, - /** - * Returns if the view is loaded. - *

- * @returns {Boolean} true if the view is loaded. - * - * @see #onLoad - */ - isLoaded: function () { - return !!this._clientDiv; - }, - /** - * @class This is the event sent when the user right clicks or otherwise invokes the context menu of the view. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onContextMenu} - *

- * - * @name orion.textview.ContextMenuEvent - * - * @property {Number} x The pointer location on the x axis, relative to the document the user is editing. - * @property {Number} y The pointer location on the y axis, relative to the document the user is editing. - * @property {Number} screenX The pointer location on the x axis, relative to the screen. This is copied from the DOM contextmenu event.screenX property. - * @property {Number} screenY The pointer location on the y axis, relative to the screen. This is copied from the DOM contextmenu event.screenY property. - */ - /** - * This event is sent when the user invokes the view context menu. - * - * @event - * @param {orion.textview.ContextMenuEvent} contextMenuEvent the event - */ - onContextMenu: function(contextMenuEvent) { - return this.dispatchEvent(contextMenuEvent); - }, - onDragStart: function(dragEvent) { - return this.dispatchEvent(dragEvent); - }, - onDrag: function(dragEvent) { - return this.dispatchEvent(dragEvent); - }, - onDragEnd: function(dragEvent) { - return this.dispatchEvent(dragEvent); - }, - onDragEnter: function(dragEvent) { - return this.dispatchEvent(dragEvent); - }, - onDragOver: function(dragEvent) { - return this.dispatchEvent(dragEvent); - }, - onDragLeave: function(dragEvent) { - return this.dispatchEvent(dragEvent); - }, - onDrop: function(dragEvent) { - return this.dispatchEvent(dragEvent); - }, - /** - * @class This is the event sent when the text view is destroyed. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onDestroy} - *

- * @name orion.textview.DestroyEvent - */ - /** - * This event is sent when the text view has been destroyed. - * - * @event - * @param {orion.textview.DestroyEvent} destroyEvent the event - * - * @see #destroy - */ - onDestroy: function(destroyEvent) { - return this.dispatchEvent(destroyEvent); - }, - /** - * @class This object is used to define style information for the text view. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onLineStyle} - *

- * @name orion.textview.Style - * - * @property {String} styleClass A CSS class name. - * @property {Object} style An object with CSS properties. - * @property {String} tagName A DOM tag name. - * @property {Object} attributes An object with DOM attributes. - */ - /** - * @class This object is used to style range. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onLineStyle} - *

- * @name orion.textview.StyleRange - * - * @property {Number} start The start character offset, relative to the document, where the style should be applied. - * @property {Number} end The end character offset (exclusive), relative to the document, where the style should be applied. - * @property {orion.textview.Style} style The style for the range. - */ - /** - * @class This is the event sent when the text view needs the style information for a line. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onLineStyle} - *

- * @name orion.textview.LineStyleEvent - * - * @property {orion.textview.TextView} textView The text view. - * @property {Number} lineIndex The line index. - * @property {String} lineText The line text. - * @property {Number} lineStart The character offset, relative to document, of the first character in the line. - * @property {orion.textview.Style} style The style for the entire line (output argument). - * @property {orion.textview.StyleRange[]} ranges An array of style ranges for the line (output argument). - */ - /** - * This event is sent when the text view needs the style information for a line. - * - * @event - * @param {orion.textview.LineStyleEvent} lineStyleEvent the event - */ - onLineStyle: function(lineStyleEvent) { - return this.dispatchEvent(lineStyleEvent); - }, - /** - * @class This is the event sent when the text view has loaded its contents. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onLoad} - *

- * @name orion.textview.LoadEvent - */ - /** - * This event is sent when the text view has loaded its contents. - * - * @event - * @param {orion.textview.LoadEvent} loadEvent the event - */ - onLoad: function(loadEvent) { - return this.dispatchEvent(loadEvent); - }, - /** - * @class This is the event sent when the text in the model has changed. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onModelChanged}
- * {@link orion.textview.TextModel#onChanged} - *

- * @name orion.textview.ModelChangedEvent - * - * @property {Number} start The character offset in the model where the change has occurred. - * @property {Number} removedCharCount The number of characters removed from the model. - * @property {Number} addedCharCount The number of characters added to the model. - * @property {Number} removedLineCount The number of lines removed from the model. - * @property {Number} addedLineCount The number of lines added to the model. - */ - /** - * This event is sent when the text in the model has changed. - * - * @event - * @param {orion.textview.ModelChangedEvent} modelChangedEvent the event - */ - onModelChanged: function(modelChangedEvent) { - return this.dispatchEvent(modelChangedEvent); - }, - /** - * @class This is the event sent when the text in the model is about to change. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onModelChanging}
- * {@link orion.textview.TextModel#onChanging} - *

- * @name orion.textview.ModelChangingEvent - * - * @property {String} text The text that is about to be inserted in the model. - * @property {Number} start The character offset in the model where the change will occur. - * @property {Number} removedCharCount The number of characters being removed from the model. - * @property {Number} addedCharCount The number of characters being added to the model. - * @property {Number} removedLineCount The number of lines being removed from the model. - * @property {Number} addedLineCount The number of lines being added to the model. - */ - /** - * This event is sent when the text in the model is about to change. - * - * @event - * @param {orion.textview.ModelChangingEvent} modelChangingEvent the event - */ - onModelChanging: function(modelChangingEvent) { - return this.dispatchEvent(modelChangingEvent); - }, - /** - * @class This is the event sent when the text is modified by the text view. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onModify} - *

- * @name orion.textview.ModifyEvent - */ - /** - * This event is sent when the text view has changed text in the model. - *

- * If the text is changed directly through the model API, this event - * is not sent. - *

- * - * @event - * @param {orion.textview.ModifyEvent} modifyEvent the event - */ - onModify: function(modifyEvent) { - return this.dispatchEvent(modifyEvent); - }, - onMouseDown: function(mouseEvent) { - return this.dispatchEvent(mouseEvent); - }, - onMouseUp: function(mouseEvent) { - return this.dispatchEvent(mouseEvent); - }, - onMouseMove: function(mouseEvent) { - return this.dispatchEvent(mouseEvent); - }, - onMouseOver: function(mouseEvent) { - return this.dispatchEvent(mouseEvent); - }, - onMouseOut: function(mouseEvent) { - return this.dispatchEvent(mouseEvent); - }, - /** - * @class This is the event sent when the selection changes in the text view. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onSelection} - *

- * @name orion.textview.SelectionEvent - * - * @property {orion.textview.Selection} oldValue The old selection. - * @property {orion.textview.Selection} newValue The new selection. - */ - /** - * This event is sent when the text view selection has changed. - * - * @event - * @param {orion.textview.SelectionEvent} selectionEvent the event - */ - onSelection: function(selectionEvent) { - return this.dispatchEvent(selectionEvent); - }, - /** - * @class This is the event sent when the text view scrolls. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onScroll} - *

- * @name orion.textview.ScrollEvent - * - * @property oldValue The old scroll {x,y}. - * @property newValue The new scroll {x,y}. - */ - /** - * This event is sent when the text view scrolls vertically or horizontally. - * - * @event - * @param {orion.textview.ScrollEvent} scrollEvent the event - */ - onScroll: function(scrollEvent) { - return this.dispatchEvent(scrollEvent); - }, - /** - * @class This is the event sent when the text is about to be modified by the text view. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onVerify} - *

- * @name orion.textview.VerifyEvent - * - * @property {String} text The text being inserted. - * @property {Number} start The start offset of the text range to be replaced. - * @property {Number} end The end offset (exclusive) of the text range to be replaced. - */ - /** - * This event is sent when the text view is about to change text in the model. - *

- * If the text is changed directly through the model API, this event - * is not sent. - *

- *

- * Listeners are allowed to change these parameters. Setting text to null - * or undefined stops the change. - *

- * - * @event - * @param {orion.textview.VerifyEvent} verifyEvent the event - */ - onVerify: function(verifyEvent) { - return this.dispatchEvent(verifyEvent); - }, - /** - * @class This is the event sent when the text view has unloaded its contents. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onLoad} - *

- * @name orion.textview.UnloadEvent - */ - /** - * This event is sent when the text view has unloaded its contents. - * - * @event - * @param {orion.textview.UnloadEvent} unloadEvent the event - */ - onUnload: function(unloadEvent) { - return this.dispatchEvent(unloadEvent); - }, - /** - * @class This is the event sent when the text view is focused. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onFocus}
- *

- * @name orion.textview.FocusEvent - */ - /** - * This event is sent when the text view is focused. - * - * @event - * @param {orion.textview.FocusEvent} focusEvent the event - */ - onFocus: function(focusEvent) { - return this.dispatchEvent(focusEvent); - }, - /** - * @class This is the event sent when the text view goes out of focus. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#event:onBlur}
- *

- * @name orion.textview.BlurEvent - */ - /** - * This event is sent when the text view goes out of focus. - * - * @event - * @param {orion.textview.BlurEvent} blurEvent the event - */ - onBlur: function(blurEvent) { - return this.dispatchEvent(blurEvent); - }, - /** - * Redraws the entire view, including rulers. - * - * @see #redrawLines - * @see #redrawRange - * @see #setRedraw - */ - redraw: function() { - if (this._redrawCount > 0) { return; } - var lineCount = this._model.getLineCount(); - var rulers = this.getRulers(); - for (var i = 0; i < rulers.length; i++) { - this.redrawLines(0, lineCount, rulers[i]); - } - this.redrawLines(0, lineCount); - }, - /** - * Redraws the text in the given line range. - *

- * The line at the end index is not redrawn. - *

- * - * @param {Number} [startLine=0] the start line - * @param {Number} [endLine=line count] the end line - * - * @see #redraw - * @see #redrawRange - * @see #setRedraw - */ - redrawLines: function(startLine, endLine, ruler) { - if (this._redrawCount > 0) { return; } - if (startLine === undefined) { startLine = 0; } - if (endLine === undefined) { endLine = this._model.getLineCount(); } - if (startLine === endLine) { return; } - var div = this._clientDiv; - if (!div) { return; } - if (ruler) { - var location = ruler.getLocation();//"left" or "right" - var divRuler = location === "left" ? this._leftDiv : this._rightDiv; - var cells = divRuler.firstChild.rows[0].cells; - for (var i = 0; i < cells.length; i++) { - if (cells[i].firstChild._ruler === ruler) { - div = cells[i].firstChild; - break; - } - } - } - if (ruler) { - div.rulerChanged = true; - } - if (!ruler || ruler.getOverview() === "page") { - var child = div.firstChild; - while (child) { - var lineIndex = child.lineIndex; - if (startLine <= lineIndex && lineIndex < endLine) { - child.lineChanged = true; - } - child = child.nextSibling; - } - } - if (!ruler) { - if (startLine <= this._maxLineIndex && this._maxLineIndex < endLine) { - this._checkMaxLineIndex = this._maxLineIndex; - this._maxLineIndex = -1; - this._maxLineWidth = 0; - } - } - this._queueUpdatePage(); - }, - /** - * Redraws the text in the given range. - *

- * The character at the end offset is not redrawn. - *

- * - * @param {Number} [start=0] the start offset of text range - * @param {Number} [end=char count] the end offset of text range - * - * @see #redraw - * @see #redrawLines - * @see #setRedraw - */ - redrawRange: function(start, end) { - if (this._redrawCount > 0) { return; } - var model = this._model; - if (start === undefined) { start = 0; } - if (end === undefined) { end = model.getCharCount(); } - var startLine = model.getLineAtOffset(start); - var endLine = model.getLineAtOffset(Math.max(start, end - 1)) + 1; - this.redrawLines(startLine, endLine); - }, - /** - * Removes a ruler from the text view. - * - * @param {orion.textview.Ruler} ruler the ruler. - */ - removeRuler: function (ruler) { - var rulers = this._rulers; - for (var i=0; i - * If the action name is a predefined action, the given handler executes before - * the default action handler. If the given handler returns true, the - * default action handler is not called. - *

- * - * @param {String} name the action name. - * @param {Function} handler the action handler. - * - * @see #getActions - * @see #invokeAction - */ - setAction: function(name, handler) { - if (!name) { return; } - var actions = this._actions; - for (var i = 0; i < actions.length; i++) { - var a = actions[i]; - if (a.name === name) { - a.userHandler = handler; - return; - } - } - actions.push({name: name, userHandler: handler}); - }, - /** - * Associates a key binding with the given action name. Any previous - * association with the specified key binding is overwriten. If the - * action name is null, the association is removed. - * - * @param {orion.textview.KeyBinding} keyBinding the key binding - * @param {String} name the action - */ - setKeyBinding: function(keyBinding, name) { - var keyBindings = this._keyBindings; - for (var i = 0; i < keyBindings.length; i++) { - var kb = keyBindings[i]; - if (kb.keyBinding.equals(keyBinding)) { - if (name) { - kb.name = name; - } else { - if (kb.predefined) { - kb.name = null; - } else { - var oldName = kb.name; - keyBindings.splice(i, 1); - var index = 0; - while (index < keyBindings.length && oldName !== keyBindings[index].name) { - index++; - } - if (index === keyBindings.length) { - /*

- * Removing all the key bindings associated to an user action will cause - * the user action to be removed. TextView predefined actions are never - * removed (so they can be reinstalled in the future). - *

- */ - var actions = this._actions; - for (var j = 0; j < actions.length; j++) { - if (actions[j].name === oldName) { - if (!actions[j].defaultHandler) { - actions.splice(j, 1); - } - } - } - } - } - } - return; - } - } - if (name) { - keyBindings.push({keyBinding: keyBinding, name: name}); - } - }, - /** - * Sets the caret offset relative to the start of the document. - * - * @param {Number} caret the caret offset relative to the start of the document. - * @param {Boolean} [show=true] if true, the view will scroll if needed to show the caret location. - * - * @see #getCaretOffset - * @see #setSelection - * @see #getSelection - */ - setCaretOffset: function(offset, show) { - var charCount = this._model.getCharCount(); - offset = Math.max(0, Math.min (offset, charCount)); - var selection = new Selection(offset, offset, false); - this._setSelection (selection, show === undefined || show); - }, - /** - * Sets the horizontal pixel. - *

- * The horizontal pixel is the pixel position that is currently at - * the left edge of the view. This position is relative to the - * beginning of the document. - *

- * - * @param {Number} pixel the horizontal pixel. - * - * @see #getHorizontalPixel - * @see #convert - */ - setHorizontalPixel: function(pixel) { - if (!this._clientDiv) { return; } - pixel = Math.max(0, pixel); - this._scrollView(pixel - this._getScroll().x, 0); - }, - /** - * Sets whether the view should update the DOM. - *

- * This can be used to improve the performance. - *

- * When the flag is set to true, - * the entire view is marked as needing to be redrawn. - * Nested calls to this method are stacked. - *

- * - * @param {Boolean} redraw the new redraw state - * - * @see #redraw - */ - setRedraw: function(redraw) { - if (redraw) { - if (--this._redrawCount === 0) { - this.redraw(); - } - } else { - this._redrawCount++; - } - }, - /** - * Sets the text model of the text view. - * - * @param {orion.textview.TextModel} model the text model of the view. - */ - setModel: function(model) { - if (!model) { return; } - if (model === this._model) { return; } - this._model.removeEventListener("Changing", this._modelListener.onChanging); - this._model.removeEventListener("Changed", this._modelListener.onChanged); - var oldLineCount = this._model.getLineCount(); - var oldCharCount = this._model.getCharCount(); - var newLineCount = model.getLineCount(); - var newCharCount = model.getCharCount(); - var newText = model.getText(); - var e = { - type: "ModelChanging", - text: newText, - start: 0, - removedCharCount: oldCharCount, - addedCharCount: newCharCount, - removedLineCount: oldLineCount, - addedLineCount: newLineCount - }; - this.onModelChanging(e); - this._model = model; - e = { - type: "ModelChanged", - start: 0, - removedCharCount: oldCharCount, - addedCharCount: newCharCount, - removedLineCount: oldLineCount, - addedLineCount: newLineCount - }; - this.onModelChanged(e); - this._model.addEventListener("Changing", this._modelListener.onChanging); - this._model.addEventListener("Changed", this._modelListener.onChanged); - this._reset(); - this._updatePage(); - }, - /** - * Sets the view options for the view. - * - * @param {orion.textview.TextViewOptions} options the view options. - * - * @see #getOptions - */ - setOptions: function (options) { - var defaultOptions = this._defaultOptions(); - var recreate = false, option, created = this._clientDiv; - if (created) { - for (option in options) { - if (options.hasOwnProperty(option)) { - if (defaultOptions[option].recreate) { - recreate = true; - break; - } - } - } - } - var changed = false; - for (option in options) { - if (options.hasOwnProperty(option)) { - var newValue = options[option], oldValue = this["_" + option]; - if (this._compare(oldValue, newValue)) { continue; } - changed = true; - if (!recreate) { - var update = defaultOptions[option].update; - if (created && update) { - if (update.call(this, newValue)) { - recreate = true; - } - continue; - } - } - this["_" + option] = this._clone(newValue); - } - } - if (changed) { - if (recreate) { - var oldParent = this._frame.parentNode; - oldParent.removeChild(this._frame); - this._parent.appendChild(this._frame); - } - } - }, - /** - * Sets the text view selection. - *

- * The selection is defined by a start and end character offset relative to the - * document. The character at end offset is not included in the selection. - *

- *

- * The caret is always placed at the end offset. The start offset can be - * greater than the end offset to place the caret at the beginning of the - * selection. - *

- *

- * Clamps out of range offsets. - *

- * - * @param {Number} start the start offset of the selection - * @param {Number} end the end offset of the selection - * @param {Boolean} [show=true] if true, the view will scroll if needed to show the caret location. - * - * @see #getSelection - */ - setSelection: function (start, end, show) { - var caret = start > end; - if (caret) { - var tmp = start; - start = end; - end = tmp; - } - var charCount = this._model.getCharCount(); - start = Math.max(0, Math.min (start, charCount)); - end = Math.max(0, Math.min (end, charCount)); - var selection = new Selection(start, end, caret); - this._setSelection(selection, show === undefined || show); - }, - /** - * Replaces the text in the given range with the given text. - *

- * The character at the end offset is not replaced. - *

- *

- * When both start and end parameters - * are not specified, the text view places the caret at the beginning - * of the document and scrolls to make it visible. - *

- * - * @param {String} text the new text. - * @param {Number} [start=0] the start offset of text range. - * @param {Number} [end=char count] the end offset of text range. - * - * @see #getText - */ - setText: function (text, start, end) { - var reset = start === undefined && end === undefined; - if (start === undefined) { start = 0; } - if (end === undefined) { end = this._model.getCharCount(); } - this._modifyContent({text: text, start: start, end: end, _code: true}, !reset); - if (reset) { - this._columnX = -1; - this._setSelection(new Selection (0, 0, false), true); - - /* - * Bug in Firefox. For some reason, the caret does not show after the - * view is refreshed. The fix is to toggle the contentEditable state and - * force the clientDiv to loose and receive focus if it is focused. - */ - if (isFirefox) { - this._fixCaret(); - } - } - }, - /** - * Sets the top index. - *

- * The top index is the line that is currently at the top of the text view. This - * line may be partially visible depending on the vertical scroll of the view. - *

- * - * @param {Number} topIndex the index of the top line. - * - * @see #getBottomIndex - * @see #getTopIndex - */ - setTopIndex: function(topIndex) { - if (!this._clientDiv) { return; } - var model = this._model; - if (model.getCharCount() === 0) { - return; - } - var lineCount = model.getLineCount(); - var lineHeight = this._getLineHeight(); - var pageSize = Math.max(1, Math.min(lineCount, Math.floor(this._getClientHeight () / lineHeight))); - if (topIndex < 0) { - topIndex = 0; - } else if (topIndex > lineCount - pageSize) { - topIndex = lineCount - pageSize; - } - var pixel = topIndex * lineHeight - this._getScroll().y; - this._scrollView(0, pixel); - }, - /** - * Sets the top pixel. - *

- * The top pixel is the pixel position that is currently at - * the top edge of the view. This position is relative to the - * beginning of the document. - *

- * - * @param {Number} pixel the top pixel. - * - * @see #getBottomPixel - * @see #getTopPixel - * @see #convert - */ - setTopPixel: function(pixel) { - if (!this._clientDiv) { return; } - var lineHeight = this._getLineHeight(); - var clientHeight = this._getClientHeight(); - var lineCount = this._model.getLineCount(); - pixel = Math.min(Math.max(0, pixel), lineHeight * lineCount - clientHeight); - this._scrollView(0, pixel - this._getScroll().y); - }, - /** - * Scrolls the selection into view if needed. - * - * @returns true if the view was scrolled. - * - * @see #getSelection - * @see #setSelection - */ - showSelection: function() { - return this._showCaret(true); - }, - - /**************************************** Event handlers *********************************/ - _handleBodyMouseDown: function (e) { - if (!e) { e = window.event; } - if (isFirefox && e.which === 1) { - this._clientDiv.contentEditable = false; - (this._overlayDiv || this._clientDiv).draggable = true; - this._ignoreBlur = true; - } - - /* - * Prevent clicks outside of the view from taking focus - * away the view. Note that in Firefox and Opera clicking on the - * scrollbar also take focus from the view. Other browsers - * do not have this problem and stopping the click over the - * scrollbar for them causes mouse capture problems. - */ - var topNode = isOpera || (isFirefox && !this._overlayDiv) ? this._clientDiv : this._overlayDiv || this._viewDiv; - - var temp = e.target ? e.target : e.srcElement; - while (temp) { - if (topNode === temp) { - return; - } - temp = temp.parentNode; - } - if (e.preventDefault) { e.preventDefault(); } - if (e.stopPropagation){ e.stopPropagation(); } - if (!isW3CEvents) { - /* In IE 8 is not possible to prevent the default handler from running - * during mouse down event using usual API. The workaround is to use - * setCapture/releaseCapture. - */ - topNode.setCapture(); - setTimeout(function() { topNode.releaseCapture(); }, 0); - } - }, - _handleBodyMouseUp: function (e) { - if (!e) { e = window.event; } - if (isFirefox && e.which === 1) { - this._clientDiv.contentEditable = true; - (this._overlayDiv || this._clientDiv).draggable = false; - - /* - * Bug in Firefox. For some reason, Firefox stops showing the caret - * in some cases. For example when the user cancels a drag operation - * by pressing ESC. The fix is to detect that the drag operation was - * cancelled, toggle the contentEditable state and force the clientDiv - * to loose and receive focus if it is focused. - */ - this._fixCaret(); - this._ignoreBlur = false; - } - }, - _handleBlur: function (e) { - if (!e) { e = window.event; } - if (this._ignoreBlur) { return; } - this._hasFocus = false; - /* - * Bug in IE 8 and earlier. For some reason when text is deselected - * the overflow selection at the end of some lines does not get redrawn. - * The fix is to create a DOM element in the body to force a redraw. - */ - if (isIE < 9) { - if (!this._getSelection().isEmpty()) { - var document = this._frameDocument; - var child = document.createElement("DIV"); - var body = document.body; - body.appendChild(child); - body.removeChild(child); - } - } - if (isFirefox || isIE) { - if (this._selDiv1) { - var color = isIE ? "transparent" : "#AFAFAF"; - this._selDiv1.style.background = color; - this._selDiv2.style.background = color; - this._selDiv3.style.background = color; - } - } - if (!this._ignoreFocus) { - this.onBlur({type: "Blur"}); - } - }, - _handleContextMenu: function (e) { - if (!e) { e = window.event; } - if (isFirefox && this._lastMouseButton === 3) { - // We need to update the DOM selection, because on - // right-click the caret moves to the mouse location. - // See bug 366312. - var timeDiff = e.timeStamp - this._lastMouseTime; - if (timeDiff <= this._clickTime) { - this._updateDOMSelection(); - } - } - if (this.isListening("ContextMenu")) { - var evt = this._createMouseEvent("ContextMenu", e); - evt.screenX = e.screenX; - evt.screenY = e.screenY; - this.onContextMenu(evt); - } - if (e.preventDefault) { e.preventDefault(); } - return false; - }, - _handleCopy: function (e) { - if (this._ignoreCopy) { return; } - if (!e) { e = window.event; } - if (this._doCopy(e)) { - if (e.preventDefault) { e.preventDefault(); } - return false; - } - }, - _handleCut: function (e) { - if (!e) { e = window.event; } - if (this._doCut(e)) { - if (e.preventDefault) { e.preventDefault(); } - return false; - } - }, - _handleDOMAttrModified: function (e) { - if (!e) { e = window.event; } - var ancestor = false; - var parent = this._parent; - while (parent) { - if (parent === e.target) { - ancestor = true; - break; - } - parent = parent.parentNode; - } - if (!ancestor) { return; } - var state = this._getVisible(); - if (state === "visible") { - this._createView(); - } else if (state === "hidden") { - this._destroyView(); - } - }, - _handleDataModified: function(e) { - this._startIME(); - }, - _handleDblclick: function (e) { - if (!e) { e = window.event; } - var time = e.timeStamp ? e.timeStamp : new Date().getTime(); - this._lastMouseTime = time; - if (this._clickCount !== 2) { - this._clickCount = 2; - this._handleMouse(e); - } - }, - _handleDragStart: function (e) { - if (!e) { e = window.event; } - if (isFirefox) { - var self = this; - setTimeout(function() { - self._clientDiv.contentEditable = true; - self._clientDiv.draggable = false; - self._ignoreBlur = false; - }, 0); - } - if (this.isListening("DragStart") && this._dragOffset !== -1) { - this._isMouseDown = false; - this.onDragStart(this._createMouseEvent("DragStart", e)); - this._dragOffset = -1; - } else { - if (e.preventDefault) { e.preventDefault(); } - return false; - } - }, - _handleDrag: function (e) { - if (!e) { e = window.event; } - if (this.isListening("Drag")) { - this.onDrag(this._createMouseEvent("Drag", e)); - } - }, - _handleDragEnd: function (e) { - if (!e) { e = window.event; } - this._dropTarget = false; - this._dragOffset = -1; - if (this.isListening("DragEnd")) { - this.onDragEnd(this._createMouseEvent("DragEnd", e)); - } - if (isFirefox) { - this._fixCaret(); - /* - * Bug in Firefox. For some reason, Firefox stops showing the caret when the - * selection is dropped onto itself. The fix is to detected the case and - * call fixCaret() a second time. - */ - if (e.dataTransfer.dropEffect === "none" && !e.dataTransfer.mozUserCancelled) { - this._fixCaret(); - } - } - }, - _handleDragEnter: function (e) { - if (!e) { e = window.event; } - var prevent = true; - this._dropTarget = true; - if (this.isListening("DragEnter")) { - prevent = false; - this.onDragEnter(this._createMouseEvent("DragEnter", e)); - } - /* - * Webkit will not send drop events if this event is not prevented, as spec in HTML5. - * Firefox and IE do not follow this spec for contentEditable. Note that preventing this - * event will result is loss of functionality (insertion mark, etc). - */ - if (isWebkit || prevent) { - if (e.preventDefault) { e.preventDefault(); } - return false; - } - }, - _handleDragOver: function (e) { - if (!e) { e = window.event; } - var prevent = true; - if (this.isListening("DragOver")) { - prevent = false; - this.onDragOver(this._createMouseEvent("DragOver", e)); - } - /* - * Webkit will not send drop events if this event is not prevented, as spec in HTML5. - * Firefox and IE do not follow this spec for contentEditable. Note that preventing this - * event will result is loss of functionality (insertion mark, etc). - */ - if (isWebkit || prevent) { - if (prevent) { e.dataTransfer.dropEffect = "none"; } - if (e.preventDefault) { e.preventDefault(); } - return false; - } - }, - _handleDragLeave: function (e) { - if (!e) { e = window.event; } - this._dropTarget = false; - if (this.isListening("DragLeave")) { - this.onDragLeave(this._createMouseEvent("DragLeave", e)); - } - }, - _handleDrop: function (e) { - if (!e) { e = window.event; } - this._dropTarget = false; - if (this.isListening("Drop")) { - this.onDrop(this._createMouseEvent("Drop", e)); - } - /* - * This event must be prevented otherwise the user agent will modify - * the DOM. Note that preventing the event on some user agents (i.e. IE) - * indicates that the operation is cancelled. This causes the dropEffect to - * be set to none in the dragend event causing the implementor to not execute - * the code responsible by the move effect. - */ - if (e.preventDefault) { e.preventDefault(); } - return false; - }, - _handleDocFocus: function (e) { - if (!e) { e = window.event; } - this._clientDiv.focus(); - }, - _handleFocus: function (e) { - if (!e) { e = window.event; } - this._hasFocus = true; - /* - * Feature in IE. The selection is not restored when the - * view gets focus and the caret is always placed at the - * beginning of the document. The fix is to update the DOM - * selection during the focus event. - */ - if (isIE) { - this._updateDOMSelection(); - } - if (isFirefox || isIE) { - if (this._selDiv1) { - var color = this._hightlightRGB; - this._selDiv1.style.background = color; - this._selDiv2.style.background = color; - this._selDiv3.style.background = color; - } - } - if (!this._ignoreFocus) { - this.onFocus({type: "Focus"}); - } - }, - _handleKeyDown: function (e) { - if (!e) { e = window.event; } - if (isPad) { - if (e.keyCode === 8) { - this._doBackspace({}); - e.preventDefault(); - } - return; - } - switch (e.keyCode) { - case 16: /* Shift */ - case 17: /* Control */ - case 18: /* Alt */ - case 91: /* Command */ - break; - default: - this._setLinksVisible(false); - } - if (e.keyCode === 229) { - if (this._readonly) { - if (e.preventDefault) { e.preventDefault(); } - return false; - } - var startIME = true; - - /* - * Bug in Safari. Some Control+key combinations send key events - * with keyCode equals to 229. This is unexpected and causes the - * view to start an IME composition. The fix is to ignore these - * events. - */ - if (isSafari && isMac) { - if (e.ctrlKey) { - startIME = false; - } - } - if (startIME) { - this._startIME(); - } - } else { - this._commitIME(); - } - /* - * Feature in Firefox. When a key is held down the browser sends - * right number of keypress events but only one keydown. This is - * unexpected and causes the view to only execute an action - * just one time. The fix is to ignore the keydown event and - * execute the actions from the keypress handler. - * Note: This only happens on the Mac and Linux (Firefox 3.6). - * - * Feature in Opera. Opera sends keypress events even for non-printable - * keys. The fix is to handle actions in keypress instead of keydown. - */ - if (((isMac || isLinux) && isFirefox < 4) || isOpera) { - this._keyDownEvent = e; - return true; - } - - if (this._doAction(e)) { - if (e.preventDefault) { - e.preventDefault(); - } else { - e.cancelBubble = true; - e.returnValue = false; - e.keyCode = 0; - } - return false; - } - }, - _handleKeyPress: function (e) { - if (!e) { e = window.event; } - /* - * Feature in Embedded WebKit. Embedded WekKit on Mac runs in compatibility mode and - * generates key press events for these Unicode values (Function keys). This does not - * happen in Safari or Chrome. The fix is to ignore these key events. - */ - if (isMac && isWebkit) { - if ((0xF700 <= e.keyCode && e.keyCode <= 0xF7FF) || e.keyCode === 13 || e.keyCode === 8) { - if (e.preventDefault) { e.preventDefault(); } - return false; - } - } - if (((isMac || isLinux) && isFirefox < 4) || isOpera) { - if (this._doAction(this._keyDownEvent)) { - if (e.preventDefault) { e.preventDefault(); } - return false; - } - } - var ctrlKey = isMac ? e.metaKey : e.ctrlKey; - if (e.charCode !== undefined) { - if (ctrlKey) { - switch (e.charCode) { - /* - * In Firefox and Safari if ctrl+v, ctrl+c ctrl+x is canceled - * the clipboard events are not sent. The fix to allow - * the browser to handles these key events. - */ - case 99://c - case 118://v - case 120://x - return true; - } - } - } - var ignore = false; - if (isMac) { - if (e.ctrlKey || e.metaKey) { ignore = true; } - } else { - if (isFirefox) { - //Firefox clears the state mask when ALT GR generates input - if (e.ctrlKey || e.altKey) { ignore = true; } - } else { - //IE and Chrome only send ALT GR when input is generated - if (e.ctrlKey ^ e.altKey) { ignore = true; } - } - } - if (!ignore) { - var key = isOpera ? e.which : (e.charCode !== undefined ? e.charCode : e.keyCode); - if (key > 31) { - this._doContent(String.fromCharCode (key)); - if (e.preventDefault) { e.preventDefault(); } - return false; - } - } - }, - _handleKeyUp: function (e) { - if (!e) { e = window.event; } - var ctrlKey = isMac ? e.metaKey : e.ctrlKey; - if (!ctrlKey) { - this._setLinksVisible(false); - } - // don't commit for space (it happens during JP composition) - if (e.keyCode === 13) { - this._commitIME(); - } - }, - _handleLinkClick: function (e) { - if (!e) { e = window.event; } - var ctrlKey = isMac ? e.metaKey : e.ctrlKey; - if (!ctrlKey) { - if (e.preventDefault) { e.preventDefault(); } - return false; - } - }, - _handleLoad: function (e) { - var state = this._getVisible(); - if (state === "visible" || (state === "hidden" && isWebkit)) { - this._createView(); - } - }, - _handleMouse: function (e) { - var result = true; - var target = this._frameWindow; - if (isIE || (isFirefox && !this._overlayDiv)) { target = this._clientDiv; } - if (this._overlayDiv) { - if (this._hasFocus) { - this._ignoreFocus = true; - } - var self = this; - setTimeout(function () { - self.focus(); - self._ignoreFocus = false; - }, 0); - } - if (this._clickCount === 1) { - result = this._setSelectionTo(e.clientX, e.clientY, e.shiftKey, !isOpera && this.isListening("DragStart")); - if (result) { this._setGrab(target); } - } else { - /* - * Feature in IE8 and older, the sequence of events in the IE8 event model - * for a doule-click is: - * - * down - * up - * up - * dblclick - * - * Given that the mouse down/up events are not balanced, it is not possible to - * grab on mouse down and ungrab on mouse up. The fix is to grab on the first - * mouse down and ungrab on mouse move when the button 1 is not set. - */ - if (isW3CEvents) { this._setGrab(target); } - - this._doubleClickSelection = null; - this._setSelectionTo(e.clientX, e.clientY, e.shiftKey); - this._doubleClickSelection = this._getSelection(); - } - return result; - }, - _handleMouseDown: function (e) { - if (!e) { e = window.event; } - if (this.isListening("MouseDown")) { - this.onMouseDown(this._createMouseEvent("MouseDown", e)); - } - if (this._linksVisible) { - var target = e.target || e.srcElement; - if (target.tagName !== "A") { - this._setLinksVisible(false); - } else { - return; - } - } - this._commitIME(); - - var button = e.which; // 1 - left, 2 - middle, 3 - right - if (!button) { - // if IE 8 or older - if (e.button === 4) { button = 2; } - if (e.button === 2) { button = 3; } - if (e.button === 1) { button = 1; } - } - - // For middle click we always need getTime(). See _getClipboardText(). - var time = button !== 2 && e.timeStamp ? e.timeStamp : new Date().getTime(); - var timeDiff = time - this._lastMouseTime; - var deltaX = Math.abs(this._lastMouseX - e.clientX); - var deltaY = Math.abs(this._lastMouseY - e.clientY); - var sameButton = this._lastMouseButton === button; - this._lastMouseX = e.clientX; - this._lastMouseY = e.clientY; - this._lastMouseTime = time; - this._lastMouseButton = button; - - if (button === 1) { - this._isMouseDown = true; - if (sameButton && timeDiff <= this._clickTime && deltaX <= this._clickDist && deltaY <= this._clickDist) { - this._clickCount++; - } else { - this._clickCount = 1; - } - if (this._handleMouse(e) && (isOpera || isChrome || (isFirefox && !this._overlayDiv))) { - if (!this._hasFocus) { - this.focus(); - } - e.preventDefault(); - } - } - }, - _handleMouseOver: function (e) { - if (!e) { e = window.event; } - if (this.isListening("MouseOver")) { - this.onMouseOver(this._createMouseEvent("MouseOver", e)); - } - }, - _handleMouseOut: function (e) { - if (!e) { e = window.event; } - if (this.isListening("MouseOut")) { - this.onMouseOut(this._createMouseEvent("MouseOut", e)); - } - }, - _handleMouseMove: function (e) { - if (!e) { e = window.event; } - if (this.isListening("MouseMove")) { - var topNode = this._overlayDiv || this._clientDiv; - var temp = e.target ? e.target : e.srcElement; - while (temp) { - if (topNode === temp) { - this.onMouseMove(this._createMouseEvent("MouseMove", e)); - break; - } - temp = temp.parentNode; - } - } - if (this._dropTarget) { - return; - } - /* - * Bug in IE9. IE sends one mouse event when the user changes the text by - * pasting or undo. These operations usually happen with the Ctrl key - * down which causes the view to enter link mode. Link mode does not end - * because there are no further events. The fix is to only enter link - * mode when the coordinates of the mouse move event have changed. - */ - var changed = this._linksVisible || this._lastMouseMoveX !== e.clientX || this._lastMouseMoveY !== e.clientY; - this._lastMouseMoveX = e.clientX; - this._lastMouseMoveY = e.clientY; - this._setLinksVisible(changed && !this._isMouseDown && (isMac ? e.metaKey : e.ctrlKey)); - - /* - * Feature in IE8 and older, the sequence of events in the IE8 event model - * for a doule-click is: - * - * down - * up - * up - * dblclick - * - * Given that the mouse down/up events are not balanced, it is not possible to - * grab on mouse down and ungrab on mouse up. The fix is to grab on the first - * mouse down and ungrab on mouse move when the button 1 is not set. - * - * In order to detect double-click and drag gestures, it is necessary to send - * a mouse down event from mouse move when the button is still down and isMouseDown - * flag is not set. - */ - if (!isW3CEvents) { - if (e.button === 0) { - this._setGrab(null); - return true; - } - if (!this._isMouseDown && e.button === 1 && (this._clickCount & 1) !== 0) { - this._clickCount = 2; - return this._handleMouse(e, this._clickCount); - } - } - if (!this._isMouseDown || this._dragOffset !== -1) { - return; - } - - var x = e.clientX; - var y = e.clientY; - if (isChrome) { - if (e.currentTarget !== this._frameWindow) { - var rect = this._frame.getBoundingClientRect(); - x -= rect.left; - y -= rect.top; - } - } - var viewPad = this._getViewPadding(); - var viewRect = this._viewDiv.getBoundingClientRect(); - var width = this._getClientWidth (), height = this._getClientHeight(); - var leftEdge = viewRect.left + viewPad.left; - var topEdge = viewRect.top + viewPad.top; - var rightEdge = viewRect.left + viewPad.left + width; - var bottomEdge = viewRect.top + viewPad.top + height; - var model = this._model; - var caretLine = model.getLineAtOffset(this._getSelection().getCaret()); - if (y < topEdge && caretLine !== 0) { - this._doAutoScroll("up", x, y - topEdge); - } else if (y > bottomEdge && caretLine !== model.getLineCount() - 1) { - this._doAutoScroll("down", x, y - bottomEdge); - } else if (x < leftEdge) { - this._doAutoScroll("left", x - leftEdge, y); - } else if (x > rightEdge) { - this._doAutoScroll("right", x - rightEdge, y); - } else { - this._endAutoScroll(); - this._setSelectionTo(x, y, true); - /* - * Feature in IE. IE does redraw the selection background right - * away after the selection changes because of mouse move events. - * The fix is to call getBoundingClientRect() on the - * body element to force the selection to be redraw. Some how - * calling this method forces a redraw. - */ - if (isIE) { - var body = this._frameDocument.body; - body.getBoundingClientRect(); - } - } - }, - _createMouseEvent: function(type, e) { - var scroll = this._getScroll(); - var viewRect = this._viewDiv.getBoundingClientRect(); - var viewPad = this._getViewPadding(); - var x = e.clientX + scroll.x - viewRect.left - viewPad.left; - var y = e.clientY + scroll.y - viewRect.top; - return { - type: type, - event: e, - x: x, - y: y - }; - }, - _handleMouseUp: function (e) { - if (!e) { e = window.event; } - if (this.isListening("MouseUp")) { - this.onMouseUp(this._createMouseEvent("MouseUp", e)); - } - if (this._linksVisible) { - return; - } - var left = e.which ? e.button === 0 : e.button === 1; - if (left) { - if (this._dragOffset !== -1) { - var selection = this._getSelection(); - selection.extend(this._dragOffset); - selection.collapse(); - this._setSelection(selection, true, true); - this._dragOffset = -1; - } - this._isMouseDown = false; - this._endAutoScroll(); - - /* - * Feature in IE8 and older, the sequence of events in the IE8 event model - * for a doule-click is: - * - * down - * up - * up - * dblclick - * - * Given that the mouse down/up events are not balanced, it is not possible to - * grab on mouse down and ungrab on mouse up. The fix is to grab on the first - * mouse down and ungrab on mouse move when the button 1 is not set. - */ - if (isW3CEvents) { this._setGrab(null); } - - /* - * Note that there cases when Firefox sets the DOM selection in mouse up. - * This happens for example after a cancelled drag operation. - * - * Note that on Chrome and IE, the caret stops blicking if mouse up is - * prevented. - */ - if (isFirefox) { - e.preventDefault(); - } - } - }, - _handleMouseWheel: function (e) { - if (!e) { e = window.event; } - var lineHeight = this._getLineHeight(); - var pixelX = 0, pixelY = 0; - // Note: On the Mac the correct behaviour is to scroll by pixel. - if (isFirefox) { - var pixel; - if (isMac) { - pixel = e.detail * 3; - } else { - var limit = 256; - pixel = Math.max(-limit, Math.min(limit, e.detail)) * lineHeight; - } - if (e.axis === e.HORIZONTAL_AXIS) { - pixelX = pixel; - } else { - pixelY = pixel; - } - } else { - //Webkit - if (isMac) { - /* - * In Safari, the wheel delta is a multiple of 120. In order to - * convert delta to pixel values, it is necessary to divide delta - * by 40. - * - * In Chrome and Safari 5, the wheel delta depends on the type of the - * mouse. In general, it is the pixel value for Mac mice and track pads, - * but it is a multiple of 120 for other mice. There is no presise - * way to determine if it is pixel value or a multiple of 120. - * - * Note that the current approach does not calculate the correct - * pixel value for Mac mice when the delta is a multiple of 120. - */ - var denominatorX = 40, denominatorY = 40; - if (e.wheelDeltaX % 120 !== 0) { denominatorX = 1; } - if (e.wheelDeltaY % 120 !== 0) { denominatorY = 1; } - pixelX = -e.wheelDeltaX / denominatorX; - if (-1 < pixelX && pixelX < 0) { pixelX = -1; } - if (0 < pixelX && pixelX < 1) { pixelX = 1; } - pixelY = -e.wheelDeltaY / denominatorY; - if (-1 < pixelY && pixelY < 0) { pixelY = -1; } - if (0 < pixelY && pixelY < 1) { pixelY = 1; } - } else { - pixelX = -e.wheelDeltaX; - var linesToScroll = 8; - pixelY = (-e.wheelDeltaY / 120 * linesToScroll) * lineHeight; - } - } - /* - * Feature in Safari. If the event target is removed from the DOM - * safari stops smooth scrolling. The fix is keep the element target - * in the DOM and remove it on a later time. - * - * Note: Using a timer is not a solution, because the timeout needs to - * be at least as long as the gesture (which is too long). - */ - if (isSafari) { - var lineDiv = e.target; - while (lineDiv && lineDiv.lineIndex === undefined) { - lineDiv = lineDiv.parentNode; - } - this._mouseWheelLine = lineDiv; - } - var oldScroll = this._getScroll(); - this._scrollView(pixelX, pixelY); - var newScroll = this._getScroll(); - if (isSafari) { this._mouseWheelLine = null; } - if (oldScroll.x !== newScroll.x || oldScroll.y !== newScroll.y) { - if (e.preventDefault) { e.preventDefault(); } - return false; - } - }, - _handlePaste: function (e) { - if (this._ignorePaste) { return; } - if (!e) { e = window.event; } - if (this._doPaste(e)) { - if (isIE) { - /* - * Bug in IE, - */ - var self = this; - this._ignoreFocus = true; - setTimeout(function() { - self._updateDOMSelection(); - this._ignoreFocus = false; - }, 0); - } - if (e.preventDefault) { e.preventDefault(); } - return false; - } - }, - _handleResize: function (e) { - if (!e) { e = window.event; } - var element = this._frameDocument.documentElement; - var newWidth = element.clientWidth; - var newHeight = element.clientHeight; - if (this._frameWidth !== newWidth || this._frameHeight !== newHeight) { - this._frameWidth = newWidth; - this._frameHeight = newHeight; - /* - * Feature in IE7. For some reason, sometimes Internet Explorer 7 - * returns incorrect values for element.getBoundingClientRect() when - * inside a resize handler. The fix is to queue the work. - */ - if (isIE < 9) { - this._queueUpdatePage(); - } else { - this._updatePage(); - } - } - }, - _handleRulerEvent: function (e) { - if (!e) { e = window.event; } - var target = e.target ? e.target : e.srcElement; - var lineIndex = target.lineIndex; - var element = target; - while (element && !element._ruler) { - if (lineIndex === undefined && element.lineIndex !== undefined) { - lineIndex = element.lineIndex; - } - element = element.parentNode; - } - var ruler = element ? element._ruler : null; - if (lineIndex === undefined && ruler && ruler.getOverview() === "document") { - var buttonHeight = isPad ? 0 : 17; - var clientHeight = this._getClientHeight (); - var lineCount = this._model.getLineCount (); - var viewPad = this._getViewPadding(); - var trackHeight = clientHeight + viewPad.top + viewPad.bottom - 2 * buttonHeight; - lineIndex = Math.floor((e.clientY - buttonHeight) * lineCount / trackHeight); - if (!(0 <= lineIndex && lineIndex < lineCount)) { - lineIndex = undefined; - } - } - if (ruler) { - switch (e.type) { - case "click": - if (ruler.onClick) { ruler.onClick(lineIndex, e); } - break; - case "dblclick": - if (ruler.onDblClick) { ruler.onDblClick(lineIndex, e); } - break; - case "mousemove": - if (ruler.onMouseMove) { ruler.onMouseMove(lineIndex, e); } - break; - case "mouseover": - if (ruler.onMouseOver) { ruler.onMouseOver(lineIndex, e); } - break; - case "mouseout": - if (ruler.onMouseOut) { ruler.onMouseOut(lineIndex, e); } - break; - } - } - }, - _handleScroll: function () { - var scroll = this._getScroll(); - var oldX = this._hScroll; - var oldY = this._vScroll; - if (oldX !== scroll.x || oldY !== scroll.y) { - this._hScroll = scroll.x; - this._vScroll = scroll.y; - this._commitIME(); - this._updatePage(oldY === scroll.y); - var e = { - type: "Scroll", - oldValue: {x: oldX, y: oldY}, - newValue: scroll - }; - this.onScroll(e); - } - }, - _handleSelectStart: function (e) { - if (!e) { e = window.event; } - if (this._ignoreSelect) { - if (e && e.preventDefault) { e.preventDefault(); } - return false; - } - }, - _handleUnload: function (e) { - if (!e) { e = window.event; } - this._destroyView(); - }, - _handleInput: function (e) { - var textArea = this._textArea; - this._doContent(textArea.value); - textArea.selectionStart = textArea.selectionEnd = 0; - textArea.value = ""; - e.preventDefault(); - }, - _handleTextInput: function (e) { - this._doContent(e.data); - e.preventDefault(); - }, - _touchConvert: function (touch) { - var rect = this._frame.getBoundingClientRect(); - var body = this._parentDocument.body; - return {left: touch.clientX - rect.left - body.scrollLeft, top: touch.clientY - rect.top - body.scrollTop}; - }, - _handleTextAreaClick: function (e) { - var pt = this._touchConvert(e); - this._clickCount = 1; - this._ignoreDOMSelection = false; - this._setSelectionTo(pt.left, pt.top, false); - var textArea = this._textArea; - textArea.focus(); - }, - _handleTouchStart: function (e) { - var touches = e.touches, touch, pt, sel; - this._touchMoved = false; - this._touchStartScroll = undefined; - if (touches.length === 1) { - touch = touches[0]; - var pageX = touch.pageX; - var pageY = touch.pageY; - this._touchStartX = pageX; - this._touchStartY = pageY; - this._touchStartTime = e.timeStamp; - this._touchStartScroll = this._getScroll(); - sel = this._getSelection(); - pt = this._touchConvert(touches[0]); - this._touchGesture = "none"; - if (!sel.isEmpty()) { - if (this._hitOffset(sel.end, pt.left, pt.top)) { - this._touchGesture = "extendEnd"; - } else if (this._hitOffset(sel.start, pt.left, pt.top)) { - this._touchGesture = "extendStart"; - } - } - if (this._touchGesture === "none") { - var textArea = this._textArea; - textArea.value = ""; - textArea.style.left = "-1000px"; - textArea.style.top = "-1000px"; - textArea.style.width = "3000px"; - textArea.style.height = "3000px"; - } - } else if (touches.length === 2) { - this._touchGesture = "select"; - if (this._touchTimeout) { - clearTimeout(this._touchTimeout); - this._touchTimeout = null; - } - pt = this._touchConvert(touches[0]); - var offset1 = this._getXToOffset(this._getYToLine(pt.top), pt.left); - pt = this._touchConvert(touches[1]); - var offset2 = this._getXToOffset(this._getYToLine(pt.top), pt.left); - sel = this._getSelection(); - sel.setCaret(offset1); - sel.extend(offset2); - this._setSelection(sel, true, true); - } - //Cannot prevent to show magnifier -// e.preventDefault(); - }, - _handleTouchMove: function (e) { - this._touchMoved = true; - var touches = e.touches, pt, sel; - if (touches.length === 1) { - var touch = touches[0]; - var pageX = touch.pageX; - var pageY = touch.pageY; - var deltaX = this._touchStartX - pageX; - var deltaY = this._touchStartY - pageY; - pt = this._touchConvert(touch); - sel = this._getSelection(); - if (this._touchGesture === "none") { - if ((e.timeStamp - this._touchStartTime) < 200 && (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5)) { - this._touchGesture = "scroll"; - } else { - this._touchGesture = "caret"; - } - } - if (this._touchGesture === "select") { - if (this._hitOffset(sel.end, pt.left, pt.top)) { - this._touchGesture = "extendEnd"; - } else if (this._hitOffset(sel.start, pt.left, pt.top)) { - this._touchGesture = "extendStart"; - } else { - this._touchGesture = "caret"; - } - } - switch (this._touchGesture) { - case "scroll": - this._touchStartX = pageX; - this._touchStartY = pageY; - this._scrollView(deltaX, deltaY); - break; - case "extendStart": - case "extendEnd": - this._clickCount = 1; - var lineIndex = this._getYToLine(pt.top); - var offset = this._getXToOffset(lineIndex, pt.left); - sel.setCaret(this._touchGesture === "extendStart" ? sel.end : sel.start); - sel.extend(offset); - if (offset >= sel.end && this._touchGesture === "extendStart") { - this._touchGesture = "extendEnd"; - } - if (offset <= sel.start && this._touchGesture === "extendEnd") { - this._touchGesture = "extendStart"; - } - this._setSelection(sel, true, true); - break; - case "caret": - this._setSelectionTo(pt.left, pt.top, false); - break; - } - } else if (touches.length === 2) { - pt = this._touchConvert(touches[0]); - var offset1 = this._getXToOffset(this._getYToLine(pt.top), pt.left); - pt = this._touchConvert(touches[1]); - var offset2 = this._getXToOffset(this._getYToLine(pt.top), pt.left); - sel = this._getSelection(); - sel.setCaret(offset1); - sel.extend(offset2); - this._setSelection(sel, true, true); - } - e.preventDefault(); - }, - _handleTouchEnd: function (e) { - var self = this; - if (!this._touchMoved) { - if (e.touches.length === 0 && e.changedTouches.length === 1) { - var touch = e.changedTouches[0]; - var pt = this._touchConvert(touch); - var textArea = this._textArea; - textArea.value = ""; - textArea.style.left = "-1000px"; - textArea.style.top = "-1000px"; - textArea.style.width = "3000px"; - textArea.style.height = "3000px"; - setTimeout(function() { - self._clickCount = 1; - self._ignoreDOMSelection = false; - self._setSelectionTo(pt.left, pt.top, false); - }, 300); - } - } - if (e.touches.length === 0) { - setTimeout(function() { - var selection = self._getSelection(); - var text = self._model.getText(selection.start, selection.end); - var textArea = self._textArea; - textArea.value = text; - textArea.selectionStart = 0; - textArea.selectionEnd = text.length; - if (!selection.isEmpty()) { - var touchRect = self._touchDiv.getBoundingClientRect(); - var bounds = self._getOffsetBounds(selection.start); - textArea.style.left = (touchRect.width / 2) + "px"; - textArea.style.top = ((bounds.top > 40 ? bounds.top - 30 : bounds.top + 30)) + "px"; - } - }, 0); - } -// e.preventDefault(); - }, - - /************************************ Actions ******************************************/ - _doAction: function (e) { - var keyBindings = this._keyBindings; - for (var i = 0; i < keyBindings.length; i++) { - var kb = keyBindings[i]; - if (kb.keyBinding.match(e)) { - if (kb.name) { - var actions = this._actions; - for (var j = 0; j < actions.length; j++) { - var a = actions[j]; - if (a.name === kb.name) { - if (a.userHandler) { - if (!a.userHandler()) { - if (a.defaultHandler) { - a.defaultHandler(); - } else { - return false; - } - } - } else if (a.defaultHandler) { - a.defaultHandler(); - } - break; - } - } - } - return true; - } - } - return false; - }, - _doBackspace: function (args) { - var selection = this._getSelection(); - if (selection.isEmpty()) { - var model = this._model; - var caret = selection.getCaret(); - var lineIndex = model.getLineAtOffset(caret); - var lineStart = model.getLineStart(lineIndex); - if (caret === lineStart) { - if (lineIndex > 0) { - selection.extend(model.getLineEnd(lineIndex - 1)); - } - } else { - var removeTab = false; - if (this._expandTab && args.unit === "character" && (caret - lineStart) % this._tabSize === 0) { - var lineText = model.getText(lineStart, caret); - removeTab = !/[^ ]/.test(lineText); // Only spaces between line start and caret. - } - if (removeTab) { - selection.extend(caret - this._tabSize); - } else { - selection.extend(this._getOffset(caret, args.unit, -1)); - } - } - } - this._modifyContent({text: "", start: selection.start, end: selection.end}, true); - return true; - }, - _doContent: function (text) { - var selection = this._getSelection(); - this._modifyContent({text: text, start: selection.start, end: selection.end, _ignoreDOMSelection: true}, true); - }, - _doCopy: function (e) { - var selection = this._getSelection(); - if (!selection.isEmpty()) { - var text = this._getBaseText(selection.start, selection.end); - return this._setClipboardText(text, e); - } - return true; - }, - _doCursorNext: function (args) { - if (!args.select) { - if (this._clearSelection("next")) { return true; } - } - var model = this._model; - var selection = this._getSelection(); - var caret = selection.getCaret(); - var lineIndex = model.getLineAtOffset(caret); - if (caret === model.getLineEnd(lineIndex)) { - if (lineIndex + 1 < model.getLineCount()) { - selection.extend(model.getLineStart(lineIndex + 1)); - } - } else { - selection.extend(this._getOffset(caret, args.unit, 1)); - } - if (!args.select) { selection.collapse(); } - this._setSelection(selection, true); - return true; - }, - _doCursorPrevious: function (args) { - if (!args.select) { - if (this._clearSelection("previous")) { return true; } - } - var model = this._model; - var selection = this._getSelection(); - var caret = selection.getCaret(); - var lineIndex = model.getLineAtOffset(caret); - if (caret === model.getLineStart(lineIndex)) { - if (lineIndex > 0) { - selection.extend(model.getLineEnd(lineIndex - 1)); - } - } else { - selection.extend(this._getOffset(caret, args.unit, -1)); - } - if (!args.select) { selection.collapse(); } - this._setSelection(selection, true); - return true; - }, - _doCut: function (e) { - var selection = this._getSelection(); - if (!selection.isEmpty()) { - var text = this._getBaseText(selection.start, selection.end); - this._doContent(""); - return this._setClipboardText(text, e); - } - return true; - }, - _doDelete: function (args) { - var selection = this._getSelection(); - if (selection.isEmpty()) { - var model = this._model; - var caret = selection.getCaret(); - var lineIndex = model.getLineAtOffset(caret); - if (caret === model.getLineEnd (lineIndex)) { - if (lineIndex + 1 < model.getLineCount()) { - selection.extend(model.getLineStart(lineIndex + 1)); - } - } else { - selection.extend(this._getOffset(caret, args.unit, 1)); - } - } - this._modifyContent({text: "", start: selection.start, end: selection.end}, true); - return true; - }, - _doEnd: function (args) { - var selection = this._getSelection(); - var model = this._model; - if (args.ctrl) { - selection.extend(model.getCharCount()); - } else { - var lineIndex = model.getLineAtOffset(selection.getCaret()); - selection.extend(model.getLineEnd(lineIndex)); - } - if (!args.select) { selection.collapse(); } - this._setSelection(selection, true); - return true; - }, - _doEnter: function (args) { - var model = this._model; - var selection = this._getSelection(); - this._doContent(model.getLineDelimiter()); - if (args && args.noCursor) { - selection.end = selection.start; - this._setSelection(selection); - } - return true; - }, - _doHome: function (args) { - var selection = this._getSelection(); - var model = this._model; - if (args.ctrl) { - selection.extend(0); - } else { - var lineIndex = model.getLineAtOffset(selection.getCaret()); - selection.extend(model.getLineStart(lineIndex)); - } - if (!args.select) { selection.collapse(); } - this._setSelection(selection, true); - return true; - }, - _doLineDown: function (args) { - var model = this._model; - var selection = this._getSelection(); - var caret = selection.getCaret(); - var lineIndex = model.getLineAtOffset(caret); - if (lineIndex + 1 < model.getLineCount()) { - var scrollX = this._getScroll().x; - var x = this._columnX; - if (x === -1 || args.wholeLine || (args.select && isIE)) { - var offset = args.wholeLine ? model.getLineEnd(lineIndex + 1) : caret; - x = this._getOffsetToX(offset) + scrollX; - } - selection.extend(this._getXToOffset(lineIndex + 1, x - scrollX)); - if (!args.select) { selection.collapse(); } - this._setSelection(selection, true, true); - this._columnX = x; - } - return true; - }, - _doLineUp: function (args) { - var model = this._model; - var selection = this._getSelection(); - var caret = selection.getCaret(); - var lineIndex = model.getLineAtOffset(caret); - if (lineIndex > 0) { - var scrollX = this._getScroll().x; - var x = this._columnX; - if (x === -1 || args.wholeLine || (args.select && isIE)) { - var offset = args.wholeLine ? model.getLineStart(lineIndex - 1) : caret; - x = this._getOffsetToX(offset) + scrollX; - } - selection.extend(this._getXToOffset(lineIndex - 1, x - scrollX)); - if (!args.select) { selection.collapse(); } - this._setSelection(selection, true, true); - this._columnX = x; - } - return true; - }, - _doPageDown: function (args) { - var model = this._model; - var selection = this._getSelection(); - var caret = selection.getCaret(); - var caretLine = model.getLineAtOffset(caret); - var lineCount = model.getLineCount(); - if (caretLine < lineCount - 1) { - var scroll = this._getScroll(); - var clientHeight = this._getClientHeight(); - var lineHeight = this._getLineHeight(); - var lines = Math.floor(clientHeight / lineHeight); - var scrollLines = Math.min(lineCount - caretLine - 1, lines); - scrollLines = Math.max(1, scrollLines); - var x = this._columnX; - if (x === -1 || (args.select && isIE)) { - x = this._getOffsetToX(caret) + scroll.x; - } - selection.extend(this._getXToOffset(caretLine + scrollLines, x - scroll.x)); - if (!args.select) { selection.collapse(); } - var verticalMaximum = lineCount * lineHeight; - var scrollOffset = scroll.y + scrollLines * lineHeight; - if (scrollOffset + clientHeight > verticalMaximum) { - scrollOffset = verticalMaximum - clientHeight; - } - this._setSelection(selection, true, true, scrollOffset - scroll.y); - this._columnX = x; - } - return true; - }, - _doPageUp: function (args) { - var model = this._model; - var selection = this._getSelection(); - var caret = selection.getCaret(); - var caretLine = model.getLineAtOffset(caret); - if (caretLine > 0) { - var scroll = this._getScroll(); - var clientHeight = this._getClientHeight(); - var lineHeight = this._getLineHeight(); - var lines = Math.floor(clientHeight / lineHeight); - var scrollLines = Math.max(1, Math.min(caretLine, lines)); - var x = this._columnX; - if (x === -1 || (args.select && isIE)) { - x = this._getOffsetToX(caret) + scroll.x; - } - selection.extend(this._getXToOffset(caretLine - scrollLines, x - scroll.x)); - if (!args.select) { selection.collapse(); } - var scrollOffset = Math.max(0, scroll.y - scrollLines * lineHeight); - this._setSelection(selection, true, true, scrollOffset - scroll.y); - this._columnX = x; - } - return true; - }, - _doPaste: function(e) { - var self = this; - var result = this._getClipboardText(e, function(text) { - if (text) { - if (isLinux && self._lastMouseButton === 2) { - var timeDiff = new Date().getTime() - self._lastMouseTime; - if (timeDiff <= self._clickTime) { - self._setSelectionTo(self._lastMouseX, self._lastMouseY); - } - } - self._doContent(text); - } - }); - return result !== null; - }, - _doScroll: function (args) { - var type = args.type; - var model = this._model; - var lineCount = model.getLineCount(); - var clientHeight = this._getClientHeight(); - var lineHeight = this._getLineHeight(); - var verticalMaximum = lineCount * lineHeight; - var verticalScrollOffset = this._getScroll().y; - var pixel; - switch (type) { - case "textStart": pixel = 0; break; - case "textEnd": pixel = verticalMaximum - clientHeight; break; - case "pageDown": pixel = verticalScrollOffset + clientHeight; break; - case "pageUp": pixel = verticalScrollOffset - clientHeight; break; - case "centerLine": - var selection = this._getSelection(); - var lineStart = model.getLineAtOffset(selection.start); - var lineEnd = model.getLineAtOffset(selection.end); - var selectionHeight = (lineEnd - lineStart + 1) * lineHeight; - pixel = (lineStart * lineHeight) - (clientHeight / 2) + (selectionHeight / 2); - break; - } - if (pixel !== undefined) { - pixel = Math.min(Math.max(0, pixel), verticalMaximum - clientHeight); - this._scrollView(0, pixel - verticalScrollOffset); - } - }, - _doSelectAll: function (args) { - var model = this._model; - var selection = this._getSelection(); - selection.setCaret(0); - selection.extend(model.getCharCount()); - this._setSelection(selection, false); - return true; - }, - _doTab: function (args) { - var text = "\t"; - if (this._expandTab) { - var model = this._model; - var caret = this._getSelection().getCaret(); - var lineIndex = model.getLineAtOffset(caret); - var lineStart = model.getLineStart(lineIndex); - var spaces = this._tabSize - ((caret - lineStart) % this._tabSize); - text = (new Array(spaces + 1)).join(" "); - } - this._doContent(text); - return true; - }, - - /************************************ Internals ******************************************/ - _applyStyle: function(style, node, reset) { - if (reset) { - var attrs = node.attributes; - for (var i= attrs.length; i-->0;) { - if (attrs[i].specified) { - node.removeAttributeNode(attrs[i]); - } - } - } - if (!style) { - return; - } - if (style.styleClass) { - node.className = style.styleClass; - } - var properties = style.style; - if (properties) { - for (var s in properties) { - if (properties.hasOwnProperty(s)) { - node.style[s] = properties[s]; - } - } - } - var attributes = style.attributes; - if (attributes) { - for (var a in attributes) { - if (attributes.hasOwnProperty(a)) { - node.setAttribute(a, attributes[a]); - } - } - } - }, - _autoScroll: function () { - var selection = this._getSelection(); - var line; - var x = this._autoScrollX; - if (this._autoScrollDir === "up" || this._autoScrollDir === "down") { - var scroll = this._autoScrollY / this._getLineHeight(); - scroll = scroll < 0 ? Math.floor(scroll) : Math.ceil(scroll); - line = this._model.getLineAtOffset(selection.getCaret()); - line = Math.max(0, Math.min(this._model.getLineCount() - 1, line + scroll)); - } else if (this._autoScrollDir === "left" || this._autoScrollDir === "right") { - line = this._getYToLine(this._autoScrollY); - x += this._getOffsetToX(selection.getCaret()); - } - selection.extend(this._getXToOffset(line, x)); - this._setSelection(selection, true); - }, - _autoScrollTimer: function () { - this._autoScroll(); - var self = this; - this._autoScrollTimerID = setTimeout(function () {self._autoScrollTimer();}, this._AUTO_SCROLL_RATE); - }, - _calculateLineHeight: function() { - var parent = this._clientDiv; - var document = this._frameDocument; - var c = " "; - var line = document.createElement("DIV"); - line.style.position = "fixed"; - line.style.left = "-1000px"; - var span1 = document.createElement("SPAN"); - span1.appendChild(document.createTextNode(c)); - line.appendChild(span1); - var span2 = document.createElement("SPAN"); - span2.style.fontStyle = "italic"; - span2.appendChild(document.createTextNode(c)); - line.appendChild(span2); - var span3 = document.createElement("SPAN"); - span3.style.fontWeight = "bold"; - span3.appendChild(document.createTextNode(c)); - line.appendChild(span3); - var span4 = document.createElement("SPAN"); - span4.style.fontWeight = "bold"; - span4.style.fontStyle = "italic"; - span4.appendChild(document.createTextNode(c)); - line.appendChild(span4); - parent.appendChild(line); - var lineRect = line.getBoundingClientRect(); - var spanRect1 = span1.getBoundingClientRect(); - var spanRect2 = span2.getBoundingClientRect(); - var spanRect3 = span3.getBoundingClientRect(); - var spanRect4 = span4.getBoundingClientRect(); - var h1 = spanRect1.bottom - spanRect1.top; - var h2 = spanRect2.bottom - spanRect2.top; - var h3 = spanRect3.bottom - spanRect3.top; - var h4 = spanRect4.bottom - spanRect4.top; - var fontStyle = 0; - var lineHeight = lineRect.bottom - lineRect.top; - if (h2 > h1) { - fontStyle = 1; - } - if (h3 > h2) { - fontStyle = 2; - } - if (h4 > h3) { - fontStyle = 3; - } - var style; - if (fontStyle !== 0) { - style = {style: {}}; - if ((fontStyle & 1) !== 0) { - style.style.fontStyle = "italic"; - } - if ((fontStyle & 2) !== 0) { - style.style.fontWeight = "bold"; - } - } - this._largestFontStyle = style; - parent.removeChild(line); - return lineHeight; - }, - _calculatePadding: function() { - var document = this._frameDocument; - var parent = this._clientDiv; - var pad = this._getPadding(this._viewDiv); - var div1 = document.createElement("DIV"); - div1.style.position = "fixed"; - div1.style.left = "-1000px"; - div1.style.paddingLeft = pad.left + "px"; - div1.style.paddingTop = pad.top + "px"; - div1.style.paddingRight = pad.right + "px"; - div1.style.paddingBottom = pad.bottom + "px"; - div1.style.width = "100px"; - div1.style.height = "100px"; - var div2 = document.createElement("DIV"); - div2.style.width = "100%"; - div2.style.height = "100%"; - div1.appendChild(div2); - parent.appendChild(div1); - var rect1 = div1.getBoundingClientRect(); - var rect2 = div2.getBoundingClientRect(); - parent.removeChild(div1); - pad = { - left: rect2.left - rect1.left, - top: rect2.top - rect1.top, - right: rect1.right - rect2.right, - bottom: rect1.bottom - rect2.bottom - }; - return pad; - }, - _clearSelection: function (direction) { - var selection = this._getSelection(); - if (selection.isEmpty()) { return false; } - if (direction === "next") { - selection.start = selection.end; - } else { - selection.end = selection.start; - } - this._setSelection(selection, true); - return true; - }, - _clone: function (obj) { - /*Note that this code only works because of the limited types used in TextViewOptions */ - if (obj instanceof Array) { - return obj.slice(0); - } - return obj; - }, - _compare: function (s1, s2) { - if (s1 === s2) { return true; } - if (s1 && !s2 || !s1 && s2) { return false; } - if ((s1 && s1.constructor === String) || (s2 && s2.constructor === String)) { return false; } - if (s1 instanceof Array || s2 instanceof Array) { - if (!(s1 instanceof Array && s2 instanceof Array)) { return false; } - if (s1.length !== s2.length) { return false; } - for (var i = 0; i < s1.length; i++) { - if (!this._compare(s1[i], s2[i])) { - return false; - } - } - return true; - } - if (!(s1 instanceof Object) || !(s2 instanceof Object)) { return false; } - var p; - for (p in s1) { - if (s1.hasOwnProperty(p)) { - if (!s2.hasOwnProperty(p)) { return false; } - if (!this._compare(s1[p], s2[p])) {return false; } - } - } - for (p in s2) { - if (!s1.hasOwnProperty(p)) { return false; } - } - return true; - }, - _commitIME: function () { - if (this._imeOffset === -1) { return; } - // make the state of the IME match the state the view expects it be in - // when the view commits the text and IME also need to be committed - // this can be accomplished by changing the focus around - this._scrollDiv.focus(); - this._clientDiv.focus(); - - var model = this._model; - var lineIndex = model.getLineAtOffset(this._imeOffset); - var lineStart = model.getLineStart(lineIndex); - var newText = this._getDOMText(lineIndex); - var oldText = model.getLine(lineIndex); - var start = this._imeOffset - lineStart; - var end = start + newText.length - oldText.length; - if (start !== end) { - var insertText = newText.substring(start, end); - this._doContent(insertText); - } - this._imeOffset = -1; - }, - _convertDelimiter: function (text, addTextFunc, addDelimiterFunc) { - var cr = 0, lf = 0, index = 0, length = text.length; - while (index < length) { - if (cr !== -1 && cr <= index) { cr = text.indexOf("\r", index); } - if (lf !== -1 && lf <= index) { lf = text.indexOf("\n", index); } - var start = index, end; - if (lf === -1 && cr === -1) { - addTextFunc(text.substring(index)); - break; - } - if (cr !== -1 && lf !== -1) { - if (cr + 1 === lf) { - end = cr; - index = lf + 1; - } else { - end = cr < lf ? cr : lf; - index = (cr < lf ? cr : lf) + 1; - } - } else if (cr !== -1) { - end = cr; - index = cr + 1; - } else { - end = lf; - index = lf + 1; - } - addTextFunc(text.substring(start, end)); - addDelimiterFunc(); - } - }, - _createActions: function () { - var KeyBinding = mKeyBinding.KeyBinding; - //no duplicate keybindings - var bindings = this._keyBindings = []; - - // Cursor Navigation - bindings.push({name: "lineUp", keyBinding: new KeyBinding(38), predefined: true}); - bindings.push({name: "lineDown", keyBinding: new KeyBinding(40), predefined: true}); - bindings.push({name: "charPrevious", keyBinding: new KeyBinding(37), predefined: true}); - bindings.push({name: "charNext", keyBinding: new KeyBinding(39), predefined: true}); - if (isMac) { - bindings.push({name: "scrollPageUp", keyBinding: new KeyBinding(33), predefined: true}); - bindings.push({name: "scrollPageDown", keyBinding: new KeyBinding(34), predefined: true}); - bindings.push({name: "pageUp", keyBinding: new KeyBinding(33, null, null, true), predefined: true}); - bindings.push({name: "pageDown", keyBinding: new KeyBinding(34, null, null, true), predefined: true}); - bindings.push({name: "lineStart", keyBinding: new KeyBinding(37, true), predefined: true}); - bindings.push({name: "lineEnd", keyBinding: new KeyBinding(39, true), predefined: true}); - bindings.push({name: "wordPrevious", keyBinding: new KeyBinding(37, null, null, true), predefined: true}); - bindings.push({name: "wordNext", keyBinding: new KeyBinding(39, null, null, true), predefined: true}); - bindings.push({name: "scrollTextStart", keyBinding: new KeyBinding(36), predefined: true}); - bindings.push({name: "scrollTextEnd", keyBinding: new KeyBinding(35), predefined: true}); - bindings.push({name: "textStart", keyBinding: new KeyBinding(38, true), predefined: true}); - bindings.push({name: "textEnd", keyBinding: new KeyBinding(40, true), predefined: true}); - bindings.push({name: "scrollPageUp", keyBinding: new KeyBinding(38, null, null, null, true), predefined: true}); - bindings.push({name: "scrollPageDown", keyBinding: new KeyBinding(40, null, null, null, true), predefined: true}); - bindings.push({name: "lineStart", keyBinding: new KeyBinding(37, null, null, null, true), predefined: true}); - bindings.push({name: "lineEnd", keyBinding: new KeyBinding(39, null, null, null, true), predefined: true}); - //TODO These two actions should be changed to paragraph start and paragraph end when word wrap is implemented - bindings.push({name: "lineStart", keyBinding: new KeyBinding(38, null, null, true), predefined: true}); - bindings.push({name: "lineEnd", keyBinding: new KeyBinding(40, null, null, true), predefined: true}); - } else { - bindings.push({name: "pageUp", keyBinding: new KeyBinding(33), predefined: true}); - bindings.push({name: "pageDown", keyBinding: new KeyBinding(34), predefined: true}); - bindings.push({name: "lineStart", keyBinding: new KeyBinding(36), predefined: true}); - bindings.push({name: "lineEnd", keyBinding: new KeyBinding(35), predefined: true}); - bindings.push({name: "wordPrevious", keyBinding: new KeyBinding(37, true), predefined: true}); - bindings.push({name: "wordNext", keyBinding: new KeyBinding(39, true), predefined: true}); - bindings.push({name: "textStart", keyBinding: new KeyBinding(36, true), predefined: true}); - bindings.push({name: "textEnd", keyBinding: new KeyBinding(35, true), predefined: true}); - } - if (isFirefox && isLinux) { - bindings.push({name: "lineUp", keyBinding: new KeyBinding(38, true), predefined: true}); - bindings.push({name: "lineDown", keyBinding: new KeyBinding(40, true), predefined: true}); - } - - // Select Cursor Navigation - bindings.push({name: "selectLineUp", keyBinding: new KeyBinding(38, null, true), predefined: true}); - bindings.push({name: "selectLineDown", keyBinding: new KeyBinding(40, null, true), predefined: true}); - bindings.push({name: "selectCharPrevious", keyBinding: new KeyBinding(37, null, true), predefined: true}); - bindings.push({name: "selectCharNext", keyBinding: new KeyBinding(39, null, true), predefined: true}); - bindings.push({name: "selectPageUp", keyBinding: new KeyBinding(33, null, true), predefined: true}); - bindings.push({name: "selectPageDown", keyBinding: new KeyBinding(34, null, true), predefined: true}); - if (isMac) { - bindings.push({name: "selectLineStart", keyBinding: new KeyBinding(37, true, true), predefined: true}); - bindings.push({name: "selectLineEnd", keyBinding: new KeyBinding(39, true, true), predefined: true}); - bindings.push({name: "selectWordPrevious", keyBinding: new KeyBinding(37, null, true, true), predefined: true}); - bindings.push({name: "selectWordNext", keyBinding: new KeyBinding(39, null, true, true), predefined: true}); - bindings.push({name: "selectTextStart", keyBinding: new KeyBinding(36, null, true), predefined: true}); - bindings.push({name: "selectTextEnd", keyBinding: new KeyBinding(35, null, true), predefined: true}); - bindings.push({name: "selectTextStart", keyBinding: new KeyBinding(38, true, true), predefined: true}); - bindings.push({name: "selectTextEnd", keyBinding: new KeyBinding(40, true, true), predefined: true}); - bindings.push({name: "selectLineStart", keyBinding: new KeyBinding(37, null, true, null, true), predefined: true}); - bindings.push({name: "selectLineEnd", keyBinding: new KeyBinding(39, null, true, null, true), predefined: true}); - //TODO These two actions should be changed to select paragraph start and select paragraph end when word wrap is implemented - bindings.push({name: "selectLineStart", keyBinding: new KeyBinding(38, null, true, true), predefined: true}); - bindings.push({name: "selectLineEnd", keyBinding: new KeyBinding(40, null, true, true), predefined: true}); - } else { - if (isLinux) { - bindings.push({name: "selectWholeLineUp", keyBinding: new KeyBinding(38, true, true), predefined: true}); - bindings.push({name: "selectWholeLineDown", keyBinding: new KeyBinding(40, true, true), predefined: true}); - } - bindings.push({name: "selectLineStart", keyBinding: new KeyBinding(36, null, true), predefined: true}); - bindings.push({name: "selectLineEnd", keyBinding: new KeyBinding(35, null, true), predefined: true}); - bindings.push({name: "selectWordPrevious", keyBinding: new KeyBinding(37, true, true), predefined: true}); - bindings.push({name: "selectWordNext", keyBinding: new KeyBinding(39, true, true), predefined: true}); - bindings.push({name: "selectTextStart", keyBinding: new KeyBinding(36, true, true), predefined: true}); - bindings.push({name: "selectTextEnd", keyBinding: new KeyBinding(35, true, true), predefined: true}); - } - - //Misc - bindings.push({name: "deletePrevious", keyBinding: new KeyBinding(8), predefined: true}); - bindings.push({name: "deletePrevious", keyBinding: new KeyBinding(8, null, true), predefined: true}); - bindings.push({name: "deleteNext", keyBinding: new KeyBinding(46), predefined: true}); - bindings.push({name: "deleteWordPrevious", keyBinding: new KeyBinding(8, true), predefined: true}); - bindings.push({name: "deleteWordPrevious", keyBinding: new KeyBinding(8, true, true), predefined: true}); - bindings.push({name: "deleteWordNext", keyBinding: new KeyBinding(46, true), predefined: true}); - bindings.push({name: "tab", keyBinding: new KeyBinding(9), predefined: true}); - bindings.push({name: "enter", keyBinding: new KeyBinding(13), predefined: true}); - bindings.push({name: "enter", keyBinding: new KeyBinding(13, null, true), predefined: true}); - bindings.push({name: "selectAll", keyBinding: new KeyBinding('a', true), predefined: true}); - if (isMac) { - bindings.push({name: "deleteNext", keyBinding: new KeyBinding(46, null, true), predefined: true}); - bindings.push({name: "deleteWordPrevious", keyBinding: new KeyBinding(8, null, null, true), predefined: true}); - bindings.push({name: "deleteWordNext", keyBinding: new KeyBinding(46, null, null, true), predefined: true}); - } - - /* - * Feature in IE/Chrome: prevent ctrl+'u', ctrl+'i', and ctrl+'b' from applying styles to the text. - * - * Note that Chrome applies the styles on the Mac with Ctrl instead of Cmd. - */ - if (!isFirefox) { - var isMacChrome = isMac && isChrome; - bindings.push({name: null, keyBinding: new KeyBinding('u', !isMacChrome, false, false, isMacChrome), predefined: true}); - bindings.push({name: null, keyBinding: new KeyBinding('i', !isMacChrome, false, false, isMacChrome), predefined: true}); - bindings.push({name: null, keyBinding: new KeyBinding('b', !isMacChrome, false, false, isMacChrome), predefined: true}); - } - - if (isFirefox) { - bindings.push({name: "copy", keyBinding: new KeyBinding(45, true), predefined: true}); - bindings.push({name: "paste", keyBinding: new KeyBinding(45, null, true), predefined: true}); - bindings.push({name: "cut", keyBinding: new KeyBinding(46, null, true), predefined: true}); - } - - // Add the emacs Control+ ... key bindings. - if (isMac) { - bindings.push({name: "lineStart", keyBinding: new KeyBinding("a", false, false, false, true), predefined: true}); - bindings.push({name: "lineEnd", keyBinding: new KeyBinding("e", false, false, false, true), predefined: true}); - bindings.push({name: "lineUp", keyBinding: new KeyBinding("p", false, false, false, true), predefined: true}); - bindings.push({name: "lineDown", keyBinding: new KeyBinding("n", false, false, false, true), predefined: true}); - bindings.push({name: "charPrevious", keyBinding: new KeyBinding("b", false, false, false, true), predefined: true}); - bindings.push({name: "charNext", keyBinding: new KeyBinding("f", false, false, false, true), predefined: true}); - bindings.push({name: "deletePrevious", keyBinding: new KeyBinding("h", false, false, false, true), predefined: true}); - bindings.push({name: "deleteNext", keyBinding: new KeyBinding("d", false, false, false, true), predefined: true}); - bindings.push({name: "deleteLineEnd", keyBinding: new KeyBinding("k", false, false, false, true), predefined: true}); - if (isFirefox) { - bindings.push({name: "scrollPageDown", keyBinding: new KeyBinding("v", false, false, false, true), predefined: true}); - bindings.push({name: "deleteLineStart", keyBinding: new KeyBinding("u", false, false, false, true), predefined: true}); - bindings.push({name: "deleteWordPrevious", keyBinding: new KeyBinding("w", false, false, false, true), predefined: true}); - } else { - bindings.push({name: "pageDown", keyBinding: new KeyBinding("v", false, false, false, true), predefined: true}); - bindings.push({name: "centerLine", keyBinding: new KeyBinding("l", false, false, false, true), predefined: true}); - bindings.push({name: "enterNoCursor", keyBinding: new KeyBinding("o", false, false, false, true), predefined: true}); - //TODO implement: y (yank), t (transpose) - } - } - - //1 to 1, no duplicates - var self = this; - this._actions = [ - {name: "lineUp", defaultHandler: function() {return self._doLineUp({select: false});}}, - {name: "lineDown", defaultHandler: function() {return self._doLineDown({select: false});}}, - {name: "lineStart", defaultHandler: function() {return self._doHome({select: false, ctrl:false});}}, - {name: "lineEnd", defaultHandler: function() {return self._doEnd({select: false, ctrl:false});}}, - {name: "charPrevious", defaultHandler: function() {return self._doCursorPrevious({select: false, unit:"character"});}}, - {name: "charNext", defaultHandler: function() {return self._doCursorNext({select: false, unit:"character"});}}, - {name: "pageUp", defaultHandler: function() {return self._doPageUp({select: false});}}, - {name: "pageDown", defaultHandler: function() {return self._doPageDown({select: false});}}, - {name: "scrollPageUp", defaultHandler: function() {return self._doScroll({type: "pageUp"});}}, - {name: "scrollPageDown", defaultHandler: function() {return self._doScroll({type: "pageDown"});}}, - {name: "wordPrevious", defaultHandler: function() {return self._doCursorPrevious({select: false, unit:"word"});}}, - {name: "wordNext", defaultHandler: function() {return self._doCursorNext({select: false, unit:"word"});}}, - {name: "textStart", defaultHandler: function() {return self._doHome({select: false, ctrl:true});}}, - {name: "textEnd", defaultHandler: function() {return self._doEnd({select: false, ctrl:true});}}, - {name: "scrollTextStart", defaultHandler: function() {return self._doScroll({type: "textStart"});}}, - {name: "scrollTextEnd", defaultHandler: function() {return self._doScroll({type: "textEnd"});}}, - {name: "centerLine", defaultHandler: function() {return self._doScroll({type: "centerLine"});}}, - - {name: "selectLineUp", defaultHandler: function() {return self._doLineUp({select: true});}}, - {name: "selectLineDown", defaultHandler: function() {return self._doLineDown({select: true});}}, - {name: "selectWholeLineUp", defaultHandler: function() {return self._doLineUp({select: true, wholeLine: true});}}, - {name: "selectWholeLineDown", defaultHandler: function() {return self._doLineDown({select: true, wholeLine: true});}}, - {name: "selectLineStart", defaultHandler: function() {return self._doHome({select: true, ctrl:false});}}, - {name: "selectLineEnd", defaultHandler: function() {return self._doEnd({select: true, ctrl:false});}}, - {name: "selectCharPrevious", defaultHandler: function() {return self._doCursorPrevious({select: true, unit:"character"});}}, - {name: "selectCharNext", defaultHandler: function() {return self._doCursorNext({select: true, unit:"character"});}}, - {name: "selectPageUp", defaultHandler: function() {return self._doPageUp({select: true});}}, - {name: "selectPageDown", defaultHandler: function() {return self._doPageDown({select: true});}}, - {name: "selectWordPrevious", defaultHandler: function() {return self._doCursorPrevious({select: true, unit:"word"});}}, - {name: "selectWordNext", defaultHandler: function() {return self._doCursorNext({select: true, unit:"word"});}}, - {name: "selectTextStart", defaultHandler: function() {return self._doHome({select: true, ctrl:true});}}, - {name: "selectTextEnd", defaultHandler: function() {return self._doEnd({select: true, ctrl:true});}}, - - {name: "deletePrevious", defaultHandler: function() {return self._doBackspace({unit:"character"});}}, - {name: "deleteNext", defaultHandler: function() {return self._doDelete({unit:"character"});}}, - {name: "deleteWordPrevious", defaultHandler: function() {return self._doBackspace({unit:"word"});}}, - {name: "deleteWordNext", defaultHandler: function() {return self._doDelete({unit:"word"});}}, - {name: "deleteLineStart", defaultHandler: function() {return self._doBackspace({unit: "line"});}}, - {name: "deleteLineEnd", defaultHandler: function() {return self._doDelete({unit: "line"});}}, - {name: "tab", defaultHandler: function() {return self._doTab();}}, - {name: "enter", defaultHandler: function() {return self._doEnter();}}, - {name: "enterNoCursor", defaultHandler: function() {return self._doEnter({noCursor:true});}}, - {name: "selectAll", defaultHandler: function() {return self._doSelectAll();}}, - {name: "copy", defaultHandler: function() {return self._doCopy();}}, - {name: "cut", defaultHandler: function() {return self._doCut();}}, - {name: "paste", defaultHandler: function() {return self._doPaste();}} - ]; - }, - _createLine: function(parent, div, document, lineIndex, model) { - var lineText = model.getLine(lineIndex); - var lineStart = model.getLineStart(lineIndex); - var e = {type:"LineStyle", textView: this, lineIndex: lineIndex, lineText: lineText, lineStart: lineStart}; - this.onLineStyle(e); - var lineDiv = div || document.createElement("DIV"); - if (!div || !this._compare(div.viewStyle, e.style)) { - this._applyStyle(e.style, lineDiv, div); - lineDiv.viewStyle = e.style; - } - lineDiv.lineIndex = lineIndex; - var ranges = []; - var data = {tabOffset: 0, ranges: ranges}; - this._createRanges(e.ranges, lineText, 0, lineText.length, lineStart, data); - - /* - * A trailing span with a whitespace is added for three different reasons: - * 1. Make sure the height of each line is the largest of the default font - * in normal, italic, bold, and italic-bold. - * 2. When full selection is off, Firefox, Opera and IE9 do not extend the - * selection at the end of the line when the line is fully selected. - * 3. The height of a div with only an empty span is zero. - */ - var c = " "; - if (!this._fullSelection && isIE < 9) { - /* - * IE8 already selects extra space at end of a line fully selected, - * adding another space at the end of the line causes the selection - * to look too big. The fix is to use a zero-width space (\uFEFF) instead. - */ - c = "\uFEFF"; - } - if (isWebkit) { - /* - * Feature in WekKit. Adding a regular white space to the line will - * cause the longest line in the view to wrap even though "pre" is set. - * The fix is to use the zero-width non-joiner character (\u200C) instead. - * Note: To not use \uFEFF because in old version of Chrome this character - * shows a glyph; - */ - c = "\u200C"; - } - ranges.push({text: c, style: this._largestFontStyle, ignoreChars: 1}); - - var range, span, style, oldSpan, oldStyle, text, oldText, end = 0, oldEnd = 0, next; - var changeCount, changeStart; - if (div) { - var modelChangedEvent = div.modelChangedEvent; - if (modelChangedEvent) { - if (modelChangedEvent.removedLineCount === 0 && modelChangedEvent.addedLineCount === 0) { - changeStart = modelChangedEvent.start - lineStart; - changeCount = modelChangedEvent.addedCharCount - modelChangedEvent.removedCharCount; - } else { - changeStart = -1; - } - div.modelChangedEvent = undefined; - } - oldSpan = div.firstChild; - } - for (var i = 0; i < ranges.length; i++) { - range = ranges[i]; - text = range.text; - end += text.length; - style = range.style; - if (oldSpan) { - oldText = oldSpan.firstChild.data; - oldStyle = oldSpan.viewStyle; - if (oldText === text && this._compare(style, oldStyle)) { - oldEnd += oldText.length; - oldSpan._rectsCache = undefined; - span = oldSpan = oldSpan.nextSibling; - continue; - } else { - while (oldSpan) { - if (changeStart !== -1) { - var spanEnd = end; - if (spanEnd >= changeStart) { - spanEnd -= changeCount; - } - var length = oldSpan.firstChild.data.length; - if (oldEnd + length > spanEnd) { break; } - oldEnd += length; - } - next = oldSpan.nextSibling; - lineDiv.removeChild(oldSpan); - oldSpan = next; - } - } - } - span = this._createSpan(lineDiv, document, text, style, range.ignoreChars); - if (oldSpan) { - lineDiv.insertBefore(span, oldSpan); - } else { - lineDiv.appendChild(span); - } - if (div) { - div.lineWidth = undefined; - } - } - if (div) { - var tmp = span ? span.nextSibling : null; - while (tmp) { - next = tmp.nextSibling; - div.removeChild(tmp); - tmp = next; - } - } else { - parent.appendChild(lineDiv); - } - return lineDiv; - }, - _createRanges: function(ranges, text, start, end, lineStart, data) { - if (start >= end) { return; } - if (ranges) { - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - if (range.end <= lineStart + start) { continue; } - var styleStart = Math.max(lineStart + start, range.start) - lineStart; - if (styleStart >= end) { break; } - var styleEnd = Math.min(lineStart + end, range.end) - lineStart; - if (styleStart < styleEnd) { - styleStart = Math.max(start, styleStart); - styleEnd = Math.min(end, styleEnd); - if (start < styleStart) { - this._createRange(text, start, styleStart, null, data); - } - while (i + 1 < ranges.length && ranges[i + 1].start - lineStart === styleEnd && this._compare(range.style, ranges[i + 1].style)) { - range = ranges[i + 1]; - styleEnd = Math.min(lineStart + end, range.end) - lineStart; - i++; - } - this._createRange(text, styleStart, styleEnd, range.style, data); - start = styleEnd; - } - } - } - if (start < end) { - this._createRange(text, start, end, null, data); - } - }, - _createRange: function(text, start, end, style, data) { - if (start >= end) { return; } - var tabSize = this._customTabSize, range; - if (tabSize && tabSize !== 8) { - var tabIndex = text.indexOf("\t", start); - while (tabIndex !== -1 && tabIndex < end) { - if (start < tabIndex) { - range = {text: text.substring(start, tabIndex), style: style}; - data.ranges.push(range); - data.tabOffset += range.text.length; - } - var spacesCount = tabSize - (data.tabOffset % tabSize); - if (spacesCount > 0) { - //TODO hack to preserve text length in getDOMText() - var spaces = "\u00A0"; - for (var i = 1; i < spacesCount; i++) { - spaces += " "; - } - range = {text: spaces, style: style, ignoreChars: spacesCount - 1}; - data.ranges.push(range); - data.tabOffset += range.text.length; - } - start = tabIndex + 1; - tabIndex = text.indexOf("\t", start); - } - } - if (start < end) { - range = {text: text.substring(start, end), style: style}; - data.ranges.push(range); - data.tabOffset += range.text.length; - } - }, - _createSpan: function(parent, document, text, style, ignoreChars) { - var isLink = style && style.tagName === "A"; - if (isLink) { parent.hasLink = true; } - var tagName = isLink && this._linksVisible ? "A" : "SPAN"; - var child = document.createElement(tagName); - child.appendChild(document.createTextNode(text)); - this._applyStyle(style, child); - if (tagName === "A") { - var self = this; - addHandler(child, "click", function(e) { return self._handleLinkClick(e); }, false); - } - child.viewStyle = style; - if (ignoreChars) { - child.ignoreChars = ignoreChars; - } - return child; - }, - _createRuler: function(ruler) { - if (!this._clientDiv) { return; } - var document = this._frameDocument; - var body = document.body; - var side = ruler.getLocation(); - var rulerParent = side === "left" ? this._leftDiv : this._rightDiv; - if (!rulerParent) { - rulerParent = document.createElement("DIV"); - rulerParent.style.overflow = "hidden"; - rulerParent.style.MozUserSelect = "none"; - rulerParent.style.WebkitUserSelect = "none"; - if (isIE) { - rulerParent.attachEvent("onselectstart", function() {return false;}); - } - rulerParent.style.position = "absolute"; - rulerParent.style.top = "0px"; - rulerParent.style.cursor = "default"; - body.appendChild(rulerParent); - if (side === "left") { - this._leftDiv = rulerParent; - rulerParent.className = "viewLeftRuler"; - } else { - this._rightDiv = rulerParent; - rulerParent.className = "viewRightRuler"; - } - var table = document.createElement("TABLE"); - rulerParent.appendChild(table); - table.cellPadding = "0px"; - table.cellSpacing = "0px"; - table.border = "0px"; - table.insertRow(0); - var self = this; - addHandler(rulerParent, "click", function(e) { self._handleRulerEvent(e); }); - addHandler(rulerParent, "dblclick", function(e) { self._handleRulerEvent(e); }); - addHandler(rulerParent, "mousemove", function(e) { self._handleRulerEvent(e); }); - addHandler(rulerParent, "mouseover", function(e) { self._handleRulerEvent(e); }); - addHandler(rulerParent, "mouseout", function(e) { self._handleRulerEvent(e); }); - } - var div = document.createElement("DIV"); - div._ruler = ruler; - div.rulerChanged = true; - div.style.position = "relative"; - var row = rulerParent.firstChild.rows[0]; - var index = row.cells.length; - var cell = row.insertCell(index); - cell.vAlign = "top"; - cell.appendChild(div); - }, - _createFrame: function() { - if (this.frame) { return; } - var parent = this._parent; - while (parent.hasChildNodes()) { parent.removeChild(parent.lastChild); } - var parentDocument = parent.ownerDocument; - this._parentDocument = parentDocument; - var frame = parentDocument.createElement("IFRAME"); - this._frame = frame; - frame.frameBorder = "0px";//for IE, needs to be set before the frame is added to the parent - frame.style.border = "0px"; - frame.style.width = "100%"; - frame.style.height = "100%"; - frame.scrolling = "no"; - var self = this; - /* - * Note that it is not possible to create the contents of the frame if the - * parent is not connected to the document. Only create it when the load - * event is trigged. - */ - this._loadHandler = function(e) { - self._handleLoad(e); - }; - addHandler(frame, "load", this._loadHandler, !!isFirefox); - if (!isWebkit) { - /* - * Feature in IE and Firefox. It is not possible to get the style of an - * element if it is not layed out because one of the ancestor has - * style.display = none. This means that the view cannot be created in this - * situations, since no measuring can be performed. The fix is to listen - * for DOMAttrModified and create or destroy the view when the style.display - * attribute changes. - */ - addHandler(parentDocument, "DOMAttrModified", this._attrModifiedHandler = function(e) { - self._handleDOMAttrModified(e); - }); - } - parent.appendChild(frame); - /* create synchronously if possible */ - if (this._sync) { - this._handleLoad(); - } - }, - _getFrameHTML: function() { - var html = []; - html.push(""); - html.push(""); - html.push(""); - if (isIE < 9) { - html.push(""); - } - html.push(""); - if (this._stylesheet) { - var stylesheet = typeof(this._stylesheet) === "string" ? [this._stylesheet] : this._stylesheet; - for (var i = 0; i < stylesheet.length; i++) { - var sheet = stylesheet[i]; - var isLink = this._isLinkURL(sheet); - if (isLink && this._sync) { - try { - var objXml = new XMLHttpRequest(); - if (objXml.overrideMimeType) { - objXml.overrideMimeType("text/css"); - } - objXml.open("GET", sheet, false); - objXml.send(null); - sheet = objXml.responseText; - isLink = false; - } catch (e) {} - } - if (isLink) { - html.push(""); - } else { - html.push(""); - } - } - } - /* - * Feature in WebKit. In WebKit, window load will not wait for the style sheets - * to be loaded unless there is script element after the style sheet link elements. - */ - html.push(""); - html.push(""); - html.push(""); - html.push(""); - return html.join(""); - }, - _createView: function() { - if (this._frameDocument) { return; } - var frameWindow = this._frameWindow = this._frame.contentWindow; - var frameDocument = this._frameDocument = frameWindow.document; - var self = this; - function write() { - frameDocument.open("text/html", "replace"); - frameDocument.write(self._getFrameHTML()); - frameDocument.close(); - self._windowLoadHandler = function(e) { - /* - * Bug in Safari. Safari sends the window load event before the - * style sheets are loaded. The fix is to defer creation of the - * contents until the document readyState changes to complete. - */ - if (self._isDocumentReady()) { - self._createContent(); - } - }; - addHandler(frameWindow, "load", self._windowLoadHandler); - } - write(); - if (this._sync) { - this._createContent(); - } else { - /* - * Bug in Webkit. Webkit does not send the load event for the iframe window when the main page - * loads as a result of backward or forward navigation. - * The fix is to use a timer to create the content only when the document is ready. - */ - this._createViewTimer = function() { - if (self._clientDiv) { return; } - if (self._isDocumentReady()) { - self._createContent(); - } else { - setTimeout(self._createViewTimer, 10); - } - }; - setTimeout(this._createViewTimer, 10); - } - }, - _isDocumentReady: function() { - var frameDocument = this._frameDocument; - if (!frameDocument) { return false; } - if (frameDocument.readyState === "complete") { - return true; - } else if (frameDocument.readyState === "interactive" && isFirefox) { - /* - * Bug in Firefox. Firefox does not change the document ready state to complete - * all the time. The fix is to wait for the ready state to be "interactive" and check that - * all css rules are initialized. - */ - var styleSheets = frameDocument.styleSheets; - var styleSheetCount = 1; - if (this._stylesheet) { - styleSheetCount += typeof(this._stylesheet) === "string" ? 1 : this._stylesheet.length; - } - if (styleSheetCount === styleSheets.length) { - var index = 0; - while (index < styleSheets.length) { - var count = 0; - try { - count = styleSheets.item(index).cssRules.length; - } catch (ex) { - /* - * Feature in Firefox. To determine if a stylesheet is loaded the number of css rules is used, if the - * stylesheet is not loaded this operation will throw an invalid access error. When a stylesheet from - * a different domain is loaded, accessing the css rules will result in a security exception. In this - * case count is set to 1 to indicate the stylesheet is loaded. - */ - if (ex.code !== DOMException.INVALID_ACCESS_ERR) { - count = 1; - } - } - if (count === 0) { break; } - index++; - } - return index === styleSheets.length; - } - } - return false; - }, - _createContent: function() { - if (this._clientDiv) { return; } - var parent = this._parent; - var parentDocument = this._parentDocument; - var frameDocument = this._frameDocument; - var body = frameDocument.body; - this._setThemeClass(this._themeClass, true); - body.style.margin = "0px"; - body.style.borderWidth = "0px"; - body.style.padding = "0px"; - - var textArea; - if (isPad) { - var touchDiv = parentDocument.createElement("DIV"); - this._touchDiv = touchDiv; - touchDiv.style.position = "absolute"; - touchDiv.style.border = "0px"; - touchDiv.style.padding = "0px"; - touchDiv.style.margin = "0px"; - touchDiv.style.zIndex = "2"; - touchDiv.style.overflow = "hidden"; - touchDiv.style.background="transparent"; - touchDiv.style.WebkitUserSelect = "none"; - parent.appendChild(touchDiv); - - textArea = parentDocument.createElement("TEXTAREA"); - this._textArea = textArea; - textArea.style.position = "absolute"; - textArea.style.whiteSpace = "pre"; - textArea.style.left = "-1000px"; - textArea.tabIndex = 1; - textArea.autocapitalize = "off"; - textArea.autocorrect = "off"; - textArea.className = "viewContainer"; - textArea.style.background = "transparent"; - textArea.style.color = "transparent"; - textArea.style.border = "0px"; - textArea.style.padding = "0px"; - textArea.style.margin = "0px"; - textArea.style.borderRadius = "0px"; - textArea.style.WebkitAppearance = "none"; - textArea.style.WebkitTapHighlightColor = "transparent"; - touchDiv.appendChild(textArea); - } - if (isFirefox) { - var clipboardDiv = frameDocument.createElement("DIV"); - this._clipboardDiv = clipboardDiv; - clipboardDiv.style.position = "fixed"; - clipboardDiv.style.whiteSpace = "pre"; - clipboardDiv.style.left = "-1000px"; - body.appendChild(clipboardDiv); - } - - var viewDiv = frameDocument.createElement("DIV"); - viewDiv.className = "view"; - this._viewDiv = viewDiv; - viewDiv.id = "viewDiv"; - viewDiv.tabIndex = -1; - viewDiv.style.overflow = "auto"; - viewDiv.style.position = "absolute"; - viewDiv.style.top = "0px"; - viewDiv.style.borderWidth = "0px"; - viewDiv.style.margin = "0px"; - viewDiv.style.outline = "none"; - body.appendChild(viewDiv); - - var scrollDiv = frameDocument.createElement("DIV"); - this._scrollDiv = scrollDiv; - scrollDiv.id = "scrollDiv"; - scrollDiv.style.margin = "0px"; - scrollDiv.style.borderWidth = "0px"; - scrollDiv.style.padding = "0px"; - viewDiv.appendChild(scrollDiv); - - if (isFirefox) { - var clipDiv = frameDocument.createElement("DIV"); - this._clipDiv = clipDiv; - clipDiv.id = "clipDiv"; - clipDiv.style.position = "fixed"; - clipDiv.style.overflow = "hidden"; - clipDiv.style.margin = "0px"; - clipDiv.style.borderWidth = "0px"; - clipDiv.style.padding = "0px"; - scrollDiv.appendChild(clipDiv); - - var clipScrollDiv = frameDocument.createElement("DIV"); - this._clipScrollDiv = clipScrollDiv; - clipScrollDiv.id = "clipScrollDiv"; - clipScrollDiv.style.position = "absolute"; - clipScrollDiv.style.height = "1px"; - clipScrollDiv.style.top = "-1000px"; - clipDiv.appendChild(clipScrollDiv); - } - - this._setFullSelection(this._fullSelection, true); - - var clientDiv = frameDocument.createElement("DIV"); - clientDiv.className = "viewContent"; - this._clientDiv = clientDiv; - clientDiv.id = "clientDiv"; - clientDiv.style.whiteSpace = "pre"; - clientDiv.style.position = this._clipDiv ? "absolute" : "fixed"; - clientDiv.style.borderWidth = "0px"; - clientDiv.style.margin = "0px"; - clientDiv.style.padding = "0px"; - clientDiv.style.outline = "none"; - clientDiv.style.zIndex = "1"; - if (isPad) { - clientDiv.style.WebkitTapHighlightColor = "transparent"; - } - (this._clipDiv || scrollDiv).appendChild(clientDiv); - - if (isFirefox && !clientDiv.setCapture) { - var overlayDiv = frameDocument.createElement("DIV"); - this._overlayDiv = overlayDiv; - overlayDiv.id = "overlayDiv"; - overlayDiv.style.position = clientDiv.style.position; - overlayDiv.style.borderWidth = clientDiv.style.borderWidth; - overlayDiv.style.margin = clientDiv.style.margin; - overlayDiv.style.padding = clientDiv.style.padding; - overlayDiv.style.cursor = "text"; - overlayDiv.style.zIndex = "2"; - (this._clipDiv || scrollDiv).appendChild(overlayDiv); - } - if (!isPad) { - clientDiv.contentEditable = "true"; - } - this._lineHeight = this._calculateLineHeight(); - this._viewPadding = this._calculatePadding(); - if (isIE) { - body.style.lineHeight = this._lineHeight + "px"; - } - this._setTabSize(this._tabSize, true); - this._hookEvents(); - var rulers = this._rulers; - for (var i=0; i 0 || v > 0) { - viewDiv.scrollLeft = h; - viewDiv.scrollTop = v; - } - this.onLoad({type: "Load"}); - }, - _defaultOptions: function() { - return { - parent: {value: undefined, recreate: true, update: null}, - model: {value: undefined, recreate: false, update: this.setModel}, - readonly: {value: false, recreate: false, update: null}, - fullSelection: {value: true, recreate: false, update: this._setFullSelection}, - tabSize: {value: 8, recreate: false, update: this._setTabSize}, - expandTab: {value: false, recreate: false, update: null}, - stylesheet: {value: [], recreate: false, update: this._setStyleSheet}, - themeClass: {value: undefined, recreate: false, update: this._setThemeClass}, - sync: {value: false, recreate: false, update: null} - }; - }, - _destroyFrame: function() { - var frame = this._frame; - if (!frame) { return; } - if (this._loadHandler) { - removeHandler(frame, "load", this._loadHandler, !!isFirefox); - this._loadHandler = null; - } - if (this._attrModifiedHandler) { - removeHandler(this._parentDocument, "DOMAttrModified", this._attrModifiedHandler); - this._attrModifiedHandler = null; - } - frame.parentNode.removeChild(frame); - this._frame = null; - }, - _destroyRuler: function(ruler) { - var side = ruler.getLocation(); - var rulerParent = side === "left" ? this._leftDiv : this._rightDiv; - if (rulerParent) { - var row = rulerParent.firstChild.rows[0]; - var cells = row.cells; - for (var index = 0; index < cells.length; index++) { - var cell = cells[index]; - if (cell.firstChild._ruler === ruler) { break; } - } - if (index === cells.length) { return; } - row.cells[index]._ruler = undefined; - row.deleteCell(index); - } - }, - _destroyView: function() { - var clientDiv = this._clientDiv; - if (!clientDiv) { return; } - this._setGrab(null); - this._unhookEvents(); - if (this._windowLoadHandler) { - removeHandler(this._frameWindow, "load", this._windowLoadHandler); - this._windowLoadHandler = null; - } - - /* Destroy timers */ - if (this._autoScrollTimerID) { - clearTimeout(this._autoScrollTimerID); - this._autoScrollTimerID = null; - } - if (this._updateTimer) { - clearTimeout(this._updateTimer); - this._updateTimer = null; - } - - /* Destroy DOM */ - var parent = this._frameDocument.body; - while (parent.hasChildNodes()) { parent.removeChild(parent.lastChild); } - if (this._touchDiv) { - this._parent.removeChild(this._touchDiv); - this._touchDiv = null; - } - this._selDiv1 = null; - this._selDiv2 = null; - this._selDiv3 = null; - this._insertedSelRule = false; - this._textArea = null; - this._clipboardDiv = null; - this._scrollDiv = null; - this._viewDiv = null; - this._clipDiv = null; - this._clipScrollDiv = null; - this._clientDiv = null; - this._overlayDiv = null; - this._leftDiv = null; - this._rightDiv = null; - this._frameDocument = null; - this._frameWindow = null; - this.onUnload({type: "Unload"}); - }, - _doAutoScroll: function (direction, x, y) { - this._autoScrollDir = direction; - this._autoScrollX = x; - this._autoScrollY = y; - if (!this._autoScrollTimerID) { - this._autoScrollTimer(); - } - }, - _endAutoScroll: function () { - if (this._autoScrollTimerID) { clearTimeout(this._autoScrollTimerID); } - this._autoScrollDir = undefined; - this._autoScrollTimerID = undefined; - }, - _fixCaret: function() { - var clientDiv = this._clientDiv; - if (clientDiv) { - var hasFocus = this._hasFocus; - this._ignoreFocus = true; - if (hasFocus) { clientDiv.blur(); } - clientDiv.contentEditable = false; - clientDiv.contentEditable = true; - if (hasFocus) { clientDiv.focus(); } - this._ignoreFocus = false; - } - }, - _getBaseText: function(start, end) { - var model = this._model; - /* This is the only case the view access the base model, alternatively the view could use a event to application to customize the text */ - if (model.getBaseModel) { - start = model.mapOffset(start); - end = model.mapOffset(end); - model = model.getBaseModel(); - } - return model.getText(start, end); - }, - _getBoundsAtOffset: function (offset) { - var model = this._model; - var document = this._frameDocument; - var clientDiv = this._clientDiv; - var lineIndex = model.getLineAtOffset(offset); - var dummy; - var child = this._getLineNode(lineIndex); - if (!child) { - child = dummy = this._createLine(clientDiv, null, document, lineIndex, model); - } - var result = null; - if (offset < model.getLineEnd(lineIndex)) { - var lineOffset = model.getLineStart(lineIndex); - var lineChild = child.firstChild; - while (lineChild) { - var textNode = lineChild.firstChild; - var nodeLength = textNode.length; - if (lineChild.ignoreChars) { - nodeLength -= lineChild.ignoreChars; - } - if (lineOffset + nodeLength > offset) { - var index = offset - lineOffset; - var range; - if (isRangeRects) { - range = document.createRange(); - range.setStart(textNode, index); - range.setEnd(textNode, index + 1); - result = range.getBoundingClientRect(); - } else if (isIE) { - range = document.body.createTextRange(); - range.moveToElementText(lineChild); - range.collapse(); - range.moveEnd("character", index + 1); - range.moveStart("character", index); - result = range.getBoundingClientRect(); - } else { - var text = textNode.data; - lineChild.removeChild(textNode); - lineChild.appendChild(document.createTextNode(text.substring(0, index))); - var span = document.createElement("SPAN"); - span.appendChild(document.createTextNode(text.substring(index, index + 1))); - lineChild.appendChild(span); - lineChild.appendChild(document.createTextNode(text.substring(index + 1))); - result = span.getBoundingClientRect(); - lineChild.innerHTML = ""; - lineChild.appendChild(textNode); - if (!dummy) { - /* - * Removing the element node that holds the selection start or end - * causes the selection to be lost. The fix is to detect this case - * and restore the selection. - */ - var s = this._getSelection(); - if ((lineOffset <= s.start && s.start < lineOffset + nodeLength) || (lineOffset <= s.end && s.end < lineOffset + nodeLength)) { - this._updateDOMSelection(); - } - } - } - if (isIE) { - var logicalXDPI = window.screen.logicalXDPI; - var deviceXDPI = window.screen.deviceXDPI; - result.left = result.left * logicalXDPI / deviceXDPI; - result.right = result.right * logicalXDPI / deviceXDPI; - } - break; - } - lineOffset += nodeLength; - lineChild = lineChild.nextSibling; - } - } - if (!result) { - var rect = this._getLineBoundingClientRect(child); - result = {left: rect.right, right: rect.right}; - } - if (dummy) { clientDiv.removeChild(dummy); } - return result; - }, - _getBottomIndex: function (fullyVisible) { - var child = this._bottomChild; - if (fullyVisible && this._getClientHeight() > this._getLineHeight()) { - var rect = child.getBoundingClientRect(); - var clientRect = this._clientDiv.getBoundingClientRect(); - if (rect.bottom > clientRect.bottom) { - child = this._getLinePrevious(child) || child; - } - } - return child.lineIndex; - }, - _getFrameHeight: function() { - return this._frameDocument.documentElement.clientHeight; - }, - _getFrameWidth: function() { - return this._frameDocument.documentElement.clientWidth; - }, - _getClientHeight: function() { - var viewPad = this._getViewPadding(); - return Math.max(0, this._viewDiv.clientHeight - viewPad.top - viewPad.bottom); - }, - _getClientWidth: function() { - var viewPad = this._getViewPadding(); - return Math.max(0, this._viewDiv.clientWidth - viewPad.left - viewPad.right); - }, - _getClipboardText: function (event, handler) { - var delimiter = this._model.getLineDelimiter(); - var clipboadText, text; - if (this._frameWindow.clipboardData) { - //IE - clipboadText = []; - text = this._frameWindow.clipboardData.getData("Text"); - this._convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);}); - text = clipboadText.join(""); - if (handler) { handler(text); } - return text; - } - if (isFirefox) { - this._ignoreFocus = true; - var document = this._frameDocument; - var clipboardDiv = this._clipboardDiv; - clipboardDiv.innerHTML = "
";
-				clipboardDiv.firstChild.focus();
-				var self = this;
-				var _getText = function() {
-					var noteText = self._getTextFromElement(clipboardDiv);
-					clipboardDiv.innerHTML = "";
-					clipboadText = [];
-					self._convertDelimiter(noteText, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
-					return clipboadText.join("");
-				};
-				
-				/* Try execCommand first. Works on firefox with clipboard permission. */
-				var result = false;
-				this._ignorePaste = true;
-
-				/* Do not try execCommand if middle-click is used, because if we do, we get the clipboard text, not the primary selection text. */
-				if (!isLinux || this._lastMouseButton !== 2) {
-					try {
-						result = document.execCommand("paste", false, null);
-					} catch (ex) {
-						/* Firefox can throw even when execCommand() works, see bug 362835. */
-						result = clipboardDiv.childNodes.length > 1 || clipboardDiv.firstChild && clipboardDiv.firstChild.childNodes.length > 0;
-					}
-				}
-				this._ignorePaste = false;
-				if (!result) {
-					/* Try native paste in DOM, works for firefox during the paste event. */
-					if (event) {
-						setTimeout(function() {
-							self.focus();
-							text = _getText();
-							if (text && handler) {
-								handler(text);
-							}
-							self._ignoreFocus = false;
-						}, 0);
-						return null;
-					} else {
-						/* no event and no clipboard permission, paste can't be performed */
-						this.focus();
-						this._ignoreFocus = false;
-						return "";
-					}
-				}
-				this.focus();
-				this._ignoreFocus = false;
-				text = _getText();
-				if (text && handler) {
-					handler(text);
-				}
-				return text;
-			}
-			//webkit
-			if (event && event.clipboardData) {
-				/*
-				* Webkit (Chrome/Safari) allows getData during the paste event
-				* Note: setData is not allowed, not even during copy/cut event
-				*/
-				clipboadText = [];
-				text = event.clipboardData.getData("text/plain");
-				this._convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
-				text = clipboadText.join("");
-				if (text && handler) {
-					handler(text);
-				}
-				return text;
-			} else {
-				//TODO try paste using extension (Chrome only)
-			}
-			return "";
-		},
-		_getDOMText: function(lineIndex) {
-			var child = this._getLineNode(lineIndex);
-			var lineChild = child.firstChild;
-			var text = "";
-			while (lineChild) {
-				var textNode = lineChild.firstChild;
-				while (textNode) {
-					if (lineChild.ignoreChars) {
-						for (var i = 0; i < textNode.length; i++) {
-							var ch = textNode.data.substring(i, i + 1);
-							if (ch !== " ") {
-								text += ch;
-							}
-						}
-					} else {
-						text += textNode.data;
-					}
-					textNode = textNode.nextSibling;
-				}
-				lineChild = lineChild.nextSibling;
-			}
-			return text;
-		},
-		_getTextFromElement: function(element) {
-			var document = element.ownerDocument;
-			var window = document.defaultView;
-			if (!window.getSelection) {
-				return element.innerText || element.textContent;
-			}
-
-			var newRange = document.createRange();
-			newRange.selectNode(element);
-
-			var selection = window.getSelection();
-			var oldRanges = [], i;
-			for (i = 0; i < selection.rangeCount; i++) {
-				oldRanges.push(selection.getRangeAt(i));
-			}
-
-			this._ignoreSelect = true;
-			selection.removeAllRanges();
-			selection.addRange(newRange);
-
-			var text = selection.toString();
-
-			selection.removeAllRanges();
-			for (i = 0; i < oldRanges.length; i++) {
-				selection.addRange(oldRanges[i]);
-			}
-
-			this._ignoreSelect = false;
-			return text;
-		},
-		_getViewPadding: function() {
-			return this._viewPadding;
-		},
-		_getLineBoundingClientRect: function (child) {
-			var rect = child.getBoundingClientRect();
-			var lastChild = child.lastChild;
-			//Remove any artificial trailing whitespace in the line
-			while (lastChild && lastChild.ignoreChars === lastChild.firstChild.length) {
-				lastChild = lastChild.previousSibling;
-			}
-			if (!lastChild) {
-				return {left: rect.left, top: rect.top, right: rect.left, bottom: rect.bottom};
-			}
-			var lastRect = lastChild.getBoundingClientRect();
-			return {left: rect.left, top: rect.top, right: lastRect.right, bottom: rect.bottom};
-		},
-		_getLineHeight: function() {
-			return this._lineHeight;
-		},
-		_getLineNode: function (lineIndex) {
-			var clientDiv = this._clientDiv;
-			var child = clientDiv.firstChild;
-			while (child) {
-				if (lineIndex === child.lineIndex) {
-					return child;
-				}
-				child = child.nextSibling;
-			}
-			return undefined;
-		},
-		_getLineNext: function (lineNode) {
-			var node = lineNode ? lineNode.nextSibling : this._clientDiv.firstChild;
-			while (node && node.lineIndex === -1) {
-				node = node.nextSibling;
-			}
-			return node;
-		},
-		_getLinePrevious: function (lineNode) {
-			var node = lineNode ? lineNode.previousSibling : this._clientDiv.lastChild;
-			while (node && node.lineIndex === -1) {
-				node = node.previousSibling;
-			}
-			return node;
-		},
-		_getOffset: function (offset, unit, direction) {
-			if (unit === "line") {
-				var model = this._model;
-				var lineIndex = model.getLineAtOffset(offset);
-				if (direction > 0) {
-					return model.getLineEnd(lineIndex);
-				}
-				return model.getLineStart(lineIndex);
-			}
-			if (unit === "wordend") {
-				return this._getOffset_W3C(offset, unit, direction);
-			}
-			return isIE ? this._getOffset_IE(offset, unit, direction) : this._getOffset_W3C(offset, unit, direction);
-		},
-		_getOffset_W3C: function (offset, unit, direction) {
-			function _isPunctuation(c) {
-				return (33 <= c && c <= 47) || (58 <= c && c <= 64) || (91 <= c && c <= 94) || c === 96 || (123 <= c && c <= 126);
-			}
-			function _isWhitespace(c) {
-				return c === 32 || c === 9;
-			}
-			if (unit === "word" || unit === "wordend") {
-				var model = this._model;
-				var lineIndex = model.getLineAtOffset(offset);
-				var lineText = model.getLine(lineIndex);
-				var lineStart = model.getLineStart(lineIndex);
-				var lineEnd = model.getLineEnd(lineIndex);
-				var lineLength = lineText.length;
-				var offsetInLine = offset - lineStart;
-				
-				
-				var c, previousPunctuation, previousLetterOrDigit, punctuation, letterOrDigit;
-				if (direction > 0) {
-					if (offsetInLine === lineLength) { return lineEnd; }
-					c = lineText.charCodeAt(offsetInLine);
-					previousPunctuation = _isPunctuation(c); 
-					previousLetterOrDigit = !previousPunctuation && !_isWhitespace(c);
-					offsetInLine++;
-					while (offsetInLine < lineLength) {
-						c = lineText.charCodeAt(offsetInLine);
-						punctuation = _isPunctuation(c);
-						if (unit === "wordend") {
-							if (!punctuation && previousPunctuation) { break; }
-						} else {
-							if (punctuation && !previousPunctuation) { break; }
-						}
-						letterOrDigit  = !punctuation && !_isWhitespace(c);
-						if (unit === "wordend") {
-							if (!letterOrDigit && previousLetterOrDigit) { break; }
-						} else {
-							if (letterOrDigit && !previousLetterOrDigit) { break; }
-						}
-						previousLetterOrDigit = letterOrDigit;
-						previousPunctuation = punctuation;
-						offsetInLine++;
-					}
-				} else {
-					if (offsetInLine === 0) { return lineStart; }
-					offsetInLine--;
-					c = lineText.charCodeAt(offsetInLine);
-					previousPunctuation = _isPunctuation(c); 
-					previousLetterOrDigit = !previousPunctuation && !_isWhitespace(c);
-					while (0 < offsetInLine) {
-						c = lineText.charCodeAt(offsetInLine - 1);
-						punctuation = _isPunctuation(c);
-						if (unit === "wordend") {
-							if (punctuation && !previousPunctuation) { break; }
-						} else {
-							if (!punctuation && previousPunctuation) { break; }
-						}
-						letterOrDigit  = !punctuation && !_isWhitespace(c);
-						if (unit === "wordend") {
-							if (letterOrDigit && !previousLetterOrDigit) { break; }
-						} else {
-							if (!letterOrDigit && previousLetterOrDigit) { break; }
-						}
-						previousLetterOrDigit = letterOrDigit;
-						previousPunctuation = punctuation;
-						offsetInLine--;
-					}
-				}
-				return lineStart + offsetInLine;
-			}
-			return offset + direction;
-		},
-		_getOffset_IE: function (offset, unit, direction) {
-			var document = this._frameDocument;
-			var model = this._model;
-			var lineIndex = model.getLineAtOffset(offset);
-			var clientDiv = this._clientDiv;
-			var dummy;
-			var child = this._getLineNode(lineIndex);
-			if (!child) {
-				child = dummy = this._createLine(clientDiv, null, document, lineIndex, model);
-			}
-			var result = 0, range, length;
-			var lineOffset = model.getLineStart(lineIndex);
-			if (offset === model.getLineEnd(lineIndex)) {
-				range = document.body.createTextRange();
-				range.moveToElementText(child.lastChild);
-				length = range.text.length;
-				range.moveEnd(unit, direction);
-				result = offset + range.text.length - length;
-			} else if (offset === lineOffset && direction < 0) {
-				result = lineOffset;
-			} else {
-				var lineChild = child.firstChild;
-				while (lineChild) {
-					var textNode = lineChild.firstChild;
-					var nodeLength = textNode.length;
-					if (lineChild.ignoreChars) {
-						nodeLength -= lineChild.ignoreChars;
-					}
-					if (lineOffset + nodeLength > offset) {
-						range = document.body.createTextRange();
-						if (offset === lineOffset && direction < 0) {
-							range.moveToElementText(lineChild.previousSibling);
-						} else {
-							range.moveToElementText(lineChild);
-							range.collapse();
-							range.moveEnd("character", offset - lineOffset);
-						}
-						length = range.text.length;
-						range.moveEnd(unit, direction);
-						result = offset + range.text.length - length;
-						break;
-					}
-					lineOffset = nodeLength + lineOffset;
-					lineChild = lineChild.nextSibling;
-				}
-			}
-			if (dummy) { clientDiv.removeChild(dummy); }
-			return result;
-		},
-		_getOffsetToX: function (offset) {
-			return this._getBoundsAtOffset(offset).left;
-		},
-		_getPadding: function (node) {
-			var left,top,right,bottom;
-			if (node.currentStyle) {
-				left = node.currentStyle.paddingLeft;
-				top = node.currentStyle.paddingTop;
-				right = node.currentStyle.paddingRight;
-				bottom = node.currentStyle.paddingBottom;
-			} else if (this._frameWindow.getComputedStyle) {
-				var style = this._frameWindow.getComputedStyle(node, null);
-				left = style.getPropertyValue("padding-left");
-				top = style.getPropertyValue("padding-top");
-				right = style.getPropertyValue("padding-right");
-				bottom = style.getPropertyValue("padding-bottom");
-			}
-			return {
-					left: parseInt(left, 10), 
-					top: parseInt(top, 10),
-					right: parseInt(right, 10),
-					bottom: parseInt(bottom, 10)
-			};
-		},
-		_getScroll: function() {
-			var viewDiv = this._viewDiv;
-			return {x: viewDiv.scrollLeft, y: viewDiv.scrollTop};
-		},
-		_getSelection: function () {
-			return this._selection.clone();
-		},
-		_getTopIndex: function (fullyVisible) {
-			var child = this._topChild;
-			if (fullyVisible && this._getClientHeight() > this._getLineHeight()) {
-				var rect = child.getBoundingClientRect();
-				var viewPad = this._getViewPadding();
-				var viewRect = this._viewDiv.getBoundingClientRect();
-				if (rect.top < viewRect.top + viewPad.top) {
-					child = this._getLineNext(child) || child;
-				}
-			}
-			return child.lineIndex;
-		},
-		_getXToOffset: function (lineIndex, x) {
-			var model = this._model;
-			var lineStart = model.getLineStart(lineIndex);
-			var lineEnd = model.getLineEnd(lineIndex);
-			if (lineStart === lineEnd) {
-				return lineStart;
-			}
-			var document = this._frameDocument;
-			var clientDiv = this._clientDiv;
-			var dummy;
-			var child = this._getLineNode(lineIndex);
-			if (!child) {
-				child = dummy = this._createLine(clientDiv, null, document, lineIndex, model);
-			}
-			var lineRect = this._getLineBoundingClientRect(child);
-			if (x < lineRect.left) { x = lineRect.left; }
-			if (x > lineRect.right) { x = lineRect.right; }
-			/*
-			* Bug in IE 8 and earlier. The coordinates of getClientRects() are relative to
-			* the browser window.  The fix is to convert to the frame window before using it. 
-			*/
-			var deltaX = 0, rects;
-			if (isIE < 9) {
-				rects = child.getClientRects();
-				var minLeft = rects[0].left;
-				for (var i=1; i 1) {
-								var mid = Math.floor((high + low) / 2);
-								start = low + 1;
-								end = mid === nodeLength - 1 && lineChild.ignoreChars ? textNode.length : mid + 1;
-								if (isRangeRects) {
-									range.setStart(textNode, start);
-									range.setEnd(textNode, end);
-								} else {
-									range.moveToElementText(lineChild);
-									range.move("character", start);
-									range.moveEnd("character", end - start);
-								}
-								rects = range.getClientRects();
-								var found = false;
-								for (var k = 0; k < rects.length; k++) {
-									rect = rects[k];
-									var rangeLeft = rect.left * logicalXDPI / deviceXDPI - deltaX;
-									var rangeRight = rect.right * logicalXDPI / deviceXDPI - deltaX;
-									if (rangeLeft <= x && x < rangeRight) {
-										found = true;
-										break;
-									}
-								}
-								if (found) {
-									high = mid;
-								} else {
-									low = mid;
-								}
-							}
-							offset += high;
-							start = high;
-							end = high === nodeLength - 1 && lineChild.ignoreChars ? textNode.length : Math.min(high + 1, textNode.length);
-							if (isRangeRects) {
-								range.setStart(textNode, start);
-								range.setEnd(textNode, end);
-							} else {
-								range.moveToElementText(lineChild);
-								range.move("character", start);
-								range.moveEnd("character", end - start);
-							}
-							rect = range.getClientRects()[0];
-							//TODO test for character trailing (wrong for bidi)
-							if (x > ((rect.left * logicalXDPI / deviceXDPI - deltaX) + ((rect.right - rect.left) * logicalXDPI / deviceXDPI / 2))) {
-								offset++;
-							}
-						} else {
-							var newText = [];
-							for (var q = 0; q < nodeLength; q++) {
-								newText.push("");
-								if (q === nodeLength - 1) {
-									newText.push(textNode.data.substring(q));
-								} else {
-									newText.push(textNode.data.substring(q, q + 1));
-								}
-								newText.push("");
-							}
-							lineChild.innerHTML = newText.join("");
-							var rangeChild = lineChild.firstChild;
-							while (rangeChild) {
-								rect = rangeChild.getBoundingClientRect();
-								if (rect.left <= x && x < rect.right) {
-									//TODO test for character trailing (wrong for bidi)
-									if (x > rect.left + (rect.right - rect.left) / 2) {
-										offset++;
-									}
-									break;
-								}
-								offset++;
-								rangeChild = rangeChild.nextSibling;
-							}
-							if (!dummy) {
-								lineChild.innerHTML = "";
-								lineChild.appendChild(textNode);
-								/*
-								 * Removing the element node that holds the selection start or end
-								 * causes the selection to be lost. The fix is to detect this case
-								 * and restore the selection. 
-								 */
-								var s = this._getSelection();
-								if ((offset <= s.start && s.start < offset + nodeLength) || (offset <= s.end && s.end < offset + nodeLength)) {
-									this._updateDOMSelection();
-								}
-							}
-						}
-						break done;
-					}
-				}
-				offset += nodeLength;
-				lineChild = lineChild.nextSibling;
-			}
-			if (dummy) { clientDiv.removeChild(dummy); }
-			return Math.min(lineEnd, Math.max(lineStart, offset));
-		},
-		_getYToLine: function (y) {
-			var viewPad = this._getViewPadding();
-			var viewRect = this._viewDiv.getBoundingClientRect();
-			y -= viewRect.top + viewPad.top;
-			var lineHeight = this._getLineHeight();
-			var lineIndex = Math.floor((y + this._getScroll().y) / lineHeight);
-			var lineCount = this._model.getLineCount();
-			return Math.max(0, Math.min(lineCount - 1, lineIndex));
-		},
-		_getOffsetBounds: function(offset) {
-			var model = this._model;
-			var lineIndex = model.getLineAtOffset(offset);
-			var lineHeight = this._getLineHeight();
-			var scroll = this._getScroll();
-			var viewPad = this._getViewPadding();
-			var viewRect = this._viewDiv.getBoundingClientRect();
-			var bounds = this._getBoundsAtOffset(offset);
-			var left = bounds.left;
-			var right = bounds.right;
-			var top = (lineIndex * lineHeight) - scroll.y + viewRect.top + viewPad.top;
-			var bottom = top + lineHeight;
-			return {left: left, top: top, right: right, bottom: bottom};
-		},
-		_getVisible: function() {
-			var temp = this._parent;
-			var parentDocument = temp.ownerDocument;
-			while (temp !== parentDocument) {
-				var hidden;
-				if (isIE < 9) {
-					hidden = temp.currentStyle && temp.currentStyle.display === "none";
-				} else {
-					var tempStyle = parentDocument.defaultView.getComputedStyle(temp, null);
-					hidden = tempStyle && tempStyle.getPropertyValue("display") === "none";
-				}
-				if (hidden) { return "hidden"; }
-				temp =  temp.parentNode;
-				if (!temp) { return "disconnected"; }
-			}
-			return "visible";
-		},
-		_hitOffset: function (offset, x, y) {
-			var bounds = this._getOffsetBounds(offset);
-			var left = bounds.left;
-			var right = bounds.right;
-			var top = bounds.top;
-			var bottom = bounds.bottom;
-			var area = 20;
-			left -= area;
-			top -= area;
-			right += area;
-			bottom += area;
-			return (left <= x && x <= right && top <= y && y <= bottom);
-		},
-		_hookEvents: function() {
-			var self = this;
-			this._modelListener = {
-				/** @private */
-				onChanging: function(modelChangingEvent) {
-					self._onModelChanging(modelChangingEvent);
-				},
-				/** @private */
-				onChanged: function(modelChangedEvent) {
-					self._onModelChanged(modelChangedEvent);
-				}
-			};
-			this._model.addEventListener("Changing", this._modelListener.onChanging);
-			this._model.addEventListener("Changed", this._modelListener.onChanged);
-			
-			var clientDiv = this._clientDiv;
-			var viewDiv = this._viewDiv;
-			var body = this._frameDocument.body; 
-			var handlers = this._handlers = [];
-			var resizeNode = isIE < 9 ? this._frame : this._frameWindow;
-			var focusNode = isPad ? this._textArea : (isIE ||  isFirefox ? this._clientDiv: this._frameWindow);
-			handlers.push({target: this._frameWindow, type: "unload", handler: function(e) { return self._handleUnload(e);}});
-			handlers.push({target: resizeNode, type: "resize", handler: function(e) { return self._handleResize(e);}});
-			handlers.push({target: focusNode, type: "blur", handler: function(e) { return self._handleBlur(e);}});
-			handlers.push({target: focusNode, type: "focus", handler: function(e) { return self._handleFocus(e);}});
-			handlers.push({target: viewDiv, type: "scroll", handler: function(e) { return self._handleScroll(e);}});
-			if (isPad) {
-				var touchDiv = this._touchDiv;
-				var textArea = this._textArea;
-				handlers.push({target: textArea, type: "keydown", handler: function(e) { return self._handleKeyDown(e);}});
-				handlers.push({target: textArea, type: "input", handler: function(e) { return self._handleInput(e); }});
-				handlers.push({target: textArea, type: "textInput", handler: function(e) { return self._handleTextInput(e); }});
-				handlers.push({target: textArea, type: "click", handler: function(e) { return self._handleTextAreaClick(e); }});
-				handlers.push({target: touchDiv, type: "touchstart", handler: function(e) { return self._handleTouchStart(e); }});
-				handlers.push({target: touchDiv, type: "touchmove", handler: function(e) { return self._handleTouchMove(e); }});
-				handlers.push({target: touchDiv, type: "touchend", handler: function(e) { return self._handleTouchEnd(e); }});
-			} else {
-				var topNode = this._overlayDiv || this._clientDiv;
-				var grabNode = isIE ? clientDiv : this._frameWindow;
-				handlers.push({target: clientDiv, type: "keydown", handler: function(e) { return self._handleKeyDown(e);}});
-				handlers.push({target: clientDiv, type: "keypress", handler: function(e) { return self._handleKeyPress(e);}});
-				handlers.push({target: clientDiv, type: "keyup", handler: function(e) { return self._handleKeyUp(e);}});
-				handlers.push({target: clientDiv, type: "selectstart", handler: function(e) { return self._handleSelectStart(e);}});
-				handlers.push({target: clientDiv, type: "contextmenu", handler: function(e) { return self._handleContextMenu(e);}});
-				handlers.push({target: clientDiv, type: "copy", handler: function(e) { return self._handleCopy(e);}});
-				handlers.push({target: clientDiv, type: "cut", handler: function(e) { return self._handleCut(e);}});
-				handlers.push({target: clientDiv, type: "paste", handler: function(e) { return self._handlePaste(e);}});
-				handlers.push({target: clientDiv, type: "mousedown", handler: function(e) { return self._handleMouseDown(e);}});
-				handlers.push({target: clientDiv, type: "mouseover", handler: function(e) { return self._handleMouseOver(e);}});
-				handlers.push({target: clientDiv, type: "mouseout", handler: function(e) { return self._handleMouseOut(e);}});
-				handlers.push({target: grabNode, type: "mouseup", handler: function(e) { return self._handleMouseUp(e);}});
-				handlers.push({target: grabNode, type: "mousemove", handler: function(e) { return self._handleMouseMove(e);}});
-				handlers.push({target: body, type: "mousedown", handler: function(e) { return self._handleBodyMouseDown(e);}});
-				handlers.push({target: body, type: "mouseup", handler: function(e) { return self._handleBodyMouseUp(e);}});
-				handlers.push({target: topNode, type: "dragstart", handler: function(e) { return self._handleDragStart(e);}});
-				handlers.push({target: topNode, type: "drag", handler: function(e) { return self._handleDrag(e);}});
-				handlers.push({target: topNode, type: "dragend", handler: function(e) { return self._handleDragEnd(e);}});
-				handlers.push({target: topNode, type: "dragenter", handler: function(e) { return self._handleDragEnter(e);}});
-				handlers.push({target: topNode, type: "dragover", handler: function(e) { return self._handleDragOver(e);}});
-				handlers.push({target: topNode, type: "dragleave", handler: function(e) { return self._handleDragLeave(e);}});
-				handlers.push({target: topNode, type: "drop", handler: function(e) { return self._handleDrop(e);}});
-				if (isChrome) {
-					handlers.push({target: this._parentDocument, type: "mousemove", handler: function(e) { return self._handleMouseMove(e);}});
-					handlers.push({target: this._parentDocument, type: "mouseup", handler: function(e) { return self._handleMouseUp(e);}});
-				}
-				if (isIE) {
-					handlers.push({target: this._frameDocument, type: "activate", handler: function(e) { return self._handleDocFocus(e); }});
-				}
-				if (isFirefox) {
-					handlers.push({target: this._frameDocument, type: "focus", handler: function(e) { return self._handleDocFocus(e); }});
-				}
-				if (!isIE && !isOpera) {
-					var wheelEvent = isFirefox ? "DOMMouseScroll" : "mousewheel";
-					handlers.push({target: this._viewDiv, type: wheelEvent, handler: function(e) { return self._handleMouseWheel(e); }});
-				}
-				if (isFirefox && !isWindows) {
-					handlers.push({target: this._clientDiv, type: "DOMCharacterDataModified", handler: function (e) { return self._handleDataModified(e); }});
-				}
-				if (this._overlayDiv) {
-					handlers.push({target: this._overlayDiv, type: "mousedown", handler: function(e) { return self._handleMouseDown(e);}});
-					handlers.push({target: this._overlayDiv, type: "mouseover", handler: function(e) { return self._handleMouseOver(e);}});
-					handlers.push({target: this._overlayDiv, type: "mouseout", handler: function(e) { return self._handleMouseOut(e);}});
-					handlers.push({target: this._overlayDiv, type: "contextmenu", handler: function(e) { return self._handleContextMenu(e); }});
-				}
-				if (!isW3CEvents) {
-					handlers.push({target: this._clientDiv, type: "dblclick", handler: function(e) { return self._handleDblclick(e); }});
-				}
-			}
-			for (var i=0; i start) {
-				if (selection.end > start && selection.start < start + removedCharCount) {
-					// selection intersects replaced text. set caret behind text change
-					selection.setCaret(start + addedCharCount);
-				} else {
-					// move selection to keep same text selected
-					selection.start +=  addedCharCount - removedCharCount;
-					selection.end +=  addedCharCount - removedCharCount;
-				}
-				this._setSelection(selection, false, false);
-			}
-			
-			var model = this._model;
-			var startLine = model.getLineAtOffset(start);
-			var child = this._getLineNext();
-			while (child) {
-				var lineIndex = child.lineIndex;
-				if (startLine <= lineIndex && lineIndex <= startLine + removedLineCount) {
-					if (startLine === lineIndex && !child.modelChangedEvent && !child.lineRemoved) {
-						child.modelChangedEvent = modelChangedEvent;
-						child.lineChanged = true;
-					} else {
-						child.lineRemoved = true;
-						child.lineChanged = false;
-						child.modelChangedEvent = null;
-					}
-				}
-				if (lineIndex > startLine + removedLineCount) {
-					child.lineIndex = lineIndex + addedLineCount - removedLineCount;
-				}
-				child = this._getLineNext(child);
-			}
-			if (startLine <= this._maxLineIndex && this._maxLineIndex <= startLine + removedLineCount) {
-				this._checkMaxLineIndex = this._maxLineIndex;
-				this._maxLineIndex = -1;
-				this._maxLineWidth = 0;
-			}
-			this._updatePage();
-		},
-		_onModelChanging: function(modelChangingEvent) {
-			modelChangingEvent.type = "ModelChanging";
-			this.onModelChanging(modelChangingEvent);
-			modelChangingEvent.type = "Changing";
-		},
-		_queueUpdatePage: function() {
-			if (this._updateTimer) { return; }
-			var self = this;
-			this._updateTimer = setTimeout(function() { 
-				self._updateTimer = null;
-				self._updatePage();
-			}, 0);
-		},
-		_reset: function() {
-			this._maxLineIndex = -1;
-			this._maxLineWidth = 0;
-			this._columnX = -1;
-			this._topChild = null;
-			this._bottomChild = null;
-			this._partialY = 0;
-			this._setSelection(new Selection (0, 0, false), false, false);
-			if (this._viewDiv) {
-				this._viewDiv.scrollLeft = 0;
-				this._viewDiv.scrollTop = 0;
-			}
-			var clientDiv = this._clientDiv;
-			if (clientDiv) {
-				var child = clientDiv.firstChild;
-				while (child) {
-					child.lineRemoved = true;
-					child = child.nextSibling;
-				}
-				/*
-				* Bug in Firefox.  For some reason, the caret does not show after the
-				* view is refreshed.  The fix is to toggle the contentEditable state and
-				* force the clientDiv to loose and receive focus if it is focused.
-				*/
-				if (isFirefox) {
-					this._ignoreFocus = false;
-					var hasFocus = this._hasFocus;
-					if (hasFocus) { clientDiv.blur(); }
-					clientDiv.contentEditable = false;
-					clientDiv.contentEditable = true;
-					if (hasFocus) { clientDiv.focus(); }
-					this._ignoreFocus = false;
-				}
-			}
-		},
-		_resizeTouchDiv: function() {
-			var viewRect = this._viewDiv.getBoundingClientRect();
-			var parentRect = this._frame.getBoundingClientRect();
-			var temp = this._frame;
-			while (temp) {
-				if (temp.style && temp.style.top) { break; }
-				temp = temp.parentNode;
-			}
-			var parentTop = parentRect.top;
-			if (temp) {
-				parentTop -= temp.getBoundingClientRect().top;
-			} else {
-				parentTop += this._parentDocument.body.scrollTop;
-			}
-			temp = this._frame;
-			while (temp) {
-				if (temp.style && temp.style.left) { break; }
-				temp = temp.parentNode;
-			}
-			var parentLeft = parentRect.left;
-			if (temp) {
-				parentLeft -= temp.getBoundingClientRect().left;
-			} else {
-				parentLeft += this._parentDocument.body.scrollLeft;
-			}
-			var touchDiv = this._touchDiv;
-			touchDiv.style.left = (parentLeft + viewRect.left) + "px";
-			touchDiv.style.top = (parentTop + viewRect.top) + "px";
-			touchDiv.style.width = viewRect.width + "px";
-			touchDiv.style.height = viewRect.height + "px";
-		},
-		_scrollView: function (pixelX, pixelY) {
-			/*
-			* Always set _ensureCaretVisible to false so that the view does not scroll
-			* to show the caret when scrollView is not called from showCaret().
-			*/
-			this._ensureCaretVisible = false;
-			
-			/*
-			* Scrolling is done only by setting the scrollLeft and scrollTop fields in the
-			* view div. This causes an updatePage from the scroll event. In some browsers 
-			* this event is asynchronous and forcing update page to run synchronously
-			* leads to redraw problems. 
-			* On Chrome 11, the view redrawing at times when holding PageDown/PageUp key.
-			* On Firefox 4 for Linux, the view redraws the first page when holding 
-			* PageDown/PageUp key, but it will not redraw again until the key is released.
-			*/
-			var viewDiv = this._viewDiv;
-			if (pixelX) { viewDiv.scrollLeft += pixelX; }
-			if (pixelY) { viewDiv.scrollTop += pixelY; }
-		},
-		_setClipboardText: function (text, event) {
-			var clipboardText;
-			if (this._frameWindow.clipboardData) {
-				//IE
-				clipboardText = [];
-				this._convertDelimiter(text, function(t) {clipboardText.push(t);}, function() {clipboardText.push(platformDelimiter);});
-				return this._frameWindow.clipboardData.setData("Text", clipboardText.join(""));
-			}
-			/* Feature in Chrome, clipboardData.setData is no-op on Chrome even though it returns true */
-			if (isChrome || isFirefox || !event) {
-				var window = this._frameWindow;
-				var document = this._frameDocument;
-				var child = document.createElement("PRE");
-				child.style.position = "fixed";
-				child.style.left = "-1000px";
-				this._convertDelimiter(text, 
-					function(t) {
-						child.appendChild(document.createTextNode(t));
-					}, 
-					function() {
-						child.appendChild(document.createElement("BR"));
-					}
-				);
-				child.appendChild(document.createTextNode(" "));
-				this._clientDiv.appendChild(child);
-				var range = document.createRange();
-				range.setStart(child.firstChild, 0);
-				range.setEndBefore(child.lastChild);
-				var sel = window.getSelection();
-				if (sel.rangeCount > 0) { sel.removeAllRanges(); }
-				sel.addRange(range);
-				var self = this;
-				/** @ignore */
-				var cleanup = function() {
-					if (child && child.parentNode === self._clientDiv) {
-						self._clientDiv.removeChild(child);
-					}
-					self._updateDOMSelection();
-				};
-				var result = false;
-				/* 
-				* Try execCommand first, it works on firefox with clipboard permission,
-				* chrome 5, safari 4.
-				*/
-				this._ignoreCopy = true;
-				try {
-					result = document.execCommand("copy", false, null);
-				} catch (e) {}
-				this._ignoreCopy = false;
-				if (!result) {
-					if (event) {
-						setTimeout(cleanup, 0);
-						return false;
-					}
-				}
-				/* no event and no permission, copy can not be done */
-				cleanup();
-				return true;
-			}
-			if (event && event.clipboardData) {
-				//webkit
-				clipboardText = [];
-				this._convertDelimiter(text, function(t) {clipboardText.push(t);}, function() {clipboardText.push(platformDelimiter);});
-				return event.clipboardData.setData("text/plain", clipboardText.join("")); 
-			}
-		},
-		_setDOMSelection: function (startNode, startOffset, endNode, endOffset) {
-			var window = this._frameWindow;
-			var document = this._frameDocument;
-			var startLineNode, startLineOffset, endLineNode, endLineOffset;
-			var offset = 0;
-			var lineChild = startNode.firstChild;
-			var node, nodeLength, model = this._model;
-			var startLineEnd = model.getLine(startNode.lineIndex).length;
-			while (lineChild) {
-				node = lineChild.firstChild;
-				nodeLength = node.length;
-				if (lineChild.ignoreChars) {
-					nodeLength -= lineChild.ignoreChars;
-				}
-				if (offset + nodeLength > startOffset || offset + nodeLength >= startLineEnd) {
-					startLineNode = node;
-					startLineOffset = startOffset - offset;
-					if (lineChild.ignoreChars && nodeLength > 0 && startLineOffset === nodeLength) {
-						startLineOffset += lineChild.ignoreChars; 
-					}
-					break;
-				}
-				offset += nodeLength;
-				lineChild = lineChild.nextSibling;
-			}
-			offset = 0;
-			lineChild = endNode.firstChild;
-			var endLineEnd = this._model.getLine(endNode.lineIndex).length;
-			while (lineChild) {
-				node = lineChild.firstChild;
-				nodeLength = node.length;
-				if (lineChild.ignoreChars) {
-					nodeLength -= lineChild.ignoreChars;
-				}
-				if (nodeLength + offset > endOffset || offset + nodeLength >= endLineEnd) {
-					endLineNode = node;
-					endLineOffset = endOffset - offset;
-					if (lineChild.ignoreChars && nodeLength > 0 && endLineOffset === nodeLength) {
-						endLineOffset += lineChild.ignoreChars; 
-					}
-					break;
-				}
-				offset += nodeLength;
-				lineChild = lineChild.nextSibling;
-			}
-			
-			this._setDOMFullSelection(startNode, startOffset, startLineEnd, endNode, endOffset, endLineEnd);
-			if (isPad) { return; }
-
-			var range;
-			if (window.getSelection) {
-				//W3C
-				range = document.createRange();
-				range.setStart(startLineNode, startLineOffset);
-				range.setEnd(endLineNode, endLineOffset);
-				var sel = window.getSelection();
-				this._ignoreSelect = false;
-				if (sel.rangeCount > 0) { sel.removeAllRanges(); }
-				sel.addRange(range);
-				this._ignoreSelect = true;
-			} else if (document.selection) {
-				//IE < 9
-				var body = document.body;
-
-				/*
-				* Bug in IE. For some reason when text is deselected the overflow
-				* selection at the end of some lines does not get redrawn.  The
-				* fix is to create a DOM element in the body to force a redraw.
-				*/
-				var child = document.createElement("DIV");
-				body.appendChild(child);
-				body.removeChild(child);
-				
-				range = body.createTextRange();
-				range.moveToElementText(startLineNode.parentNode);
-				range.moveStart("character", startLineOffset);
-				var endRange = body.createTextRange();
-				endRange.moveToElementText(endLineNode.parentNode);
-				endRange.moveStart("character", endLineOffset);
-				range.setEndPoint("EndToStart", endRange);
-				this._ignoreSelect = false;
-				range.select();
-				this._ignoreSelect = true;
-			}
-		},
-		_setDOMFullSelection: function(startNode, startOffset, startLineEnd, endNode, endOffset, endLineEnd) {
-			var model = this._model;
-			if (this._selDiv1) {
-				var startLineBounds, l;
-				startLineBounds = this._getLineBoundingClientRect(startNode);
-				if (startOffset === 0) {
-					l = startLineBounds.left;
-				} else {
-					if (startOffset >= startLineEnd) {
-						l = startLineBounds.right;
-					} else {
-						this._ignoreDOMSelection = true;
-						l = this._getBoundsAtOffset(model.getLineStart(startNode.lineIndex) + startOffset).left;
-						this._ignoreDOMSelection = false;
-					}
-				}
-				var textArea = this._textArea;
-				if (textArea && isPad) {
-					textArea.selectionStart = textArea.selectionEnd = 0;
-					var rect = this._frame.getBoundingClientRect();
-					var touchRect = this._touchDiv.getBoundingClientRect();
-					var viewBounds = this._viewDiv.getBoundingClientRect();
-					if (!(viewBounds.left <= l && l <= viewBounds.left + viewBounds.width &&
-						viewBounds.top <= startLineBounds.top && startLineBounds.top <= viewBounds.top + viewBounds.height) ||
-						!(startNode === endNode && startOffset === endOffset))
-					{
-						textArea.style.left = "-1000px";
-					} else {
-						textArea.style.left = (l - 4 + rect.left - touchRect.left) + "px";
-					}
-					textArea.style.top = (startLineBounds.top + rect.top - touchRect.top) + "px";
-					textArea.style.width = "6px";
-					textArea.style.height = (startLineBounds.bottom - startLineBounds.top) + "px";
-				}
-			
-				var selDiv = this._selDiv1;
-				selDiv.style.width = "0px";
-				selDiv.style.height = "0px";
-				selDiv = this._selDiv2;
-				selDiv.style.width = "0px";
-				selDiv.style.height = "0px";
-				selDiv = this._selDiv3;
-				selDiv.style.width = "0px";
-				selDiv.style.height = "0px";
-				if (!(startNode === endNode && startOffset === endOffset)) {
-					var handleWidth = isPad ? 2 : 0;
-					var handleBorder = handleWidth + "px blue solid";
-					var viewPad = this._getViewPadding();
-					var clientRect = this._clientDiv.getBoundingClientRect();
-					var viewRect = this._viewDiv.getBoundingClientRect();
-					var left = viewRect.left + viewPad.left;
-					var right = clientRect.right;
-					var top = viewRect.top + viewPad.top;
-					var bottom = clientRect.bottom;
-					var hd = 0, vd = 0;
-					if (this._clipDiv) {
-						var clipRect = this._clipDiv.getBoundingClientRect();
-						hd = clipRect.left - this._clipDiv.scrollLeft;
-						vd = clipRect.top;
-					}
-					var r;
-					var endLineBounds = this._getLineBoundingClientRect(endNode);
-					if (endOffset === 0) {
-						r = endLineBounds.left;
-					} else {
-						if (endOffset >= endLineEnd) {
-							r = endLineBounds.right;
-						} else {
-							this._ignoreDOMSelection = true;
-							r = this._getBoundsAtOffset(model.getLineStart(endNode.lineIndex) + endOffset).left;
-							this._ignoreDOMSelection = false;
-						}
-					}
-					var sel1Div = this._selDiv1;
-					var sel1Left = Math.min(right, Math.max(left, l));
-					var sel1Top = Math.min(bottom, Math.max(top, startLineBounds.top));
-					var sel1Right = right;
-					var sel1Bottom = Math.min(bottom, Math.max(top, startLineBounds.bottom));
-					sel1Div.style.left = (sel1Left - hd) + "px";
-					sel1Div.style.top = (sel1Top - vd) + "px";
-					sel1Div.style.width = Math.max(0, sel1Right - sel1Left) + "px";
-					sel1Div.style.height = Math.max(0, sel1Bottom - sel1Top) + (isPad ? 1 : 0) + "px";
-					if (isPad) {
-						sel1Div.style.borderLeft = handleBorder;
-						sel1Div.style.borderRight = "0px";
-					}
-					if (startNode === endNode) {
-						sel1Right = Math.min(r, right);
-						sel1Div.style.width = Math.max(0, sel1Right - sel1Left - handleWidth * 2) + "px";
-						if (isPad) {
-							sel1Div.style.borderRight = handleBorder;
-						}
-					} else {
-						var sel3Left = left;
-						var sel3Top = Math.min(bottom, Math.max(top, endLineBounds.top));
-						var sel3Right = Math.min(right, Math.max(left, r));
-						var sel3Bottom = Math.min(bottom, Math.max(top, endLineBounds.bottom));
-						var sel3Div = this._selDiv3;
-						sel3Div.style.left = (sel3Left - hd) + "px";
-						sel3Div.style.top = (sel3Top - vd) + "px";
-						sel3Div.style.width = Math.max(0, sel3Right - sel3Left - handleWidth) + "px";
-						sel3Div.style.height = Math.max(0, sel3Bottom - sel3Top) + "px";
-						if (isPad) {
-							sel3Div.style.borderRight = handleBorder;
-						}
-						if (sel3Top - sel1Bottom > 0) {
-							var sel2Div = this._selDiv2;
-							sel2Div.style.left = (left - hd)  + "px";
-							sel2Div.style.top = (sel1Bottom - vd) + "px";
-							sel2Div.style.width = Math.max(0, right - left) + "px";
-							sel2Div.style.height = Math.max(0, sel3Top - sel1Bottom) + (isPad ? 1 : 0) + "px";
-						}
-					}
-				}
-			}
-		},
-		_setGrab: function (target) {
-			if (target === this._grabControl) { return; }
-			if (target) {
-				if (target.setCapture) { target.setCapture(); }
-				this._grabControl = target;
-			} else {
-				if (this._grabControl.releaseCapture) { this._grabControl.releaseCapture(); }
-				this._grabControl = null;
-			}
-		},
-		_setLinksVisible: function(visible) {
-			if (this._linksVisible === visible) { return; }
-			this._linksVisible = visible;
-			/*
-			* Feature in IE.  The client div looses focus and does not regain it back
-			* when the content editable flag is reset. The fix is to remember that it
-			* had focus when the flag is cleared and give focus back to the div when
-			* the flag is set.
-			*/
-			if (isIE && visible) {
-				this._hadFocus = this._hasFocus;
-			}
-			var clientDiv = this._clientDiv;
-			clientDiv.contentEditable = !visible;
-			if (this._hadFocus && !visible) {
-				clientDiv.focus();
-			}
-			if (this._overlayDiv) {
-				this._overlayDiv.style.zIndex = visible ? "-1" : "1";
-			}
-			var document = this._frameDocument;
-			var line = this._getLineNext();
-			while (line) {
-				if (line.hasLink) {
-					var lineChild = line.firstChild;
-					while (lineChild) {
-						var next = lineChild.nextSibling;
-						var style = lineChild.viewStyle;
-						if (style && style.tagName === "A") {
-							line.replaceChild(this._createSpan(line, document, lineChild.firstChild.data, style), lineChild);
-						}
-						lineChild = next;
-					}
-				}
-				line = this._getLineNext(line);
-			}
-		},
-		_setSelection: function (selection, scroll, update, pageScroll) {
-			if (selection) {
-				this._columnX = -1;
-				if (update === undefined) { update = true; }
-				var oldSelection = this._selection; 
-				if (!oldSelection.equals(selection)) {
-					this._selection = selection;
-					var e = {
-						type: "Selection",
-						oldValue: {start:oldSelection.start, end:oldSelection.end},
-						newValue: {start:selection.start, end:selection.end}
-					};
-					this.onSelection(e);
-				}
-				/* 
-				* Always showCaret(), even when the selection is not changing, to ensure the
-				* caret is visible. Note that some views do not scroll to show the caret during
-				* keyboard navigation when the selection does not chanage. For example, line down
-				* when the caret is already at the last line.
-				*/
-				if (scroll) { update = !this._showCaret(false, pageScroll); }
-				
-				/* 
-				* Sometimes the browser changes the selection 
-				* as result of method calls or "leaked" events. 
-				* The fix is to set the visual selection even
-				* when the logical selection is not changed.
-				*/
-				if (update) { this._updateDOMSelection(); }
-			}
-		},
-		_setSelectionTo: function (x, y, extent, drag) {
-			var model = this._model, offset;
-			var selection = this._getSelection();
-			var lineIndex = this._getYToLine(y);
-			if (this._clickCount === 1) {
-				offset = this._getXToOffset(lineIndex, x);
-				if (drag && !extent) {
-					if (selection.start <= offset && offset < selection.end) {
-						this._dragOffset = offset;
-						return false;
-					}
-				}
-				selection.extend(offset);
-				if (!extent) { selection.collapse(); }
-			} else {
-				var word = (this._clickCount & 1) === 0;
-				var start, end;
-				if (word) {
-					offset = this._getXToOffset(lineIndex, x);
-					if (this._doubleClickSelection) {
-						if (offset >= this._doubleClickSelection.start) {
-							start = this._doubleClickSelection.start;
-							end = this._getOffset(offset, "wordend", +1);
-						} else {
-							start = this._getOffset(offset, "word", -1);
-							end = this._doubleClickSelection.end;
-						}
-					} else {
-						start = this._getOffset(offset, "word", -1);
-						end = this._getOffset(start, "wordend", +1);
-					}
-				} else {
-					if (this._doubleClickSelection) {
-						var doubleClickLine = model.getLineAtOffset(this._doubleClickSelection.start);
-						if (lineIndex >= doubleClickLine) {
-							start = model.getLineStart(doubleClickLine);
-							end = model.getLineEnd(lineIndex);
-						} else {
-							start = model.getLineStart(lineIndex);
-							end = model.getLineEnd(doubleClickLine);
-						}
-					} else {
-						start = model.getLineStart(lineIndex);
-						end = model.getLineEnd(lineIndex);
-					}
-				}
-				selection.setCaret(start);
-				selection.extend(end);
-			} 
-			this._setSelection(selection, true, true);
-			return true;
-		},
-		_setStyleSheet: function(stylesheet) {
-			var oldstylesheet = this._stylesheet;
-			if (!(oldstylesheet instanceof Array)) {
-				oldstylesheet = [oldstylesheet];
-			}
-			this._stylesheet = stylesheet;
-			if (!(stylesheet instanceof Array)) {
-				stylesheet = [stylesheet];
-			}
-			var document = this._frameDocument;
-			var documentStylesheet = document.styleSheets;
-			var head = document.getElementsByTagName("head")[0];
-			var changed = false;
-			var i = 0, sheet, oldsheet, documentSheet, ownerNode, styleNode, textNode;
-			while (i < stylesheet.length) {
-				if (i >= oldstylesheet.length) { break; }
-				sheet = stylesheet[i];
-				oldsheet = oldstylesheet[i];
-				if (sheet !== oldsheet) {
-					if (this._isLinkURL(sheet)) {
-						return true;
-					} else {
-						documentSheet = documentStylesheet[i+1];
-						ownerNode = documentSheet.ownerNode;
-						styleNode = document.createElement('STYLE');
-						textNode = document.createTextNode(sheet);
-						styleNode.appendChild(textNode);
-						head.replaceChild(styleNode, ownerNode);
-						changed = true;
-					}
-				}
-				i++;
-			}
-			if (i < oldstylesheet.length) {
-				while (i < oldstylesheet.length) {
-					sheet = oldstylesheet[i];
-					if (this._isLinkURL(sheet)) {
-						return true;
-					} else {
-						documentSheet = documentStylesheet[i+1];
-						ownerNode = documentSheet.ownerNode;
-						head.removeChild(ownerNode);
-						changed = true;
-					}
-					i++;
-				}
-			} else {
-				while (i < stylesheet.length) {
-					sheet = stylesheet[i];
-					if (this._isLinkURL(sheet)) {
-						return true;
-					} else {
-						styleNode = document.createElement('STYLE');
-						textNode = document.createTextNode(sheet);
-						styleNode.appendChild(textNode);
-						head.appendChild(styleNode);
-						changed = true;
-					}
-					i++;
-				}
-			}
-			if (changed) {
-				this._updateStyle();
-			}
-			return false;
-		},
-		_setFullSelection: function(fullSelection, init) {
-			this._fullSelection = fullSelection;
-			
-			/* 
-			* Bug in IE 8. For some reason, during scrolling IE does not reflow the elements
-			* that are used to compute the location for the selection divs. This causes the
-			* divs to be placed at the wrong location. The fix is to disabled full selection for IE8.
-			*/
-			if (isIE < 9) {
-				this._fullSelection = false;
-			}
-			if (isWebkit) {
-				this._fullSelection = true;
-			}
-			var parent = this._clipDiv || this._scrollDiv;
-			if (!parent) {
-				return;
-			}
-			if (!isPad && !this._fullSelection) {
-				if (this._selDiv1) {
-					parent.removeChild(this._selDiv1);
-					this._selDiv1 = null;
-				}
-				if (this._selDiv2) {
-					parent.removeChild(this._selDiv2);
-					this._selDiv2 = null;
-				}
-				if (this._selDiv3) {
-					parent.removeChild(this._selDiv3);
-					this._selDiv3 = null;
-				}
-				return;
-			}
-			
-			if (!this._selDiv1 && (isPad || (this._fullSelection && !isWebkit))) {
-				var frameDocument = this._frameDocument;
-				this._hightlightRGB = "Highlight";
-				var selDiv1 = frameDocument.createElement("DIV");
-				this._selDiv1 = selDiv1;
-				selDiv1.id = "selDiv1";
-				selDiv1.style.position = this._clipDiv ? "absolute" : "fixed";
-				selDiv1.style.borderWidth = "0px";
-				selDiv1.style.margin = "0px";
-				selDiv1.style.padding = "0px";
-				selDiv1.style.outline = "none";
-				selDiv1.style.background = this._hightlightRGB;
-				selDiv1.style.width = "0px";
-				selDiv1.style.height = "0px";
-				selDiv1.style.zIndex = "0";
-				parent.appendChild(selDiv1);
-				var selDiv2 = frameDocument.createElement("DIV");
-				this._selDiv2 = selDiv2;
-				selDiv2.id = "selDiv2";
-				selDiv2.style.position = this._clipDiv ? "absolute" : "fixed";
-				selDiv2.style.borderWidth = "0px";
-				selDiv2.style.margin = "0px";
-				selDiv2.style.padding = "0px";
-				selDiv2.style.outline = "none";
-				selDiv2.style.background = this._hightlightRGB;
-				selDiv2.style.width = "0px";
-				selDiv2.style.height = "0px";
-				selDiv2.style.zIndex = "0";
-				parent.appendChild(selDiv2);
-				var selDiv3 = frameDocument.createElement("DIV");
-				this._selDiv3 = selDiv3;
-				selDiv3.id = "selDiv3";
-				selDiv3.style.position = this._clipDiv ? "absolute" : "fixed";
-				selDiv3.style.borderWidth = "0px";
-				selDiv3.style.margin = "0px";
-				selDiv3.style.padding = "0px";
-				selDiv3.style.outline = "none";
-				selDiv3.style.background = this._hightlightRGB;
-				selDiv3.style.width = "0px";
-				selDiv3.style.height = "0px";
-				selDiv3.style.zIndex = "0";
-				parent.appendChild(selDiv3);
-				
-				/*
-				* Bug in Firefox. The Highlight color is mapped to list selection
-				* background instead of the text selection background.  The fix
-				* is to map known colors using a table or fallback to light blue.
-				*/
-				if (isFirefox && isMac) {
-					var style = this._frameWindow.getComputedStyle(selDiv3, null);
-					var rgb = style.getPropertyValue("background-color");
-					switch (rgb) {
-						case "rgb(119, 141, 168)": rgb = "rgb(199, 208, 218)"; break;
-						case "rgb(127, 127, 127)": rgb = "rgb(198, 198, 198)"; break;
-						case "rgb(255, 193, 31)": rgb = "rgb(250, 236, 115)"; break;
-						case "rgb(243, 70, 72)": rgb = "rgb(255, 176, 139)"; break;
-						case "rgb(255, 138, 34)": rgb = "rgb(255, 209, 129)"; break;
-						case "rgb(102, 197, 71)": rgb = "rgb(194, 249, 144)"; break;
-						case "rgb(140, 78, 184)": rgb = "rgb(232, 184, 255)"; break;
-						default: rgb = "rgb(180, 213, 255)"; break;
-					}
-					this._hightlightRGB = rgb;
-					selDiv1.style.background = rgb;
-					selDiv2.style.background = rgb;
-					selDiv3.style.background = rgb;
-					if (!this._insertedSelRule) {
-						var styleSheet = frameDocument.styleSheets[0];
-						styleSheet.insertRule("::-moz-selection {background: " + rgb + "; }", 0);
-						this._insertedSelRule = true;
-					}
-				}
-				if (!init) {
-					this._updateDOMSelection();
-				}
-			}
-		},
-		_setTabSize: function (tabSize, init) {
-			this._tabSize = tabSize;
-			this._customTabSize = undefined;
-			var clientDiv = this._clientDiv;
-			if (isOpera) {
-				if (clientDiv) { clientDiv.style.OTabSize = this._tabSize+""; }
-			} else if (isFirefox >= 4) {
-				if (clientDiv) {  clientDiv.style.MozTabSize = this._tabSize+""; }
-			} else if (this._tabSize !== 8) {
-				this._customTabSize = this._tabSize;
-				if (!init) {
-					this.redrawLines();
-				}
-			}
-		},
-		_setThemeClass: function (themeClass, init) {
-			this._themeClass = themeClass;
-			var document = this._frameDocument;
-			if (document) {
-				var viewContainerClass = "viewContainer";
-				if (this._themeClass) { viewContainerClass += " " + this._themeClass; }
-				document.body.className = viewContainerClass;
-				if (!init) {
-					this._updateStyle();
-				}
-			}
-		},
-		_showCaret: function (allSelection, pageScroll) {
-			if (!this._clientDiv) { return; }
-			var model = this._model;
-			var selection = this._getSelection();
-			var scroll = this._getScroll();
-			var caret = selection.getCaret();
-			var start = selection.start;
-			var end = selection.end;
-			var startLine = model.getLineAtOffset(start); 
-			var endLine = model.getLineAtOffset(end);
-			var endInclusive = Math.max(Math.max(start, model.getLineStart(endLine)), end - 1);
-			var viewPad = this._getViewPadding();
-			
-			var clientWidth = this._getClientWidth();
-			var leftEdge = viewPad.left;
-			var rightEdge = viewPad.left + clientWidth;
-			var bounds = this._getBoundsAtOffset(caret === start ? start : endInclusive);
-			var left = bounds.left;
-			var right = bounds.right;
-			var minScroll = clientWidth / 4;
-			if (allSelection && !selection.isEmpty() && startLine === endLine) {
-				bounds = this._getBoundsAtOffset(caret === end ? start : endInclusive);
-				var selectionWidth = caret === start ? bounds.right - left : right - bounds.left;
-				if ((clientWidth - minScroll) > selectionWidth) {
-					if (left > bounds.left) { left = bounds.left; }
-					if (right < bounds.right) { right = bounds.right; }
-				}
-			}
-			var viewRect = this._viewDiv.getBoundingClientRect(); 
-			left -= viewRect.left;
-			right -= viewRect.left;
-			var pixelX = 0;
-			if (left < leftEdge) {
-				pixelX = Math.min(left - leftEdge, -minScroll);
-			}
-			if (right > rightEdge) {
-				var maxScroll = this._scrollDiv.scrollWidth - scroll.x - clientWidth;
-				pixelX = Math.min(maxScroll,  Math.max(right - rightEdge, minScroll));
-			}
-
-			var pixelY = 0;
-			var topIndex = this._getTopIndex(true);
-			var bottomIndex = this._getBottomIndex(true);
-			var caretLine = model.getLineAtOffset(caret);
-			var clientHeight = this._getClientHeight();
-			if (!(topIndex <= caretLine && caretLine <= bottomIndex)) {
-				var lineHeight = this._getLineHeight();
-				var selectionHeight = allSelection ? (endLine - startLine) * lineHeight : 0;
-				pixelY = caretLine * lineHeight;
-				pixelY -= scroll.y;
-				if (pixelY + lineHeight > clientHeight) {
-					pixelY -= clientHeight - lineHeight;
-					if (caret === start && start !== end) {
-						pixelY += Math.min(clientHeight - lineHeight, selectionHeight);
-					}
-				} else {
-					if (caret === end) {
-						pixelY -= Math.min (clientHeight - lineHeight, selectionHeight);
-					}
-				}
-				if (pageScroll) {
-					if (pageScroll > 0) {
-						if (pixelY > 0) {
-							pixelY = Math.max(pixelY, pageScroll);
-						}
-					} else {
-						if (pixelY < 0) {
-							pixelY = Math.min(pixelY, pageScroll);
-						}
-					}
-				}
-			}
-
-			if (pixelX !== 0 || pixelY !== 0) {
-				this._scrollView (pixelX, pixelY);
-				/*
-				* When the view scrolls it is possible that one of the scrollbars can show over the caret.
-				* Depending on the browser scrolling can be synchronous (Safari), in which case the change 
-				* can be detected before showCaret() returns. When scrolling is asynchronous (most browsers), 
-				* the detection is done during the next update page.
-				*/
-				if (clientHeight !== this._getClientHeight() || clientWidth !== this._getClientWidth()) {
-					this._showCaret();
-				} else {
-					this._ensureCaretVisible = true;
-				}
-				return true;
-			}
-			return false;
-		},
-		_startIME: function () {
-			if (this._imeOffset !== -1) { return; }
-			var selection = this._getSelection();
-			if (!selection.isEmpty()) {
-				this._modifyContent({text: "", start: selection.start, end: selection.end}, true);
-			}
-			this._imeOffset = selection.start;
-		},
-		_unhookEvents: function() {
-			this._model.removeEventListener("Changing", this._modelListener.onChanging);
-			this._model.removeEventListener("Changed", this._modelListener.onChanged);
-			this._modelListener = null;
-			for (var i=0; i lastNode.lineIndex) {
-				topNode = lastNode;
-				topOffset = 0;
-			} else {
-				topNode = this._getLineNode(startLine);
-				topOffset = selection.start - model.getLineStart(startLine);
-			}
-
-			if (endLine < firstNode.lineIndex) {
-				bottomNode = firstNode;
-				bottomOffset = 0;
-			} else if (endLine > lastNode.lineIndex) {
-				bottomNode = lastNode;
-				bottomOffset = 0;
-			} else {
-				bottomNode = this._getLineNode(endLine);
-				bottomOffset = selection.end - model.getLineStart(endLine);
-			}
-			this._setDOMSelection(topNode, topOffset, bottomNode, bottomOffset);
-		},
-		_updatePage: function(hScrollOnly) {
-			if (this._redrawCount > 0) { return; }
-			if (this._updateTimer) {
-				clearTimeout(this._updateTimer);
-				this._updateTimer = null;
-				hScrollOnly = false;
-			}
-			var clientDiv = this._clientDiv;
-			if (!clientDiv) { return; }
-			var model = this._model;
-			var scroll = this._getScroll();
-			var viewPad = this._getViewPadding();
-			var lineCount = model.getLineCount();
-			var lineHeight = this._getLineHeight();
-			var firstLine = Math.max(0, scroll.y) / lineHeight;
-			var topIndex = Math.floor(firstLine);
-			var lineStart = Math.max(0, topIndex - 1);
-			var top = Math.round((firstLine - lineStart) * lineHeight);
-			var partialY = this._partialY = Math.round((firstLine - topIndex) * lineHeight);
-			var scrollWidth, scrollHeight = lineCount * lineHeight;
-			var leftWidth, clientWidth, clientHeight;
-			if (hScrollOnly) {
-				clientWidth = this._getClientWidth();
-				clientHeight = this._getClientHeight();
-				leftWidth = this._leftDiv ? this._leftDiv.scrollWidth : 0;
-				scrollWidth = Math.max(this._maxLineWidth, clientWidth);
-			} else {
-				var document = this._frameDocument;
-				var frameWidth = this._getFrameWidth();
-				var frameHeight = this._getFrameHeight();
-				document.body.style.width = frameWidth + "px";
-				document.body.style.height = frameHeight + "px";
-
-				/* Update view height in order to have client height computed */
-				var viewDiv = this._viewDiv;
-				viewDiv.style.height = Math.max(0, (frameHeight - viewPad.top - viewPad.bottom)) + "px";
-				clientHeight = this._getClientHeight();
-				var linesPerPage = Math.floor((clientHeight + partialY) / lineHeight);
-				var bottomIndex = Math.min(topIndex + linesPerPage, lineCount - 1);
-				var lineEnd = Math.min(bottomIndex + 1, lineCount - 1);
-				
-				var lineIndex, lineWidth;
-				var child = clientDiv.firstChild;
-				while (child) {
-					lineIndex = child.lineIndex;
-					var nextChild = child.nextSibling;
-					if (!(lineStart <= lineIndex && lineIndex <= lineEnd) || child.lineRemoved || child.lineIndex === -1) {
-						if (this._mouseWheelLine === child) {
-							child.style.display = "none";
-							child.lineIndex = -1;
-						} else {
-							clientDiv.removeChild(child);
-						}
-					}
-					child = nextChild;
-				}
-	
-				child = this._getLineNext();
-				var frag = document.createDocumentFragment();
-				for (lineIndex=lineStart; lineIndex<=lineEnd; lineIndex++) {
-					if (!child || child.lineIndex > lineIndex) {
-						this._createLine(frag, null, document, lineIndex, model);
-					} else {
-						if (frag.firstChild) {
-							clientDiv.insertBefore(frag, child);
-							frag = document.createDocumentFragment();
-						}
-						if (child && child.lineChanged) {
-							child = this._createLine(frag, child, document, lineIndex, model);
-							child.lineChanged = false;
-						}
-						child = this._getLineNext(child);
-					}
-				}
-				if (frag.firstChild) { clientDiv.insertBefore(frag, child); }
-	
-				/*
-				* Feature in WekKit. Webkit limits the width of the lines
-				* computed below to the width of the client div.  This causes
-				* the lines to be wrapped even though "pre" is set.  The fix
-				* is to set the width of the client div to a larger number
-				* before computing the lines width.  Note that this value is
-				* reset to the appropriate value further down.
-				*/ 
-				if (isWebkit) {
-					clientDiv.style.width = (0x7FFFF).toString() + "px";
-				}
-	
-				var rect;
-				child = this._getLineNext();
-				while (child) {
-					lineWidth = child.lineWidth;
-					if (lineWidth === undefined) {
-						rect = this._getLineBoundingClientRect(child);
-						lineWidth = child.lineWidth = rect.right - rect.left;
-					}
-					if (lineWidth >= this._maxLineWidth) {
-						this._maxLineWidth = lineWidth;
-						this._maxLineIndex = child.lineIndex;
-					}
-					if (child.lineIndex === topIndex) { this._topChild = child; }
-					if (child.lineIndex === bottomIndex) { this._bottomChild = child; }
-					if (this._checkMaxLineIndex === child.lineIndex) { this._checkMaxLineIndex = -1; }
-					child = this._getLineNext(child);
-				}
-				if (this._checkMaxLineIndex !== -1) {
-					lineIndex = this._checkMaxLineIndex;
-					this._checkMaxLineIndex = -1;
-					if (0 <= lineIndex && lineIndex < lineCount) {
-						var dummy = this._createLine(clientDiv, null, document, lineIndex, model);
-						rect = this._getLineBoundingClientRect(dummy);
-						lineWidth = rect.right - rect.left;
-						if (lineWidth >= this._maxLineWidth) {
-							this._maxLineWidth = lineWidth;
-							this._maxLineIndex = lineIndex;
-						}
-						clientDiv.removeChild(dummy);
-					}
-				}
-	
-				// Update rulers
-				this._updateRuler(this._leftDiv, topIndex, bottomIndex);
-				this._updateRuler(this._rightDiv, topIndex, bottomIndex);
-				
-				leftWidth = this._leftDiv ? this._leftDiv.scrollWidth : 0;
-				var rightWidth = this._rightDiv ? this._rightDiv.scrollWidth : 0;
-				viewDiv.style.left = leftWidth + "px";
-				viewDiv.style.width = Math.max(0, frameWidth - leftWidth - rightWidth - viewPad.left - viewPad.right) + "px";
-				if (this._rightDiv) {
-					this._rightDiv.style.left = (frameWidth - rightWidth) + "px"; 
-				}
-				/* Need to set the height first in order for the width to consider the vertical scrollbar */
-				var scrollDiv = this._scrollDiv;
-				scrollDiv.style.height = scrollHeight + "px";
-				/*
-				* TODO if frameHeightWithoutHScrollbar < scrollHeight  < frameHeightWithHScrollbar and the horizontal bar is visible, 
-				* then the clientWidth is wrong because the vertical scrollbar is showing. To correct code should hide both scrollbars 
-				* at this point.
-				*/
-				clientWidth = this._getClientWidth();
-				var width = Math.max(this._maxLineWidth, clientWidth);
-				/*
-				* Except by IE 8 and earlier, all other browsers are not allocating enough space for the right padding 
-				* in the scrollbar. It is possible this a bug since all other paddings are considered.
-				*/
-				scrollWidth = width;
-				if (!isIE || isIE >= 9) { width += viewPad.right; }
-				scrollDiv.style.width = width + "px";
-				if (this._clipScrollDiv) {
-					this._clipScrollDiv.style.width = width + "px";
-				}
-				/* Get the left scroll after setting the width of the scrollDiv as this can change the horizontal scroll offset. */
-				scroll = this._getScroll();
-				var rulerHeight = clientHeight + viewPad.top + viewPad.bottom;
-				this._updateRulerSize(this._leftDiv, rulerHeight);
-				this._updateRulerSize(this._rightDiv, rulerHeight);
-			}
-			var left = scroll.x;	
-			var clipDiv = this._clipDiv;
-			var overlayDiv = this._overlayDiv;
-			var clipLeft, clipTop;
-			if (clipDiv) {
-				clipDiv.scrollLeft = left;			
-				clipLeft = leftWidth + viewPad.left;
-				clipTop = viewPad.top;
-				var clipWidth = clientWidth;
-				var clipHeight = clientHeight;
-				var clientLeft = 0, clientTop = -top;
-				if (scroll.x === 0) {
-					clipLeft -= viewPad.left;
-					clipWidth += viewPad.left;
-					clientLeft = viewPad.left;
-				} 
-				if (scroll.x + clientWidth === scrollWidth) {
-					clipWidth += viewPad.right;
-				}
-				if (scroll.y === 0) {
-					clipTop -= viewPad.top;
-					clipHeight += viewPad.top;
-					clientTop += viewPad.top;
-				}
-				if (scroll.y + clientHeight === scrollHeight) { 
-					clipHeight += viewPad.bottom; 
-				}
-				clipDiv.style.left = clipLeft + "px";
-				clipDiv.style.top = clipTop + "px";
-				clipDiv.style.width = clipWidth + "px";
-				clipDiv.style.height = clipHeight + "px";
-				clientDiv.style.left = clientLeft + "px";
-				clientDiv.style.top = clientTop + "px";
-				clientDiv.style.width = scrollWidth + "px";
-				clientDiv.style.height = (clientHeight + top) + "px";
-				if (overlayDiv) {
-					overlayDiv.style.left = clientDiv.style.left;
-					overlayDiv.style.top = clientDiv.style.top;
-					overlayDiv.style.width = clientDiv.style.width;
-					overlayDiv.style.height = clientDiv.style.height;
-				}
-			} else {
-				clipLeft = left;
-				clipTop = top;
-				var clipRight = left + clientWidth;
-				var clipBottom = top + clientHeight;
-				if (clipLeft === 0) { clipLeft -= viewPad.left; }
-				if (clipTop === 0) { clipTop -= viewPad.top; }
-				if (clipRight === scrollWidth) { clipRight += viewPad.right; }
-				if (scroll.y + clientHeight === scrollHeight) { clipBottom += viewPad.bottom; }
-				clientDiv.style.clip = "rect(" + clipTop + "px," + clipRight + "px," + clipBottom + "px," + clipLeft + "px)";
-				clientDiv.style.left = (-left + leftWidth + viewPad.left) + "px";
-				clientDiv.style.width = (isWebkit ? scrollWidth : clientWidth + left) + "px";
-				if (!hScrollOnly) {
-					clientDiv.style.top = (-top + viewPad.top) + "px";
-					clientDiv.style.height = (clientHeight + top) + "px";
-				}
-				if (overlayDiv) {
-					overlayDiv.style.clip = clientDiv.style.clip;
-					overlayDiv.style.left = clientDiv.style.left;
-					overlayDiv.style.width = clientDiv.style.width;
-					if (!hScrollOnly) {
-						overlayDiv.style.top = clientDiv.style.top;
-						overlayDiv.style.height = clientDiv.style.height;
-					}
-				}
-			}
-			this._updateDOMSelection();
-
-			/*
-			* If the client height changed during the update page it means that scrollbar has either been shown or hidden.
-			* When this happens update page has to run again to ensure that the top and bottom lines div are correct.
-			* 
-			* Note: On IE, updateDOMSelection() has to be called before getting the new client height because it
-			* forces the client area to be recomputed.
-			*/
-			var ensureCaretVisible = this._ensureCaretVisible;
-			this._ensureCaretVisible = false;
-			if (clientHeight !== this._getClientHeight()) {
-				this._updatePage();
-				if (ensureCaretVisible) {
-					this._showCaret();
-				}
-			}
-			if (isPad) {
-				var self = this;
-				setTimeout(function() {self._resizeTouchDiv();}, 0);
-			}
-		},
-		_updateRulerSize: function (divRuler, rulerHeight) {
-			if (!divRuler) { return; }
-			var partialY = this._partialY;
-			var lineHeight = this._getLineHeight();
-			var cells = divRuler.firstChild.rows[0].cells;
-			for (var i = 0; i < cells.length; i++) {
-				var div = cells[i].firstChild;
-				var offset = lineHeight;
-				if (div._ruler.getOverview() === "page") { offset += partialY; }
-				div.style.top = -offset + "px";
-				div.style.height = (rulerHeight + offset) + "px";
-				div = div.nextSibling;
-			}
-			divRuler.style.height = rulerHeight + "px";
-		},
-		_updateRuler: function (divRuler, topIndex, bottomIndex) {
-			if (!divRuler) { return; }
-			var cells = divRuler.firstChild.rows[0].cells;
-			var lineHeight = this._getLineHeight();
-			var parentDocument = this._frameDocument;
-			var viewPad = this._getViewPadding();
-			for (var i = 0; i < cells.length; i++) {
-				var div = cells[i].firstChild;
-				var ruler = div._ruler;
-				if (div.rulerChanged) {
-					this._applyStyle(ruler.getRulerStyle(), div);
-				}
-				
-				var widthDiv;
-				var child = div.firstChild;
-				if (child) {
-					widthDiv = child;
-					child = child.nextSibling;
-				} else {
-					widthDiv = parentDocument.createElement("DIV");
-					widthDiv.style.visibility = "hidden";
-					div.appendChild(widthDiv);
-				}
-				var lineIndex, annotation;
-				if (div.rulerChanged) {
-					if (widthDiv) {
-						lineIndex = -1;
-						annotation = ruler.getWidestAnnotation();
-						if (annotation) {
-							this._applyStyle(annotation.style, widthDiv);
-							if (annotation.html) {
-								widthDiv.innerHTML = annotation.html;
-							}
-						}
-						widthDiv.lineIndex = lineIndex;
-						widthDiv.style.height = (lineHeight + viewPad.top) + "px";
-					}
-				}
-
-				var overview = ruler.getOverview(), lineDiv, frag, annotations;
-				if (overview === "page") {
-					annotations = ruler.getAnnotations(topIndex, bottomIndex + 1);
-					while (child) {
-						lineIndex = child.lineIndex;
-						var nextChild = child.nextSibling;
-						if (!(topIndex <= lineIndex && lineIndex <= bottomIndex) || child.lineChanged) {
-							div.removeChild(child);
-						}
-						child = nextChild;
-					}
-					child = div.firstChild.nextSibling;
-					frag = parentDocument.createDocumentFragment();
-					for (lineIndex=topIndex; lineIndex<=bottomIndex; lineIndex++) {
-						if (!child || child.lineIndex > lineIndex) {
-							lineDiv = parentDocument.createElement("DIV");
-							annotation = annotations[lineIndex];
-							if (annotation) {
-								this._applyStyle(annotation.style, lineDiv);
-								if (annotation.html) {
-									lineDiv.innerHTML = annotation.html;
-								}
-								lineDiv.annotation = annotation;
-							}
-							lineDiv.lineIndex = lineIndex;
-							lineDiv.style.height = lineHeight + "px";
-							frag.appendChild(lineDiv);
-						} else {
-							if (frag.firstChild) {
-								div.insertBefore(frag, child);
-								frag = parentDocument.createDocumentFragment();
-							}
-							if (child) {
-								child = child.nextSibling;
-							}
-						}
-					}
-					if (frag.firstChild) { div.insertBefore(frag, child); }
-				} else {
-					var buttonHeight = isPad ? 0 : 17;
-					var clientHeight = this._getClientHeight ();
-					var lineCount = this._model.getLineCount ();
-					var contentHeight = lineHeight * lineCount;
-					var trackHeight = clientHeight + viewPad.top + viewPad.bottom - 2 * buttonHeight;
-					var divHeight;
-					if (contentHeight < trackHeight) {
-						divHeight = lineHeight;
-					} else {
-						divHeight = trackHeight / lineCount;
-					}
-					if (div.rulerChanged) {
-						var count = div.childNodes.length;
-						while (count > 1) {
-							div.removeChild(div.lastChild);
-							count--;
-						}
-						annotations = ruler.getAnnotations(0, lineCount);
-						frag = parentDocument.createDocumentFragment();
-						for (var prop in annotations) {
-							lineIndex = prop >>> 0;
-							if (lineIndex < 0) { continue; }
-							lineDiv = parentDocument.createElement("DIV");
-							annotation = annotations[prop];
-							this._applyStyle(annotation.style, lineDiv);
-							lineDiv.style.position = "absolute";
-							lineDiv.style.top = buttonHeight + lineHeight + Math.floor(lineIndex * divHeight) + "px";
-							if (annotation.html) {
-								lineDiv.innerHTML = annotation.html;
-							}
-							lineDiv.annotation = annotation;
-							lineDiv.lineIndex = lineIndex;
-							frag.appendChild(lineDiv);
-						}
-						div.appendChild(frag);
-					} else if (div._oldTrackHeight !== trackHeight) {
-						lineDiv = div.firstChild ? div.firstChild.nextSibling : null;
-						while (lineDiv) {
-							lineDiv.style.top = buttonHeight + lineHeight + Math.floor(lineDiv.lineIndex * divHeight) + "px";
-							lineDiv = lineDiv.nextSibling;
-						}
-					}
-					div._oldTrackHeight = trackHeight;
-				}
-				div.rulerChanged = false;
-				div = div.nextSibling;
-			}
-		},
-		_updateStyle: function () {
-			var document = this._frameDocument;
-			if (isIE) {
-				document.body.style.lineHeight = "normal";
-			}
-			this._lineHeight = this._calculateLineHeight();
-			this._viewPadding = this._calculatePadding();
-			if (isIE) {
-				document.body.style.lineHeight = this._lineHeight + "px";
-			}
-			this.redraw();
-		}
-	};//end prototype
-	mEventTarget.EventTarget.addMixin(TextView.prototype);
-	
-	return {TextView: TextView};
-});
-
-/*******************************************************************************
- * @license
- * Copyright (c) 2010, 2011 IBM Corporation and others.
- * All rights reserved. This program and the accompanying materials are made 
- * available under the terms of the Eclipse Public License v1.0 
- * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
- * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
- * 
- * Contributors: 
- *		Felipe Heidrich (IBM Corporation) - initial API and implementation
- *		Silenio Quarti (IBM Corporation) - initial API and implementation
- ******************************************************************************/
- 
-/*global define */
-
-define("orion/textview/textDND", [], function() {
-
-	function TextDND(view, undoStack) {
-		this._view = view;
-		this._undoStack = undoStack;
-		this._dragSelection = null;
-		this._dropOffset = -1;
-		this._dropText = null;
-		var self = this;
-		this._listener = {
-			onDragStart: function (evt) {
-				self._onDragStart(evt);
-			},
-			onDragEnd: function (evt) {
-				self._onDragEnd(evt);
-			},
-			onDragEnter: function (evt) {
-				self._onDragEnter(evt);
-			},
-			onDragOver: function (evt) {
-				self._onDragOver(evt);
-			},
-			onDrop: function (evt) {
-				self._onDrop(evt);
-			},
-			onDestroy: function (evt) {
-				self._onDestroy(evt);
-			}
-		};
-		view.addEventListener("DragStart", this._listener.onDragStart);
-		view.addEventListener("DragEnd", this._listener.onDragEnd);
-		view.addEventListener("DragEnter", this._listener.onDragEnter);
-		view.addEventListener("DragOver", this._listener.onDragOver);
-		view.addEventListener("Drop", this._listener.onDrop);
-		view.addEventListener("Destroy", this._listener.onDestroy);
-	}
-	TextDND.prototype = {
-		destroy: function() {
-			var view = this._view;
-			if (!view) { return; }
-			view.removeEventListener("DragStart", this._listener.onDragStart);
-			view.removeEventListener("DragEnd", this._listener.onDragEnd);
-			view.removeEventListener("DragEnter", this._listener.onDragEnter);
-			view.removeEventListener("DragOver", this._listener.onDragOver);
-			view.removeEventListener("Drop", this._listener.onDrop);
-			view.removeEventListener("Destroy", this._listener.onDestroy);
-			this._view = null;
-		},
-		_onDestroy: function(e) {
-			this.destroy();
-		},
-		_onDragStart: function(e) {
-			var view = this._view;
-			var selection = view.getSelection();
-			var model = view.getModel();
-			if (model.getBaseModel) {
-				selection.start = model.mapOffset(selection.start);
-				selection.end = model.mapOffset(selection.end);
-				model = model.getBaseModel();
-			}
-			var text = model.getText(selection.start, selection.end);
-			if (text) {
-				this._dragSelection = selection;
-				e.event.dataTransfer.effectAllowed = "copyMove";
-				e.event.dataTransfer.setData("Text", text);
-			}
-		},
-		_onDragEnd: function(e) {
-			var view = this._view;
-			if (this._dragSelection) {
-				if (this._undoStack) { this._undoStack.startCompoundChange(); }
-				var move = e.event.dataTransfer.dropEffect === "move";
-				if (move) {
-					view.setText("", this._dragSelection.start, this._dragSelection.end);
-				}
-				if (this._dropText) {
-					var text = this._dropText;
-					var offset = this._dropOffset;
-					if (move) {
-						if (offset >= this._dragSelection.end) {
-							offset -= this._dragSelection.end - this._dragSelection.start;
-						} else if (offset >= this._dragSelection.start) {
-							offset = this._dragSelection.start;
-						}
-					}
-					view.setText(text, offset, offset);
-					view.setSelection(offset, offset + text.length);
-					this._dropText = null;
-					this._dropOffset = -1;
-				}
-				if (this._undoStack) { this._undoStack.endCompoundChange(); }
-			}
-			this._dragSelection = null;
-		},
-		_onDragEnter: function(e) {
-			this._onDragOver(e);
-		},
-		_onDragOver: function(e) {
-			var types = e.event.dataTransfer.types;
-			if (types) {
-				var allowed = types.contains ? types.contains("text/plain") : types.indexOf("text/plain") !== -1;
-				if (!allowed) {
-					e.event.dataTransfer.dropEffect = "none";
-				}
-			}
-		},
-		_onDrop: function(e) {
-			var view = this._view;
-			var text = e.event.dataTransfer.getData("Text");
-			if (text) {
-				var offset = view.getOffsetAtLocation(e.x, e.y);
-				if (this._dragSelection) {
-					this._dropOffset = offset;
-					this._dropText = text;
-				} else {
-					view.setText(text, offset, offset);
-					view.setSelection(offset, offset + text.length);
-				}
-			}
-		}
-	};
-
-	return {TextDND: TextDND};
-});/******************************************************************************* 
- * @license
- * Copyright (c) 2011 IBM Corporation and others.
- * All rights reserved. This program and the accompanying materials are made 
- * available under the terms of the Eclipse Public License v1.0 
- * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
- * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
- * 
- * Contributors: IBM Corporation - initial API and implementation 
- ******************************************************************************/
-
-/*jslint */
-/*global define */
-
-define("orion/editor/htmlGrammar", [], function() {
-
-	/**
-	 * Provides a grammar that can do some very rough syntax highlighting for HTML.
-	 * @class orion.syntax.HtmlGrammar
-	 */
-	function HtmlGrammar() {
-		/**
-		 * Object containing the grammar rules.
-		 * @public
-		 * @type Object
-		 */
-		return {
-			"name": "HTML",
-			"scopeName": "source.html",
-			"uuid": "3B5C76FB-EBB5-D930-F40C-047D082CE99B",
-			"patterns": [
-				// TODO unicode?
-				{
-					"match": "]+>",
-					"name": "entity.name.tag.doctype.html"
-				},
-				{
-					"begin": "",
-					"beginCaptures": {
-						"0": { "name": "punctuation.definition.comment.html" }
-					},
-					"endCaptures": {
-						"0": { "name": "punctuation.definition.comment.html" }
-					},
-					"patterns": [
-						{
-							"match": "--",
-							"name": "invalid.illegal.badcomment.html"
-						}
-					],
-					"contentName": "comment.block.html"
-				},
-				{ // startDelimiter + tagName
-					"match": "<[A-Za-z0-9_\\-:]+(?= ?)",
-					"name": "entity.name.tag.html"
-				},
-				{ "include": "#attrName" },
-				{ "include": "#qString" },
-				{ "include": "#qqString" },
-				// TODO attrName, qString, qqString should be applied first while inside a tag
-				{ // startDelimiter + slash + tagName + endDelimiter
-					"match": "",
-					"name": "entity.name.tag.html"
-				},
-				{ // end delimiter of open tag
-					"match": ">", 
-					"name": "entity.name.tag.html"
-				} ],
-			"repository": {
-				"attrName": { // attribute name
-					"match": "[A-Za-z\\-:]+(?=\\s*=\\s*['\"])",
-					"name": "entity.other.attribute.name.html"
-				},
-				"qqString": { // double quoted string
-					"match": "(\")[^\"]+(\")",
-					"name": "string.quoted.double.html"
-				},
-				"qString": { // single quoted string
-					"match": "(')[^']+(\')",
-					"name": "string.quoted.single.html"
-				}
-			}
-		};
-	}
-
-	return {HtmlGrammar: HtmlGrammar};
-});
-/******************************************************************************* 
- * @license
- * Copyright (c) 2011 IBM Corporation and others.
- * All rights reserved. This program and the accompanying materials are made 
- * available under the terms of the Eclipse Public License v1.0 
- * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
- * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
- * 
- * Contributors: IBM Corporation - initial API and implementation 
- ******************************************************************************/
-
-/*jslint regexp:false laxbreak:true*/
-/*global define */
-
-define("orion/editor/textMateStyler", ['orion/editor/regex'], function(mRegex) {
-
-var RegexUtil = {
-	// Rules to detect some unsupported Oniguruma features
-	unsupported: [
-		{regex: /\(\?[ims\-]:/, func: function(match) { return "option on/off for subexp"; }},
-		{regex: /\(\?<([=!])/, func: function(match) { return (match[1] === "=") ? "lookbehind" : "negative lookbehind"; }},
-		{regex: /\(\?>/, func: function(match) { return "atomic group"; }}
-	],
-	
-	/**
-	 * @param {String} str String giving a regular expression pattern from a TextMate grammar.
-	 * @param {String} [flags] [ismg]+
-	 * @returns {RegExp}
-	 */
-	toRegExp: function(str) {
-		function fail(feature, match) {
-			throw new Error("Unsupported regex feature \"" + feature + "\": \"" + match[0] + "\" at index: "
-					+ match.index + " in " + match.input);
-		}
-		// Turns an extended regex pattern into a normal one
-		function normalize(/**String*/ str) {
-			var result = "";
-			var insideCharacterClass = false;
-			var len = str.length;
-			for (var i=0; i < len; ) {
-				var chr = str[i];
-				if (!insideCharacterClass && chr === "#") {
-					// skip to eol
-					while (i < len && chr !== "\r" && chr !== "\n") {
-						chr = str[++i];
-					}
-				} else if (!insideCharacterClass && /\s/.test(chr)) {
-					// skip whitespace
-					while (i < len && /\s/.test(chr)) { 
-						chr = str[++i];
-					}
-				} else if (chr === "\\") {
-					result += chr;
-					if (!/\s/.test(str[i+1])) {
-						result += str[i+1];
-						i += 1;
-					}
-					i += 1;
-				} else if (chr === "[") {
-					insideCharacterClass = true;
-					result += chr;
-					i += 1;
-				} else if (chr === "]") {
-					insideCharacterClass = false;
-					result += chr;
-					i += 1;
-				} else {
-					result += chr;
-					i += 1;
-				}
-			}
-			return result;
-		}
-		
-		var flags = "";
-		var i;
-		
-		// Handle global "x" flag (whitespace/comments)
-		str = RegexUtil.processGlobalFlag("x", str, function(subexp) {
-				return normalize(subexp);
-			});
-		
-		// Handle global "i" flag (case-insensitive)
-		str = RegexUtil.processGlobalFlag("i", str, function(subexp) {
-				flags += "i";
-				return subexp;
-			});
-		
-		// Check for remaining unsupported syntax
-		for (i=0; i < this.unsupported.length; i++) {
-			var match;
-			if ((match = this.unsupported[i].regex.exec(str))) {
-				fail(this.unsupported[i].func(match), match);
-			}
-		}
-		
-		return new RegExp(str, flags);
-	},
-	
-	/**
-	 * Checks if flag applies to entire pattern. If so, obtains replacement string by calling processor
-	 * on the unwrapped pattern. Handles 2 possible syntaxes: (?f)pat and (?f:pat)
-	 */
-	processGlobalFlag: function(/**String*/ flag, /**String*/ str, /**Function*/ processor) {
-		function getMatchingCloseParen(/*String*/pat, /*Number*/start) {
-			var depth = 0,
-			    len = pat.length,
-			    flagStop = -1;
-			for (var i=start; i < len && flagStop === -1; i++) {
-				switch (pat[i]) {
-					case "\\":
-						i++; // escape: skip next char
-						break;
-					case "(":
-						depth++;
-						break;
-					case ")":
-						depth--;
-						if (depth === 0) {
-							flagStop = i;
-						}
-						break;
-				}
-			}
-			return flagStop;
-		}
-		var flag1 = "(?" + flag + ")",
-		    flag2 = "(?" + flag + ":";
-		if (str.substring(0, flag1.length) === flag1) {
-			return processor(str.substring(flag1.length));
-		} else if (str.substring(0, flag2.length) === flag2) {
-			var flagStop = getMatchingCloseParen(str, 0);
-			if (flagStop < str.length-1) {
-				throw new Error("Only a " + flag2 + ") group that encloses the entire regex is supported in: " + str);
-			}
-			return processor(str.substring(flag2.length, flagStop));
-		}
-		return str;
-	},
-	
-	hasBackReference: function(/**RegExp*/ regex) {
-		return (/\\\d+/).test(regex.source);
-	},
-	
-	/** @returns {RegExp} A regex made by substituting any backreferences in regex for the value of the property
-	 * in sub with the same name as the backreferenced group number. */
-	getSubstitutedRegex: function(/**RegExp*/ regex, /**Object*/ sub, /**Boolean*/ escape) {
-		escape = (typeof escape === "undefined") ? true : false;
-		var exploded = regex.source.split(/(\\\d+)/g);
-		var array = [];
-		for (var i=0; i < exploded.length; i++) {
-			var term = exploded[i];
-			var backrefMatch = /\\(\d+)/.exec(term);
-			if (backrefMatch) {
-				var text = sub[backrefMatch[1]] || "";
-				array.push(escape ? mRegex.escape(text) : text);
-			} else {
-				array.push(term);
-			}
-		}
-		return new RegExp(array.join(""));
-	},
-	
-	/**
-	 * Builds a version of regex with every non-capturing term converted into a capturing group. This is a workaround
-	 * for JavaScript's lack of API to get the index at which a matched group begins in the input string.

- * Using the "groupified" regex, we can sum the lengths of matches from consuming groups 1..n-1 to obtain the - * starting index of group n. (A consuming group is a capturing group that is not inside a lookahead assertion).

- * Example: groupify(/(a+)x+(b+)/) === /(a+)(x+)(b+)/
- * Example: groupify(/(?:x+(a+))b+/) === /(?:(x+)(a+))(b+)/ - * @param {RegExp} regex The regex to groupify. - * @param {Object} [backRefOld2NewMap] Optional. If provided, the backreference numbers in regex will be updated using the - * properties of this object rather than the new group numbers of regex itself. - *
  • [0] {RegExp} The groupified version of the input regex.
  • - *
  • [1] {Object} A map containing old-group to new-group info. Each property is a capturing group number of regex - * and its value is the corresponding capturing group number of [0].
  • - *
  • [2] {Object} A map indicating which capturing groups of [0] are also consuming groups. If a group number is found - * as a property in this object, then it's a consuming group.
- */ - groupify: function(regex, backRefOld2NewMap) { - var NON_CAPTURING = 1, - CAPTURING = 2, - LOOKAHEAD = 3, - NEW_CAPTURING = 4; - var src = regex.source, - len = src.length; - var groups = [], - lookaheadDepth = 0, - newGroups = [], - oldGroupNumber = 1, - newGroupNumber = 1; - var result = [], - old2New = {}, - consuming = {}; - for (var i=0; i < len; i++) { - var curGroup = groups[groups.length-1]; - var chr = src[i]; - switch (chr) { - case "(": - // If we're in new capturing group, close it since ( signals end-of-term - if (curGroup === NEW_CAPTURING) { - groups.pop(); - result.push(")"); - newGroups[newGroups.length-1].end = i; - } - var peek2 = (i + 2 < len) ? (src[i+1] + "" + src[i+2]) : null; - if (peek2 === "?:" || peek2 === "?=" || peek2 === "?!") { - // Found non-capturing group or lookahead assertion. Note that we preserve non-capturing groups - // as such, but any term inside them will become a new capturing group (unless it happens to - // also be inside a lookahead). - var groupType; - if (peek2 === "?:") { - groupType = NON_CAPTURING; - } else { - groupType = LOOKAHEAD; - lookaheadDepth++; - } - groups.push(groupType); - newGroups.push({ start: i, end: -1, type: groupType /*non capturing*/ }); - result.push(chr); - result.push(peek2); - i += peek2.length; - } else { - groups.push(CAPTURING); - newGroups.push({ start: i, end: -1, type: CAPTURING, oldNum: oldGroupNumber, num: newGroupNumber }); - result.push(chr); - if (lookaheadDepth === 0) { - consuming[newGroupNumber] = null; - } - old2New[oldGroupNumber] = newGroupNumber; - oldGroupNumber++; - newGroupNumber++; - } - break; - case ")": - var group = groups.pop(); - if (group === LOOKAHEAD) { lookaheadDepth--; } - newGroups[newGroups.length-1].end = i; - result.push(chr); - break; - case "*": - case "+": - case "?": - case "}": - // Unary operator. If it's being applied to a capturing group, we need to add a new capturing group - // enclosing the pair - var op = chr; - var prev = src[i-1], - prevIndex = i-1; - if (chr === "}") { - for (var j=i-1; src[j] !== "{" && j >= 0; j--) {} - prev = src[j-1]; - prevIndex = j-1; - op = src.substring(j, i+1); - } - var lastGroup = newGroups[newGroups.length-1]; - if (prev === ")" && (lastGroup.type === CAPTURING || lastGroup.type === NEW_CAPTURING)) { - // Shove in the new group's (, increment num/start in from [lastGroup.start .. end] - result.splice(lastGroup.start, 0, "("); - result.push(op); - result.push(")"); - var newGroup = { start: lastGroup.start, end: result.length-1, type: NEW_CAPTURING, num: lastGroup.num }; - for (var k=0; k < newGroups.length; k++) { - group = newGroups[k]; - if (group.type === CAPTURING || group.type === NEW_CAPTURING) { - if (group.start >= lastGroup.start && group.end <= prevIndex) { - group.start += 1; - group.end += 1; - group.num = group.num + 1; - if (group.type === CAPTURING) { - old2New[group.oldNum] = group.num; - } - } - } - } - newGroups.push(newGroup); - newGroupNumber++; - break; - } else { - // Fallthrough to default - } - default: - if (chr !== "|" && curGroup !== CAPTURING && curGroup !== NEW_CAPTURING) { - // Not in a capturing group, so make a new one to hold this term. - // Perf improvement: don't create the new group if we're inside a lookahead, since we don't - // care about them (nothing inside a lookahead actually consumes input so we don't need it) - if (lookaheadDepth === 0) { - groups.push(NEW_CAPTURING); - newGroups.push({ start: i, end: -1, type: NEW_CAPTURING, num: newGroupNumber }); - result.push("("); - consuming[newGroupNumber] = null; - newGroupNumber++; - } - } - result.push(chr); - if (chr === "\\") { - var peek = src[i+1]; - // Eat next so following iteration doesn't think it's a real special character - result.push(peek); - i += 1; - } - break; - } - } - while (groups.length) { - // Close any remaining new capturing groups - groups.pop(); - result.push(")"); - } - var newRegex = new RegExp(result.join("")); - - // Update backreferences so they refer to the new group numbers. Use backRefOld2NewMap if provided - var subst = {}; - backRefOld2NewMap = backRefOld2NewMap || old2New; - for (var prop in backRefOld2NewMap) { - if (backRefOld2NewMap.hasOwnProperty(prop)) { - subst[prop] = "\\" + backRefOld2NewMap[prop]; - } - } - newRegex = this.getSubstitutedRegex(newRegex, subst, false); - - return [newRegex, old2New, consuming]; - }, - - /** @returns {Boolean} True if the captures object assigns scope to a matching group other than "0". */ - complexCaptures: function(capturesObj) { - if (!capturesObj) { return false; } - for (var prop in capturesObj) { - if (capturesObj.hasOwnProperty(prop)) { - if (prop !== "0") { - return true; - } - } - } - return false; - } -}; - - /** - * @name orion.editor.TextMateStyler - * @class A styler that knows how to apply a subset of the TextMate grammar format to style a line. - * - *

Styling from a grammar:

- *

Each scope name given in the grammar is converted to an array of CSS class names. For example - * a region of text with scope keyword.control.php will be assigned the CSS classes
- * keyword, keyword-control, keyword-control-php

- * - *

A CSS file can give rules matching any of these class names to provide generic or more specific styling. - * For example,

- *

.keyword { font-color: blue; }

- *

colors all keywords blue, while

- *

.keyword-control-php { font-weight: bold; }

- *

bolds only PHP control keywords.

- * - *

This is useful when using grammars that adhere to TextMate's - * scope name conventions, - * as a single CSS rule can provide consistent styling to similar constructs across different languages.

- * - *

Top-level grammar constructs:

- *
  • patterns, repository (with limitations, see "Other Features") are supported.
  • - *
  • scopeName, firstLineMatch, foldingStartMarker, foldingStopMarker are not supported.
  • - *
  • fileTypes is not supported. When using the Orion service registry, the "orion.edit.highlighter" - * service serves a similar purpose.
  • - *
- * - *

Regular expression constructs:

- *
    - *
  • match patterns are supported.
  • - *
  • begin .. end patterns are supported.
  • - *
  • The "extended" regex forms (?x) and (?x:...) are supported, but only when they - * apply to the entire regex pattern.
  • - *
  • Matching is done using native JavaScript RegExps. As a result, many features of the Oniguruma regex - * engine used by TextMate are not supported. - * Unsupported features include: - *
    • Named captures
    • - *
    • Setting flags inside subgroups (eg. (?i:a)b)
    • - *
    • Lookbehind and negative lookbehind
    • - *
    • Subexpression call
    • - *
    • etc.
    • - *
    - *
  • - *
- * - *

Scope-assignment constructs:

- *
    - *
  • captures, beginCaptures, endCaptures are supported.
  • - *
  • name and contentName are supported.
  • - *
- * - *

Other features:

- *
    - *
  • applyEndPatternLast is supported.
  • - *
  • include is supported, but only when it references a rule in the current grammar's repository. - * Including $self, $base, or rule.from.another.grammar is not supported.
  • - *
- * - * @description Creates a new TextMateStyler. - * @extends orion.editor.AbstractStyler - * @param {orion.textview.TextView} textView The TextView to provide styling for. - * @param {Object} grammar The TextMate grammar to use for styling the TextView, as a JavaScript object. You can - * produce this object by running a PList-to-JavaScript conversion tool on a TextMate .tmLanguage file. - * @param {Object[]} [externalGrammars] Additional grammar objects that will be used to resolve named rule references. - */ - function TextMateStyler(textView, grammar, externalGrammars) { - this.initialize(textView); - // Copy grammar object(s) since we will mutate them - this.grammar = this.copy(grammar); - this.externalGrammars = externalGrammars ? this.copy(externalGrammars) : []; - - this._styles = {}; /* key: {String} scopeName, value: {String[]} cssClassNames */ - this._tree = null; - this._allGrammars = {}; /* key: {String} scopeName of grammar, value: {Object} grammar */ - this.preprocess(this.grammar); - } - TextMateStyler.prototype = /** @lends orion.editor.TextMateStyler.prototype */ { - initialize: function(textView) { - this.textView = textView; - var self = this; - this._listener = { - onModelChanged: function(e) { - self.onModelChanged(e); - }, - onDestroy: function(e) { - self.onDestroy(e); - }, - onLineStyle: function(e) { - self.onLineStyle(e); - } - }; - textView.addEventListener("ModelChanged", this._listener.onModelChanged); - textView.addEventListener("Destroy", this._listener.onDestroy); - textView.addEventListener("LineStyle", this._listener.onLineStyle); - textView.redrawLines(); - }, - onDestroy: function(/**eclipse.DestroyEvent*/ e) { - this.destroy(); - }, - destroy: function() { - if (this.textView) { - this.textView.removeEventListener("ModelChanged", this._listener.onModelChanged); - this.textView.removeEventListener("Destroy", this._listener.onDestroy); - this.textView.removeEventListener("LineStyle", this._listener.onLineStyle); - this.textView = null; - } - this.grammar = null; - this._styles = null; - this._tree = null; - this._listener = null; - }, - /** @private */ - copy: function(obj) { - return JSON.parse(JSON.stringify(obj)); - }, - /** @private */ - preprocess: function(grammar) { - var stack = [grammar]; - for (; stack.length !== 0; ) { - var rule = stack.pop(); - if (rule._resolvedRule && rule._typedRule) { - continue; - } -// console.debug("Process " + (rule.include || rule.name)); - - // Look up include'd rule, create typed *Rule instance - rule._resolvedRule = this._resolve(rule); - rule._typedRule = this._createTypedRule(rule); - - // Convert the scope names to styles and cache them for later - this.addStyles(rule.name); - this.addStyles(rule.contentName); - this.addStylesForCaptures(rule.captures); - this.addStylesForCaptures(rule.beginCaptures); - this.addStylesForCaptures(rule.endCaptures); - - if (rule._resolvedRule !== rule) { - // Add include target - stack.push(rule._resolvedRule); - } - if (rule.patterns) { - // Add subrules - for (var i=0; i < rule.patterns.length; i++) { - stack.push(rule.patterns[i]); - } - } - } - }, - - /** - * @private - * Adds eclipse.Style objects for scope to our _styles cache. - * @param {String} scope A scope name, like "constant.character.php". - */ - addStyles: function(scope) { - if (scope && !this._styles[scope]) { - this._styles[scope] = []; - var scopeArray = scope.split("."); - for (var i = 0; i < scopeArray.length; i++) { - this._styles[scope].push(scopeArray.slice(0, i + 1).join("-")); - } - } - }, - /** @private */ - addStylesForCaptures: function(/**Object*/ captures) { - for (var prop in captures) { - if (captures.hasOwnProperty(prop)) { - var scope = captures[prop].name; - this.addStyles(scope); - } - } - }, - /** - * A rule that contains subrules ("patterns" in TextMate parlance) but has no "begin" or "end". - * Also handles top level of grammar. - * @private - */ - ContainerRule: (function() { - function ContainerRule(/**Object*/ rule) { - this.rule = rule; - this.subrules = rule.patterns; - } - ContainerRule.prototype.valueOf = function() { return "aa"; }; - return ContainerRule; - }()), - /** - * A rule that is delimited by "begin" and "end" matches, which may be separated by any number of - * lines. This type of rule may contain subrules, which apply only inside the begin .. end region. - * @private - */ - BeginEndRule: (function() { - function BeginEndRule(/**Object*/ rule) { - this.rule = rule; - // TODO: the TextMate blog claims that "end" is optional. - this.beginRegex = RegexUtil.toRegExp(rule.begin); - this.endRegex = RegexUtil.toRegExp(rule.end); - this.subrules = rule.patterns || []; - - this.endRegexHasBackRef = RegexUtil.hasBackReference(this.endRegex); - - // Deal with non-0 captures - var complexCaptures = RegexUtil.complexCaptures(rule.captures); - var complexBeginEnd = RegexUtil.complexCaptures(rule.beginCaptures) || RegexUtil.complexCaptures(rule.endCaptures); - this.isComplex = complexCaptures || complexBeginEnd; - if (this.isComplex) { - var bg = RegexUtil.groupify(this.beginRegex); - this.beginRegex = bg[0]; - this.beginOld2New = bg[1]; - this.beginConsuming = bg[2]; - - var eg = RegexUtil.groupify(this.endRegex, this.beginOld2New /*Update end's backrefs to begin's new group #s*/); - this.endRegex = eg[0]; - this.endOld2New = eg[1]; - this.endConsuming = eg[2]; - } - } - BeginEndRule.prototype.valueOf = function() { return this.beginRegex; }; - return BeginEndRule; - }()), - /** - * A rule with a "match" pattern. - * @private - */ - MatchRule: (function() { - function MatchRule(/**Object*/ rule) { - this.rule = rule; - this.matchRegex = RegexUtil.toRegExp(rule.match); - this.isComplex = RegexUtil.complexCaptures(rule.captures); - if (this.isComplex) { - var mg = RegexUtil.groupify(this.matchRegex); - this.matchRegex = mg[0]; - this.matchOld2New = mg[1]; - this.matchConsuming = mg[2]; - } - } - MatchRule.prototype.valueOf = function() { return this.matchRegex; }; - return MatchRule; - }()), - /** - * @param {Object} rule A rule from the grammar. - * @returns {MatchRule|BeginEndRule|ContainerRule} - * @private - */ - _createTypedRule: function(rule) { - if (rule.match) { - return new this.MatchRule(rule); - } else if (rule.begin) { - return new this.BeginEndRule(rule); - } else { - return new this.ContainerRule(rule); - } - }, - /** - * Resolves a rule from the grammar (which may be an include) into the real rule that it points to. - * @private - */ - _resolve: function(rule) { - var resolved = rule; - if (rule.include) { - if (rule.begin || rule.end || rule.match) { - throw new Error("Unexpected regex pattern in \"include\" rule " + rule.include); - } - var name = rule.include; - if (name[0] === "#") { - resolved = this.grammar.repository && this.grammar.repository[name.substring(1)]; - if (!resolved) { throw new Error("Couldn't find included rule " + name + " in grammar repository"); } - } else if (name === "$self") { - resolved = this.grammar; - } else if (name === "$base") { - // $base is only relevant when including rules from foreign grammars - throw new Error("Include \"$base\" is not supported"); - } else { - resolved = this._allGrammars[name]; - if (!resolved) { - for (var i=0; i < this.externalGrammars.length; i++) { - var grammar = this.externalGrammars[i]; - if (grammar.scopeName === name) { - this.preprocess(grammar); - this._allGrammars[name] = grammar; - resolved = grammar; - break; - } - } - } - } - } - return resolved; - }, - /** @private */ - ContainerNode: (function() { - function ContainerNode(parent, rule) { - this.parent = parent; - this.rule = rule; - this.children = []; - - this.start = null; - this.end = null; - } - ContainerNode.prototype.addChild = function(child) { - this.children.push(child); - }; - ContainerNode.prototype.valueOf = function() { - var r = this.rule; - return "ContainerNode { " + (r.include || "") + " " + (r.name || "") + (r.comment || "") + "}"; - }; - return ContainerNode; - }()), - /** @private */ - BeginEndNode: (function() { - function BeginEndNode(parent, rule, beginMatch) { - this.parent = parent; - this.rule = rule; - this.children = []; - - this.setStart(beginMatch); - this.end = null; // will be set eventually during parsing (may be EOF) - this.endMatch = null; // may remain null if we never match our "end" pattern - - // Build a new regex if the "end" regex has backrefs since they refer to matched groups of beginMatch - if (rule.endRegexHasBackRef) { - this.endRegexSubstituted = RegexUtil.getSubstitutedRegex(rule.endRegex, beginMatch); - } else { - this.endRegexSubstituted = null; - } - } - BeginEndNode.prototype.addChild = function(child) { - this.children.push(child); - }; - /** @return {Number} This node's index in its parent's "children" list */ - BeginEndNode.prototype.getIndexInParent = function(node) { - return this.parent ? this.parent.children.indexOf(this) : -1; - }; - /** @param {RegExp.match} beginMatch */ - BeginEndNode.prototype.setStart = function(beginMatch) { - this.start = beginMatch.index; - this.beginMatch = beginMatch; - }; - /** @param {RegExp.match|Number} endMatchOrLastChar */ - BeginEndNode.prototype.setEnd = function(endMatchOrLastChar) { - if (endMatchOrLastChar && typeof(endMatchOrLastChar) === "object") { - var endMatch = endMatchOrLastChar; - this.endMatch = endMatch; - this.end = endMatch.index + endMatch[0].length; - } else { - var lastChar = endMatchOrLastChar; - this.endMatch = null; - this.end = lastChar; - } - }; - BeginEndNode.prototype.shiftStart = function(amount) { - this.start += amount; - this.beginMatch.index += amount; - }; - BeginEndNode.prototype.shiftEnd = function(amount) { - this.end += amount; - if (this.endMatch) { this.endMatch.index += amount; } - }; - BeginEndNode.prototype.valueOf = function() { - return "{" + this.rule.beginRegex + " range=" + this.start + ".." + this.end + "}"; - }; - return BeginEndNode; - }()), - /** Pushes rules onto stack such that rules[startFrom] is on top - * @private - */ - push: function(/**Array*/ stack, /**Array*/ rules) { - if (!rules) { return; } - for (var i = rules.length; i > 0; ) { - stack.push(rules[--i]); - } - }, - /** Executes regex on text, and returns the match object with its index - * offset by the given amount. - * @returns {RegExp.match} - * @private - */ - exec: function(/**RegExp*/ regex, /**String*/ text, /**Number*/ offset) { - var match = regex.exec(text); - if (match) { match.index += offset; } - regex.lastIndex = 0; // Just in case - return match; - }, - /** @returns {Number} The position immediately following the match. - * @private - */ - afterMatch: function(/**RegExp.match*/ match) { - return match.index + match[0].length; - }, - /** - * @returns {RegExp.match} If node is a BeginEndNode and its rule's "end" pattern matches the text. - * @private - */ - getEndMatch: function(/**Node*/ node, /**String*/ text, /**Number*/ offset) { - if (node instanceof this.BeginEndNode) { - var rule = node.rule; - var endRegex = node.endRegexSubstituted || rule.endRegex; - if (!endRegex) { return null; } - return this.exec(endRegex, text, offset); - } - return null; - }, - /** Called once when file is first loaded to build the parse tree. Tree is updated incrementally thereafter - * as buffer is modified. - * @private - */ - initialParse: function() { - var last = this.textView.getModel().getCharCount(); - // First time; make parse tree for whole buffer - var root = new this.ContainerNode(null, this.grammar._typedRule); - this._tree = root; - this.parse(this._tree, false, 0); - }, - onModelChanged: function(/**eclipse.ModelChangedEvent*/ e) { - var addedCharCount = e.addedCharCount, - addedLineCount = e.addedLineCount, - removedCharCount = e.removedCharCount, - removedLineCount = e.removedLineCount, - start = e.start; - if (!this._tree) { - this.initialParse(); - } else { - var model = this.textView.getModel(); - var charCount = model.getCharCount(); - - // For rs, we must rewind to the line preceding the line 'start' is on. We can't rely on start's - // line since it may've been changed in a way that would cause a new beginMatch at its lineStart. - var rs = model.getLineEnd(model.getLineAtOffset(start) - 1); // may be < 0 - var fd = this.getFirstDamaged(rs, rs); - rs = rs === -1 ? 0 : rs; - var stoppedAt; - if (fd) { - // [rs, re] is the region we need to verify. If we find the structure of the tree - // has changed in that area, then we may need to reparse the rest of the file. - stoppedAt = this.parse(fd, true, rs, start, addedCharCount, removedCharCount); - } else { - // FIXME: fd == null ? - stoppedAt = charCount; - } - this.textView.redrawRange(rs, stoppedAt); - } - }, - /** @returns {BeginEndNode|ContainerNode} The result of taking the first (smallest "start" value) - * node overlapping [start,end] and drilling down to get its deepest damaged descendant (if any). - * @private - */ - getFirstDamaged: function(start, end) { - // If start === 0 we actually have to start from the root because there is no position - // we can rely on. (First index is damaged) - if (start < 0) { - return this._tree; - } - - var nodes = [this._tree]; - var result = null; - while (nodes.length) { - var n = nodes.pop(); - if (!n.parent /*n is root*/ || this.isDamaged(n, start, end)) { - // n is damaged by the edit, so go into its children - // Note: If a node is damaged, then some of its descendents MAY be damaged - // If a node is undamaged, then ALL of its descendents are undamaged - if (n instanceof this.BeginEndNode) { - result = n; - } - // Examine children[0] last - for (var i=0; i < n.children.length; i++) { - nodes.push(n.children[i]); - } - } - } - return result || this._tree; - }, - /** @returns true If n overlaps the interval [start,end]. - * @private - */ - isDamaged: function(/**BeginEndNode*/ n, start, end) { - // Note strict > since [2,5] doesn't overlap [5,7] - return (n.start <= end && n.end > start); - }, - /** - * Builds tree from some of the buffer content - * - * TODO cleanup params - * @param {BeginEndNode|ContainerNode} origNode The deepest node that overlaps [rs,rs], or the root. - * @param {Boolean} repairing - * @param {Number} rs See _onModelChanged() - * @param {Number} [editStart] Only used for repairing === true - * @param {Number} [addedCharCount] Only used for repairing === true - * @param {Number} [removedCharCount] Only used for repairing === true - * @returns {Number} The end position that redrawRange should be called for. - * @private - */ - parse: function(origNode, repairing, rs, editStart, addedCharCount, removedCharCount) { - var model = this.textView.getModel(); - var lastLineStart = model.getLineStart(model.getLineCount() - 1); - var eof = model.getCharCount(); - var initialExpected = this.getInitialExpected(origNode, rs); - - // re is best-case stopping point; if we detect change to tree, we must continue past it - var re = -1; - if (repairing) { - origNode.repaired = true; - origNode.endNeedsUpdate = true; - var lastChild = origNode.children[origNode.children.length-1]; - var delta = addedCharCount - removedCharCount; - var lastChildLineEnd = lastChild ? model.getLineEnd(model.getLineAtOffset(lastChild.end + delta)) : -1; - var editLineEnd = model.getLineEnd(model.getLineAtOffset(editStart + removedCharCount)); - re = Math.max(lastChildLineEnd, editLineEnd); - } - re = (re === -1) ? eof : re; - - var expected = initialExpected; - var node = origNode; - var matchedChildOrEnd = false; - var pos = rs; - var redrawEnd = -1; - while (node && (!repairing || (pos < re))) { - var matchInfo = this.getNextMatch(model, node, pos); - if (!matchInfo) { - // Go to next line, if any - pos = (pos >= lastLineStart) ? eof : model.getLineStart(model.getLineAtOffset(pos) + 1); - } - var match = matchInfo && matchInfo.match, - rule = matchInfo && matchInfo.rule, - isSub = matchInfo && matchInfo.isSub, - isEnd = matchInfo && matchInfo.isEnd; - if (isSub) { - pos = this.afterMatch(match); - if (rule instanceof this.BeginEndRule) { - matchedChildOrEnd = true; - // Matched a child. Did we expect that? - if (repairing && rule === expected.rule && node === expected.parent) { - // Yes: matched expected child - var foundChild = expected; - foundChild.setStart(match); - // Note: the 'end' position for this node will either be matched, or fixed up by us post-loop - foundChild.repaired = true; - foundChild.endNeedsUpdate = true; - node = foundChild; // descend - expected = this.getNextExpected(expected, "begin"); - } else { - if (repairing) { - // No: matched unexpected child. - this.prune(node, expected); - repairing = false; - } - - // Add the new child (will replace 'expected' in node's children list) - var subNode = new this.BeginEndNode(node, rule, match); - node.addChild(subNode); - node = subNode; // descend - } - } else { - // Matched a MatchRule; no changes to tree required - } - } else if (isEnd || pos === eof) { - if (node instanceof this.BeginEndNode) { - if (match) { - matchedChildOrEnd = true; - redrawEnd = Math.max(redrawEnd, node.end); // if end moved up, must still redraw to its old value - node.setEnd(match); - pos = this.afterMatch(match); - // Matched node's end. Did we expect that? - if (repairing && node === expected && node.parent === expected.parent) { - // Yes: found the expected end of node - node.repaired = true; - delete node.endNeedsUpdate; - expected = this.getNextExpected(expected, "end"); - } else { - if (repairing) { - // No: found an unexpected end - this.prune(node, expected); - repairing = false; - } - } - } else { - // Force-ending a BeginEndNode that runs until eof - node.setEnd(eof); - delete node.endNeedsUpdate; - } - } - node = node.parent; // ascend - } - - if (repairing && pos >= re && !matchedChildOrEnd) { - // Reached re without matching any begin/end => initialExpected itself was removed => repair fail - this.prune(origNode, initialExpected); - repairing = false; - } - } // end loop - // TODO: do this for every node we end? - this.removeUnrepairedChildren(origNode, repairing, rs); - - //console.debug("parsed " + (pos - rs) + " of " + model.getCharCount + "buf"); - this.cleanup(repairing, origNode, rs, re, eof, addedCharCount, removedCharCount); - if (repairing) { - return Math.max(redrawEnd, pos); - } else { - return pos; // where we stopped reparsing - } - }, - /** Helper for parse() in the repair case. To be called when ending a node, as any children that - * lie in [rs,node.end] and were not repaired must've been deleted. - * @private - */ - removeUnrepairedChildren: function(node, repairing, start) { - if (repairing) { - var children = node.children; - var removeFrom = -1; - for (var i=0; i < children.length; i++) { - var child = children[i]; - if (!child.repaired && this.isDamaged(child, start, Number.MAX_VALUE /*end doesn't matter*/)) { - removeFrom = i; - break; - } - } - if (removeFrom !== -1) { - node.children.length = removeFrom; - } - } - }, - /** Helper for parse() in the repair case - * @private - */ - cleanup: function(repairing, origNode, rs, re, eof, addedCharCount, removedCharCount) { - var i, node, maybeRepairedNodes; - if (repairing) { - // The repair succeeded, so update stale begin/end indices by simple translation. - var delta = addedCharCount - removedCharCount; - // A repaired node's end can't exceed re, but it may exceed re-delta+1. - // TODO: find a way to guarantee disjoint intervals for repaired vs unrepaired, then stop using flag - var maybeUnrepairedNodes = this.getIntersecting(re-delta+1, eof); - maybeRepairedNodes = this.getIntersecting(rs, re); - // Handle unrepaired nodes. They are those intersecting [re-delta+1, eof] that don't have the flag - for (i=0; i < maybeUnrepairedNodes.length; i++) { - node = maybeUnrepairedNodes[i]; - if (!node.repaired && node instanceof this.BeginEndNode) { - node.shiftEnd(delta); - node.shiftStart(delta); - } - } - // Translate 'end' index of repaired node whose 'end' was not matched in loop (>= re) - for (i=0; i < maybeRepairedNodes.length; i++) { - node = maybeRepairedNodes[i]; - if (node.repaired && node.endNeedsUpdate) { - node.shiftEnd(delta); - } - delete node.endNeedsUpdate; - delete node.repaired; - } - } else { - // Clean up after ourself - maybeRepairedNodes = this.getIntersecting(rs, re); - for (i=0; i < maybeRepairedNodes.length; i++) { - delete maybeRepairedNodes[i].repaired; - } - } - }, - /** - * @param model {orion.textview.TextModel} - * @param node {Node} - * @param pos {Number} - * @param [matchRulesOnly] {Boolean} Optional, if true only "match" subrules will be considered. - * @returns {Object} A match info object with properties: - * {Boolean} isEnd - * {Boolean} isSub - * {RegExp.match} match - * {(Match|BeginEnd)Rule} rule - * @private - */ - getNextMatch: function(model, node, pos, matchRulesOnly) { - var lineIndex = model.getLineAtOffset(pos); - var lineEnd = model.getLineEnd(lineIndex); - var line = model.getText(pos, lineEnd); - - var stack = [], - expandedContainers = [], - subMatches = [], - subrules = []; - this.push(stack, node.rule.subrules); - while (stack.length) { - var next = stack.length ? stack.pop() : null; - var subrule = next && next._resolvedRule._typedRule; - if (subrule instanceof this.ContainerRule && expandedContainers.indexOf(subrule) === -1) { - // Expand ContainerRule by pushing its subrules on - expandedContainers.push(subrule); - this.push(stack, subrule.subrules); - continue; - } - if (subrule && matchRulesOnly && !(subrule.matchRegex)) { - continue; - } - var subMatch = subrule && this.exec(subrule.matchRegex || subrule.beginRegex, line, pos); - if (subMatch) { - subMatches.push(subMatch); - subrules.push(subrule); - } - } - - var bestSub = Number.MAX_VALUE, - bestSubIndex = -1; - for (var i=0; i < subMatches.length; i++) { - var match = subMatches[i]; - if (match.index < bestSub) { - bestSub = match.index; - bestSubIndex = i; - } - } - - if (!matchRulesOnly) { - // See if the "end" pattern of the active begin/end node matches. - // TODO: The active begin/end node may not be the same as the node that holds the subrules - var activeBENode = node; - var endMatch = this.getEndMatch(node, line, pos); - if (endMatch) { - var doEndLast = activeBENode.rule.applyEndPatternLast; - var endWins = bestSubIndex === -1 || (endMatch.index < bestSub) || (!doEndLast && endMatch.index === bestSub); - if (endWins) { - return {isEnd: true, rule: activeBENode.rule, match: endMatch}; - } - } - } - return bestSubIndex === -1 ? null : {isSub: true, rule: subrules[bestSubIndex], match: subMatches[bestSubIndex]}; - }, - /** - * Gets the node corresponding to the first match we expect to see in the repair. - * @param {BeginEndNode|ContainerNode} node The node returned via getFirstDamaged(rs,rs) -- may be the root. - * @param {Number} rs See _onModelChanged() - * Note that because rs is a line end (or 0, a line start), it will intersect a beginMatch or - * endMatch either at their 0th character, or not at all. (begin/endMatches can't cross lines). - * This is the only time we rely on the start/end values from the pre-change tree. After this - * we only look at node ordering, never use the old indices. - * @returns {Node} - * @private - */ - getInitialExpected: function(node, rs) { - // TODO: Kind of weird.. maybe ContainerNodes should have start & end set, like BeginEndNodes - var i, child; - if (node === this._tree) { - // get whichever of our children comes after rs - for (i=0; i < node.children.length; i++) { - child = node.children[i]; // BeginEndNode - if (child.start >= rs) { - return child; - } - } - } else if (node instanceof this.BeginEndNode) { - if (node.endMatch) { - // Which comes next after rs: our nodeEnd or one of our children? - var nodeEnd = node.endMatch.index; - for (i=0; i < node.children.length; i++) { - child = node.children[i]; // BeginEndNode - if (child.start >= rs) { - break; - } - } - if (child && child.start < nodeEnd) { - return child; // Expect child as the next match - } - } else { - // No endMatch => node goes until eof => it end should be the next match - } - } - return node; // We expect node to end, so it should be the next match - }, - /** - * Helper for repair() to tell us what kind of event we expect next. - * @param {Node} expected Last value returned by this method. - * @param {String} event "begin" if the last value of expected was matched as "begin", - * or "end" if it was matched as an end. - * @returns {Node} The next expected node to match, or null. - * @private - */ - getNextExpected: function(/**Node*/ expected, event) { - var node = expected; - if (event === "begin") { - var child = node.children[0]; - if (child) { - return child; - } else { - return node; - } - } else if (event === "end") { - var parent = node.parent; - if (parent) { - var nextSibling = parent.children[parent.children.indexOf(node) + 1]; - if (nextSibling) { - return nextSibling; - } else { - return parent; - } - } - } - return null; - }, - /** Helper for parse() when repairing. Prunes out the unmatched nodes from the tree so we can continue parsing. - * @private - */ - prune: function(/**BeginEndNode|ContainerNode*/ node, /**Node*/ expected) { - var expectedAChild = expected.parent === node; - if (expectedAChild) { - // Expected child wasn't matched; prune it and all siblings after it - node.children.length = expected.getIndexInParent(); - } else if (node instanceof this.BeginEndNode) { - // Expected node to end but it didn't; set its end unknown and we'll match it eventually - node.endMatch = null; - node.end = null; - } - // Reparsing from node, so prune the successors outside of node's subtree - if (node.parent) { - node.parent.children.length = node.getIndexInParent() + 1; - } - }, - onLineStyle: function(/**eclipse.LineStyleEvent*/ e) { - function byStart(r1, r2) { - return r1.start - r2.start; - } - - if (!this._tree) { - // In some cases it seems onLineStyle is called before onModelChanged, so we need to parse here - this.initialParse(); - } - var lineStart = e.lineStart, - model = this.textView.getModel(), - lineEnd = model.getLineEnd(e.lineIndex); - - var rs = model.getLineEnd(model.getLineAtOffset(lineStart) - 1); // may be < 0 - var node = this.getFirstDamaged(rs, rs); - - var scopes = this.getLineScope(model, node, lineStart, lineEnd); - e.ranges = this.toStyleRanges(scopes); - // Editor requires StyleRanges must be in ascending order by 'start', or else some will be ignored - e.ranges.sort(byStart); - }, - /** Runs parse algorithm on [start, end] in the context of node, assigning scope as we find matches. - * @private - */ - getLineScope: function(model, node, start, end) { - var pos = start; - var expected = this.getInitialExpected(node, start); - var scopes = [], - gaps = []; - while (node && (pos < end)) { - var matchInfo = this.getNextMatch(model, node, pos); - if (!matchInfo) { - break; // line is over - } - var match = matchInfo && matchInfo.match, - rule = matchInfo && matchInfo.rule, - isSub = matchInfo && matchInfo.isSub, - isEnd = matchInfo && matchInfo.isEnd; - if (match.index !== pos) { - // gap [pos..match.index] - gaps.push({ start: pos, end: match.index, node: node}); - } - if (isSub) { - pos = this.afterMatch(match); - if (rule instanceof this.BeginEndRule) { - // Matched a "begin", assign its scope and descend into it - this.addBeginScope(scopes, match, rule); - node = expected; // descend - expected = this.getNextExpected(expected, "begin"); - } else { - // Matched a child MatchRule; - this.addMatchScope(scopes, match, rule); - } - } else if (isEnd) { - pos = this.afterMatch(match); - // Matched and "end", assign its end scope and go up - this.addEndScope(scopes, match, rule); - expected = this.getNextExpected(expected, "end"); - node = node.parent; // ascend - } - } - if (pos < end) { - gaps.push({ start: pos, end: end, node: node }); - } - var inherited = this.getInheritedLineScope(gaps, start, end); - return scopes.concat(inherited); - }, - /** @private */ - getInheritedLineScope: function(gaps, start, end) { - var scopes = []; - for (var i=0; i < gaps.length; i++) { - var gap = gaps[i]; - var node = gap.node; - while (node) { - // if node defines a contentName or name, apply it - var rule = node.rule.rule; - var name = rule.name, - contentName = rule.contentName; - // TODO: if both are given, we don't resolve the conflict. contentName always wins - var scope = contentName || name; - if (scope) { - this.addScopeRange(scopes, gap.start, gap.end, scope); - break; - } - node = node.parent; - } - } - return scopes; - }, - /** @private */ - addBeginScope: function(scopes, match, typedRule) { - var rule = typedRule.rule; - this.addCapturesScope(scopes, match, (rule.beginCaptures || rule.captures), typedRule.isComplex, typedRule.beginOld2New, typedRule.beginConsuming); - }, - /** @private */ - addEndScope: function(scopes, match, typedRule) { - var rule = typedRule.rule; - this.addCapturesScope(scopes, match, (rule.endCaptures || rule.captures), typedRule.isComplex, typedRule.endOld2New, typedRule.endConsuming); - }, - /** @private */ - addMatchScope: function(scopes, match, typedRule) { - var rule = typedRule.rule, - name = rule.name, - captures = rule.captures; - if (captures) { - // captures takes priority over name - this.addCapturesScope(scopes, match, captures, typedRule.isComplex, typedRule.matchOld2New, typedRule.matchConsuming); - } else { - this.addScope(scopes, match, name); - } - }, - /** @private */ - addScope: function(scopes, match, name) { - if (!name) { return; } - scopes.push({start: match.index, end: this.afterMatch(match), scope: name }); - }, - /** @private */ - addScopeRange: function(scopes, start, end, name) { - if (!name) { return; } - scopes.push({start: start, end: end, scope: name }); - }, - /** @private */ - addCapturesScope: function(/**Array*/scopes, /*RegExp.match*/ match, /**Object*/captures, /**Boolean*/isComplex, /**Object*/old2New, /**Object*/consuming) { - if (!captures) { return; } - if (!isComplex) { - this.addScope(scopes, match, captures[0] && captures[0].name); - } else { - // apply scopes captures[1..n] to matching groups [1]..[n] of match - - // Sum up the lengths of preceding consuming groups to get the start offset for each matched group. - var newGroupStarts = {1: 0}; - var sum = 0; - for (var num = 1; match[num] !== undefined; num++) { - if (consuming[num] !== undefined) { - sum += match[num].length; - } - if (match[num+1] !== undefined) { - newGroupStarts[num + 1] = sum; - } - } - // Map the group numbers referred to in captures object to the new group numbers, and get the actual matched range. - var start = match.index; - for (var oldGroupNum = 1; captures[oldGroupNum]; oldGroupNum++) { - var scope = captures[oldGroupNum].name; - var newGroupNum = old2New[oldGroupNum]; - var groupStart = start + newGroupStarts[newGroupNum]; - // Not every capturing group defined in regex need match every time the regex is run. - // eg. (a)|b matches "b" but group 1 is undefined - if (typeof match[newGroupNum] !== "undefined") { - var groupEnd = groupStart + match[newGroupNum].length; - this.addScopeRange(scopes, groupStart, groupEnd, scope); - } - } - } - }, - /** @returns {Node[]} In depth-first order - * @private - */ - getIntersecting: function(start, end) { - var result = []; - var nodes = this._tree ? [this._tree] : []; - while (nodes.length) { - var n = nodes.pop(); - var visitChildren = false; - if (n instanceof this.ContainerNode) { - visitChildren = true; - } else if (this.isDamaged(n, start, end)) { - visitChildren = true; - result.push(n); - } - if (visitChildren) { - var len = n.children.length; -// for (var i=len-1; i >= 0; i--) { -// nodes.push(n.children[i]); -// } - for (var i=0; i < len; i++) { - nodes.push(n.children[i]); - } - } - } - return result.reverse(); - }, - /** - * Applies the grammar to obtain the {@link eclipse.StyleRange[]} for the given line. - * @returns eclipse.StyleRange[] - * @private - */ - toStyleRanges: function(/**ScopeRange[]*/ scopeRanges) { - var styleRanges = []; - for (var i=0; i < scopeRanges.length; i++) { - var scopeRange = scopeRanges[i]; - var classNames = this._styles[scopeRange.scope]; - if (!classNames) { throw new Error("styles not found for " + scopeRange.scope); } - var classNamesString = classNames.join(" "); - styleRanges.push({start: scopeRange.start, end: scopeRange.end, style: {styleClass: classNamesString}}); -// console.debug("{start " + styleRanges[i].start + ", end " + styleRanges[i].end + ", style: " + styleRanges[i].style.styleClass + "}"); - } - return styleRanges; - } - }; - - return { - RegexUtil: RegexUtil, - TextMateStyler: TextMateStyler - }; -}); -/******************************************************************************* - * @license - * Copyright (c) 2010, 2011 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of the Eclipse Public License v1.0 - * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution - * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). - * - * Contributors: IBM Corporation - initial API and implementation - * Alex Lakatos - fix for bug#369781 - ******************************************************************************/ - -/*global document window navigator define */ - -define("examples/textview/textStyler", ['orion/textview/annotations'], function(mAnnotations) { - - var JS_KEYWORDS = - ["break", - "case", "class", "catch", "continue", "const", - "debugger", "default", "delete", "do", - "else", "enum", "export", "extends", - "false", "finally", "for", "function", - "if", "implements", "import", "in", "instanceof", "interface", - "let", - "new", "null", - "package", "private", "protected", "public", - "return", - "static", "super", "switch", - "this", "throw", "true", "try", "typeof", - "undefined", - "var", "void", - "while", "with", - "yield"]; - - var JAVA_KEYWORDS = - ["abstract", - "boolean", "break", "byte", - "case", "catch", "char", "class", "continue", - "default", "do", "double", - "else", "extends", - "false", "final", "finally", "float", "for", - "if", "implements", "import", "instanceof", "int", "interface", - "long", - "native", "new", "null", - "package", "private", "protected", "public", - "return", - "short", "static", "super", "switch", "synchronized", - "this", "throw", "throws", "transient", "true", "try", - "void", "volatile", - "while"]; - - var CSS_KEYWORDS = - ["alignment-adjust", "alignment-baseline", "animation", "animation-delay", "animation-direction", "animation-duration", - "animation-iteration-count", "animation-name", "animation-play-state", "animation-timing-function", "appearance", - "azimuth", "backface-visibility", "background", "background-attachment", "background-clip", "background-color", - "background-image", "background-origin", "background-position", "background-repeat", "background-size", "baseline-shift", - "binding", "bleed", "bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target", "border", "border-bottom", - "border-bottom-color", "border-bottom-left-radius", "border-bottom-right-radius", "border-bottom-style", "border-bottom-width", - "border-collapse", "border-color", "border-image", "border-image-outset", "border-image-repeat", "border-image-slice", - "border-image-source", "border-image-width", "border-left", "border-left-color", "border-left-style", "border-left-width", - "border-radius", "border-right", "border-right-color", "border-right-style", "border-right-width", "border-spacing", "border-style", - "border-top", "border-top-color", "border-top-left-radius", "border-top-right-radius", "border-top-style", "border-top-width", - "border-width", "bottom", "box-align", "box-decoration-break", "box-direction", "box-flex", "box-flex-group", "box-lines", - "box-ordinal-group", "box-orient", "box-pack", "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", - "caption-side", "clear", "clip", "color", "color-profile", "column-count", "column-fill", "column-gap", "column-rule", - "column-rule-color", "column-rule-style", "column-rule-width", "column-span", "column-width", "columns", "content", "counter-increment", - "counter-reset", "crop", "cue", "cue-after", "cue-before", "cursor", "direction", "display", "dominant-baseline", - "drop-initial-after-adjust", "drop-initial-after-align", "drop-initial-before-adjust", "drop-initial-before-align", "drop-initial-size", - "drop-initial-value", "elevation", "empty-cells", "fit", "fit-position", "flex-align", "flex-flow", "flex-inline-pack", "flex-order", - "flex-pack", "float", "float-offset", "font", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", - "font-variant", "font-weight", "grid-columns", "grid-rows", "hanging-punctuation", "height", "hyphenate-after", - "hyphenate-before", "hyphenate-character", "hyphenate-lines", "hyphenate-resource", "hyphens", "icon", "image-orientation", - "image-rendering", "image-resolution", "inline-box-align", "left", "letter-spacing", "line-height", "line-stacking", - "line-stacking-ruby", "line-stacking-shift", "line-stacking-strategy", "list-style", "list-style-image", "list-style-position", - "list-style-type", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "mark", "mark-after", "mark-before", - "marker-offset", "marks", "marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed", "marquee-style", "max-height", - "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", "nav-left", "nav-right", "nav-up", "opacity", "orphans", - "outline", "outline-color", "outline-offset", "outline-style", "outline-width", "overflow", "overflow-style", "overflow-x", - "overflow-y", "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", "page", "page-break-after", "page-break-before", - "page-break-inside", "page-policy", "pause", "pause-after", "pause-before", "perspective", "perspective-origin", "phonemes", "pitch", - "pitch-range", "play-during", "position", "presentation-level", "punctuation-trim", "quotes", "rendering-intent", "resize", - "rest", "rest-after", "rest-before", "richness", "right", "rotation", "rotation-point", "ruby-align", "ruby-overhang", "ruby-position", - "ruby-span", "size", "speak", "speak-header", "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", "table-layout", - "target", "target-name", "target-new", "target-position", "text-align", "text-align-last", "text-decoration", "text-emphasis", - "text-height", "text-indent", "text-justify", "text-outline", "text-shadow", "text-transform", "text-wrap", "top", "transform", - "transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property", - "transition-timing-function", "unicode-bidi", "vector-effect", "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family", - "voice-pitch", "voice-pitch-range", "voice-rate", "voice-stress", "voice-volume", "volume", "white-space", "white-space-collapse", - "widows", "width", "word-break", "word-spacing", "word-wrap", "z-index" - ]; - - // Scanner constants - var UNKOWN = 1; - var KEYWORD = 2; - var STRING = 3; - var SINGLELINE_COMMENT = 4; - var MULTILINE_COMMENT = 5; - var DOC_COMMENT = 6; - var WHITE = 7; - var WHITE_TAB = 8; - var WHITE_SPACE = 9; - var HTML_MARKUP = 10; - var DOC_TAG = 11; - var TASK_TAG = 12; - - // Styles - var singleCommentStyle = {styleClass: "token_singleline_comment"}; - var multiCommentStyle = {styleClass: "token_multiline_comment"}; - var docCommentStyle = {styleClass: "token_doc_comment"}; - var htmlMarkupStyle = {styleClass: "token_doc_html_markup"}; - var tasktagStyle = {styleClass: "token_task_tag"}; - var doctagStyle = {styleClass: "token_doc_tag"}; - var stringStyle = {styleClass: "token_string"}; - var keywordStyle = {styleClass: "token_keyword"}; - var spaceStyle = {styleClass: "token_space"}; - var tabStyle = {styleClass: "token_tab"}; - var caretLineStyle = {styleClass: "line_caret"}; - - function Scanner (keywords, whitespacesVisible) { - this.keywords = keywords; - this.whitespacesVisible = whitespacesVisible; - this.setText(""); - } - - Scanner.prototype = { - getOffset: function() { - return this.offset; - }, - getStartOffset: function() { - return this.startOffset; - }, - getData: function() { - return this.text.substring(this.startOffset, this.offset); - }, - getDataLength: function() { - return this.offset - this.startOffset; - }, - _default: function(c) { - var keywords = this.keywords; - switch (c) { - case 32: // SPACE - case 9: // TAB - if (this.whitespacesVisible) { - return c === 32 ? WHITE_SPACE : WHITE_TAB; - } - do { - c = this._read(); - } while(c === 32 || c === 9); - this._unread(c); - return WHITE; - case 123: // { - case 125: // } - case 40: // ( - case 41: // ) - case 91: // [ - case 93: // ] - case 60: // < - case 62: // > - // BRACKETS - return c; - default: - var isCSS = this.isCSS; - if ((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57) || (0x2d === c && isCSS)) { //LETTER OR UNDERSCORE OR NUMBER - var off = this.offset - 1; - do { - c = this._read(); - } while((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57) || (0x2d === c && isCSS)); //LETTER OR UNDERSCORE OR NUMBER - this._unread(c); - if (keywords.length > 0) { - var word = this.text.substring(off, this.offset); - //TODO slow - for (var i=0; i comment - c = this._read(); - if (!this.isCSS) { - if (c === 47) { // SLASH -> single line - while (true) { - c = this._read(); - if ((c === -1) || (c === 10) || (c === 13)) { - this._unread(c); - return SINGLELINE_COMMENT; - } - } - } - } - if (c === 42) { // STAR -> multi line - c = this._read(); - var token = MULTILINE_COMMENT; - if (c === 42) { - token = DOC_COMMENT; - } - while (true) { - while (c === 42) { - c = this._read(); - if (c === 47) { - return token; - } - } - if (c === -1) { - this._unread(c); - return token; - } - c = this._read(); - } - } - this._unread(c); - return UNKOWN; - case 39: // SINGLE QUOTE -> char const - while(true) { - c = this._read(); - switch (c) { - case 39: - return STRING; - case 13: - case 10: - case -1: - this._unread(c); - return STRING; - case 92: // BACKSLASH - c = this._read(); - break; - } - } - break; - case 34: // DOUBLE QUOTE -> string - while(true) { - c = this._read(); - switch (c) { - case 34: // DOUBLE QUOTE - return STRING; - case 13: - case 10: - case -1: - this._unread(c); - return STRING; - case 92: // BACKSLASH - c = this._read(); - break; - } - } - break; - default: - return this._default(c); - } - } - }, - setText: function(text) { - this.text = text; - this.offset = 0; - this.startOffset = 0; - } - }; - - function WhitespaceScanner () { - Scanner.call(this, null, true); - } - WhitespaceScanner.prototype = new Scanner(null); - WhitespaceScanner.prototype.nextToken = function() { - this.startOffset = this.offset; - while (true) { - var c = this._read(); - switch (c) { - case -1: return null; - case 32: // SPACE - return WHITE_SPACE; - case 9: // TAB - return WHITE_TAB; - default: - do { - c = this._read(); - } while(!(c === 32 || c === 9 || c === -1)); - this._unread(c); - return UNKOWN; - } - } - }; - - function CommentScanner (whitespacesVisible) { - Scanner.call(this, null, whitespacesVisible); - } - CommentScanner.prototype = new Scanner(null); - CommentScanner.prototype.setType = function(type) { - this._type = type; - }; - CommentScanner.prototype.nextToken = function() { - this.startOffset = this.offset; - while (true) { - var c = this._read(); - switch (c) { - case -1: return null; - case 32: // SPACE - case 9: // TAB - if (this.whitespacesVisible) { - return c === 32 ? WHITE_SPACE : WHITE_TAB; - } - do { - c = this._read(); - } while(c === 32 || c === 9); - this._unread(c); - return WHITE; - case 60: // < - if (this._type === DOC_COMMENT) { - do { - c = this._read(); - } while(!(c === 62 || c === -1)); // > - if (c === 62) { - return HTML_MARKUP; - } - } - return UNKOWN; - case 64: // @ - if (this._type === DOC_COMMENT) { - do { - c = this._read(); - } while((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57)); //LETTER OR UNDERSCORE OR NUMBER - this._unread(c); - return DOC_TAG; - } - return UNKOWN; - case 84: // T - if ((c = this._read()) === 79) { // O - if ((c = this._read()) === 68) { // D - if ((c = this._read()) === 79) { // O - c = this._read(); - if (!((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57))) { - this._unread(c); - return TASK_TAG; - } - this._unread(c); - } else { - this._unread(c); - } - } else { - this._unread(c); - } - } else { - this._unread(c); - } - //FALL THROUGH - default: - do { - c = this._read(); - } while(!(c === 32 || c === 9 || c === -1 || c === 60 || c === 64 || c === 84)); - this._unread(c); - return UNKOWN; - } - } - }; - - function FirstScanner () { - Scanner.call(this, null, false); - } - FirstScanner.prototype = new Scanner(null); - FirstScanner.prototype._default = function(c) { - while(true) { - c = this._read(); - switch (c) { - case 47: // SLASH - case 34: // DOUBLE QUOTE - case 39: // SINGLE QUOTE - case -1: - this._unread(c); - return UNKOWN; - } - } - }; - - function TextStyler (view, lang, annotationModel) { - this.commentStart = "/*"; - this.commentEnd = "*/"; - var keywords = []; - switch (lang) { - case "java": keywords = JAVA_KEYWORDS; break; - case "js": keywords = JS_KEYWORDS; break; - case "css": keywords = CSS_KEYWORDS; break; - } - this.whitespacesVisible = false; - this.detectHyperlinks = true; - this.highlightCaretLine = false; - this.foldingEnabled = true; - this.detectTasks = true; - this._scanner = new Scanner(keywords, this.whitespacesVisible); - this._firstScanner = new FirstScanner(); - this._commentScanner = new CommentScanner(this.whitespacesVisible); - this._whitespaceScanner = new WhitespaceScanner(); - //TODO these scanners are not the best/correct way to parse CSS - if (lang === "css") { - this._scanner.isCSS = true; - this._firstScanner.isCSS = true; - } - this.view = view; - this.annotationModel = annotationModel; - this._bracketAnnotations = undefined; - - var self = this; - this._listener = { - onChanged: function(e) { - self._onModelChanged(e); - }, - onDestroy: function(e) { - self._onDestroy(e); - }, - onLineStyle: function(e) { - self._onLineStyle(e); - }, - onSelection: function(e) { - self._onSelection(e); - } - }; - var model = view.getModel(); - if (model.getBaseModel) { - model.getBaseModel().addEventListener("Changed", this._listener.onChanged); - } else { - //TODO still needed to keep the event order correct (styler before view) - view.addEventListener("ModelChanged", this._listener.onChanged); - } - view.addEventListener("Selection", this._listener.onSelection); - view.addEventListener("Destroy", this._listener.onDestroy); - view.addEventListener("LineStyle", this._listener.onLineStyle); - this._computeComments (); - this._computeFolding(); - view.redrawLines(); - } - - TextStyler.prototype = { - getClassNameForToken: function(token) { - switch (token) { - case "singleLineComment": return singleCommentStyle.styleClass; - case "multiLineComment": return multiCommentStyle.styleClass; - case "docComment": return docCommentStyle.styleClass; - case "docHtmlComment": return htmlMarkupStyle.styleClass; - case "tasktag": return tasktagStyle.styleClass; - case "doctag": return doctagStyle.styleClass; - case "string": return stringStyle.styleClass; - case "keyword": return keywordStyle.styleClass; - case "space": return spaceStyle.styleClass; - case "tab": return tabStyle.styleClass; - case "caretLine": return caretLineStyle.styleClass; - } - return null; - }, - destroy: function() { - var view = this.view; - if (view) { - var model = view.getModel(); - if (model.getBaseModel) { - model.getBaseModel().removeEventListener("Changed", this._listener.onChanged); - } else { - view.removeEventListener("ModelChanged", this._listener.onChanged); - } - view.removeEventListener("Selection", this._listener.onSelection); - view.removeEventListener("Destroy", this._listener.onDestroy); - view.removeEventListener("LineStyle", this._listener.onLineStyle); - this.view = null; - } - }, - setHighlightCaretLine: function(highlight) { - this.highlightCaretLine = highlight; - }, - setWhitespacesVisible: function(visible) { - this.whitespacesVisible = visible; - this._scanner.whitespacesVisible = visible; - this._commentScanner.whitespacesVisible = visible; - }, - setDetectHyperlinks: function(enabled) { - this.detectHyperlinks = enabled; - }, - setFoldingEnabled: function(enabled) { - this.foldingEnabled = enabled; - }, - setDetectTasks: function(enabled) { - this.detectTasks = enabled; - }, - _binarySearch: function (array, offset, inclusive, low, high) { - var index; - if (low === undefined) { low = -1; } - if (high === undefined) { high = array.length; } - while (high - low > 1) { - index = Math.floor((high + low) / 2); - if (offset <= array[index].start) { - high = index; - } else if (inclusive && offset < array[index].end) { - high = index; - break; - } else { - low = index; - } - } - return high; - }, - _computeComments: function() { - var model = this.view.getModel(); - if (model.getBaseModel) { model = model.getBaseModel(); } - this.comments = this._findComments(model.getText()); - }, - _computeFolding: function() { - if (!this.foldingEnabled) { return; } - var view = this.view; - var viewModel = view.getModel(); - if (!viewModel.getBaseModel) { return; } - var annotationModel = this.annotationModel; - if (!annotationModel) { return; } - annotationModel.removeAnnotations("orion.annotation.folding"); - var add = []; - var baseModel = viewModel.getBaseModel(); - var comments = this.comments; - for (var i=0; i
", {styleClass: "annotation expanded"}, - "", {styleClass: "annotation collapsed"}); - }, - _computeTasks: function(type, commentStart, commentEnd) { - if (!this.detectTasks) { return; } - var annotationModel = this.annotationModel; - if (!annotationModel) { return; } - var view = this.view; - var viewModel = view.getModel(), baseModel = viewModel; - if (viewModel.getBaseModel) { baseModel = viewModel.getBaseModel(); } - var annotations = annotationModel.getAnnotations(commentStart, commentEnd); - var remove = []; - var annotationType = "orion.annotation.task"; - while (annotations.hasNext()) { - var annotation = annotations.next(); - if (annotation.type === annotationType) { - remove.push(annotation); - } - } - var add = []; - var scanner = this._commentScanner; - scanner.setText(baseModel.getText(commentStart, commentEnd)); - var token; - while ((token = scanner.nextToken())) { - var tokenStart = scanner.getStartOffset() + commentStart; - if (token === TASK_TAG) { - var end = baseModel.getLineEnd(baseModel.getLineAtOffset(tokenStart)); - if (type !== SINGLELINE_COMMENT) { - end = Math.min(end, commentEnd - this.commentEnd.length); - } - add.push({ - start: tokenStart, - end: end, - type: annotationType, - title: baseModel.getText(tokenStart, end), - style: {styleClass: "annotation task"}, - html: "
", - overviewStyle: {styleClass: "annotationOverview task"}, - rangeStyle: {styleClass: "annotationRange task"} - }); - } - } - annotationModel.replaceAnnotations(remove, add); - }, - _getLineStyle: function(lineIndex) { - if (this.highlightCaretLine) { - var view = this.view; - var model = view.getModel(); - var selection = view.getSelection(); - if (selection.start === selection.end && model.getLineAtOffset(selection.start) === lineIndex) { - return caretLineStyle; - } - } - return null; - }, - _getStyles: function(model, text, start) { - if (model.getBaseModel) { - start = model.mapOffset(start); - } - var end = start + text.length; - - var styles = []; - - // for any sub range that is not a comment, parse code generating tokens (keywords, numbers, brackets, line comments, etc) - var offset = start, comments = this.comments; - var startIndex = this._binarySearch(comments, start, true); - for (var i = startIndex; i < comments.length; i++) { - if (comments[i].start >= end) { break; } - var commentStart = comments[i].start; - var commentEnd = comments[i].end; - if (offset < commentStart) { - this._parse(text.substring(offset - start, commentStart - start), offset, styles); - } - var style = comments[i].type === DOC_COMMENT ? docCommentStyle : multiCommentStyle; - if (this.whitespacesVisible || this.detectHyperlinks) { - var s = Math.max(offset, commentStart); - var e = Math.min(end, commentEnd); - this._parseComment(text.substring(s - start, e - start), s, styles, style, comments[i].type); - } else { - styles.push({start: commentStart, end: commentEnd, style: style}); - } - offset = commentEnd; - } - if (offset < end) { - this._parse(text.substring(offset - start, end - start), offset, styles); - } - if (model.getBaseModel) { - for (var j = 0; j < styles.length; j++) { - var length = styles[j].end - styles[j].start; - styles[j].start = model.mapOffset(styles[j].start, true); - styles[j].end = styles[j].start + length; - } - } - return styles; - }, - _parse: function(text, offset, styles) { - var scanner = this._scanner; - scanner.setText(text); - var token; - while ((token = scanner.nextToken())) { - var tokenStart = scanner.getStartOffset() + offset; - var style = null; - switch (token) { - case KEYWORD: style = keywordStyle; break; - case STRING: - if (this.whitespacesVisible) { - this._parseString(scanner.getData(), tokenStart, styles, stringStyle); - continue; - } else { - style = stringStyle; - } - break; - case DOC_COMMENT: - this._parseComment(scanner.getData(), tokenStart, styles, docCommentStyle, token); - continue; - case SINGLELINE_COMMENT: - this._parseComment(scanner.getData(), tokenStart, styles, singleCommentStyle, token); - continue; - case MULTILINE_COMMENT: - this._parseComment(scanner.getData(), tokenStart, styles, multiCommentStyle, token); - continue; - case WHITE_TAB: - if (this.whitespacesVisible) { - style = tabStyle; - } - break; - case WHITE_SPACE: - if (this.whitespacesVisible) { - style = spaceStyle; - } - break; - } - styles.push({start: tokenStart, end: scanner.getOffset() + offset, style: style}); - } - }, - _parseComment: function(text, offset, styles, s, type) { - var scanner = this._commentScanner; - scanner.setText(text); - scanner.setType(type); - var token; - while ((token = scanner.nextToken())) { - var tokenStart = scanner.getStartOffset() + offset; - var style = s; - switch (token) { - case WHITE_TAB: - if (this.whitespacesVisible) { - style = tabStyle; - } - break; - case WHITE_SPACE: - if (this.whitespacesVisible) { - style = spaceStyle; - } - break; - case HTML_MARKUP: - style = htmlMarkupStyle; - break; - case DOC_TAG: - style = doctagStyle; - break; - case TASK_TAG: - style = tasktagStyle; - break; - default: - if (this.detectHyperlinks) { - style = this._detectHyperlinks(scanner.getData(), tokenStart, styles, style); - } - } - if (style) { - styles.push({start: tokenStart, end: scanner.getOffset() + offset, style: style}); - } - } - }, - _parseString: function(text, offset, styles, s) { - var scanner = this._whitespaceScanner; - scanner.setText(text); - var token; - while ((token = scanner.nextToken())) { - var tokenStart = scanner.getStartOffset() + offset; - var style = s; - switch (token) { - case WHITE_TAB: - if (this.whitespacesVisible) { - style = tabStyle; - } - break; - case WHITE_SPACE: - if (this.whitespacesVisible) { - style = spaceStyle; - } - break; - } - if (style) { - styles.push({start: tokenStart, end: scanner.getOffset() + offset, style: style}); - } - } - }, - _detectHyperlinks: function(text, offset, styles, s) { - var href = null, index, linkStyle; - if ((index = text.indexOf("://")) > 0) { - href = text; - var start = index; - while (start > 0) { - var c = href.charCodeAt(start - 1); - if (!((97 <= c && c <= 122) || (65 <= c && c <= 90) || 0x2d === c || (48 <= c && c <= 57))) { //LETTER OR DASH OR NUMBER - break; - } - start--; - } - if (start > 0) { - var brackets = "\"\"''(){}[]<>"; - index = brackets.indexOf(href.substring(start - 1, start)); - if (index !== -1 && (index & 1) === 0 && (index = href.lastIndexOf(brackets.substring(index + 1, index + 2))) !== -1) { - var end = index; - linkStyle = this._clone(s); - linkStyle.tagName = "A"; - linkStyle.attributes = {href: href.substring(start, end)}; - styles.push({start: offset, end: offset + start, style: s}); - styles.push({start: offset + start, end: offset + end, style: linkStyle}); - styles.push({start: offset + end, end: offset + text.length, style: s}); - return null; - } - } - } else if (text.toLowerCase().indexOf("bug#") === 0) { - href = "https://bugs.eclipse.org/bugs/show_bug.cgi?id=" + parseInt(text.substring(4), 10); - } - if (href) { - linkStyle = this._clone(s); - linkStyle.tagName = "A"; - linkStyle.attributes = {href: href}; - return linkStyle; - } - return s; - }, - _clone: function(obj) { - if (!obj) { return obj; } - var newObj = {}; - for (var p in obj) { - if (obj.hasOwnProperty(p)) { - var value = obj[p]; - newObj[p] = value; - } - } - return newObj; - }, - _findComments: function(text, offset) { - offset = offset || 0; - var scanner = this._firstScanner, token; - scanner.setText(text); - var result = []; - while ((token = scanner.nextToken())) { - if (token === MULTILINE_COMMENT || token === DOC_COMMENT) { - var comment = { - start: scanner.getStartOffset() + offset, - end: scanner.getOffset() + offset, - type: token - }; - result.push(comment); - //TODO can we avoid this work if edition does not overlap comment? - this._computeTasks(token, scanner.getStartOffset() + offset, scanner.getOffset() + offset); - } - if (token === SINGLELINE_COMMENT) { - //TODO can we avoid this work if edition does not overlap comment? - this._computeTasks(token, scanner.getStartOffset() + offset, scanner.getOffset() + offset); - } - } - return result; - }, - _findMatchingBracket: function(model, offset) { - var brackets = "{}()[]<>"; - var bracket = model.getText(offset, offset + 1); - var bracketIndex = brackets.indexOf(bracket, 0); - if (bracketIndex === -1) { return -1; } - var closingBracket; - if (bracketIndex & 1) { - closingBracket = brackets.substring(bracketIndex - 1, bracketIndex); - } else { - closingBracket = brackets.substring(bracketIndex + 1, bracketIndex + 2); - } - var lineIndex = model.getLineAtOffset(offset); - var lineText = model.getLine(lineIndex); - var lineStart = model.getLineStart(lineIndex); - var lineEnd = model.getLineEnd(lineIndex); - brackets = this._findBrackets(bracket, closingBracket, lineText, lineStart, lineStart, lineEnd); - for (var i=0; i= 0 ? 1 : -1; - if (brackets[i] * sign === offset) { - var level = 1; - if (bracketIndex & 1) { - i--; - for (; i>=0; i--) { - sign = brackets[i] >= 0 ? 1 : -1; - level += sign; - if (level === 0) { - return brackets[i] * sign; - } - } - lineIndex -= 1; - while (lineIndex >= 0) { - lineText = model.getLine(lineIndex); - lineStart = model.getLineStart(lineIndex); - lineEnd = model.getLineEnd(lineIndex); - brackets = this._findBrackets(bracket, closingBracket, lineText, lineStart, lineStart, lineEnd); - for (var j=brackets.length - 1; j>=0; j--) { - sign = brackets[j] >= 0 ? 1 : -1; - level += sign; - if (level === 0) { - return brackets[j] * sign; - } - } - lineIndex--; - } - } else { - i++; - for (; i= 0 ? 1 : -1; - level += sign; - if (level === 0) { - return brackets[i] * sign; - } - } - lineIndex += 1; - var lineCount = model.getLineCount (); - while (lineIndex < lineCount) { - lineText = model.getLine(lineIndex); - lineStart = model.getLineStart(lineIndex); - lineEnd = model.getLineEnd(lineIndex); - brackets = this._findBrackets(bracket, closingBracket, lineText, lineStart, lineStart, lineEnd); - for (var k=0; k= 0 ? 1 : -1; - level += sign; - if (level === 0) { - return brackets[k] * sign; - } - } - lineIndex++; - } - } - break; - } - } - return -1; - }, - _findBrackets: function(bracket, closingBracket, text, textOffset, start, end) { - var result = []; - var bracketToken = bracket.charCodeAt(0); - var closingBracketToken = closingBracket.charCodeAt(0); - // for any sub range that is not a comment, parse code generating tokens (keywords, numbers, brackets, line comments, etc) - var offset = start, scanner = this._scanner, token, comments = this.comments; - var startIndex = this._binarySearch(comments, start, true); - for (var i = startIndex; i < comments.length; i++) { - if (comments[i].start >= end) { break; } - var commentStart = comments[i].start; - var commentEnd = comments[i].end; - if (offset < commentStart) { - scanner.setText(text.substring(offset - start, commentStart - start)); - while ((token = scanner.nextToken())) { - if (token === bracketToken) { - result.push(scanner.getStartOffset() + offset - start + textOffset); - } else if (token === closingBracketToken) { - result.push(-(scanner.getStartOffset() + offset - start + textOffset)); - } - } - } - offset = commentEnd; - } - if (offset < end) { - scanner.setText(text.substring(offset - start, end - start)); - while ((token = scanner.nextToken())) { - if (token === bracketToken) { - result.push(scanner.getStartOffset() + offset - start + textOffset); - } else if (token === closingBracketToken) { - result.push(-(scanner.getStartOffset() + offset - start + textOffset)); - } - } - } - return result; - }, - _onDestroy: function(e) { - this.destroy(); - }, - _onLineStyle: function (e) { - if (e.textView === this.view) { - e.style = this._getLineStyle(e.lineIndex); - } - e.ranges = this._getStyles(e.textView.getModel(), e.lineText, e.lineStart); - }, - _onSelection: function(e) { - var oldSelection = e.oldValue; - var newSelection = e.newValue; - var view = this.view; - var model = view.getModel(); - var lineIndex; - if (this.highlightCaretLine) { - var oldLineIndex = model.getLineAtOffset(oldSelection.start); - lineIndex = model.getLineAtOffset(newSelection.start); - var newEmpty = newSelection.start === newSelection.end; - var oldEmpty = oldSelection.start === oldSelection.end; - if (!(oldLineIndex === lineIndex && oldEmpty && newEmpty)) { - if (oldEmpty) { - view.redrawLines(oldLineIndex, oldLineIndex + 1); - } - if ((oldLineIndex !== lineIndex || !oldEmpty) && newEmpty) { - view.redrawLines(lineIndex, lineIndex + 1); - } - } - } - if (!this.annotationModel) { return; } - var remove = this._bracketAnnotations, add, caret; - if (newSelection.start === newSelection.end && (caret = view.getCaretOffset()) > 0) { - var mapCaret = caret - 1; - if (model.getBaseModel) { - mapCaret = model.mapOffset(mapCaret); - model = model.getBaseModel(); - } - var bracket = this._findMatchingBracket(model, mapCaret); - if (bracket !== -1) { - add = [{ - start: bracket, - end: bracket + 1, - type: "orion.annotation.matchingBracket", - title: "Matching Bracket", - html: "
", - overviewStyle: {styleClass: "annotationOverview matchingBracket"}, - rangeStyle: {styleClass: "annotationRange matchingBracket"} - }, - { - start: mapCaret, - end: mapCaret + 1, - type: "orion.annotation.currentBracket", - title: "Current Bracket", - html: "
", - overviewStyle: {styleClass: "annotationOverview currentBracket"}, - rangeStyle: {styleClass: "annotationRange currentBracket"} - }]; - } - } - this._bracketAnnotations = add; - this.annotationModel.replaceAnnotations(remove, add); - }, - _onModelChanged: function(e) { - var start = e.start; - var removedCharCount = e.removedCharCount; - var addedCharCount = e.addedCharCount; - var changeCount = addedCharCount - removedCharCount; - var view = this.view; - var viewModel = view.getModel(); - var baseModel = viewModel.getBaseModel ? viewModel.getBaseModel() : viewModel; - var end = start + removedCharCount; - var charCount = baseModel.getCharCount(); - var commentCount = this.comments.length; - var lineStart = baseModel.getLineStart(baseModel.getLineAtOffset(start)); - var commentStart = this._binarySearch(this.comments, lineStart, true); - var commentEnd = this._binarySearch(this.comments, end, false, commentStart - 1, commentCount); - - var ts; - if (commentStart < commentCount && this.comments[commentStart].start <= lineStart && lineStart < this.comments[commentStart].end) { - ts = this.comments[commentStart].start; - if (ts > start) { ts += changeCount; } - } else { - if (commentStart === commentCount && commentCount > 0 && charCount - changeCount === this.comments[commentCount - 1].end) { - ts = this.comments[commentCount - 1].start; - } else { - ts = lineStart; - } - } - var te; - if (commentEnd < commentCount) { - te = this.comments[commentEnd].end; - if (te > start) { te += changeCount; } - commentEnd += 1; - } else { - commentEnd = commentCount; - te = charCount;//TODO could it be smaller? - } - var text = baseModel.getText(ts, te), comment; - var newComments = this._findComments(text, ts), i; - for (i = commentStart; i < this.comments.length; i++) { - comment = this.comments[i]; - if (comment.start > start) { comment.start += changeCount; } - if (comment.start > start) { comment.end += changeCount; } - } - var redraw = (commentEnd - commentStart) !== newComments.length; - if (!redraw) { - for (i=0; i start) { - annotationStart -= changeCount; - } - if (annotationEnd > start) { - annotationEnd -= changeCount; - } - if (annotationStart <= start && start < annotationEnd && annotationStart <= end && end < annotationEnd) { - var startLine = baseModel.getLineAtOffset(annotation.start); - var endLine = baseModel.getLineAtOffset(annotation.end); - if (startLine !== endLine) { - if (!annotation.expanded) { - annotation.expand(); - annotationModel.modifyAnnotation(annotation); - } - } else { - annotationModel.removeAnnotation(annotation); - } - } - } - } - } - var add = []; - for (i = 0; i < newComments.length; i++) { - comment = newComments[i]; - for (var j = 0; j < all.length; j++) { - if (all[j].start === comment.start && all[j].end === comment.end) { - break; - } - } - if (j === all.length) { - annotation = this._createFoldingAnnotation(viewModel, baseModel, comment.start, comment.end); - if (annotation) { - add.push(annotation); - } - } - } - annotationModel.replaceAnnotations(remove, add); - } - } - }; - - return {TextStyler: TextStyler}; -}); diff --git a/browser/devtools/sourceeditor/source-editor-orion.jsm b/browser/devtools/sourceeditor/source-editor-orion.jsm deleted file mode 100644 index aa326478332a..000000000000 --- a/browser/devtools/sourceeditor/source-editor-orion.jsm +++ /dev/null @@ -1,2131 +0,0 @@ -/* vim:set ts=2 sw=2 sts=2 et tw=80: - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const Cu = Components.utils; -const Ci = Components.interfaces; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource:///modules/devtools/sourceeditor/source-editor-ui.jsm"); - -XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper", - "@mozilla.org/widget/clipboardhelper;1", - "nsIClipboardHelper"); - -const ORION_SCRIPT = "chrome://browser/content/devtools/orion.js"; -const ORION_IFRAME = "data:text/html;charset=utf8," + - "" + - "" + - "" + - "
" + - ""; - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - -/** - * Maximum allowed vertical offset for the line index when you call - * SourceEditor.setCaretPosition(). - * - * @type number - */ -const VERTICAL_OFFSET = 3; - -/** - * The primary selection update delay. On Linux, the X11 primary selection is - * updated to hold the currently selected text. - * - * @type number - */ -const PRIMARY_SELECTION_DELAY = 100; - -/** - * Predefined themes for syntax highlighting. This objects maps - * SourceEditor.THEMES to Orion CSS files. - */ -const ORION_THEMES = { - mozilla: ["chrome://browser/skin/devtools/orion.css"], -}; - -/** - * Known Orion editor events you can listen for. This object maps several of the - * SourceEditor.EVENTS to Orion events. - */ -const ORION_EVENTS = { - ContextMenu: "ContextMenu", - TextChanged: "ModelChanged", - Selection: "Selection", - Focus: "Focus", - Blur: "Blur", - MouseOver: "MouseOver", - MouseOut: "MouseOut", - MouseMove: "MouseMove", -}; - -/** - * Known Orion annotation types. - */ -const ORION_ANNOTATION_TYPES = { - currentBracket: "orion.annotation.currentBracket", - matchingBracket: "orion.annotation.matchingBracket", - breakpoint: "orion.annotation.breakpoint", - task: "orion.annotation.task", - currentLine: "orion.annotation.currentLine", - debugLocation: "mozilla.annotation.debugLocation", -}; - -/** - * Default key bindings in the Orion editor. - */ -const DEFAULT_KEYBINDINGS = [ - { - action: "enter", - code: Ci.nsIDOMKeyEvent.DOM_VK_ENTER, - }, - { - action: "undo", - code: Ci.nsIDOMKeyEvent.DOM_VK_Z, - accel: true, - }, - { - action: "redo", - code: Ci.nsIDOMKeyEvent.DOM_VK_Z, - accel: true, - shift: true, - }, - { - action: "Unindent Lines", - code: Ci.nsIDOMKeyEvent.DOM_VK_TAB, - shift: true, - }, - { - action: "Move Lines Up", - code: Ci.nsIDOMKeyEvent.DOM_VK_UP, - ctrl: Services.appinfo.OS == "Darwin", - alt: true, - }, - { - action: "Move Lines Down", - code: Ci.nsIDOMKeyEvent.DOM_VK_DOWN, - ctrl: Services.appinfo.OS == "Darwin", - alt: true, - }, - { - action: "Comment/Uncomment", - code: Ci.nsIDOMKeyEvent.DOM_VK_SLASH, - accel: true, - }, - { - action: "Move to Bracket Opening", - code: Ci.nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET, - accel: true, - alt: true, - }, - { - action: "Move to Bracket Closing", - code: Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET, - accel: true, - alt: true, - }, -]; - -if (Services.appinfo.OS == "WINNT" || - Services.appinfo.OS == "Linux") { - DEFAULT_KEYBINDINGS.push({ - action: "redo", - code: Ci.nsIDOMKeyEvent.DOM_VK_Y, - accel: true, - }); -} - -this.EXPORTED_SYMBOLS = ["SourceEditor"]; - -/** - * The SourceEditor object constructor. The SourceEditor component allows you to - * provide users with an editor tailored to the specific needs of editing source - * code, aimed primarily at web developers. - * - * The editor used here is Eclipse Orion (see http://www.eclipse.org/orion). - * - * @constructor - */ -this.SourceEditor = function SourceEditor() { - // Update the SourceEditor defaults from user preferences. - - SourceEditor.DEFAULTS.tabSize = - Services.prefs.getIntPref(SourceEditor.PREFS.TAB_SIZE); - SourceEditor.DEFAULTS.expandTab = - Services.prefs.getBoolPref(SourceEditor.PREFS.EXPAND_TAB); - - this._onOrionSelection = this._onOrionSelection.bind(this); - this._onTextChanged = this._onTextChanged.bind(this); - this._onOrionContextMenu = this._onOrionContextMenu.bind(this); - - this._eventTarget = {}; - this._eventListenersQueue = []; - this.ui = new SourceEditorUI(this); -} - -SourceEditor.prototype = { - _view: null, - _iframe: null, - _model: null, - _undoStack: null, - _linesRuler: null, - _annotationRuler: null, - _overviewRuler: null, - _styler: null, - _annotationStyler: null, - _annotationModel: null, - _dragAndDrop: null, - _currentLineAnnotation: null, - _primarySelectionTimeout: null, - _mode: null, - _expandTab: null, - _tabSize: null, - _iframeWindow: null, - _eventTarget: null, - _eventListenersQueue: null, - _contextMenu: null, - _dirty: false, - - /** - * The Source Editor user interface manager. - * @type object - * An instance of the SourceEditorUI. - */ - ui: null, - - /** - * The editor container element. - * @type nsIDOMElement - */ - parentElement: null, - - /** - * Initialize the editor. - * - * @param nsIDOMElement aElement - * The DOM element where you want the editor to show. - * @param object aConfig - * Editor configuration object. See SourceEditor.DEFAULTS for the - * available configuration options. - * @param function [aCallback] - * Function you want to execute once the editor is loaded and - * initialized. - * @see SourceEditor.DEFAULTS - */ - init: function SE_init(aElement, aConfig, aCallback) - { - if (this._iframe) { - throw new Error("SourceEditor is already initialized!"); - } - - let doc = aElement.ownerDocument; - - this._iframe = doc.createElementNS(XUL_NS, "iframe"); - this._iframe.flex = 1; - - let onIframeLoad = (function() { - this._iframe.removeEventListener("load", onIframeLoad, true); - this._onIframeLoad(); - }).bind(this); - - this._iframe.addEventListener("load", onIframeLoad, true); - - this._iframe.setAttribute("src", ORION_IFRAME); - - aElement.appendChild(this._iframe); - this.parentElement = aElement; - - this._config = {}; - for (let key in SourceEditor.DEFAULTS) { - this._config[key] = key in aConfig ? - aConfig[key] : - SourceEditor.DEFAULTS[key]; - } - - // TODO: Bug 725677 - Remove the deprecated placeholderText option from the - // Source Editor initialization. - if (aConfig.placeholderText) { - this._config.initialText = aConfig.placeholderText; - Services.console.logStringMessage("SourceEditor.init() was called with the placeholderText option which is deprecated, please use initialText."); - } - - this._onReadyCallback = aCallback; - this.ui.init(); - }, - - /** - * The editor iframe load event handler. - * @private - */ - _onIframeLoad: function SE__onIframeLoad() - { - this._iframeWindow = this._iframe.contentWindow.wrappedJSObject; - let window = this._iframeWindow; - let config = this._config; - - Services.scriptloader.loadSubScript(ORION_SCRIPT, window, "utf8"); - - let TextModel = window.require("orion/textview/textModel").TextModel; - let TextView = window.require("orion/textview/textView").TextView; - - this._expandTab = config.expandTab; - this._tabSize = config.tabSize; - - let theme = config.theme; - let stylesheet = theme in ORION_THEMES ? ORION_THEMES[theme] : theme; - - this._model = new TextModel(config.initialText); - this._view = new TextView({ - model: this._model, - parent: "editor", - stylesheet: stylesheet, - tabSize: this._tabSize, - expandTab: this._expandTab, - readonly: config.readOnly, - themeClass: "mozilla" + (config.readOnly ? " readonly" : ""), - }); - - let onOrionLoad = function() { - this._view.removeEventListener("Load", onOrionLoad); - this._onOrionLoad(); - }.bind(this); - - this._view.addEventListener("Load", onOrionLoad); - if (config.highlightCurrentLine || Services.appinfo.OS == "Linux") { - this.addEventListener(SourceEditor.EVENTS.SELECTION, - this._onOrionSelection); - } - this.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, - this._onTextChanged); - - if (typeof config.contextMenu == "string") { - let chromeDocument = this.parentElement.ownerDocument; - this._contextMenu = chromeDocument.getElementById(config.contextMenu); - } else if (typeof config.contextMenu == "object" ) { - this._contextMenu = config._contextMenu; - } - if (this._contextMenu) { - this.addEventListener(SourceEditor.EVENTS.CONTEXT_MENU, - this._onOrionContextMenu); - } - - let KeyBinding = window.require("orion/textview/keyBinding").KeyBinding; - let TextDND = window.require("orion/textview/textDND").TextDND; - let Rulers = window.require("orion/textview/rulers"); - let LineNumberRuler = Rulers.LineNumberRuler; - let AnnotationRuler = Rulers.AnnotationRuler; - let OverviewRuler = Rulers.OverviewRuler; - let UndoStack = window.require("orion/textview/undoStack").UndoStack; - let AnnotationModel = window.require("orion/textview/annotations").AnnotationModel; - - this._annotationModel = new AnnotationModel(this._model); - - if (config.showAnnotationRuler) { - this._annotationRuler = new AnnotationRuler(this._annotationModel, "left", - {styleClass: "ruler annotations"}); - this._annotationRuler.onClick = this._annotationRulerClick.bind(this); - this._annotationRuler.addAnnotationType(ORION_ANNOTATION_TYPES.breakpoint); - this._annotationRuler.addAnnotationType(ORION_ANNOTATION_TYPES.debugLocation); - this._view.addRuler(this._annotationRuler); - } - - if (config.showLineNumbers) { - let rulerClass = this._annotationRuler ? - "ruler lines linesWithAnnotations" : - "ruler lines"; - - this._linesRuler = new LineNumberRuler(this._annotationModel, "left", - {styleClass: rulerClass}, {styleClass: "rulerLines odd"}, - {styleClass: "rulerLines even"}); - - this._linesRuler.onClick = this._linesRulerClick.bind(this); - this._linesRuler.onDblClick = this._linesRulerDblClick.bind(this); - this._view.addRuler(this._linesRuler); - } - - if (config.showOverviewRuler) { - this._overviewRuler = new OverviewRuler(this._annotationModel, "right", - {styleClass: "ruler overview"}); - this._overviewRuler.onClick = this._overviewRulerClick.bind(this); - - this._overviewRuler.addAnnotationType(ORION_ANNOTATION_TYPES.matchingBracket); - this._overviewRuler.addAnnotationType(ORION_ANNOTATION_TYPES.currentBracket); - this._overviewRuler.addAnnotationType(ORION_ANNOTATION_TYPES.breakpoint); - this._overviewRuler.addAnnotationType(ORION_ANNOTATION_TYPES.debugLocation); - this._overviewRuler.addAnnotationType(ORION_ANNOTATION_TYPES.task); - this._view.addRuler(this._overviewRuler); - } - - this.setMode(config.mode); - - this._undoStack = new UndoStack(this._view, config.undoLimit); - - this._dragAndDrop = new TextDND(this._view, this._undoStack); - - let actions = { - "undo": [this.undo, this], - "redo": [this.redo, this], - "tab": [this._doTab, this], - "Unindent Lines": [this._doUnindentLines, this], - "enter": [this._doEnter, this], - "Find...": [this.ui.find, this.ui], - "Find Next Occurrence": [this.ui.findNext, this.ui], - "Find Previous Occurrence": [this.ui.findPrevious, this.ui], - "Goto Line...": [this.ui.gotoLine, this.ui], - "Move Lines Down": [this._moveLines, this], - "Comment/Uncomment": [this._doCommentUncomment, this], - "Move to Bracket Opening": [this._moveToBracketOpening, this], - "Move to Bracket Closing": [this._moveToBracketClosing, this], - }; - - for (let name in actions) { - let action = actions[name]; - this._view.setAction(name, action[0].bind(action[1])); - } - - this._view.setAction("Move Lines Up", this._moveLines.bind(this, true)); - - let keys = (config.keys || []).concat(DEFAULT_KEYBINDINGS); - keys.forEach(function(aKey) { - // In Orion mod1 refers to Cmd on Macs and Ctrl on Windows and Linux. - // So, if ctrl is in aKey we use it on Windows and Linux, otherwise - // we use aKey.accel for mod1. - let mod1 = Services.appinfo.OS != "Darwin" && - "ctrl" in aKey ? aKey.ctrl : aKey.accel; - let binding = new KeyBinding(aKey.code, mod1, aKey.shift, aKey.alt, - aKey.ctrl); - this._view.setKeyBinding(binding, aKey.action); - - if (aKey.callback) { - this._view.setAction(aKey.action, aKey.callback); - } - }, this); - - this._initEventTarget(); - }, - - /** - * Initialize the private Orion EventTarget object. This is used for tracking - * our own event listeners for events outside of Orion's scope. - * @private - */ - _initEventTarget: function SE__initEventTarget() - { - let EventTarget = - this._iframeWindow.require("orion/textview/eventTarget").EventTarget; - EventTarget.addMixin(this._eventTarget); - - this._eventListenersQueue.forEach(function(aRequest) { - if (aRequest[0] == "add") { - this.addEventListener(aRequest[1], aRequest[2]); - } else { - this.removeEventListener(aRequest[1], aRequest[2]); - } - }, this); - - this._eventListenersQueue = []; - }, - - /** - * Dispatch an event to the SourceEditor event listeners. This covers only the - * SourceEditor-specific events. - * - * @private - * @param object aEvent - * The event object to dispatch to all listeners. - */ - _dispatchEvent: function SE__dispatchEvent(aEvent) - { - this._eventTarget.dispatchEvent(aEvent); - }, - - /** - * The Orion "Load" event handler. This is called when the Orion editor - * completes the initialization. - * @private - */ - _onOrionLoad: function SE__onOrionLoad() - { - this.ui.onReady(); - if (this._onReadyCallback) { - this._onReadyCallback(this); - this._onReadyCallback = null; - } - }, - - /** - * The "tab" editor action implementation. This adds support for expanded tabs - * to spaces, and support for the indentation of multiple lines at once. - * @private - */ - _doTab: function SE__doTab() - { - if (this.readOnly) { - return false; - } - - let indent = "\t"; - let selection = this.getSelection(); - let model = this._model; - let firstLine = model.getLineAtOffset(selection.start); - let firstLineStart = this.getLineStart(firstLine); - let lastLineOffset = selection.end > selection.start ? - selection.end - 1 : selection.end; - let lastLine = model.getLineAtOffset(lastLineOffset); - - if (this._expandTab) { - let offsetFromLineStart = firstLine == lastLine ? - selection.start - firstLineStart : 0; - let spaces = this._tabSize - (offsetFromLineStart % this._tabSize); - indent = (new Array(spaces + 1)).join(" "); - } - - // Do selection indentation. - if (firstLine != lastLine) { - let lines = [""]; - let lastLineEnd = this.getLineEnd(lastLine, true); - let selectedLines = lastLine - firstLine + 1; - - for (let i = firstLine; i <= lastLine; i++) { - lines.push(model.getLine(i, true)); - } - - this.startCompoundChange(); - - this.setText(lines.join(indent), firstLineStart, lastLineEnd); - - let newSelectionStart = firstLineStart == selection.start ? - selection.start : selection.start + indent.length; - let newSelectionEnd = selection.end + (selectedLines * indent.length); - - this._view.setSelection(newSelectionStart, newSelectionEnd); - - this.endCompoundChange(); - return true; - } - - return false; - }, - - /** - * The "Unindent lines" editor action implementation. This method is invoked - * when the user presses Shift-Tab. - * @private - */ - _doUnindentLines: function SE__doUnindentLines() - { - if (this.readOnly) { - return true; - } - - let indent = "\t"; - - let selection = this.getSelection(); - let model = this._model; - let firstLine = model.getLineAtOffset(selection.start); - let lastLineOffset = selection.end > selection.start ? - selection.end - 1 : selection.end; - let lastLine = model.getLineAtOffset(lastLineOffset); - - if (this._expandTab) { - indent = (new Array(this._tabSize + 1)).join(" "); - } - - let lines = []; - for (let line, i = firstLine; i <= lastLine; i++) { - line = model.getLine(i, true); - if (line.indexOf(indent) != 0) { - return true; - } - lines.push(line.substring(indent.length)); - } - - let firstLineStart = this.getLineStart(firstLine); - let lastLineStart = this.getLineStart(lastLine); - let lastLineEnd = this.getLineEnd(lastLine, true); - - this.startCompoundChange(); - - this.setText(lines.join(""), firstLineStart, lastLineEnd); - - let selectedLines = lastLine - firstLine + 1; - let newSelectionStart = firstLineStart == selection.start ? - selection.start : - Math.max(firstLineStart, - selection.start - indent.length); - let newSelectionEnd = selection.end - (selectedLines * indent.length) + - (selection.end == lastLineStart + 1 ? 1 : 0); - if (firstLine == lastLine) { - newSelectionEnd = Math.max(lastLineStart, newSelectionEnd); - } - this._view.setSelection(newSelectionStart, newSelectionEnd); - - this.endCompoundChange(); - - return true; - }, - - /** - * The editor Enter action implementation, which adds simple automatic - * indentation based on the previous line when the user presses the Enter key. - * @private - */ - _doEnter: function SE__doEnter() - { - if (this.readOnly) { - return false; - } - - let selection = this.getSelection(); - if (selection.start != selection.end) { - return false; - } - - let model = this._model; - let lineIndex = model.getLineAtOffset(selection.start); - let lineText = model.getLine(lineIndex, true); - let lineStart = this.getLineStart(lineIndex); - let index = 0; - let lineOffset = selection.start - lineStart; - while (index < lineOffset && /[ \t]/.test(lineText.charAt(index))) { - index++; - } - - if (!index) { - return false; - } - - let prefix = lineText.substring(0, index); - index = lineOffset; - while (index < lineText.length && - /[ \t]/.test(lineText.charAt(index++))) { - selection.end++; - } - - this.setText(this.getLineDelimiter() + prefix, selection.start, - selection.end); - return true; - }, - - /** - * Move lines upwards or downwards, relative to the current caret location. - * - * @private - * @param boolean aLineAbove - * True if moving lines up, false to move lines down. - */ - _moveLines: function SE__moveLines(aLineAbove) - { - if (this.readOnly) { - return false; - } - - let model = this._model; - let selection = this.getSelection(); - let firstLine = model.getLineAtOffset(selection.start); - if (firstLine == 0 && aLineAbove) { - return true; - } - - let lastLine = model.getLineAtOffset(selection.end); - let firstLineStart = this.getLineStart(firstLine); - let lastLineStart = this.getLineStart(lastLine); - if (selection.start != selection.end && lastLineStart == selection.end) { - lastLine--; - } - if (!aLineAbove && (lastLine + 1) == this.getLineCount()) { - return true; - } - - let lastLineEnd = this.getLineEnd(lastLine, true); - let text = this.getText(firstLineStart, lastLineEnd); - - if (aLineAbove) { - let aboveLine = firstLine - 1; - let aboveLineStart = this.getLineStart(aboveLine); - - this.startCompoundChange(); - if (lastLine == (this.getLineCount() - 1)) { - let delimiterStart = this.getLineEnd(aboveLine); - let delimiterEnd = this.getLineEnd(aboveLine, true); - let lineDelimiter = this.getText(delimiterStart, delimiterEnd); - text += lineDelimiter; - this.setText("", firstLineStart - lineDelimiter.length, lastLineEnd); - } else { - this.setText("", firstLineStart, lastLineEnd); - } - this.setText(text, aboveLineStart, aboveLineStart); - this.endCompoundChange(); - this.setSelection(aboveLineStart, aboveLineStart + text.length); - } else { - let belowLine = lastLine + 1; - let belowLineEnd = this.getLineEnd(belowLine, true); - - let insertAt = belowLineEnd - lastLineEnd + firstLineStart; - let lineDelimiter = ""; - if (belowLine == this.getLineCount() - 1) { - let delimiterStart = this.getLineEnd(lastLine); - lineDelimiter = this.getText(delimiterStart, lastLineEnd); - text = lineDelimiter + text.substr(0, text.length - - lineDelimiter.length); - } - this.startCompoundChange(); - this.setText("", firstLineStart, lastLineEnd); - this.setText(text, insertAt, insertAt); - this.endCompoundChange(); - this.setSelection(insertAt + lineDelimiter.length, - insertAt + text.length); - } - return true; - }, - - /** - * The Orion Selection event handler. The current caret line is - * highlighted and for Linux users the selected text is copied into the X11 - * PRIMARY buffer. - * - * @private - * @param object aEvent - * The Orion Selection event object. - */ - _onOrionSelection: function SE__onOrionSelection(aEvent) - { - if (this._config.highlightCurrentLine) { - this._highlightCurrentLine(aEvent); - } - - if (Services.appinfo.OS == "Linux") { - let window = this.parentElement.ownerDocument.defaultView; - - if (this._primarySelectionTimeout) { - window.clearTimeout(this._primarySelectionTimeout); - } - this._primarySelectionTimeout = - window.setTimeout(this._updatePrimarySelection.bind(this), - PRIMARY_SELECTION_DELAY); - } - }, - - /** - * The TextChanged event handler which tracks the dirty state of the editor. - * - * @see SourceEditor.EVENTS.TEXT_CHANGED - * @see SourceEditor.EVENTS.DIRTY_CHANGED - * @see SourceEditor.dirty - * @private - */ - _onTextChanged: function SE__onTextChanged() - { - this._updateDirty(); - }, - - /** - * The Orion contextmenu event handler. This method opens the default or - * the custom context menu popup at the pointer location. - * - * @param object aEvent - * The contextmenu event object coming from Orion. This object should - * hold the screenX and screenY properties. - */ - _onOrionContextMenu: function SE__onOrionContextMenu(aEvent) - { - if (this._contextMenu.state == "closed") { - this._contextMenu.openPopupAtScreen(aEvent.screenX || 0, - aEvent.screenY || 0, true); - } - }, - - /** - * Update the dirty state of the editor based on the undo stack. - * @private - */ - _updateDirty: function SE__updateDirty() - { - this.dirty = !this._undoStack.isClean(); - }, - - /** - * Update the X11 PRIMARY buffer to hold the current selection. - * @private - */ - _updatePrimarySelection: function SE__updatePrimarySelection() - { - this._primarySelectionTimeout = null; - - let text = this.getSelectedText(); - if (!text) { - return; - } - - clipboardHelper.copyStringToClipboard(text, - Ci.nsIClipboard.kSelectionClipboard, - this.parentElement.ownerDocument); - }, - - /** - * Highlight the current line using the Orion annotation model. - * - * @private - * @param object aEvent - * The Selection event object. - */ - _highlightCurrentLine: function SE__highlightCurrentLine(aEvent) - { - let annotationModel = this._annotationModel; - let model = this._model; - let oldAnnotation = this._currentLineAnnotation; - let newSelection = aEvent.newValue; - - let collapsed = newSelection.start == newSelection.end; - if (!collapsed) { - if (oldAnnotation) { - annotationModel.removeAnnotation(oldAnnotation); - this._currentLineAnnotation = null; - } - return; - } - - let line = model.getLineAtOffset(newSelection.start); - let lineStart = this.getLineStart(line); - let lineEnd = this.getLineEnd(line); - - let title = oldAnnotation ? oldAnnotation.title : - SourceEditorUI.strings.GetStringFromName("annotation.currentLine"); - - this._currentLineAnnotation = { - start: lineStart, - end: lineEnd, - type: ORION_ANNOTATION_TYPES.currentLine, - title: title, - html: "
", - overviewStyle: {styleClass: "annotationOverview currentLine"}, - lineStyle: {styleClass: "annotationLine currentLine"}, - }; - - annotationModel.replaceAnnotations(oldAnnotation ? [oldAnnotation] : null, - [this._currentLineAnnotation]); - }, - - /** - * The click event handler for the lines gutter. This function allows the user - * to jump to a line or to perform line selection while holding the Shift key - * down. - * - * @private - * @param number aLineIndex - * The line index where the click event occurred. - * @param object aEvent - * The DOM click event object. - */ - _linesRulerClick: function SE__linesRulerClick(aLineIndex, aEvent) - { - if (aLineIndex === undefined || aLineIndex == -1) { - return; - } - - if (aEvent.shiftKey) { - let model = this._model; - let selection = this.getSelection(); - let selectionLineStart = model.getLineAtOffset(selection.start); - let selectionLineEnd = model.getLineAtOffset(selection.end); - let newStart = aLineIndex <= selectionLineStart ? - this.getLineStart(aLineIndex) : selection.start; - let newEnd = aLineIndex <= selectionLineStart ? - selection.end : this.getLineEnd(aLineIndex); - this.setSelection(newStart, newEnd); - } else { - if (this._annotationRuler) { - this._annotationRulerClick(aLineIndex, aEvent); - } else { - this.setCaretPosition(aLineIndex); - } - } - }, - - /** - * The dblclick event handler for the lines gutter. This function selects the - * whole line where the event occurred. - * - * @private - * @param number aLineIndex - * The line index where the double click event occurred. - * @param object aEvent - * The DOM dblclick event object. - */ - _linesRulerDblClick: function SE__linesRulerDblClick(aLineIndex) - { - if (aLineIndex === undefined) { - return; - } - - let newStart = this.getLineStart(aLineIndex); - let newEnd = this.getLineEnd(aLineIndex); - this.setSelection(newStart, newEnd); - }, - - /** - * Highlight the Orion annotations. This updates the annotation styler as - * needed. - * @private - */ - _highlightAnnotations: function SE__highlightAnnotations() - { - if (this._annotationStyler) { - this._annotationStyler.destroy(); - this._annotationStyler = null; - } - - let AnnotationStyler = - this._iframeWindow.require("orion/textview/annotations").AnnotationStyler; - - let styler = new AnnotationStyler(this._view, this._annotationModel); - this._annotationStyler = styler; - - styler.addAnnotationType(ORION_ANNOTATION_TYPES.matchingBracket); - styler.addAnnotationType(ORION_ANNOTATION_TYPES.currentBracket); - styler.addAnnotationType(ORION_ANNOTATION_TYPES.task); - styler.addAnnotationType(ORION_ANNOTATION_TYPES.debugLocation); - - if (this._config.highlightCurrentLine) { - styler.addAnnotationType(ORION_ANNOTATION_TYPES.currentLine); - } - }, - - /** - * Retrieve the list of Orion Annotations filtered by type for the given text range. - * - * @private - * @param string aType - * The annotation type to filter annotations for. Use one of the keys - * in ORION_ANNOTATION_TYPES. - * @param number aStart - * Offset from where to start finding the annotations. - * @param number aEnd - * End offset for retrieving the annotations. - * @return array - * The array of annotations, filtered by type, within the given text - * range. - */ - _getAnnotationsByType: function SE__getAnnotationsByType(aType, aStart, aEnd) - { - let annotations = this._annotationModel.getAnnotations(aStart, aEnd); - let annotation, result = []; - while (annotation = annotations.next()) { - if (annotation.type == ORION_ANNOTATION_TYPES[aType]) { - result.push(annotation); - } - } - - return result; - }, - - /** - * The click event handler for the annotation ruler. - * - * @private - * @param number aLineIndex - * The line index where the click event occurred. - * @param object aEvent - * The DOM click event object. - */ - _annotationRulerClick: function SE__annotationRulerClick(aLineIndex, aEvent) - { - if (aLineIndex === undefined || aLineIndex == -1) { - return; - } - - let lineStart = this.getLineStart(aLineIndex); - let lineEnd = this.getLineEnd(aLineIndex); - let annotations = this._getAnnotationsByType("breakpoint", lineStart, lineEnd); - if (annotations.length > 0) { - this.removeBreakpoint(aLineIndex); - } else { - this.addBreakpoint(aLineIndex); - } - }, - - /** - * The click event handler for the overview ruler. When the user clicks on an - * annotation the editor jumps to the associated line. - * - * @private - * @param number aLineIndex - * The line index where the click event occurred. - * @param object aEvent - * The DOM click event object. - */ - _overviewRulerClick: function SE__overviewRulerClick(aLineIndex, aEvent) - { - if (aLineIndex === undefined || aLineIndex == -1) { - return; - } - - let model = this._model; - let lineStart = this.getLineStart(aLineIndex); - let lineEnd = this.getLineEnd(aLineIndex); - let annotations = this._annotationModel.getAnnotations(lineStart, lineEnd); - let annotation = annotations.next(); - - // Jump to the line where annotation is. If the annotation is specific to - // a substring part of the line, then select the substring. - if (!annotation || lineStart == annotation.start && lineEnd == annotation.end) { - this.setSelection(lineStart, lineStart); - } else { - this.setSelection(annotation.start, annotation.end); - } - }, - - /** - * Get the editor element. - * - * @return nsIDOMElement - * In this implementation a xul:iframe holds the editor. - */ - get editorElement() { - return this._iframe; - }, - - /** - * Helper function to retrieve the strings used for comments in the current - * editor mode. - * - * @private - * @return object - * An object that holds the following properties: - * - line: the comment string used for the start of a single line - * comment. - * - blockStart: the comment string used for the start of a comment - * block. - * - blockEnd: the comment string used for the end of a block comment. - * Null is returned for unsupported editor modes. - */ - _getCommentStrings: function SE__getCommentStrings() - { - let line = ""; - let blockCommentStart = ""; - let blockCommentEnd = ""; - - switch (this.getMode()) { - case SourceEditor.MODES.JAVASCRIPT: - line = "//"; - blockCommentStart = "/*"; - blockCommentEnd = "*/"; - break; - case SourceEditor.MODES.CSS: - blockCommentStart = "/*"; - blockCommentEnd = "*/"; - break; - case SourceEditor.MODES.HTML: - case SourceEditor.MODES.XML: - blockCommentStart = ""; - break; - default: - return null; - } - return {line: line, blockStart: blockCommentStart, blockEnd: blockCommentEnd}; - }, - - /** - * Decide whether to comment the selection/current line or to uncomment it. - * - * @private - */ - _doCommentUncomment: function SE__doCommentUncomment() - { - if (this.readOnly) { - return false; - } - - let commentObject = this._getCommentStrings(); - if (!commentObject) { - return false; - } - - let selection = this.getSelection(); - let model = this._model; - let firstLine = model.getLineAtOffset(selection.start); - let lastLine = model.getLineAtOffset(selection.end); - - // Checks for block comment. - let firstLineText = model.getLine(firstLine); - let lastLineText = model.getLine(lastLine); - let openIndex = firstLineText.indexOf(commentObject.blockStart); - let closeIndex = lastLineText.lastIndexOf(commentObject.blockEnd); - if (openIndex != -1 && closeIndex != -1 && - (firstLine != lastLine || - (closeIndex - openIndex) >= commentObject.blockStart.length)) { - return this._doUncomment(); - } - - if (!commentObject.line) { - return this._doComment(); - } - - // If the selection is not a block comment, check for the first and the last - // lines to be line commented. - let firstLastCommented = [firstLineText, - lastLineText].every(function(aLineText) { - let openIndex = aLineText.indexOf(commentObject.line); - if (openIndex != -1) { - let textUntilComment = aLineText.slice(0, openIndex); - if (!textUntilComment || /^\s+$/.test(textUntilComment)) { - return true; - } - } - return false; - }); - if (firstLastCommented) { - return this._doUncomment(); - } - - // If we reach here, then we have to comment the selection/line. - return this._doComment(); - }, - - /** - * Wrap the selected text in comments. If nothing is selected the current - * caret line is commented out. Single line and block comments depend on the - * current editor mode. - * - * @private - */ - _doComment: function SE__doComment() - { - if (this.readOnly) { - return false; - } - - let commentObject = this._getCommentStrings(); - if (!commentObject) { - return false; - } - - let selection = this.getSelection(); - - if (selection.start == selection.end) { - let selectionLine = this._model.getLineAtOffset(selection.start); - let lineStartOffset = this.getLineStart(selectionLine); - if (commentObject.line) { - this.setText(commentObject.line, lineStartOffset, lineStartOffset); - } else { - let lineEndOffset = this.getLineEnd(selectionLine); - this.startCompoundChange(); - this.setText(commentObject.blockStart, lineStartOffset, lineStartOffset); - this.setText(commentObject.blockEnd, - lineEndOffset + commentObject.blockStart.length, - lineEndOffset + commentObject.blockStart.length); - this.endCompoundChange(); - } - } else { - this.startCompoundChange(); - this.setText(commentObject.blockStart, selection.start, selection.start); - this.setText(commentObject.blockEnd, - selection.end + commentObject.blockStart.length, - selection.end + commentObject.blockStart.length); - this.endCompoundChange(); - } - - return true; - }, - - /** - * Uncomment the selected text. If nothing is selected the current caret line - * is umcommented. Single line and block comments depend on the current editor - * mode. - * - * @private - */ - _doUncomment: function SE__doUncomment() - { - if (this.readOnly) { - return false; - } - - let commentObject = this._getCommentStrings(); - if (!commentObject) { - return false; - } - - let selection = this.getSelection(); - let firstLine = this._model.getLineAtOffset(selection.start); - let lastLine = this._model.getLineAtOffset(selection.end); - - // Uncomment a block of text. - let firstLineText = this._model.getLine(firstLine); - let lastLineText = this._model.getLine(lastLine); - let openIndex = firstLineText.indexOf(commentObject.blockStart); - let closeIndex = lastLineText.lastIndexOf(commentObject.blockEnd); - if (openIndex != -1 && closeIndex != -1 && - (firstLine != lastLine || - (closeIndex - openIndex) >= commentObject.blockStart.length)) { - let firstLineStartOffset = this.getLineStart(firstLine); - let lastLineStartOffset = this.getLineStart(lastLine); - let openOffset = firstLineStartOffset + openIndex; - let closeOffset = lastLineStartOffset + closeIndex; - - this.startCompoundChange(); - this.setText("", closeOffset, closeOffset + commentObject.blockEnd.length); - this.setText("", openOffset, openOffset + commentObject.blockStart.length); - this.endCompoundChange(); - - return true; - } - - if (!commentObject.line) { - return true; - } - - // If the selected text is not a block of comment, then uncomment each line. - this.startCompoundChange(); - let lineCaret = firstLine; - while (lineCaret <= lastLine) { - let currentLine = this._model.getLine(lineCaret); - let lineStart = this.getLineStart(lineCaret); - let openIndex = currentLine.indexOf(commentObject.line); - let openOffset = lineStart + openIndex; - let textUntilComment = this.getText(lineStart, openOffset); - if (openIndex != -1 && - (!textUntilComment || /^\s+$/.test(textUntilComment))) { - this.setText("", openOffset, openOffset + commentObject.line.length); - } - lineCaret++; - } - this.endCompoundChange(); - - return true; - }, - - /** - * Helper function for _moveToBracket{Opening/Closing} to find the offset of - * matching bracket. - * - * @param number aOffset - * The offset of the bracket for which you want to find the bracket. - * @private - */ - _getMatchingBracketIndex: function SE__getMatchingBracketIndex(aOffset) - { - return this._styler._findMatchingBracket(this._model, aOffset); - }, - - /** - * Move the cursor to the matching opening bracket if at corresponding closing - * bracket, otherwise move to the opening bracket for the current block of code. - * - * @private - */ - _moveToBracketOpening: function SE__moveToBracketOpening() - { - let mode = this.getMode(); - // Returning early if not in JavaScipt or CSS mode. - if (mode != SourceEditor.MODES.JAVASCRIPT && - mode != SourceEditor.MODES.CSS) { - return false; - } - - let caretOffset = this.getCaretOffset() - 1; - let matchingIndex = this._getMatchingBracketIndex(caretOffset); - - // If the caret is not at the closing bracket "}", find the index of the - // opening bracket "{" for the current code block. - if (matchingIndex == -1 || matchingIndex > caretOffset) { - matchingIndex = -1; - let text = this.getText(); - let closingOffset = text.indexOf("}", caretOffset); - while (closingOffset > -1) { - let closingMatchingIndex = this._getMatchingBracketIndex(closingOffset); - if (closingMatchingIndex < caretOffset && closingMatchingIndex != -1) { - matchingIndex = closingMatchingIndex; - break; - } - closingOffset = text.indexOf("}", closingOffset + 1); - } - // Moving to the previous code block starting bracket if caret not inside - // any code block. - if (matchingIndex == -1) { - let lastClosingOffset = text.lastIndexOf("}", caretOffset); - while (lastClosingOffset > -1) { - let closingMatchingIndex = - this._getMatchingBracketIndex(lastClosingOffset); - if (closingMatchingIndex < caretOffset && - closingMatchingIndex != -1) { - matchingIndex = closingMatchingIndex; - break; - } - lastClosingOffset = text.lastIndexOf("}", lastClosingOffset - 1); - } - } - } - - if (matchingIndex > -1) { - this.setCaretOffset(matchingIndex + 1); - } - - return true; - }, - - /** - * Moves the cursor to the matching closing bracket if at corresponding - * opening bracket, otherwise move to the closing bracket for the current - * block of code. - * - * @private - */ - _moveToBracketClosing: function SE__moveToBracketClosing() - { - let mode = this.getMode(); - // Returning early if not in JavaScipt or CSS mode. - if (mode != SourceEditor.MODES.JAVASCRIPT && - mode != SourceEditor.MODES.CSS) { - return false; - } - - let caretOffset = this.getCaretOffset(); - let matchingIndex = this._getMatchingBracketIndex(caretOffset - 1); - - // If the caret is not at the opening bracket "{", find the index of the - // closing bracket "}" for the current code block. - if (matchingIndex == -1 || matchingIndex < caretOffset) { - matchingIndex = -1; - let text = this.getText(); - let openingOffset = text.lastIndexOf("{", caretOffset); - while (openingOffset > -1) { - let openingMatchingIndex = this._getMatchingBracketIndex(openingOffset); - if (openingMatchingIndex > caretOffset) { - matchingIndex = openingMatchingIndex; - break; - } - openingOffset = text.lastIndexOf("{", openingOffset - 1); - } - // Moving to the next code block ending bracket if caret not inside - // any code block. - if (matchingIndex == -1) { - let nextOpeningIndex = text.indexOf("{", caretOffset + 1); - while (nextOpeningIndex > -1) { - let openingMatchingIndex = - this._getMatchingBracketIndex(nextOpeningIndex); - if (openingMatchingIndex > caretOffset) { - matchingIndex = openingMatchingIndex; - break; - } - nextOpeningIndex = text.indexOf("{", nextOpeningIndex + 1); - } - } - } - - if (matchingIndex > -1) { - this.setCaretOffset(matchingIndex); - } - - return true; - }, - - /** - * Add an event listener to the editor. You can use one of the known events. - * - * @see SourceEditor.EVENTS - * - * @param string aEventType - * The event type you want to listen for. - * @param function aCallback - * The function you want executed when the event is triggered. - */ - addEventListener: function SE_addEventListener(aEventType, aCallback) - { - if (this._view && aEventType in ORION_EVENTS) { - this._view.addEventListener(ORION_EVENTS[aEventType], aCallback); - } else if (this._eventTarget.addEventListener) { - this._eventTarget.addEventListener(aEventType, aCallback); - } else { - this._eventListenersQueue.push(["add", aEventType, aCallback]); - } - }, - - /** - * Remove an event listener from the editor. You can use one of the known - * events. - * - * @see SourceEditor.EVENTS - * - * @param string aEventType - * The event type you have a listener for. - * @param function aCallback - * The function you have as the event handler. - */ - removeEventListener: function SE_removeEventListener(aEventType, aCallback) - { - if (this._view && aEventType in ORION_EVENTS) { - this._view.removeEventListener(ORION_EVENTS[aEventType], aCallback); - } else if (this._eventTarget.removeEventListener) { - this._eventTarget.removeEventListener(aEventType, aCallback); - } else { - this._eventListenersQueue.push(["remove", aEventType, aCallback]); - } - }, - - /** - * Undo a change in the editor. - * - * @return boolean - * True if there was a change undone, false otherwise. - */ - undo: function SE_undo() - { - let result = this._undoStack.undo(); - this.ui._onUndoRedo(); - return result; - }, - - /** - * Redo a change in the editor. - * - * @return boolean - * True if there was a change redone, false otherwise. - */ - redo: function SE_redo() - { - let result = this._undoStack.redo(); - this.ui._onUndoRedo(); - return result; - }, - - /** - * Check if there are changes that can be undone. - * - * @return boolean - * True if there are changes that can be undone, false otherwise. - */ - canUndo: function SE_canUndo() - { - return this._undoStack.canUndo(); - }, - - /** - * Check if there are changes that can be repeated. - * - * @return boolean - * True if there are changes that can be repeated, false otherwise. - */ - canRedo: function SE_canRedo() - { - return this._undoStack.canRedo(); - }, - - /** - * Reset the Undo stack. - */ - resetUndo: function SE_resetUndo() - { - this._undoStack.reset(); - this._updateDirty(); - this.ui._onUndoRedo(); - }, - - /** - * Set the "dirty" state of the editor. Set this to false when you save the - * text being edited. The dirty state will become true once the user makes - * changes to the text. - * - * @param boolean aNewValue - * The new dirty state: true if the text is not saved, false if you - * just saved the text. - */ - set dirty(aNewValue) - { - if (aNewValue == this._dirty) { - return; - } - - let event = { - type: SourceEditor.EVENTS.DIRTY_CHANGED, - oldValue: this._dirty, - newValue: aNewValue, - }; - - this._dirty = aNewValue; - if (!this._dirty && !this._undoStack.isClean()) { - this._undoStack.markClean(); - } - this._dispatchEvent(event); - }, - - /** - * Get the editor "dirty" state. This tells if the text is considered saved or - * not. - * - * @see SourceEditor.EVENTS.DIRTY_CHANGED - * @return boolean - * True if there are changes which are not saved, false otherwise. - */ - get dirty() - { - return this._dirty; - }, - - /** - * Start a compound change in the editor. Compound changes are grouped into - * only one change that you can undo later, after you invoke - * endCompoundChange(). - */ - startCompoundChange: function SE_startCompoundChange() - { - this._undoStack.startCompoundChange(); - }, - - /** - * End a compound change in the editor. - */ - endCompoundChange: function SE_endCompoundChange() - { - this._undoStack.endCompoundChange(); - }, - - /** - * Focus the editor. - */ - focus: function SE_focus() - { - this._view.focus(); - }, - - /** - * Get the first visible line number. - * - * @return number - * The line number, counting from 0. - */ - getTopIndex: function SE_getTopIndex() - { - return this._view.getTopIndex(); - }, - - /** - * Set the first visible line number. - * - * @param number aTopIndex - * The line number, counting from 0. - */ - setTopIndex: function SE_setTopIndex(aTopIndex) - { - this._view.setTopIndex(aTopIndex); - }, - - /** - * Check if the editor has focus. - * - * @return boolean - * True if the editor is focused, false otherwise. - */ - hasFocus: function SE_hasFocus() - { - return this._view.hasFocus(); - }, - - /** - * Get the editor content, in the given range. If no range is given you get - * the entire editor content. - * - * @param number [aStart=0] - * Optional, start from the given offset. - * @param number [aEnd=content char count] - * Optional, end offset for the text you want. If this parameter is not - * given, then the text returned goes until the end of the editor - * content. - * @return string - * The text in the given range. - */ - getText: function SE_getText(aStart, aEnd) - { - return this._view.getText(aStart, aEnd); - }, - - /** - * Get the start character offset of the line with index aLineIndex. - * - * @param number aLineIndex - * Zero based index of the line. - * @return number - * Line start offset or -1 if out of range. - */ - getLineStart: function SE_getLineStart(aLineIndex) - { - return this._model.getLineStart(aLineIndex); - }, - - /** - * Get the end character offset of the line with index aLineIndex, - * excluding the end offset. When the line delimiter is present, - * the offset is the start offset of the next line or the char count. - * Otherwise, it is the offset of the line delimiter. - * - * @param number aLineIndex - * Zero based index of the line. - * @param boolean [aIncludeDelimiter = false] - * Optional, whether or not to include the line delimiter. - * @return number - * Line end offset or -1 if out of range. - */ - getLineEnd: function SE_getLineEnd(aLineIndex, aIncludeDelimiter) - { - return this._model.getLineEnd(aLineIndex, aIncludeDelimiter); - }, - - /** - * Get the number of characters in the editor content. - * - * @return number - * The number of editor content characters. - */ - getCharCount: function SE_getCharCount() - { - return this._model.getCharCount(); - }, - - /** - * Get the selected text. - * - * @return string - * The currently selected text. - */ - getSelectedText: function SE_getSelectedText() - { - let selection = this.getSelection(); - return this.getText(selection.start, selection.end); - }, - - /** - * Replace text in the source editor with the given text, in the given range. - * - * @param string aText - * The text you want to put into the editor. - * @param number [aStart=0] - * Optional, the start offset, zero based, from where you want to start - * replacing text in the editor. - * @param number [aEnd=char count] - * Optional, the end offset, zero based, where you want to stop - * replacing text in the editor. - */ - setText: function SE_setText(aText, aStart, aEnd) - { - this._view.setText(aText, aStart, aEnd); - }, - - /** - * Drop the current selection / deselect. - */ - dropSelection: function SE_dropSelection() - { - this.setCaretOffset(this.getCaretOffset()); - }, - - /** - * Select a specific range in the editor. - * - * @param number aStart - * Selection range start. - * @param number aEnd - * Selection range end. - */ - setSelection: function SE_setSelection(aStart, aEnd) - { - this._view.setSelection(aStart, aEnd, true); - }, - - /** - * Get the current selection range. - * - * @return object - * An object with two properties, start and end, that give the - * selection range (zero based offsets). - */ - getSelection: function SE_getSelection() - { - return this._view.getSelection(); - }, - - /** - * Get the current caret offset. - * - * @return number - * The current caret offset. - */ - getCaretOffset: function SE_getCaretOffset() - { - return this._view.getCaretOffset(); - }, - - /** - * Set the caret offset. - * - * @param number aOffset - * The new caret offset you want to set. - */ - setCaretOffset: function SE_setCaretOffset(aOffset) - { - this._view.setCaretOffset(aOffset, true); - }, - - /** - * Get the caret position. - * - * @return object - * An object that holds two properties: - * - line: the line number, counting from 0. - * - col: the column number, counting from 0. - */ - getCaretPosition: function SE_getCaretPosition() - { - let offset = this.getCaretOffset(); - let line = this._model.getLineAtOffset(offset); - let lineStart = this.getLineStart(line); - let column = offset - lineStart; - return {line: line, col: column}; - }, - - /** - * Set the caret position: line and column. - * - * @param number aLine - * The new caret line location. Line numbers start from 0. - * @param number [aColumn=0] - * Optional. The new caret column location. Columns start from 0. - * @param number [aAlign=0] - * Optional. Position of the line with respect to viewport. - * Allowed values are: - * SourceEditor.VERTICAL_ALIGN.TOP target line at top of view. - * SourceEditor.VERTICAL_ALIGN.CENTER target line at center of view. - * SourceEditor.VERTICAL_ALIGN.BOTTOM target line at bottom of view. - */ - setCaretPosition: function SE_setCaretPosition(aLine, aColumn, aAlign) - { - let editorHeight = this._view.getClientArea().height; - let lineHeight = this._view.getLineHeight(); - let linesVisible = Math.floor(editorHeight/lineHeight); - let halfVisible = Math.round(linesVisible/2); - let firstVisible = this.getTopIndex(); - let lastVisible = this._view.getBottomIndex(); - let caretOffset = this.getLineStart(aLine) + (aColumn || 0); - - this._view.setSelection(caretOffset, caretOffset, false); - - // If the target line is in view, skip the vertical alignment part. - if (aLine <= lastVisible && aLine >= firstVisible) { - this._view.showSelection(); - return; - } - - // Setting the offset so that the line always falls in the upper half - // of visible lines (lower half for BOTTOM aligned). - // VERTICAL_OFFSET is the maximum allowed value. - let offset = Math.min(halfVisible, VERTICAL_OFFSET); - - let topIndex; - switch (aAlign) { - case this.VERTICAL_ALIGN.CENTER: - topIndex = Math.max(aLine - halfVisible, 0); - break; - - case this.VERTICAL_ALIGN.BOTTOM: - topIndex = Math.max(aLine - linesVisible + offset, 0); - break; - - default: // this.VERTICAL_ALIGN.TOP. - topIndex = Math.max(aLine - offset, 0); - break; - } - // Bringing down the topIndex to total lines in the editor if exceeding. - topIndex = Math.min(topIndex, this.getLineCount()); - this.setTopIndex(topIndex); - - let location = this._view.getLocationAtOffset(caretOffset); - this._view.setHorizontalPixel(location.x); - }, - - /** - * Get the line count. - * - * @return number - * The number of lines in the document being edited. - */ - getLineCount: function SE_getLineCount() - { - return this._model.getLineCount(); - }, - - /** - * Get the line delimiter used in the document being edited. - * - * @return string - * The line delimiter. - */ - getLineDelimiter: function SE_getLineDelimiter() - { - return this._model.getLineDelimiter(); - }, - - /** - * Get the indentation string used in the document being edited. - * - * @return string - * The indentation string. - */ - getIndentationString: function SE_getIndentationString() - { - if (this._expandTab) { - return (new Array(this._tabSize + 1)).join(" "); - } - return "\t"; - }, - - /** - * Set the source editor mode to the file type you are editing. - * - * @param string aMode - * One of the predefined SourceEditor.MODES. - */ - setMode: function SE_setMode(aMode) - { - if (this._styler) { - this._styler.destroy(); - this._styler = null; - } - - let window = this._iframeWindow; - - switch (aMode) { - case SourceEditor.MODES.JAVASCRIPT: - case SourceEditor.MODES.CSS: - let TextStyler = - window.require("examples/textview/textStyler").TextStyler; - - this._styler = new TextStyler(this._view, aMode, this._annotationModel); - this._styler.setFoldingEnabled(false); - break; - - case SourceEditor.MODES.HTML: - case SourceEditor.MODES.XML: - let TextMateStyler = - window.require("orion/editor/textMateStyler").TextMateStyler; - let HtmlGrammar = - window.require("orion/editor/htmlGrammar").HtmlGrammar; - this._styler = new TextMateStyler(this._view, new HtmlGrammar()); - break; - } - - this._highlightAnnotations(); - this._mode = aMode; - }, - - /** - * Get the current source editor mode. - * - * @return string - * Returns one of the predefined SourceEditor.MODES. - */ - getMode: function SE_getMode() - { - return this._mode; - }, - - /** - * Setter for the read-only state of the editor. - * @param boolean aValue - * Tells if you want the editor to read-only or not. - */ - set readOnly(aValue) - { - this._view.setOptions({ - readonly: aValue, - themeClass: "mozilla" + (aValue ? " readonly" : ""), - }); - }, - - /** - * Getter for the read-only state of the editor. - * @type boolean - */ - get readOnly() - { - return this._view.getOptions("readonly"); - }, - - /** - * Set the current debugger location at the given line index. This is useful in - * a debugger or in any other context where the user needs to track the - * current state, where a debugger-like environment is at. - * - * @param number aLineIndex - * Line index of the current debugger location, starting from 0. - * Use any negative number to clear the current location. - */ - setDebugLocation: function SE_setDebugLocation(aLineIndex) - { - let annotations = this._getAnnotationsByType("debugLocation", 0, - this.getCharCount()); - if (annotations.length > 0) { - annotations.forEach(this._annotationModel.removeAnnotation, - this._annotationModel); - } - if (aLineIndex < 0) { - return; - } - - let lineStart = this._model.getLineStart(aLineIndex); - let lineEnd = this._model.getLineEnd(aLineIndex); - let lineText = this._model.getLine(aLineIndex); - let title = SourceEditorUI.strings. - formatStringFromName("annotation.debugLocation.title", - [lineText], 1); - - let annotation = { - type: ORION_ANNOTATION_TYPES.debugLocation, - start: lineStart, - end: lineEnd, - title: title, - style: {styleClass: "annotation debugLocation"}, - html: "
", - overviewStyle: {styleClass: "annotationOverview debugLocation"}, - rangeStyle: {styleClass: "annotationRange debugLocation"}, - lineStyle: {styleClass: "annotationLine debugLocation"}, - }; - this._annotationModel.addAnnotation(annotation); - }, - - /** - * Retrieve the current debugger line index configured for this editor. - * - * @return number - * The line index starting from 0 where the current debugger is - * paused. If no debugger location has been set -1 is returned. - */ - getDebugLocation: function SE_getDebugLocation() - { - let annotations = this._getAnnotationsByType("debugLocation", 0, - this.getCharCount()); - if (annotations.length > 0) { - return this._model.getLineAtOffset(annotations[0].start); - } - return -1; - }, - - /** - * Add a breakpoint at the given line index. - * - * @param number aLineIndex - * Line index where to add the breakpoint (starts from 0). - * @param string [aCondition] - * Optional breakpoint condition. - */ - addBreakpoint: function SE_addBreakpoint(aLineIndex, aCondition) - { - let lineStart = this.getLineStart(aLineIndex); - let lineEnd = this.getLineEnd(aLineIndex); - - let annotations = this._getAnnotationsByType("breakpoint", lineStart, lineEnd); - if (annotations.length > 0) { - return; - } - - let lineText = this._model.getLine(aLineIndex); - let title = SourceEditorUI.strings. - formatStringFromName("annotation.breakpoint.title", - [lineText], 1); - - let annotation = { - type: ORION_ANNOTATION_TYPES.breakpoint, - start: lineStart, - end: lineEnd, - breakpointCondition: aCondition, - title: title, - style: {styleClass: "annotation breakpoint"}, - html: "
", - overviewStyle: {styleClass: "annotationOverview breakpoint"}, - rangeStyle: {styleClass: "annotationRange breakpoint"} - }; - this._annotationModel.addAnnotation(annotation); - - let event = { - type: SourceEditor.EVENTS.BREAKPOINT_CHANGE, - added: [{line: aLineIndex, condition: aCondition}], - removed: [], - }; - - this._dispatchEvent(event); - }, - - /** - * Remove the current breakpoint from the given line index. - * - * @param number aLineIndex - * Line index from where to remove the breakpoint (starts from 0). - * @return boolean - * True if a breakpoint was removed, false otherwise. - */ - removeBreakpoint: function SE_removeBreakpoint(aLineIndex) - { - let lineStart = this.getLineStart(aLineIndex); - let lineEnd = this.getLineEnd(aLineIndex); - - let event = { - type: SourceEditor.EVENTS.BREAKPOINT_CHANGE, - added: [], - removed: [], - }; - - let annotations = this._getAnnotationsByType("breakpoint", lineStart, lineEnd); - - annotations.forEach(function(annotation) { - this._annotationModel.removeAnnotation(annotation); - event.removed.push({line: aLineIndex, - condition: annotation.breakpointCondition}); - }, this); - - if (event.removed.length > 0) { - this._dispatchEvent(event); - } - - return event.removed.length > 0; - }, - - /** - * Get the list of breakpoints in the Source Editor instance. - * - * @return array - * The array of breakpoints. Each item is an object with two - * properties: line and condition. - */ - getBreakpoints: function SE_getBreakpoints() - { - let annotations = this._getAnnotationsByType("breakpoint", 0, - this.getCharCount()); - let breakpoints = []; - - annotations.forEach(function(annotation) { - breakpoints.push({line: this._model.getLineAtOffset(annotation.start), - condition: annotation.breakpointCondition}); - }, this); - - return breakpoints; - }, - - /** - * Convert the given rectangle from one coordinate reference to another. - * - * Known coordinate references: - * - "document" - gives the coordinates relative to the entire document. - * - "view" - gives the coordinates relative to the editor viewport. - * - * @param object aRect - * The rectangle to convert. Object properties: x, y, width and height. - * @param string aFrom - * The source coordinate reference. - * @param string aTo - * The destination coordinate reference. - * @return object aRect - * Returns the rectangle with changed coordinates. - */ - convertCoordinates: function SE_convertCoordinates(aRect, aFrom, aTo) - { - return this._view.convert(aRect, aFrom, aTo); - }, - - /** - * Get the character offset nearest to the given pixel location. - * - * @param number aX - * @param number aY - * @return number - * Returns the character offset at the given location. - */ - getOffsetAtLocation: function SE_getOffsetAtLocation(aX, aY) - { - return this._view.getOffsetAtLocation(aX, aY); - }, - - /** - * Get the pixel location, relative to the document, at the given character - * offset. - * - * @param number aOffset - * @return object - * The pixel location relative to the document being edited. Two - * properties are included: x and y. - */ - getLocationAtOffset: function SE_getLocationAtOffset(aOffset) - { - return this._view.getLocationAtOffset(aOffset); - }, - - /** - * Get the line location for a given character offset. - * - * @param number aOffset - * @return number - * The line location relative to the give character offset. - */ - getLineAtOffset: function SE_getLineAtOffset(aOffset) - { - return this._model.getLineAtOffset(aOffset); - }, - - /** - * Destroy/uninitialize the editor. - */ - destroy: function SE_destroy() - { - if (this._config.highlightCurrentLine || Services.appinfo.OS == "Linux") { - this.removeEventListener(SourceEditor.EVENTS.SELECTION, - this._onOrionSelection); - } - this._onOrionSelection = null; - - this.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, - this._onTextChanged); - this._onTextChanged = null; - - if (this._contextMenu) { - this.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU, - this._onOrionContextMenu); - this._contextMenu = null; - } - this._onOrionContextMenu = null; - - if (this._primarySelectionTimeout) { - let window = this.parentElement.ownerDocument.defaultView; - window.clearTimeout(this._primarySelectionTimeout); - this._primarySelectionTimeout = null; - } - - this._view.destroy(); - this.ui.destroy(); - this.ui = null; - - this.parentElement.removeChild(this._iframe); - this.parentElement = null; - this._iframeWindow = null; - this._iframe = null; - this._undoStack = null; - this._styler = null; - this._linesRuler = null; - this._annotationRuler = null; - this._overviewRuler = null; - this._dragAndDrop = null; - this._annotationModel = null; - this._annotationStyler = null; - this._currentLineAnnotation = null; - this._eventTarget = null; - this._eventListenersQueue = null; - this._view = null; - this._model = null; - this._config = null; - this._lastFind = null; - }, -}; diff --git a/browser/devtools/sourceeditor/source-editor-overlay.xul b/browser/devtools/sourceeditor/source-editor-overlay.xul deleted file mode 100644 index e4fb7c91f7f9..000000000000 --- a/browser/devtools/sourceeditor/source-editor-overlay.xul +++ /dev/null @@ -1,204 +0,0 @@ - - - - %editMenuStrings; - - %sourceEditorStrings; -]> - - - - - - - - - - - - - - - - - - - - - - -#ifdef XP_UNIX - -#else - -#endif - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/browser/devtools/sourceeditor/source-editor-ui.jsm b/browser/devtools/sourceeditor/source-editor-ui.jsm deleted file mode 100644 index 8b74d16238f0..000000000000 --- a/browser/devtools/sourceeditor/source-editor-ui.jsm +++ /dev/null @@ -1,332 +0,0 @@ -/* vim:set ts=2 sw=2 sts=2 et tw=80: - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const Cu = Components.utils; - -Cu.import("resource://gre/modules/Services.jsm"); - -this.EXPORTED_SYMBOLS = ["SourceEditorUI"]; - -/** - * The Source Editor component user interface. - */ -this.SourceEditorUI = function SourceEditorUI(aEditor) -{ - this.editor = aEditor; - this._onDirtyChanged = this._onDirtyChanged.bind(this); -} - -SourceEditorUI.prototype = { - /** - * Initialize the user interface. This is called by the SourceEditor.init() - * method. - */ - init: function SEU_init() - { - this._ownerWindow = this.editor.parentElement.ownerDocument.defaultView; - }, - - /** - * The UI onReady function is executed once the Source Editor completes - * initialization and it is ready for usage. Currently this code sets up the - * nsIController. - */ - onReady: function SEU_onReady() - { - if (this._ownerWindow.controllers) { - this._controller = new SourceEditorController(this.editor); - this._ownerWindow.controllers.insertControllerAt(0, this._controller); - this.editor.addEventListener(this.editor.EVENTS.DIRTY_CHANGED, - this._onDirtyChanged); - } - }, - - /** - * The "go to line" command UI. This displays a prompt that allows the user to - * input the line number to jump to. - */ - gotoLine: function SEU_gotoLine() - { - let oldLine = this.editor.getCaretPosition ? - this.editor.getCaretPosition().line : null; - let newLine = {value: oldLine !== null ? oldLine + 1 : ""}; - - let result = Services.prompt.prompt(this._ownerWindow, - SourceEditorUI.strings.GetStringFromName("gotoLineCmd.promptTitle"), - SourceEditorUI.strings.GetStringFromName("gotoLineCmd.promptMessage"), - newLine, null, {}); - - newLine.value = parseInt(newLine.value); - if (result && !isNaN(newLine.value) && --newLine.value != oldLine) { - if (this.editor.getLineCount) { - let lines = this.editor.getLineCount() - 1; - this.editor.setCaretPosition(Math.max(0, Math.min(lines, newLine.value))); - } else { - this.editor.setCaretPosition(Math.max(0, newLine.value)); - } - } - - return true; - }, - - /** - * The "find" command UI. This displays a prompt that allows the user to input - * the string to search for in the code. By default the current selection is - * used as a search string, or the last search string. - */ - find: function SEU_find() - { - let str = {value: this.editor.getSelectedText()}; - if (!str.value && this.editor.lastFind) { - str.value = this.editor.lastFind.str; - } - - let result = Services.prompt.prompt(this._ownerWindow, - SourceEditorUI.strings.GetStringFromName("findCmd.promptTitle"), - SourceEditorUI.strings.GetStringFromName("findCmd.promptMessage"), - str, null, {}); - - if (result && str.value) { - let start = this.editor.getSelection().end; - let pos = this.editor.find(str.value, {ignoreCase: true, start: start}); - if (pos == -1) { - this.editor.find(str.value, {ignoreCase: true}); - } - this._onFind(); - } - - return true; - }, - - /** - * Find the next occurrence of the last search string. - */ - findNext: function SEU_findNext() - { - let lastFind = this.editor.lastFind; - if (lastFind) { - this.editor.findNext(true); - this._onFind(); - } - - return true; - }, - - /** - * Find the previous occurrence of the last search string. - */ - findPrevious: function SEU_findPrevious() - { - let lastFind = this.editor.lastFind; - if (lastFind) { - this.editor.findPrevious(true); - this._onFind(); - } - - return true; - }, - - /** - * This executed after each find/findNext/findPrevious operation. - * @private - */ - _onFind: function SEU__onFind() - { - let lastFind = this.editor.lastFind; - if (lastFind && lastFind.index > -1) { - this.editor.setSelection(lastFind.index, lastFind.index + lastFind.str.length); - } - - if (this._ownerWindow.goUpdateCommand) { - this._ownerWindow.goUpdateCommand("cmd_findAgain"); - this._ownerWindow.goUpdateCommand("cmd_findPrevious"); - } - }, - - /** - * This is executed after each undo/redo operation. - * @private - */ - _onUndoRedo: function SEU__onUndoRedo() - { - if (this._ownerWindow.goUpdateCommand) { - this._ownerWindow.goUpdateCommand("se-cmd-undo"); - this._ownerWindow.goUpdateCommand("se-cmd-redo"); - } - }, - - /** - * The DirtyChanged event handler for the editor. This tracks the editor state - * changes to make sure the Source Editor overlay Undo/Redo commands are kept - * up to date. - * @private - */ - _onDirtyChanged: function SEU__onDirtyChanged() - { - this._onUndoRedo(); - }, - - /** - * Destroy the SourceEditorUI instance. This is called by the - * SourceEditor.destroy() method. - */ - destroy: function SEU_destroy() - { - if (this._ownerWindow.controllers) { - this.editor.removeEventListener(this.editor.EVENTS.DIRTY_CHANGED, - this._onDirtyChanged); - } - - this._ownerWindow = null; - this.editor = null; - this._controller = null; - }, -}; - -/** - * The Source Editor nsIController implements features that need to be available - * from XUL commands. - * - * @constructor - * @param object aEditor - * SourceEditor object instance for which the controller is instanced. - */ -function SourceEditorController(aEditor) -{ - this._editor = aEditor; -} - -SourceEditorController.prototype = { - /** - * Check if a command is supported by the controller. - * - * @param string aCommand - * The command name you want to check support for. - * @return boolean - * True if the command is supported, false otherwise. - */ - supportsCommand: function SEC_supportsCommand(aCommand) - { - let result; - - switch (aCommand) { - case "cmd_find": - case "cmd_findAgain": - case "cmd_findPrevious": - case "cmd_gotoLine": - case "se-cmd-undo": - case "se-cmd-redo": - case "se-cmd-cut": - case "se-cmd-paste": - case "se-cmd-delete": - case "se-cmd-selectAll": - result = true; - break; - default: - result = false; - break; - } - - return result; - }, - - /** - * Check if a command is enabled or not. - * - * @param string aCommand - * The command name you want to check if it is enabled or not. - * @return boolean - * True if the command is enabled, false otherwise. - */ - isCommandEnabled: function SEC_isCommandEnabled(aCommand) - { - let result; - - switch (aCommand) { - case "cmd_find": - case "cmd_gotoLine": - case "se-cmd-selectAll": - result = true; - break; - case "cmd_findAgain": - case "cmd_findPrevious": - result = this._editor.lastFind && this._editor.lastFind.lastFound != -1; - break; - case "se-cmd-undo": - result = this._editor.canUndo(); - break; - case "se-cmd-redo": - result = this._editor.canRedo(); - break; - case "se-cmd-cut": - case "se-cmd-delete": { - let selection = this._editor.getSelection(); - result = selection.start != selection.end && !this._editor.readOnly; - break; - } - case "se-cmd-paste": { - let window = this._editor._view._frameWindow; - let controller = window.controllers.getControllerForCommand("cmd_paste"); - result = !this._editor.readOnly && - controller.isCommandEnabled("cmd_paste"); - break; - } - default: - result = false; - break; - } - - return result; - }, - - /** - * Perform a command. - * - * @param string aCommand - * The command name you want to execute. - * @return void - */ - doCommand: function SEC_doCommand(aCommand) - { - switch (aCommand) { - case "cmd_find": - this._editor.ui.find(); - break; - case "cmd_findAgain": - this._editor.ui.findNext(); - break; - case "cmd_findPrevious": - this._editor.ui.findPrevious(); - break; - case "cmd_gotoLine": - this._editor.ui.gotoLine(); - break; - case "se-cmd-selectAll": - this._editor._view.invokeAction("selectAll"); - break; - case "se-cmd-undo": - this._editor.undo(); - break; - case "se-cmd-redo": - this._editor.redo(); - break; - case "se-cmd-cut": - this._editor.ui._ownerWindow.goDoCommand("cmd_cut"); - break; - case "se-cmd-paste": - this._editor.ui._ownerWindow.goDoCommand("cmd_paste"); - break; - case "se-cmd-delete": { - let selection = this._editor.getSelection(); - this._editor.setText("", selection.start, selection.end); - break; - } - } - }, - - onEvent: function() { } -}; diff --git a/browser/devtools/sourceeditor/source-editor.jsm b/browser/devtools/sourceeditor/source-editor.jsm deleted file mode 100644 index deb0f4455df6..000000000000 --- a/browser/devtools/sourceeditor/source-editor.jsm +++ /dev/null @@ -1,455 +0,0 @@ -/* vim:set ts=2 sw=2 sts=2 et tw=80: - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const Cu = Components.utils; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource:///modules/devtools/sourceeditor/source-editor-ui.jsm"); - -const PREF_EDITOR_COMPONENT = "devtools.editor.component"; -const SOURCEEDITOR_L10N = "chrome://browser/locale/devtools/sourceeditor.properties"; - -var component = Services.prefs.getCharPref(PREF_EDITOR_COMPONENT); -var obj = {}; -try { - if (component == "ui") { - throw new Error("The ui editor component is not available."); - } - Cu.import("resource:///modules/devtools/sourceeditor/source-editor-" + component + ".jsm", obj); -} catch (ex) { - Cu.reportError(ex); - Cu.reportError("SourceEditor component failed to load: " + component); - - // If the component does not exist, clear the user pref back to the default. - Services.prefs.clearUserPref(PREF_EDITOR_COMPONENT); - - // Load the default editor component. - component = Services.prefs.getCharPref(PREF_EDITOR_COMPONENT); - Cu.import("resource:///modules/devtools/sourceeditor/source-editor-" + component + ".jsm", obj); -} - -// Export the SourceEditor. -this.SourceEditor = obj.SourceEditor; -this.EXPORTED_SYMBOLS = ["SourceEditor"]; - -// Add the constants used by all SourceEditors. - -XPCOMUtils.defineLazyGetter(SourceEditorUI, "strings", function() { - return Services.strings.createBundle(SOURCEEDITOR_L10N); -}); - -/** - * Known SourceEditor preferences. - */ -SourceEditor.PREFS = { - TAB_SIZE: "devtools.editor.tabsize", - EXPAND_TAB: "devtools.editor.expandtab", - COMPONENT: PREF_EDITOR_COMPONENT, -}; - -/** - * Predefined source editor modes for JavaScript, CSS and other languages. - */ -SourceEditor.MODES = { - JAVASCRIPT: "js", - CSS: "css", - TEXT: "text", - HTML: "html", - XML: "xml", -}; - -/** - * Predefined themes for syntax highlighting. - */ -SourceEditor.THEMES = { - MOZILLA: "mozilla", -}; - -/** - * Source editor configuration defaults. - * @see SourceEditor.init - */ -SourceEditor.DEFAULTS = { - /** - * The text you want shown when the editor opens up. - * @type string - */ - initialText: "", - - /** - * The editor mode, based on the file type you want to edit. You can use one of - * the predefined modes. - * - * @see SourceEditor.MODES - * @type string - */ - mode: SourceEditor.MODES.TEXT, - - /** - * The syntax highlighting theme you want. You can use one of the predefined - * themes, or you can point to your CSS file. - * - * @see SourceEditor.THEMES. - * @type string - */ - theme: SourceEditor.THEMES.MOZILLA, - - /** - * How many steps should the undo stack hold. - * @type number - */ - undoLimit: 200, - - /** - * Define how many spaces to use for a tab character. This value is overridden - * by a user preference, see SourceEditor.PREFS.TAB_SIZE. - * - * @type number - */ - tabSize: 4, - - /** - * Tells if you want tab characters to be expanded to spaces. This value is - * overridden by a user preference, see SourceEditor.PREFS.EXPAND_TAB. - * @type boolean - */ - expandTab: true, - - /** - * Tells if you want the editor to be read only or not. - * @type boolean - */ - readOnly: false, - - /** - * Display the line numbers gutter. - * @type boolean - */ - showLineNumbers: false, - - /** - * Display the annotations gutter/ruler. This gutter currently supports - * annotations of breakpoint type. - * @type boolean - */ - showAnnotationRuler: false, - - /** - * Display the overview gutter/ruler. This gutter presents an overview of the - * current annotations in the editor, for example the breakpoints. - * @type boolean - */ - showOverviewRuler: false, - - /** - * Highlight the current line. - * @type boolean - */ - highlightCurrentLine: true, - - /** - * An array of objects that allows you to define custom editor keyboard - * bindings. Each object can have: - * - action - name of the editor action to invoke. - * - code - keyCode for the shortcut. - * - accel - boolean for the Accel key (Cmd on Macs, Ctrl on Linux/Windows). - * - ctrl - boolean for the Control key - * - shift - boolean for the Shift key. - * - alt - boolean for the Alt key. - * - callback - optional function to invoke, if the action is not predefined - * in the editor. - * @type array - */ - keys: null, - - /** - * The editor context menu you want to display when the user right-clicks - * within the editor. This property can be: - * - a string that tells the ID of the xul:menupopup you want. This needs to - * be available within the editor parentElement.ownerDocument. - * - an nsIDOMElement object reference pointing to the xul:menupopup you - * want to open when the contextmenu event is fired. - * - * Set this property to a falsey value to disable the default context menu. - * - * @see SourceEditor.EVENTS.CONTEXT_MENU for more control over the contextmenu - * event. - * @type string|nsIDOMElement - */ - contextMenu: "sourceEditorContextMenu", -}; - -/** - * Known editor events you can listen for. - */ -SourceEditor.EVENTS = { - /** - * The contextmenu event is fired when the editor context menu is invoked. The - * event object properties: - * - x - the pointer location on the x axis, relative to the document the - * user is editing. - * - y - the pointer location on the y axis, relative to the document the - * user is editing. - * - screenX - the pointer location on the x axis, relative to the screen. - * This value comes from the DOM contextmenu event.screenX property. - * - screenY - the pointer location on the y axis, relative to the screen. - * This value comes from the DOM contextmenu event.screenY property. - * - * @see SourceEditor.DEFAULTS.contextMenu - */ - CONTEXT_MENU: "ContextMenu", - - /** - * The TextChanged event is fired when the editor content changes. The event - * object properties: - * - start - the character offset in the document where the change has - * occured. - * - removedCharCount - the number of characters removed from the document. - * - addedCharCount - the number of characters added to the document. - */ - TEXT_CHANGED: "TextChanged", - - /** - * The Selection event is fired when the editor selection changes. The event - * object properties: - * - oldValue - the old selection range. - * - newValue - the new selection range. - * Both ranges are objects which hold two properties: start and end. - */ - SELECTION: "Selection", - - /** - * The focus event is fired when the editor is focused. - */ - FOCUS: "Focus", - - /** - * The blur event is fired when the editor goes out of focus. - */ - BLUR: "Blur", - - /** - * The MouseMove event is sent when the user moves the mouse over a line. - * The event object properties: - * - event - the DOM mousemove event object. - * - x and y - the mouse coordinates relative to the document being edited. - */ - MOUSE_MOVE: "MouseMove", - - /** - * The MouseOver event is sent when the mouse pointer enters a line. - * The event object properties: - * - event - the DOM mouseover event object. - * - x and y - the mouse coordinates relative to the document being edited. - */ - MOUSE_OVER: "MouseOver", - - /** - * This MouseOut event is sent when the mouse pointer exits a line. - * The event object properties: - * - event - the DOM mouseout event object. - * - x and y - the mouse coordinates relative to the document being edited. - */ - MOUSE_OUT: "MouseOut", - - /** - * The BreakpointChange event is fired when a new breakpoint is added or when - * a breakpoint is removed - either through API use or through the editor UI. - * Event object properties: - * - added - array that holds the new breakpoints. - * - removed - array that holds the breakpoints that have been removed. - * Each object in the added/removed arrays holds two properties: line and - * condition. - */ - BREAKPOINT_CHANGE: "BreakpointChange", - - /** - * The DirtyChanged event is fired when the dirty state of the editor is - * changed. The dirty state of the editor tells if the are text changes that - * have not been saved yet. Event object properties: oldValue and newValue. - * Both are booleans telling the old dirty state and the new state, - * respectively. - */ - DIRTY_CHANGED: "DirtyChanged", -}; - -/** - * Allowed vertical alignment options for the line index - * when you call SourceEditor.setCaretPosition(). - */ -SourceEditor.VERTICAL_ALIGN = { - TOP: 0, - CENTER: 1, - BOTTOM: 2, -}; - -/** - * Extend a destination object with properties from a source object. - * - * @param object aDestination - * @param object aSource - */ -function extend(aDestination, aSource) -{ - for (let name in aSource) { - if (!aDestination.hasOwnProperty(name)) { - aDestination[name] = aSource[name]; - } - } -} - -/** - * Add methods common to all components. - */ -extend(SourceEditor.prototype, { - // Expose the static constants on the SourceEditor instances. - EVENTS: SourceEditor.EVENTS, - MODES: SourceEditor.MODES, - THEMES: SourceEditor.THEMES, - DEFAULTS: SourceEditor.DEFAULTS, - VERTICAL_ALIGN: SourceEditor.VERTICAL_ALIGN, - - _lastFind: null, - - /** - * Find a string in the editor. - * - * @param string aString - * The string you want to search for. If |aString| is not given the - * currently selected text is used. - * @param object [aOptions] - * Optional find options: - * - start: (integer) offset to start searching from. Default: 0 if - * backwards is false. If backwards is true then start = text.length. - * - ignoreCase: (boolean) tells if you want the search to be case - * insensitive or not. Default: false. - * - backwards: (boolean) tells if you want the search to go backwards - * from the given |start| offset. Default: false. - * @return integer - * The offset where the string was found. - */ - find: function SE_find(aString, aOptions) - { - if (typeof(aString) != "string") { - return -1; - } - - aOptions = aOptions || {}; - - let str = aOptions.ignoreCase ? aString.toLowerCase() : aString; - - let text = this.getText(); - if (aOptions.ignoreCase) { - text = text.toLowerCase(); - } - - let index = aOptions.backwards ? - text.lastIndexOf(str, aOptions.start) : - text.indexOf(str, aOptions.start); - - let lastFoundIndex = index; - if (index == -1 && this.lastFind && this.lastFind.index > -1 && - this.lastFind.str === aString && - this.lastFind.ignoreCase === !!aOptions.ignoreCase) { - lastFoundIndex = this.lastFind.index; - } - - this._lastFind = { - str: aString, - index: index, - lastFound: lastFoundIndex, - ignoreCase: !!aOptions.ignoreCase, - }; - - return index; - }, - - /** - * Find the next occurrence of the last search operation. - * - * @param boolean aWrap - * Tells if you want to restart the search from the beginning of the - * document if the string is not found. - * @return integer - * The offset where the string was found. - */ - findNext: function SE_findNext(aWrap) - { - if (!this.lastFind && this.lastFind.lastFound == -1) { - return -1; - } - - let options = { - start: this.lastFind.lastFound + this.lastFind.str.length, - ignoreCase: this.lastFind.ignoreCase, - }; - - let index = this.find(this.lastFind.str, options); - if (index == -1 && aWrap) { - options.start = 0; - index = this.find(this.lastFind.str, options); - } - - return index; - }, - - /** - * Find the previous occurrence of the last search operation. - * - * @param boolean aWrap - * Tells if you want to restart the search from the end of the - * document if the string is not found. - * @return integer - * The offset where the string was found. - */ - findPrevious: function SE_findPrevious(aWrap) - { - if (!this.lastFind && this.lastFind.lastFound == -1) { - return -1; - } - - let options = { - start: this.lastFind.lastFound - this.lastFind.str.length, - ignoreCase: this.lastFind.ignoreCase, - backwards: true, - }; - - let index; - if (options.start > 0) { - index = this.find(this.lastFind.str, options); - } else { - index = this._lastFind.index = -1; - } - - if (index == -1 && aWrap) { - options.start = this.getCharCount() - 1; - index = this.find(this.lastFind.str, options); - } - - return index; - }, -}); - -/** - * Retrieve the last find operation result. This object holds the following - * properties: - * - str: the last search string. - * - index: stores the result of the most recent find operation. This is the - * index in the text where |str| was found or -1 otherwise. - * - lastFound: tracks the index where |str| was last found, throughout - * multiple find operations. This can be -1 if |str| was never found in the - * document. - * - ignoreCase: tells if the search was case insensitive or not. - * @type object - */ -Object.defineProperty(SourceEditor.prototype, "lastFind", { - get: function() { return this._lastFind; }, - enumerable: true, - configurable: true, -}); - diff --git a/browser/devtools/sourceeditor/test/browser.ini b/browser/devtools/sourceeditor/test/browser.ini index ace13b6ea830..a4f871edd507 100644 --- a/browser/devtools/sourceeditor/test/browser.ini +++ b/browser/devtools/sourceeditor/test/browser.ini @@ -9,25 +9,9 @@ support-files = codemirror.html head.js -[browser_bug650345_find.js] -[browser_bug684546_reset_undo.js] -[browser_bug684862_paste_html.js] -[browser_bug687160_line_api.js] -[browser_bug687568_pagescroll.js] -[browser_bug687573_vscroll.js] -[browser_bug687580_drag_and_drop.js] -[browser_bug695035_middle_click_paste.js] -[browser_bug700893_dirty_state.js] -[browser_bug703692_focus_blur.js] -[browser_bug707987_debugger_breakpoints.js] -[browser_bug712982_line_ruler_click.js] -[browser_bug725388_mouse_events.js] -[browser_bug725392_mouse_coords_char_offset.js] -[browser_bug725430_comment_uncomment.js] -[browser_bug725618_moveLines_shortcut.js] -[browser_bug729480_line_vertical_align.js] -[browser_bug729960_block_bracket_jump.js] -[browser_bug731721_debugger_stepping.js] -[browser_bug744021_next_prev_bracket_jump.js] +[browser_editor_basic.js] +[browser_editor_cursor.js] +[browser_editor_history.js] +[browser_editor_markers.js] [browser_codemirror.js] -[browser_sourceeditor_initialization.js] \ No newline at end of file +[browser_sourceeditor_initialization.js] diff --git a/browser/devtools/sourceeditor/test/browser_bug650345_find.js b/browser/devtools/sourceeditor/test/browser_bug650345_find.js deleted file mode 100644 index 23a3d2eddf0d..000000000000 --- a/browser/devtools/sourceeditor/test/browser_bug650345_find.js +++ /dev/null @@ -1,149 +0,0 @@ -/* vim: set ts=2 et sw=2 tw=80: */ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -let tempScope = {}; -Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope); -let SourceEditor = tempScope.SourceEditor; - -let testWin; -let editor; - -function test() -{ - waitForExplicitFinish(); - - const windowUrl = "data:text/xml," + - ""; - const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; - - testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null); - testWin.addEventListener("load", function onWindowLoad() { - testWin.removeEventListener("load", onWindowLoad, false); - waitForFocus(initEditor, testWin); - }, false); -} - -function initEditor() -{ - let hbox = testWin.document.querySelector("hbox"); - editor = new SourceEditor(); - editor.init(hbox, {}, editorLoaded); -} - -function editorLoaded() -{ - editor.focus(); - - let text = "foobar bug650345\nBug650345 bazbaz\nfoobar omg\ntest"; - editor.setText(text); - - let needle = "foobar"; - is(editor.find(), -1, "find() works"); - ok(!editor.lastFind, "no editor.lastFind yet"); - - is(editor.find(needle), 0, "find('" + needle + "') works"); - is(editor.lastFind.str, needle, "lastFind.str is correct"); - is(editor.lastFind.index, 0, "lastFind.index is correct"); - is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct"); - is(editor.lastFind.ignoreCase, false, "lastFind.ignoreCase is correct"); - - let newIndex = text.indexOf(needle, needle.length); - is(editor.findNext(), newIndex, "findNext() works"); - is(editor.lastFind.str, needle, "lastFind.str is correct"); - is(editor.lastFind.index, newIndex, "lastFind.index is correct"); - is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct"); - is(editor.lastFind.ignoreCase, false, "lastFind.ignoreCase is correct"); - - is(editor.findNext(), -1, "findNext() works again"); - is(editor.lastFind.index, -1, "lastFind.index is correct"); - is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct"); - - is(editor.findPrevious(), 0, "findPrevious() works"); - is(editor.lastFind.index, 0, "lastFind.index is correct"); - is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct"); - - is(editor.findPrevious(), -1, "findPrevious() works again"); - is(editor.lastFind.index, -1, "lastFind.index is correct"); - is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct"); - - is(editor.findNext(), newIndex, "findNext() works"); - is(editor.lastFind.index, newIndex, "lastFind.index is correct"); - is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct"); - - is(editor.findNext(true), 0, "findNext(true) works"); - is(editor.lastFind.index, 0, "lastFind.index is correct"); - is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct"); - - is(editor.findNext(true), newIndex, "findNext(true) works again"); - is(editor.lastFind.index, newIndex, "lastFind.index is correct"); - is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct"); - - is(editor.findPrevious(true), 0, "findPrevious(true) works"); - is(editor.lastFind.index, 0, "lastFind.index is correct"); - is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct"); - - is(editor.findPrevious(true), newIndex, "findPrevious(true) works again"); - is(editor.lastFind.index, newIndex, "lastFind.index is correct"); - is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct"); - - needle = "error"; - is(editor.find(needle), -1, "find('" + needle + "') works"); - is(editor.lastFind.str, needle, "lastFind.str is correct"); - is(editor.lastFind.index, -1, "lastFind.index is correct"); - is(editor.lastFind.lastFound, -1, "lastFind.lastFound is correct"); - is(editor.lastFind.ignoreCase, false, "lastFind.ignoreCase is correct"); - - is(editor.findNext(), -1, "findNext() works"); - is(editor.lastFind.str, needle, "lastFind.str is correct"); - is(editor.lastFind.index, -1, "lastFind.index is correct"); - is(editor.lastFind.lastFound, -1, "lastFind.lastFound is correct"); - is(editor.findNext(true), -1, "findNext(true) works"); - - is(editor.findPrevious(), -1, "findPrevious() works"); - is(editor.findPrevious(true), -1, "findPrevious(true) works"); - - needle = "bug650345"; - newIndex = text.indexOf(needle); - - is(editor.find(needle), newIndex, "find('" + needle + "') works"); - is(editor.findNext(), -1, "findNext() works"); - is(editor.findNext(true), newIndex, "findNext(true) works"); - is(editor.findPrevious(), -1, "findPrevious() works"); - is(editor.findPrevious(true), newIndex, "findPrevious(true) works"); - is(editor.lastFind.index, newIndex, "lastFind.index is correct"); - is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct"); - - is(editor.find(needle, {ignoreCase: 1}), newIndex, - "find('" + needle + "', {ignoreCase: 1}) works"); - is(editor.lastFind.ignoreCase, true, "lastFind.ignoreCase is correct"); - - let newIndex2 = text.toLowerCase().indexOf(needle, newIndex + needle.length); - is(editor.findNext(), newIndex2, "findNext() works"); - is(editor.findNext(), -1, "findNext() works"); - is(editor.lastFind.index, -1, "lastFind.index is correct"); - is(editor.lastFind.lastFound, newIndex2, "lastFind.lastFound is correct"); - - is(editor.findNext(true), newIndex, "findNext(true) works"); - - is(editor.findPrevious(), -1, "findPrevious() works"); - is(editor.findPrevious(true), newIndex2, "findPrevious(true) works"); - is(editor.findPrevious(), newIndex, "findPrevious() works again"); - - needle = "foobar"; - newIndex = text.indexOf(needle, 2); - is(editor.find(needle, {start: 2}), newIndex, - "find('" + needle + "', {start:2}) works"); - is(editor.findNext(), -1, "findNext() works"); - is(editor.findNext(true), 0, "findNext(true) works"); - - editor.destroy(); - - testWin.close(); - testWin = editor = null; - - waitForFocus(finish, window); -} diff --git a/browser/devtools/sourceeditor/test/browser_bug684546_reset_undo.js b/browser/devtools/sourceeditor/test/browser_bug684546_reset_undo.js deleted file mode 100644 index d54e54ab367d..000000000000 --- a/browser/devtools/sourceeditor/test/browser_bug684546_reset_undo.js +++ /dev/null @@ -1,72 +0,0 @@ -/* vim: set ts=2 et sw=2 tw=80: */ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -let tempScope = {}; -Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope); -let SourceEditor = tempScope.SourceEditor; - -let testWin; -let editor; - -function test() -{ - waitForExplicitFinish(); - - const windowUrl = "data:application/vnd.mozilla.xul+xml," + - "" + - ""; - const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; - - testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null); - testWin.addEventListener("load", function onWindowLoad() { - testWin.removeEventListener("load", onWindowLoad, false); - waitForFocus(initEditor, testWin); - }, false); -} - -function initEditor() -{ - let box = testWin.document.querySelector("box"); - - editor = new SourceEditor(); - editor.init(box, {}, editorLoaded); -} - -function editorLoaded() -{ - editor.setText("First"); - editor.setText("Second", 5); - is(editor.getText(), "FirstSecond", "text set correctly."); - editor.undo(); - is(editor.getText(), "First", "undo works."); - editor.redo(); - is(editor.getText(), "FirstSecond", "redo works."); - editor.resetUndo(); - ok(!editor.canUndo(), "canUndo() is correct"); - ok(!editor.canRedo(), "canRedo() is correct"); - editor.undo(); - is(editor.getText(), "FirstSecond", "reset undo works correctly"); - editor.setText("Third", 11); - is(editor.getText(), "FirstSecondThird", "text set correctly"); - editor.undo(); - is(editor.getText(), "FirstSecond", "undo works after reset"); - editor.redo(); - is(editor.getText(), "FirstSecondThird", "redo works after reset"); - editor.resetUndo(); - ok(!editor.canUndo(), "canUndo() is correct (again)"); - ok(!editor.canRedo(), "canRedo() is correct (again)"); - editor.undo(); - is(editor.getText(), "FirstSecondThird", "reset undo still works correctly"); - - finish(); -} - -registerCleanupFunction(function() { - editor.destroy(); - testWin.close(); - testWin = editor = null; -}); diff --git a/browser/devtools/sourceeditor/test/browser_bug684862_paste_html.js b/browser/devtools/sourceeditor/test/browser_bug684862_paste_html.js deleted file mode 100644 index 3ac37764e323..000000000000 --- a/browser/devtools/sourceeditor/test/browser_bug684862_paste_html.js +++ /dev/null @@ -1,119 +0,0 @@ -/* vim: set ts=2 et sw=2 tw=80: */ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -let tempScope = {}; -Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope); -let SourceEditor = tempScope.SourceEditor; - -let testWin; -let editor; - -function test() -{ - waitForExplicitFinish(); - - const pageUrl = "data:text/html,
  • test
  • foobarBug684862"; - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function onLoad() { - gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); - waitForFocus(pageLoaded, content); - }, true); - - content.location = pageUrl; -} - -function pageLoaded() -{ - const windowUrl = "data:application/vnd.mozilla.xul+xml," + - "" + - "