define(function(require, exports, module) { "use strict"; main.consumes = [ "Editor", "editors", "commands", "menus", "Menu", "MenuItem", "Divider", "settings", "c9", "preferences", "ui", "tabManager", "layout", "util", "threewaymerge", "error_handler" ]; main.provides = ["ace"]; return main; function main(options, imports, register) { var Editor = imports.Editor; var editors = imports.editors; var commands = imports.commands; var menus = imports.menus; var settings = imports.settings; var layout = imports.layout; var c9 = imports.c9; var ui = imports.ui; var util = imports.util; var tabs = imports.tabManager; var prefs = imports.preferences; var Menu = imports.Menu; var MenuItem = imports.MenuItem; var Divider = imports.Divider; var merge = imports.threewaymerge; var errorHandler = imports.error_handler; // Markup & Modes var cssString = require("text!./style.less"); var themes = JSON.parse(require("text!./themes.json")); var modes = require("./modes"); var extensions = Object.keys(modes.extensions); // bearable scrollbars on windows require("./scrollbar"); // Ace var lang = require("ace/lib/lang"); var Range = require("ace/range").Range; var config = require("ace/config"); var Document = require("ace/document").Document; var AceEditor = require("ace/editor").Editor; var EditSession = require("ace/edit_session").EditSession; var UndoManager = require("ace/undomanager").UndoManager; var whitespaceUtil = require("ace/ext/whitespace"); var defaultCommands = require("ace/commands/default_commands").commands; var VirtualRenderer = require("ace/virtual_renderer").VirtualRenderer; var multiSelectCommands = require("ace/multi_select").commands; // enable multiselect require("ace/multi_select"); // and error marker require("ace/ext/error_marker"); // preload html mode require("ace/mode/html"); // Needed to clear ace var dummySession = new EditSession(""); // We don't use ace workers config.setDefaultValue("session", "useWorker", false); // experiment config.setDefaultValue("editor", "fixedWidthGutter", true); EditSession.prototype.diffAndReplace = function(range, text) { var doc = this.doc; var start = doc.positionToIndex(range.start); var oldText = doc.getTextRange(range); merge.patchAce(oldText, text, doc, { offset: start, method: "quick" }); var dl = text.replace(/\r\n|\r|\n/g, doc.getNewLineCharacter()).length; return doc.indexToPosition(start + dl); }; require("ace/lib/fixoldbrowsers"); /***** Global API *****/ // Set up the generic handle var handle = editors.register("ace", "Ace", Ace, extensions); var handleEmit = handle.getEmitter(); handleEmit.setMaxListeners(1000); var mnuAce, mnuGutter; var isMinimal = options.minimal; var themeLoaded = {}; var themeCounter = 100; var lastTheme, grpSyntax, grpThemes; var theme; var skin = settings.get("user/general/@skin"); var defaultThemes = { "light" : "ace/theme/cloud9_day", "light-gray" : "ace/theme/cloud9_day", "flat-light" : "ace/theme/cloud9_day", "flat-dark" : "ace/theme/cloud9_night_low_color", "dark" : "ace/theme/cloud9_night_low_color", "dark-gray" : "ace/theme/cloud9_night_low_color" }; // Fix broken settings if (!defaultThemes[skin]) { settings.set("user/general/@skin", "dark"); skin = "dark"; } if (isMinimal) { defaultThemes[skin] = "ace/theme/textmate"; } else { require([defaultThemes[skin]], function(){}); // Preload Themes } handle.__defineGetter__("theme", function(){ return theme; }); function addCorner(ace) { if (isMinimal) return; var shadow = document.createElement("div"); shadow.className = "scroll_shadow"; var corner = document.createElement("div"); corner.className = "ace_corner"; shadow.appendChild(corner); ace.renderer.scroller.appendChild(shadow); } function addCornerStyles(theme) { var sheet = document.getElementById(theme.cssClass).sheet; sheet.insertRule( "." + theme.cssClass + " .ace_corner" + "{" + "background: radial-gradient(at 5px 5px, " + (theme.isDark ? "rgba(0,0,0,0)" : "rgba(250,250,250,0)") + "5.5px," + theme.bg + "6px)" + "}", sheet.cssRules.length ); } function setTheme(path, isPreview, fromServer, $err) { // Get Theme or wait for theme to load theme = fromServer; if (!theme) { return $err || config.loadModule(path, function(m) { setTheme(path, isPreview, m, true); }); } if (!isPreview) { if (settings.get("user/ace/@theme") != path) { settings.set("user/ace/@theme", path); // Emit theme change event var style = (theme.isDark ? "dark" : "light"); if (settings.get("user/general/@skin").indexOf(style) == -1) layout.proposeLayoutChange(style, false, "ace"); } } if (lastTheme == theme) return; if (isMinimal) { if (!themeLoaded[path]) { themeLoaded[path] = true; handleEmit("themeInit", {theme: theme, path: path}); } return; } else { if (!themeLoaded[path]) { themeLoaded[path] = true; var cssClass = theme.cssClass; var div = document.createElement("div"); document.body.appendChild(div); div.innerHTML = "
"; div.className = cssClass; theme.bg = ui.getStyle(div.firstChild, "backgroundColor"); theme.fg = ui.getStyle(div.firstChild, "color"); theme.path = path; document.body.removeChild(div); addCornerStyles(theme); // Init Theme Event handleEmit("themeInit", {theme: theme, path: path}); } tabs.containers.forEach(function(container) { if (theme.isDark) ui.setStyleClass(container, "dark"); else ui.setStyleClass(container, "", ["dark"]); }); } var lTheme = lastTheme; lastTheme = theme; handleEmit("themeChange", { lastTheme: lTheme, theme: theme, path: path }); } // Theme passed in from the server if (options.theme) { ui.insertCss(options.theme.cssText, handle); define(options.theme.path, [], options.theme); // require([options.theme.path], function(){}); setTheme(options.theme.path, null, options.theme); } /***** Default Settings *****/ var BOOL = "getBool"; var STRING = "get"; var NUMBER = "getNumber"; // Name, Default Value, Type, Old Name, Store in Project Settings var font = "Monaco, Menlo, Ubuntu Mono, Consolas, source-code-pro, monospace"; var aceSettings = [ // detected from document value ["newLineMode", "unix", STRING, "newlinemode", 1], // Per document ["tabSize", "4", NUMBER, "tabsize", 1], ["useSoftTabs", "true", BOOL, "softtabs", 1], ["guessTabSize", "true", BOOL, "guesstabsize", 1], ["useWrapMode", "false", BOOL, "wrapmode"], ["wrapToView", "true", BOOL, "wrapmodeViewport"], // Ace ["fontSize", "12", NUMBER, "fontsize"], ["fontFamily", font, STRING, "fontfamily"], ["antialiasedfonts", "false", BOOL], ["overwrite", "false", BOOL, "overwrite"], ["selectionStyle", "line", STRING, "selectstyle"], ["cursorStyle", "ace", STRING, "cursorstyle"], ["highlightActiveLine", "true", BOOL, "activeline"], ["highlightGutterLine", "true", BOOL, "gutterline"], ["showInvisibles", "false", BOOL, "showinvisibles"], ["showPrintMargin", "true", BOOL, "showprintmargin"], ["displayIndentGuides", "true", BOOL, "showindentguides"], ["printMarginColumn", "80", NUMBER, "printmargincolumn"], ["behavioursEnabled", "true", BOOL, "behaviors"], ["wrapBehavioursEnabled", "false", BOOL, "wrapbehaviors"], ["scrollSpeed", "2", NUMBER, "scrollspeed"], ["showGutter", "true", BOOL, "gutter"], ["showFoldWidgets", "true", BOOL, "folding"], ["fadeFoldWidgets", "true", BOOL, "fadefoldwidgets"], ["highlightSelectedWord", "true", BOOL, "highlightselectedword"], ["animatedScroll", "true", BOOL, "animatedscroll"], ["scrollPastEnd", "0.5", NUMBER], ["mergeUndoDeltas", "off", STRING], ["theme", defaultThemes[skin], STRING, "theme"] ]; var docSettings = aceSettings.slice(1, 6); var editorSettings = aceSettings.slice(6); var projectSettings = aceSettings.slice(0, 4); var userSettings = aceSettings.slice(4); var docLut = {}; docSettings.forEach(function(x){ docLut[x[0]] = x }); /***** Undo Manager *****/ function AceUndoManager(undoManager, session) { var state = undoManager.getState(); this.$session = session; this.$undo = undoManager; this.$aceUndo = new UndoManager(); this.$aceUndo.c9UndoProxy = undoManager; undoManager.$aceUndo = this.$aceUndo; undoManager.add = this.add; undoManager.addSelection = this.addSelection; undoManager.undo = this.undo; undoManager.redo = this.redo; undoManager.reset = this.reset; undoManager.canUndo = this.canUndo; undoManager.canRedo = this.canRedo; undoManager.getState = this.getState; undoManager.setState = this.setState; undoManager.bookmark = this.bookmarkPosition; undoManager.isAtBookmark = this.isAtBookmark; undoManager.__defineGetter__("position", this.getPosition); undoManager.__defineGetter__("length", this.getLength); undoManager._emit = this._emit = undoManager.getEmitter(); this.deleyedEmit = lang.delayedCall(this._emit.bind(null, "change")) .schedule.bind(null, 0); this.setState(state, true); } function updateDeltas(deltas) { if (deltas[0] && deltas[0].deltas) { var oldDeltas = deltas.slice(); deltas.length = 0; oldDeltas.forEach(function(x) { deltas.push.apply(deltas, x.deltas); }); } return deltas; } AceUndoManager.prototype = { add: function(delta, doc) { this.$aceUndo.add(delta, doc); this._emit("change"); }, addSelection: function(range, rev) { this.$aceUndo.addSelection(range, rev); }, undo: function(dontSelect) { this.$aceUndo.undo(dontSelect); this._emit("change"); }, redo: function(dontSelect) { this.$aceUndo.redo(dontSelect); this._emit("change"); }, reset: function(){ this.$aceUndo.reset(); this._emit("change"); }, canUndo: function() { return this.$aceUndo.canUndo(); }, canRedo: function() { return this.$aceUndo.canRedo(); }, clearUndo: function() { this.$aceUndo.$undoStack = []; this._emit("change"); }, clearRedo: function() { this.$aceUndo.$redoStack = []; this._emit("change"); }, startNewGroup: function() { return this.$aceUndo.startNewGroup(); }, markIgnored: function(from, to) { return this.$aceUndo.markIgnored(from, to); }, getState: function() { var aceUndo = this.$aceUndo; var mark = -2; var aceMark = aceUndo.mark; var stack = []; function transform(deltaSet) { if (!deltaSet || !deltaSet.filter) { errorHandler.reportError("Misformed ace delta", { delta: deltaSet }); return; } var newDelta = deltaSet.filter(function(d) { if (d.id == aceMark) mark = stack.length; return d.action == "insert" || d.action == "remove"; }); if (newDelta.length) stack.push(newDelta); } aceUndo.$undoStack.forEach(transform); var pos = stack.length - 1; if (pos == -1 && aceUndo.isAtBookmark()) mark = pos; if (aceUndo.$redoStackBaseRev == aceUndo.$rev) aceUndo.$redoStack.forEach(transform); return { mark: mark, position: pos, stack: stack }; }, setState: function(e, silent) { var aceUndo = this.$aceUndo; var stack = e.stack || []; var marked = stack[e.mark] && stack[e.mark][0]; var pos = e.position + 1; var undo = stack.slice(0, pos); var redo = stack.slice(pos); var maxRev = aceUndo.$maxRev; function check(x) { if (!x.length) return false; if (!x[0].id || x[0].id < maxRev) { x[0].id = maxRev++; } else { maxRev = x[0].id; } return true; } aceUndo.$undoStack = undo.map(updateDeltas).filter(check); aceUndo.$redoStack = redo.map(updateDeltas).filter(check); var lastDeltaGroup = stack[stack.length - 1]; var lastRev = lastDeltaGroup && lastDeltaGroup[0] && lastDeltaGroup[0].id || 0; aceUndo.$rev = lastRev; aceUndo.$redoStackBaseRev = aceUndo.$rev; aceUndo.$maxRev = Math.max(maxRev, lastRev); var markedRev = marked && marked.id; if (markedRev != null) this.$aceUndo.bookmark(markedRev); else if (e.mark == e.position) this.$aceUndo.bookmark(); else this.$aceUndo.bookmark(-1); silent || this._emit("change"); }, isAtBookmark: function() { return this.$aceUndo.isAtBookmark(); }, bookmark: function(rev) { this.$aceUndo.bookmark(rev); this._emit("change"); }, bookmarkPosition: function(index) { if (index > -1) { var stack = this.$aceUndo.$undoStack; if (index >= stack.length) { index -= stack.length; stack = this.$aceUndo.$redoStack; index = stack.length - index; } var deltaSet = stack[index]; var rev = deltaSet && deltaSet[0] && deltaSet[0].id; if (rev == null) rev = -1; this.$aceUndo.bookmark(rev); } else if (index == -1) { this.$aceUndo.bookmark(0); } else { this.$aceUndo.bookmark(index); } this._emit("change"); }, addSession: function(session) { this.$aceUndo.addSession(session); }, getPosition: function() { var aceUndo = this.$aceUndo; return aceUndo.$undoStack.length - 1; }, getLength: function() { var aceUndo = this.$aceUndo; return aceUndo.$undoStack.length + aceUndo.$redoStack.length; } }; /***** Generic Load *****/ handle.on("load", function(){ if (!isMinimal) { // Preferences setPreferences(); // Menus setMenus(); // State Management c9.on("stateChange", function(e) { if (e.state & c9.NETWORK) menus.enableItem("View/Themes"); else menus.disableItem("View/Themes"); }, handle); } // Commands setCommands(); // Settings var lastSettings = {}; function updateSettings(e, list, prefix) { var options = {}; (list || aceSettings).forEach(function(setting) { options[setting[0]] = settings[setting[2]](prefix + "/ace/@" + setting[0]); }); // When loading from settings only set editor settings docSettings.forEach(function(setting) { var val = options[setting[0]]; if (val !== undefined) { setting[1] = val; delete options[setting[0]]; } }); handleEmit("settingsUpdate", { options: options }); if (options.theme) setTheme(options.theme); util.extend(lastSettings, options); } settings.on("read", function(e) { settings.setDefaults("user/ace", userSettings); settings.setDefaults("project/ace", projectSettings); // pre load custom mime types loadCustomExtensions(); setFontSmoothing(); updateSettings(null, userSettings, "user"); updateSettings(null, projectSettings, "project"); }, handle); // Listen to changes in the settings settings.on("user/ace", function(e) { var fstyle = settings.getBool("user/ace/@antialiasedfonts"); ui.setStyleRule(".ace_editor", apf.isChrome ? "WebkitFontSmoothing" : "MozOSXFontSmoothing", fstyle && (apf.isChrome ? "antialiased" : "grayscale") || "auto"); updateSettings(e, userSettings, "user"); }, handle); settings.on("project/ace", function(e) { updateSettings(e, projectSettings, "project"); }, handle); handle.on("newListener", function(event, listener) { if (event == "settingsUpdate") listener({options: lastSettings}); }); layout.on("themeChange", function(e) { setFontSmoothing(); if (e.type !== "ace" && settings.get("user/ace/@theme") != defaultThemes[e.oldTheme]) return false; }); layout.on("themeDefaults", function(e) { if (e.type != "ace") handle.setTheme(defaultThemes[e.theme]); }, handle); // CSS ui.insertCss(cssString, options.staticPrefix, handle); }); handle.on("unload", function(){ drawn = false; }); function setFontSmoothing(){ var fstyle = settings.getBool("user/ace/@antialiasedfonts"); ui.setStyleRule(".ace_editor", apf.isChrome ? "WebkitFontSmoothing" : "MozOSXFontSmoothing", fstyle && (apf.isChrome ? "antialiased" : "grayscale") || "auto"); } var drawn; function draw(){ if (drawn) return; drawn = true; mnuAce = new Menu({ id: "menu", items: [ new MenuItem({ position: 10, command: "cut", caption: "Cut"}, handle), new MenuItem({ position: 20, command: "copy", caption: "Copy" }, handle), new MenuItem({ position: 30, command: "paste", caption: "Paste" }, handle), new Divider({ position: 40 }, handle), new MenuItem({ position: 50, command: "selectall", caption: "Select All" }, handle), new Divider({ position: 60 }, handle) ] }, handle); mnuGutter = new Menu({ id: "menuGutter" }, handle); mnuGutter.on("show", function(e) { var ace = tabs.focussedTab.editor.ace; var region = ace.renderer.$gutterLayer.getRegion(e); var line = ace.renderer.screenToTextCoordinates(e.x, e.y).row; var className = ace.session.getBreakpoints()[line] || ""; mnuGutter.meta.ace = ace; mnuGutter.meta.line = line; mnuGutter.meta.region = region; mnuGutter.meta.className = className; }); handleEmit("draw"); } /***** Commands *****/ function setCommands() { function isAce(editor, allowBlured) { if (!editor || !editor.ace) return false; return allowBlured || editor.ace.isFocused(); } function fnWrap(command) { command.group = "Code Editor"; command.readOnly = command.readOnly || false; command.focusContext = true; var isAvailable = command.isAvailable; command.isAvailable = function(editor, args, event) { // checking editor.ace instead of editor.type == "ace" to make // commands avaliable in editors inheriting from ace if (event instanceof KeyboardEvent && !isAce(editor)) editor = apf.activeElement; if (!isAce(editor, true)) return false; if (!editor.ace.commands.byName[command.name] && !command.shared) return false; return isAvailable ? isAvailable(editor.ace) : true; }; command.findEditor = function(editor, e) { if (e && apf.activeElement && apf.activeElement.ace && apf.activeElement.ace.isFocused()) return apf.activeElement.ace; return editor && editor.ace || editor; }; return command; } if (!defaultCommands.wrapped) { defaultCommands.push.apply(defaultCommands, whitespaceUtil.commands); defaultCommands.forEach(fnWrap, defaultCommands); Object.defineProperty(defaultCommands, "wrapped", {value: true, configurable: true}); } if (!multiSelectCommands.wrapped) { multiSelectCommands.forEach(fnWrap, multiSelectCommands); Object.defineProperty(multiSelectCommands, "wrapped", {value: true, configurable: true}); } commands.addCommands(defaultCommands, handle, true); commands.addCommands(multiSelectCommands, handle, true); // Override ACE key bindings (conflict with goto definition) commands.commands.togglerecording.bindKey = { mac: "Command-Shift-R", win: "Alt-Shift-R" }; commands.commands.replaymacro.bindKey = { mac: "Command-Ctrl-R", win: "Alt-R" }; commands.commands["findnext"].hint = "search for the next occurrence of the search query your entered last"; commands.commands["findnext"].msg = "Navigating to next match."; commands.commands["findprevious"].hint = "search for the previous occurrence of the search query your entered last"; commands.commands["findprevious"].msg = "Navigating to previous match."; commands.addCommand(commands.commands.togglerecording, handle); commands.addCommand(commands.commands.replaymacro, handle); // when event for cmd-z in textarea is not canceled // chrome tries to find another textarea with pending undo and focus it // we do not want this to happen when ace instance is focused commands.addCommand(fnWrap({ name: "cancelBrowserUndoInAce", bindKey: { mac: "Cmd-Z|Cmd-Shift-Z|Cmd-Y", win: "Ctrl-Z|Ctrl-Shift-Z|Ctrl-Y", position: -10000 }, group: "ignore", exec: function(e) {}, readOnly: true, shared: true }), handle); function sharedCommand(command) { command.isAvailable = function(editor) { return editor && editor.type == "ace"; }; command.group = "Code Editor"; return command; } commands.addCommand(sharedCommand({ name: "syntax", exec: function(_, syntax) { if (typeof syntax == "object") syntax = syntax.argv && syntax.argv[1] || ""; syntax = modes.caption[syntax] || modes.extensions[syntax] || syntax; var tab = tabs.focussedTab; tab && tab.editor.setOption("syntax", syntax); }, commands: modes.caption }), handle); commands.addCommand(sharedCommand({ name: "largerfont", bindKey: { mac : "Command-+|Command-=", win : "Ctrl-+|Ctrl-=" }, exec: function(e) { var currSize = settings.get("user/ace/@fontSize"); settings.set("user/ace/@fontSize", ++currSize > 72 ? 72 : currSize); } }), handle); commands.addCommand(sharedCommand({ name: "smallerfont", bindKey: { mac : "Command--", win : "Ctrl--" }, exec: function(e) { var currSize = settings.get("user/ace/@fontSize"); settings.set("user/ace/@fontSize", --currSize < 1 ? 1 : currSize); } }), handle); commands.addCommand(sharedCommand({ name: "toggleWordWrap", bindKey: {win: "Ctrl-Q", mac: "Ctrl-W"}, exec: function(editor) { editor.setOption("wrap", editor.getOption("wrap") == "off"); } }), handle); } /***** Preferences *****/ function setPreferences(){ prefs.add({ "Project" : { position: 10, "Code Editor (Ace)" : { position: 100, "Soft Tabs" : { type: "checked-spinner", checkboxPath: "project/ace/@useSoftTabs", path: "project/ace/@tabSize", min: "1", max: "64", position: 100 }, "Autodetect Tab Size on Load" : { type: "checkbox", path: "project/ace/@guessTabSize", position: 150 }, "New File Line Endings" : { type: "dropdown", path: "project/ace/@newLineMode", width: 130, items: [ { caption : "Windows (CRLF)", value : "windows" }, { caption : "Unix (LF)", value : "unix" } // { caption : "Mac OS 9 (CR)", value : "macos9" } ], position: 200 } } } }, handle); prefs.add({ "Editors" : { position: 400, "Code Editor (Ace)" : { position: 200, "Auto-pair Brackets, Quotes, etc." : { type: "checkbox", position: 1000, path: "user/ace/@behavioursEnabled" }, "Wrap Selection with Brackets, Quotes, etc." : { type: "checkbox", position: 1001, path: "user/ace/@wrapBehavioursEnabled" }, "Code Folding" : { type: "checkbox", position: 2000, path: "user/ace/@showFoldWidgets" }, "Fade Fold Widgets" : { type: "checkbox", position: 2500, path: "user/ace/@fadeFoldWidgets" }, "Full Line Selection" : { type: "checkbox", position: 3000, path: "user/ace/@selectionStyle", values: "line|text" }, "Highlight Active Line" : { type: "checkbox", position: 4000, path: "user/ace/@highlightActiveLine" }, "Highlight Gutter Line" : { type: "checkbox", position: 4000, path: "user/ace/@highlightGutterLine" }, "Show Invisible Characters" : { type: "checkbox", position: 5000, path: "user/ace/@showInvisibles" }, "Show Gutter" : { type: "checkbox", position: 6000, path: "user/ace/@showGutter" }, "Show Indent Guides" : { type: "checkbox", position: 6500, path: "user/ace/@displayIndentGuides" }, "Highlight Selected Word" : { type: "checkbox", position: 7000, path: "user/ace/@highlightSelectedWord" }, "Scroll Past the End of the Document" : { type: "dropdown", width: 150, path: "user/ace/@scrollPastEnd", items: [ { caption : "Off", value : "0" }, { caption : "Half Editor Height", value : "0.5" }, { caption : "Full Editor Height", value : "1" } ], position: 8000 }, "Animate Scrolling" : { type: "checkbox", path: "user/ace/@animatedScroll", position: 9000 }, "Font Family" : { type: "textbox", path: "user/ace/@fontFamily", position: 10000 }, "Font Size" : { type: "spinner", path: "user/ace/@fontSize", min: "1", max: "72", position: 10500 }, "Antialiased Fonts" : { type: "checkbox", path: "user/ace/@antialiasedfonts", position: 10600 }, "Show Print Margin" : { type: "checked-spinner", checkboxPath: "user/ace/@showPrintMargin", path: "user/ace/@printMarginColumn", min: "1", max: "200", position: 11000 }, "Mouse Scroll Speed" : { type: "spinner", path: "user/ace/@scrollSpeed", min: "1", max: "8", position: 13000, }, "Cursor Style" : { type: "dropdown", path: "user/ace/@cursorStyle", items: [ { caption : "Ace", value : "ace" }, { caption : "Slim", value : "slim" }, { caption : "Smooth", value : "smooth" }, { caption : "Smooth And Slim", value : "smooth slim" }, { caption : "Wide", value : "wide" }, ], position: 13500 }, "Merge Undo Deltas" : { type: "dropdown", path: "user/ace/@mergeUndoDeltas", items: [ { caption : "Always", value : "always" }, { caption : "Never", value : "off" }, { caption : "Timed", value : "true" } ], position: 14000 }, "Enable Wrapping For New Documents" : { type: "checkbox", path: "user/ace/@useWrapMode" }, } } }, handle); } /***** Menus *****/ function setMenus() { function addEditorMenu(path, commandName) { return menus.addItemByPath(path, new ui.item({ command: commandName }), c += 100, handle); } var c = 20000; addEditorMenu("Tools/Toggle Macro Recording", "togglerecording"); //@todo this needs some more work addEditorMenu("Tools/Play Macro", "replaymacro"); //@todo this needs some more work c = 600; menus.addItemByPath("Edit/~", new ui.divider(), c += 100, handle); menus.addItemByPath("Edit/Selection/", null, c += 100, handle); menus.addItemByPath("Edit/Line/", null, c += 100, handle); menus.addItemByPath("Edit/Text/", null, c += 100, handle); menus.addItemByPath("Edit/Comment/", null, c += 100, handle); menus.addItemByPath("Edit/Code Folding/", null, c += 100, handle); c = 0; addEditorMenu("Edit/Line/Indent", "indent"), addEditorMenu("Edit/Line/Outdent", "outdent"), addEditorMenu("Edit/Line/Move Line Up", "movelinesup"), addEditorMenu("Edit/Line/Move Line Down", "movelinesdown"), menus.addItemByPath("Edit/Line/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Line/Copy Lines Up", "copylinesup"), addEditorMenu("Edit/Line/Copy Lines Down", "copylinesdown"), menus.addItemByPath("Edit/Line/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Line/Remove Line", "removeline"), addEditorMenu("Edit/Line/Remove to Line End", "removetolineend"), addEditorMenu("Edit/Line/Remove to Line Start", "removetolinestart"), menus.addItemByPath("Edit/Line/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Line/Split Line", "splitline"); c = 0; addEditorMenu("Edit/Comment/Toggle Comment", "togglecomment"); c = 0; addEditorMenu("Edit/Text/Remove Word Right", "removewordright"), addEditorMenu("Edit/Text/Remove Word Left", "removewordleft"), menus.addItemByPath("Edit/Text/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Text/Align", "alignCursors"); addEditorMenu("Edit/Text/Transpose Letters", "transposeletters"); menus.addItemByPath("Edit/Text/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Text/To Upper Case", "touppercase"), addEditorMenu("Edit/Text/To Lower Case", "tolowercase"); c = 0; addEditorMenu("Edit/Code Folding/Fold", "fold"), addEditorMenu("Edit/Code Folding/Unfold", "unfold"), menus.addItemByPath("Edit/Code Folding/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Code Folding/Fold All", "foldall"), addEditorMenu("Edit/Code Folding/Unfold All", "unfoldall"); c = 0; addEditorMenu("Edit/Selection/Select All", "selectall"), addEditorMenu("Edit/Selection/Split Into Lines", "splitIntoLines"), addEditorMenu("Edit/Selection/Single Selection", "singleSelection"), menus.addItemByPath("Edit/Selection/~", new ui.divider(), c += 100, handle); menus.addItemByPath("Edit/Selection/Multiple Selections/", null, c += 100, handle); menus.addItemByPath("Edit/Selection/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Selection/Select Word Right", "selectwordright"), addEditorMenu("Edit/Selection/Select Word Left", "selectwordleft"), menus.addItemByPath("Edit/Selection/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Selection/Select to Line End", "selecttolineend"), addEditorMenu("Edit/Selection/Select to Line Start", "selecttolinestart"), menus.addItemByPath("Edit/Selection/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Selection/Select to Document End", "selecttoend"); addEditorMenu("Edit/Selection/Select to Document Start", "selecttostart"); c = 0; addEditorMenu("Edit/Selection/Multiple Selections/Add Cursor Up", "addCursorAbove"), addEditorMenu("Edit/Selection/Multiple Selections/Add Cursor Down", "addCursorBelow"), addEditorMenu("Edit/Selection/Multiple Selections/Move Active Cursor Up", "addCursorAboveSkipCurrent"), addEditorMenu("Edit/Selection/Multiple Selections/Move Active Cursor Down", "addCursorBelowSkipCurrent"), menus.addItemByPath("Edit/Selection/Multiple Selections/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Selection/Multiple Selections/Add Next Selection Match", "selectMoreAfter"), addEditorMenu("Edit/Selection/Multiple Selections/Add Previous Selection Match", "selectMoreBefore"), menus.addItemByPath("Edit/Selection/Multiple Selections/~", new ui.divider(), c += 100, handle); addEditorMenu("Edit/Selection/Multiple Selections/Merge Selection Range", "splitIntoLines"); /**** View ****/ menus.addItemByPath("View/~", new ui.divider(), 290000, handle); menus.addItemByPath("View/Font Size/", null, 290001, handle); c = 0; addEditorMenu("View/Font Size/Increase Font Size", "largerfont"); addEditorMenu("View/Font Size/Decrease Font Size", "smallerfont"); menus.addItemByPath("View/Gutter", new ui.item({ type: "check", checked: "user/ace/@showGutter" }), 500, handle); var grpNewline = new ui.group(); menus.addItemByPath("File/~", new ui.divider(), 1400, handle); menus.addItemByPath("File/Line Endings/", new ui.menu({ "onprop.visible" : function(e) { if (e.value) { var tab = tabs.focussedTab; var ace = tab && tab.editor && tab.editor.ace; if (ace && tab.editor.type == "ace") { this.enable(); var mode = ace.session.doc.getNewLineMode(); grpNewline.setValue(mode); } else { this.disable(); } } }, "onitemclick" : function(e) { var tab = tabs.focussedTab; var ace = tab && tab.editor && tab.editor.ace; if (ace && tab.editor.type == "ace") { ace.session.doc.setNewLineMode(e.value); tab.document.undoManager.bookmark(-2); } } }), 1500, handle); menus.addItemByPath("File/Line Endings/Windows (CRLF)", new ui.item({ type: "radio", value: "windows", group: grpNewline }), 200, handle); menus.addItemByPath("File/Line Endings/Unix (LF)", new ui.item({ type: "radio", value: "unix", group: grpNewline }), 300, handle); menus.addItemByPath("View/Syntax/", new ui.menu({ "onprop.visible" : function(e) { if (e.value) { if (!this.childNodes.length) rebuildSyntaxMenu(); this.$initChildren(); var tab = tabs.focussedTab; var c9Session = tab && tab.editor && tab.document.getSession(); if (!c9Session || !c9Session.session) { this.disable(); } else { this.enable(); var val = c9Session.session.syntax || c9Session.session.customSyntax || "auto"; this.select(grpSyntax, val); } } }, "onitemclick" : function(e) { var tab = tabs.focussedTab; if (tab) { var session = tab.document.getSession(); setSyntax(session, e.value); } } }), 300000, handle); menus.addItemByPath("View/~", new ui.divider(), 400000, handle); var wrapToggle = function(e) { var tab = tabs.focussedTab; var editor = tab && tab.editor; var mnuWrap = handle.getElement("mnuWrap"); var mnuWrapPM = handle.getElement("mnuWrapPrintMargin"); mnuWrapPM.setAttribute("disabled", !mnuWrap.checked); var wrap = mnuWrap.checked; if (mnuWrapPM.checked && (wrap || e.currentTarget == mnuWrapPM)) wrap = "printMargin"; editor.setOption("wrap", wrap); }; menus.addItemByPath("View/Wrap Lines", new ui.item({ id: "mnuWrap", type: "check", onclick: wrapToggle, isAvailable: function(editor) { if (!editor || editor.type != "ace") return false; var mnuWrap = handle.getElement("mnuWrap"); var mnuWrapPrintMargin = handle.getElement("mnuWrapPrintMargin"); var wrap = editor.getOption("wrap"); mnuWrap.setAttribute("checked", !ui.isFalse(wrap)); mnuWrapPrintMargin.setAttribute("checked", wrap == "printMargin"); return true; } }), 500000, handle), menus.addItemByPath("View/Wrap To Print Margin", new ui.item({ id: "mnuWrapPrintMargin", type: "check", onclick: wrapToggle, isAvailable: function(editor) { return editor && editor.type == "ace"; } }), 600000, handle); c = 0; /**** Goto ****/ menus.addItemByPath("Goto/~", new ui.divider(), c = 399, handle); addEditorMenu("Goto/Next Error", "goToNextError"); addEditorMenu("Goto/Previous Error", "goToPreviousError"); menus.addItemByPath("Goto/~", new ui.divider(), c += 200, handle); addEditorMenu("Goto/Word Right", "gotowordright"); addEditorMenu("Goto/Word Left", "gotowordleft"); menus.addItemByPath("Goto/~", new ui.divider(), c += 100, handle); addEditorMenu("Goto/Line End", "gotolineend"); addEditorMenu("Goto/Line Start", "gotolinestart"); menus.addItemByPath("Goto/~", new ui.divider(), c += 100, handle); addEditorMenu("Goto/Jump to Matching Brace", "jumptomatching"); menus.addItemByPath("Goto/~", new ui.divider(), c += 100, handle); addEditorMenu("Goto/Scroll to Selection", "centerselection"); tabs.on("focus", function(e) { var action = e.tab.editor.type != "ace" ? "disable" : "enable"; ["Edit/Comment", "Edit/Text", "Edit/Code Folding", "Edit/Convert Case", "Edit/Line", "Edit/Selection", "View/Syntax", "View/Font Size", "View/Syntax/Other", "View/Syntax", "File/Line Endings" ].forEach(function(path) { var menu = menus.get(path).menu; if (menu) menu[action](); }); }); /**** Themes ****/ grpThemes = new ui.group(); menus.addItemByPath("View/Themes/", new ui.menu({ "onprop.visible" : function(e) { if (e.value) grpThemes.setValue(settings.get("user/ace/@theme")); } }), 350000, handle); // Create Theme Menus for (var name in themes) { if (themes[name] instanceof Array) { // Add Menu Item (for submenu) menus.addItemByPath("View/Themes/" + name + "/", null, themeCounter++, handle); themes[name].forEach(function (n) { // Add Menu Item var themeprop = Object.keys(n)[0]; addThemeMenu(name + "/" + themeprop, n[themeprop], -1); }); } else { // Add Menu Item addThemeMenu(name, null, themeCounter++); } } /**** Syntax ****/ grpSyntax = new ui.group(); handle.addElement(grpNewline, grpSyntax, grpThemes); } var preview; var setMenuThemeDelayed = lang.delayedCall(function(){ setMenuTheme(preview, true); }, 150); function setMenuTheme(path, isPreview) { setTheme(path || settings.get("user/ace/@theme"), isPreview); } function addThemeMenu(name, path, index, plugin) { menus.addItemByPath("View/Themes/" + name, new ui.item({ type: "radio", value: path || themes[name], group: grpThemes, onmouseover: function(e) { preview = this.value; setMenuThemeDelayed.schedule(); }, onmouseout: function(e) { preview = null; setMenuThemeDelayed.schedule(); }, onclick: function(e) { setMenuTheme(e.currentTarget.value); } }), index == -1 ? undefined : index || themeCounter++, plugin || handle); } function addTheme(css, plugin){ var theme = { cssText: css }; var firstLine = css.split("\n", 1)[0].replace(/\/\*|\*\//g, "").trim(); firstLine.split(";").forEach(function(n){ if (!n) return; var info = n.split(":"); theme[info[0].trim()] = info[1].trim(); }); theme.isDark = theme.isDark == "true"; themes[theme.name] = theme; ui.insertCss(exports.cssText, plugin); addThemeMenu(theme.name, theme, null, plugin); handleEmit("addTheme"); plugin.addOther(function(){ delete themes[theme.name]; handleEmit("removeTheme"); }); } function rebuildSyntaxMenu() { menus.remove("View/Syntax/"); var c = 0; menus.addItemByPath("View/Syntax/Auto-Select", new ui.item({ type: "radio", value: "auto", group: grpSyntax }), c += 100, handle); menus.addItemByPath("View/Syntax/Plain Text", new ui.item({ type: "radio", value: "text", group: grpSyntax }), c += 100, handle); menus.addItemByPath("View/Syntax/~", new ui.divider(), c += 100, handle); var modeList = Object.keys(modes.byName).map(function(x) { return modes.byName[x]; }).sort(function(m1, m2) { return m2.order - m1.order || m1.caption.localeCompare(m2.caption); }); var groupNum = modeList[0] && modeList[0].order; for (var i = 0; i < modeList.length; i++) { var mode = modeList[i]; if (mode.order < 0) break; if (mode.order < groupNum) { groupNum = Math.min(mode.order, groupNum / 1000); menus.addItemByPath("View/Syntax/~", new ui.divider(), c += 100, handle); } menus.addItemByPath("View/Syntax/" + mode.caption, new ui.item({ type: "radio", value: mode.name, group: grpSyntax, }), c += 100, handle); } } var updateSyntaxMenu = lang.delayedCall(function() { rebuildSyntaxMenu(); tabs.getTabs().forEach(function(tab) { if (tab.editorType == "ace") { var c9Session = tab.document.getSession(); if (c9Session && c9Session.session) { var syntax = getSyntax(c9Session, tab.path); if (syntax) c9Session.setOption("syntax", syntax); } } }); }, 50); /***** Syntax *****/ function defineSyntax(opts) { if (!opts.name || !opts.caption) throw new Error("malformed syntax definition"); var name = opts.name; modes.byCaption[opts.caption] = opts; modes.byName[name] = opts; opts.order = opts.order || 0; if (!opts.extensions) opts.extensions = ""; opts.extensions.split("|").forEach(function(ext) { modes.extensions[ext] = name; }); updateSyntaxMenu.schedule(); } function getExtOrName(path) { var fileName = path.substr(path.lastIndexOf("/") + 1); var extPos = fileName.lastIndexOf(".") + 1; if (extPos) return fileName.substr(extPos).toLowerCase(); // special case for new files if (/^Untitled\d+$/.test(fileName)) fileName = fileName.replace(/\d+/, ""); return "^" + fileName; } function getSyntaxForPath(path) { var ext = getExtOrName(path); var modeName = modes.customExtensions[ext] || modes.extensions[ext]; return modes.byName[modeName] ? modeName : ""; } function setSyntaxForPath(path, syntax, noOverwrite) { if (!path) return false; syntax = modes.byName[syntax] ? syntax : ""; var ext = getExtOrName(path); var changed; if (syntax) { if (!modes.extensions[ext] || !noOverwrite) { modes.customExtensions[ext] = syntax; changed = true; } } else if (modes.customExtensions[ext]) { delete modes.customExtensions[ext]; changed = true; } if (changed) settings.setJson("user/ace/custom-types", modes.customExtensions); return changed; } function getMode(syntax) { syntax = (syntax || settings.get("project/ace/@defaultSyntax") || "text").toLowerCase(); if (syntax.indexOf("/") == -1) syntax = "ace/mode/" + syntax; return syntax; } function loadCustomExtensions() { var custom = settings.getJson("user/ace/custom-types"); if (!custom) return; Object.keys(custom).forEach(function(ext) { var mode = custom[ext]; if (modes.byName[mode]) modes.customExtensions[ext] = mode; }); } function detectSyntax(c9Session, path) { if (!c9Session.session || !c9Session.session.getLine) return; // todo move this into ace mode util var firstLine = c9Session.session.getLine(0); var syntax = ""; if (!firstLine) { return; } else if (/^#!/.test(firstLine)) { var match = firstLine.match(/\b(node|bash|sh)\b/); switch (match && match[1]) { case "node": syntax = "javascript"; break; case "sh": // fallthrough case "bash": syntax = "sh"; break; default: syntax = ""; break; } } else if (/<\?xml/.test(firstLine)) { syntax = "xml"; } else if (/^{/.test(firstLine)) { syntax = "json"; } else if (/\.(bashrc|inputrc)$/.test(path)) { syntax = "sh"; } else if (/\.(git(attributes|config|ignore)|npmrc)$/.test(path)) { syntax = "ini"; } return syntax; } function getSyntax(c9Session, path) { var syntax = c9Session.session.customSyntax || path && getSyntaxForPath(path) || detectSyntax(c9Session, path); return modes.byName[syntax] ? syntax : ""; } function setSyntax(c9Session, syntax, forThisOnly) { var c9doc = c9Session.session.c9doc; syntax = modes.byName[syntax] ? syntax : ""; var path = c9doc.tab.path; if (!forThisOnly && !setSyntaxForPath(path, syntax, true)) c9Session.session.customSyntax = syntax; c9doc.editor.setOption("syntax", syntax || getSyntax(c9Session, path), c9Session); } function cloneSession(session, undoManager) { var s = new EditSession(session.getDocument(), session.getMode()); if (!undoManager) undoManager = session.getUndoManager(); if (undoManager) { s.setUndoManager(undoManager); } // Overwrite the default $informUndoManager function such that new deltas // aren't added to the undo manager from the new and the old session. s.$informUndoManager = lang.delayedCall(function() { s.$deltas = []; }); // Copy over 'settings' from the session. s.setOptions(session.getOptions()); s.$foldData = session.$cloneFoldData(); var ignore = false; var changeAnnotation = function(){ if (ignore) return; ignore = true; s.setAnnotations(session.getAnnotations()); ignore = false; }; var changeMode = function(e) { s.setMode(session.getMode()); }; var changeBreakpoint = function(){ s.$breakpoints = session.$breakpoints; s._emit("changeBreakpoint", {}); }; var setWrap = function(e) { s.setOption("wrap", e.value); }; session.on("changeAnnotation", changeAnnotation)(); session.on("changeMode", changeMode); session.on("changeBreakpoint", changeBreakpoint)(); session.on("setWrap", setWrap); s.on("changeAnnotation", function(){ if (ignore) return; ignore = true; session.setAnnotations(s.getAnnotations()); ignore = false; })(); s.cleanup = function(){ session.removeListener("changeAnnotation", changeAnnotation); session.removeListener("changeMode", changeMode); session.removeListener("changeBreakpoint", changeBreakpoint); session.removeListener("setWrap", setWrap); }; s.c9doc = session.c9doc; s.cloned = true; return s; } /** * The ace handle, responsible for events that involve all ace * instances. This is the object you get when you request the ace * service in your plugin. * * Example: * * define(function(require, exports, module) { * main.consumes = ["ace"]; * main.provides = ["myplugin"]; * return main; * * function main(options, imports, register) { * var aceHandle = imports.ace; * * aceHandle.on("create", function(e) { * // This is an ace editor instance * var ace = e.editor; * }) * }); * }); * * * @class ace * @extends Plugin * @singleton */ handle.freezePublicAPI({ /** * The context menu that is displayed when right clicked in the ace * editing area. * @property {Menu} contextMenu * @readonly */ get contextMenu(){ draw(); return mnuAce }, /** * The context menu that is displayed when right clicked in the ace * gutter area. * @property {Menu} gutterContextMenu * @readonly */ get gutterContextMenu(){ draw(); return mnuGutter }, /** * Ace Themes * @property {Object} themese */ themes: themes, _events: [ /** * Fires once for each ace instance that is instantiated. * * Note that this event does not only fire for each ace instance * that is created, but it also fires for all ace instances that * have been created and are still around. * * @event create * @param {Object} e * @param {Editor} e.editor */ "create", /** * Fires when a new theme is initialized. * @event themeInit * @param {Object} e * @param {Object} e.theme Describes the theme that is initialized. * @param {String} e.theme.cssClass The css class name related to the theme. * @param {String} e.theme.bg The background color for this theme. * @param {String} e.theme.fg The foreground color for this theme. * @param {String} e.theme.path The path of this theme. * @param {Boolean} e.theme.isDark Specifies whether this is a dark theme or a light theme. * @param {String} e.path The path of the theme. */ "themeInit", /** * Fires when the current theme changes to another theme. * * See also {@link ace#setTheme}. * * @event themeChange * @param {Object} e * @param {Object} e.theme Describes the theme that is initialized. * @param {String} e.theme.cssClass The css class name related to the theme. * @param {String} e.theme.bg The background color for this theme. * @param {String} e.theme.fg The foreground color for this theme. * @param {String} e.theme.path The path of this theme. * @param {Boolean} e.theme.isDark Specifies whether this is a dark theme or a light theme. * @param {Object} e.lastTheme Describes the theme that is initialized. * @param {String} e.lastTheme.cssClass The css class name related to the theme. * @param {String} e.lastTheme.bg The background color for this theme. * @param {String} e.lastTheme.fg The foreground color for this theme. * @param {String} e.lastTheme.path The path of this theme. * @param {Boolean} e.lastTheme.isDark Specifies whether this is a dark theme or a light theme. * @param {String} e.path The path of the theme. */ "themeChange", /** * Fires when an ace's EditSession is inited for the Cloud9 document * the ace's EditSession can be get using: * doc.getSession().session * * @event initAceSession * @param {Object} e * @param {Object} e.doc The document with the EditSession created */ "initAceSession", /** * Fires when the ace context menus are drawn * @event draw */ "draw" ], /** * Set the theme for ace. * * Here's a list of default themes: * * * ace/theme/ambiance * * ace/theme/chrome * * ace/theme/clouds * * ace/theme/clouds_midnight * * ace/theme/cobalt * * ace/theme/crimson_editor * * ace/theme/dawn * * ace/theme/dreamweaver * * ace/theme/eclipse * * ace/theme/github * * ace/theme/idle_fingers * * ace/theme/kr_theme * * ace/theme/merbivore * * ace/theme/merbivore_soft * * ace/theme/mono_industrial * * ace/theme/monokai * * ace/theme/pastel_on_dark * * ace/theme/solarized_dark * * ace/theme/solarized_light * * ace/theme/textmate * * ace/theme/tomorrow * * ace/theme/tomorrow_night * * ace/theme/tomorrow_night_blue * * ace/theme/tomorrow_night_bright * * ace/theme/tomorrow_night_eighties * * ace/theme/twilight * * ace/theme/vibrant_ink * * ace/theme/xcod * * @method setTheme * @param {String} path The path of the theme file. * @fires themeInit * @fires themeChange */ setTheme: setTheme, /** * Add new syntax to the menu * * See also {@link ace#setSyntax}. * * @param {Object} syntax * @param {Object} syntax.caption Caption to display in the menu * @param {Number} syntax.order order in the menu * @param {String} syntax.name The path to corresponding ace language mode. (if doesn't contain "/" assumed to be from "ace/mode/Option Name | Possible Values |
"theme" | The path to the new theme. |
"syntax" | The path to the ace mode (e.g. ace/mode/javascript). |
"newLineMode" | One of the following values: "windows", "unix", "auto". |
"tabSize" | Number specifying the amount of spaces that represent a tab. |
"useSoftTabs" | Boolean specifying whether to insert spaces when pressing the tab key. |
"useWrapMode" | Specifies whether the text is wrapped |
"wrapToView" | Specifies whether the text is wrapped to the viewport, or to the print margin. |
"wrapBehavioursEnabled" | Specifies whether selection wraps with Brackets, Quotes, etc. |
"fontSize" | Number specifying the font size in px. |
"fontFamily" | String specifying the font family in css syntax. |
"overwrite" | Boolean toggling overwrite mode. |
"selectionStyle" | One of the following values: "line" (select the entire line), "text" (only select the text). |
"cursorStyle" | One of the following values: "ace", "slim", "smooth", "wide" |
"highlightActiveLine" | Boolean specifying whether to show highlighting of the line where the cursor is at. |
"highlightGutterLine" | Boolean specifying whether to show highlighting in the gutter of the line where the cursor is at. |
"showInvisibles" | Boolean specifying whether to show the invisible characters such as space, tab, newline. |
"printMarginColumn" | Number specifying where the print margin will be in number of characters from the gutter. |
"showPrintMargin" | Boolean specifying whether to show the print margin line (usually 80 chars) |
"displayIndentGuides" | Boolean specifying whether to show the lines at each indentation mark |
"behavioursEnabled" | Boolean specifying whether brackets are auto-paired. |
"scrollSpeed" | Number specifying the number of rows that are scrolled when using the scrollwheel. |
"showGutter" | Boolean specifying whether to show the gutter. |
"showFoldWidgets" | Boolean specifying whether to show the fold widgets. |
"fadeFoldWidgets" | Boolean specifying whether to fade the fold widgets into view on hover. |
"highlightSelectedWord" | Boolean specifying whether to highlight words where the cursor is on. |
"animatedScroll" | Boolean specifying whether scrolling is animated. |
"scrollPastEnd" | Number specifying how far the user can scroll past the end. There are 3 possible values: 0, 0.5, 1. |
"mergeUndoDeltas" | Boolean specifying whether to combine multiple operations as one on the undo stack. |