define(function(require, exports, module) { main.consumes = [ "editors", "Editor", "ui", "scm", "layout", "settings", "List", "threewaymerge", "menus", "Menu", "MenuItem", "Divider", "ace" ]; main.provides = ["diff.unified"]; return main; function main(options, imports, register) { var settings = imports.settings; var editors = imports.editors; var Editor = imports.Editor; var List = imports.List; var scmProvider = imports.scm; var layout = imports.layout; var MenuItem = imports.MenuItem; var Divider = imports.Divider; var merge = imports.threewaymerge; var Menu = imports.Menu; var ace = imports.ace; var ui = imports.ui; var dirname = require("path").dirname; var basename = require("path").basename; var DiffView = require("./diff/unified").DiffView; var escapeHTML = require("ace/lib/lang").escapeHTML; /***** Initialization *****/ var extensions = []; // :( var BGCOLOR = { "flat-light": "#F1F1F1", "flat-dark": "#3D3D3D", "light": "#D3D3D3", "light-gray": "#D3D3D3", "dark": "#3D3D3D", "dark-gray": "#3D3D3D" }; var menuAce; var menuGutter; var scm; var handle = editors.register("diff.unified", "Compare", DiffViewer, extensions); var handleEmit = handle.getEmitter(); function createMenu() { menuAce = 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); menuGutter = new Menu({ id: "menu-gutter", items: [ ] }, handle); } scmProvider.on("scm", function(implementation) { scm = implementation; handleEmit.sticky("ready"); }, handle); function DiffViewer() { // TODO it is too difficult to hook into initialization flow of ace plugin // so we have to copy paste bunch of code here :( // var Baseclass = editors.findEditor("ace"); // var plugin = new Baseclass(true, []); var plugin = new Editor(true, []); var emit = plugin.getEmitter(); var diffview; var lastAce; var lblLeft, lblRight, btnNext, btnPrev, btnFold, container; var toolbar, activeSession, activeDocument, hostedtoolbar; plugin.on("draw", function(e) { var tab = e.tab; // lblLeft = new ui.label({ flex:1 }); // lblRight = new ui.label({ flex:1, class:"right" }); // btnNext = new ui.button({ // caption: ">", // height: 24, // skin: "c9-toolbarbutton-glossy", // onclick: function() { // diffview.gotoNext(1); // } // }); // btnPrev = new ui.button({ // caption: "<", // height: 24, // skin: "c9-toolbarbutton-glossy", // onclick: function() { // diffview.gotoNext(-1); // } // }); // btnFold = new ui.button({ // caption: "Fold", // height: 24, // skin: "c9-toolbarbutton-glossy", // onclick: function() { // if (diffview.orig.session.$foldData.length) // diffview.orig.session.unfold() // else // diffview.foldUnchanged(); // } // }); container = new ui.bar({ margin: "0 0 0 20", class: "ace_diff-container" }); tab.appendChild(new ui.vsplitbox({ anchors: "0 0 0 0", childNodes: [ // toolbar = new ui.hbox({ // class: "difftoolbar", // height: 36, // align: "center", // edge: "0 5 0 3", // padding: 3, // childNodes: [ // lblLeft, // new ui.hbox({ // padding: 3, // edge: 3, // margin: "0 7 0 7", // align: "center", // class: "buttons", // childNodes: [ btnPrev, btnFold, btnNext ] // }), // lblRight // ] // }), toolbar = new ui.hbox({ height: 36 }), container ] })); createToolbar(); diffview = new DiffView(container.$ext, {}); diffview.editor.setOption("fontSize", 11); diffview.editor.renderer.scrollBarV.$minWidth = 20; // // temporary workaround for apf focus bugs // // only blur is needed sinse the rest is handled by tabManager // // todo remove this when there is proper focus manager // tab.$blur = function(e) { // var ace = plugin.ace; // can be null when called for destroyed tab // if (!ace || !e || !e.toElement || e.toElement.tagName == "menu") // return; // if (!ace.isFocused()) // ace.renderer.visualizeBlur(); // else // ace.textInput.blur(); // }; // function focusApf() { // var page = apf.findHost(diffview.container.parentElement.parentElement); // if (apf.activeElement != page) // page.focus(); // } // function updateLastAce(e, ace) { lastAce = ace; } // diffview.edit.on("focus", focusApf); // diffview.orig.on("focus", focusApf); // diffview.edit.keyBinding.setDefaultHandler(null); // diffview.orig.keyBinding.setDefaultHandler(null); // diffview.edit.on("focus", updateLastAce); // diffview.orig.on("focus", updateLastAce); // lastAce = diffview.edit; // // createProgressIndicator(e.htmlNode); // tab.on("contextmenu", function(e) { // if (!menuAce) createMenu(); // var target = e.htmlEvent.target; // var gutter = plugin.diffview.gutterEl; // // Set Gutter Context Menu // if (ui.isChildOf(gutter, target, true)) { // menuGutter.show(e.x, e.y); // } // // Set main Ace Context Menu // else { // menuAce.show(e.x, e.y); // } // return false; // }); }); /***** Method *****/ function createToolbar() { var fileMenu = createMenu("file"); var commitMenu = createMenu("commit"); var btnFile = ui.insertByIndex(toolbar, new ui.button({ caption: "Files", submenu: fileMenu.aml }), 100, plugin); var btnCommit = ui.insertByIndex(toolbar, new ui.button({ caption: "Commits", submenu: commitMenu.aml }), 200, plugin); plugin.on("toolbar", function(e) { if (e.session.branch) { btnFile.show(); btnCommit.show(); } else { btnFile.hide(); btnCommit.hide(); } }); // Other elements hostedtoolbar = ui.insertByIndex(toolbar, new ui.hbox({ style: "flex:1" }), 10000, plugin); ui.insertByIndex(toolbar, new ui.radiobutton({ label: "Unified" }), 10010, plugin); ui.insertByIndex(toolbar, new ui.radiobutton({ label: "Split" }), 10020, plugin); fileMenu.on("show", function() { var list = fileMenu.meta.$list; list.setRoot.session.files; }); commitMenu.on("show", function() { var list = commitMenu.meta.$list; list.setRoot.session.commits; }); } function createMenu(mode) { var menu = new Menu({}, plugin); var htmlNode = menu.aml.$ext; var list = menu.meta.$list = new List({ container: htmlNode, scrollMargin: [10, 0], // theme: "filetree", getIconHTML: function(node) { var icon = node.isFolder ? "folder" : "status-icon-" + node.type; // if (node.parent == conflicts) // icon = "status-icon-conflict"; // if (node.status === "loading") icon = "loading"; // if (tree.model.twoWay && !node.isFolder) // icon += " clickable"; return "" + (node.type || "") + ""; }, getCaptionHTML: function(node) { if (node.path) { var path = node.labelPath || node.path; return basename(path) + " - " + dirname(path) + ""; } return escapeHTML(node.label || node.name); } }, plugin); list.container.style.position = "absolute"; list.container.style.left = "0"; list.container.style.top = "0"; list.container.style.right = "0"; list.container.style.bottom = "0"; list.container.style.height = ""; list.renderer.scrollBarV.$minWidth = 10; if (mode == "commit") { list.commands.bindKey("Enter", function(e) { // TODO: Open diff view for commit }); list.on("afterChoose", function(e) { // TODO: Open diff view for commit }); } if (mode == "file") { list.on("userSelect", function(e) { // TODO: scroll to file in file mode }); list.commands.bindKey("Enter", function(e) { // TODO: Open file }); list.on("afterChoose", function(e) { // TODO: Open file }); } // layout.on("eachTheme", function(e){ // var height = parseInt(ui.getStyleRule(".filetree .tree-row", "height"), 10) || 22; // tree.rowHeightInner = height; // tree.rowHeight = height + 1; // if (e.changed) // tree.resize(); // }, plugin); return menu; } // function getLabelValue(path){ // var hash; // if (path.indexOf(":") > -1) { // hash = path.split(":"); // path = hash[1], hash = hash[0]; // } // var dirpath = dirname(path); // return (hash ? "" + hash + "" : "") // + basename(dirpath) + "/" + basename(path) // + " - " + dirname(dirpath) + ""; // } // function loadSession(session){ // if (session.diffSession) { // diffview.setSession(session.diffSession); // return; // } // diffview.setSession(session.diffSession = { // orig: diffview.createSession(), // edit: diffview.createSession(), // chunks: [] // }); // var diff = session.diff || {}; // if (typeof diff.patch == "string") { // diffview.setValueFromFullPatch(diff.patch); // } else { // diffview.orig.session.setValue(diff.orig || ""); // diffview.edit.session.setValue(diff.edit || ""); // } // diffview.orig.setReadOnly(true); // diffview.edit.setReadOnly(true); // var syntax = ace.getSyntaxForPath(session.newPath); // if (syntax && syntax.indexOf("/") == -1) syntax = "ace/mode/" + syntax; // if (syntax) { // diffview.orig.session.setMode(syntax); // diffview.edit.session.setMode(syntax); // } // diffview.orig.renderer.once("afterRender", function() { // if (diffview.session == session.diffSession) { // if (!diffview.chunks.length) // diffview.computeDiff(); // diffview.foldUnchanged(); // diffview.gotoNext(1); // } // }); // } function loadSession(session) { diffview.setValueFromPatch(session.diff.patch); var nodes = hostedtoolbar.childNodes; for (var i = nodes.length - 1; i >= 0; i--) { nodes[i].removeChild(nodes[i]); } // Show the hash toolbar if (session.hash) { createHashToolbar(session); } // Show the branch toolbar else if (session.branch) { createBranchToolbar(); } // Show the path toolbar else if (session.path) { createPathToolbar(); } emit("toolbar", { session: session, toolbar: toolbar, hostedtoolbar: hostedtoolbar }); } var hashLabel; function createHashToolbar(session) { if (hashLabel) { updateHashToolbar(); return; } hashLabel = new ui.label(); hostedtoolbar.appendChild(hashLabel); plugin.on("session.update", function(e) { if (e.session != activeSession || !hashLabel.parentNode) return; updateHashToolbar(); }); function updateHashToolbar() { var session = activeSession; hashLabel.setValue(session.hash + ":" + session.label + ":" + session.authorname + ":" + session.date); hostedtoolbar.appendChild(hashLabel); } updateHashToolbar(); } var branchButton, branchLabel, branchCompareButton; function createBranchToolbar() { if (branchButton) { hostedtoolbar.appendChild(hashLabel); updateBranchToolbar(); return; } branchButton = new ui.button({ caption: "base: Current Branch" }); branchLabel = new ui.label({ caption: "..." }); branchCompareButton = new ui.button({ caption: "compare: Master" }); function updateBranchToolbar() { var session = activeSession; branchButton.setCaption("base: " + session.branch); branchCompareButton.setCaption("base: " + (session.compareBranch || "origin/master")); hostedtoolbar.appendChild(branchButton); hostedtoolbar.appendChild(branchLabel); hostedtoolbar.appendChild(branchCompareButton); } updateBranchToolbar(); } function createPathToolbar() { // Nothing for now } /***** Lifecycle *****/ plugin.on("load", function() { }); plugin.on("documentLoad", function(e) { var doc = e.doc; var session = e.doc.getSession(); session.isEqual = function(options) { return ( session.path == options.path && session.hash == options.hash && session.branch == options.branch && session.context == options.context && session.compareBranch == options.compareBranch ); }; if (e.state.path) session.path = e.state.path; if (e.state.hash) session.hash = e.state.hash; if (e.state.branch) session.branch = e.state.branch; if (e.state.context) session.context = e.state.context; if (e.state.compareBranch) session.compareBranch = e.state.compareBranch; var title; if (session.branch) { title = session.branch .replace(/refs\/(?:head|remotes\/\w+)\//, ""); } else if (session.hash) { title = session.hash; } else if (session.path) { title = basename(session.path); } else { title = "Working Copy"; } doc.title = "Compare " + title; doc.tooltip = "Compare " + title; function setTheme(e) { var tab = doc.tab; if (e.theme && BGCOLOR[e.theme]) { var isDark = e.theme == "dark"; toolbar.$ext.style.backgroundColor = tab.backgroundColor = BGCOLOR[e.theme]; if (isDark) tab.classList.add("dark"); else tab.classList.remove("dark"); } // TODO diffview.editor.setTheme(settings.get("user/ace/@theme")); } layout.on("themeChange", setTheme, doc); settings.on("user/ace/@theme", setTheme, doc); setTheme({ theme: settings.get("user/general/@skin") }); }); plugin.on("documentActivate", function(e) { var session = e.doc.getSession(); if (session.diff) return loadSession(session); diffview.setValueFromPatch(""); e.doc.tab.classList.add("connecting"); var config = { context: session.context || false }; // Show a single commit if (session.hash) { config.newPath = session.hash; config.oldPath = session.hash + "^1"; } // Show all changes in a branch else if (session.branch) { config.newPath = session.branch; config.oldPath = session.compareBranch || "refs/remotes/origin/master"; if (session.path) { config.newPath += ":" + session.path; config.oldPath += ":" + session.path; } } // Show uncommitted changes in a tracked file else if (session.path) { config.newPath = session.path; } activeDocument = e.doc; activeSession = session; handle.once("ready", function() { if (activeDocument != e.doc) return; session.request = scm.loadDiff(config, function(err, diff) { e.doc.tab.classList.remove("connecting"); if (err) { e.doc.tab.classList.add("error"); return; } if (session.request == diff.request) { session.diff = diff; loadSession(session); } }); if (session.hash) { scm.getStatus({ hash: session.hash }, function(e, status) { session.authorname = ""; session.authoremail = ""; session.date = ""; session.label = ""; emit("session.update", { session: session }); }); } else if (session.branch) { // TODO fetch commits } }); }); plugin.on("documentUnload", function(e) { // var session = e.doc.getSession(); }); plugin.on("getState", function(e) { var session = e.doc.getSession(); e.state.path = session.path; e.state.hash = session.hash; e.state.branch = session.branch; e.state.compareBranch = session.compareBranch; e.state.context = session.context; }); plugin.on("setState", function(e) { var session = e.doc.getSession(); session.path = e.state.path; session.hash = e.state.hash; session.branch = e.state.branch; session.compareBranch = e.state.compareBranch; session.context = e.state.context; }); plugin.on("clear", function() { }); plugin.on("focus", function() { }); plugin.on("enable", function() { }); plugin.on("disable", function() { }); plugin.on("unload", function() { }); plugin.on("resize", function(e) { diffview && diffview.editor.resize(e); }); /***** Register and define API *****/ /** * Read Only Image Editor **/ plugin.freezePublicAPI({ get diffview() { return diffview; }, get ace () { return lastAce; } }); plugin.load(null, "ace.repl"); return plugin; } register(null, { "diff.unified": handle }); } });