diff --git a/components/active/init.js b/components/active/init.js index 369ad15..1c5a035 100755 --- a/components/active/init.js +++ b/components/active/init.js @@ -4,1014 +4,1022 @@ * warranty under the MIT License. See [root]/license.txt for more. * This information must remain intact. */ - -(function(global, $) { - - var EditSession = ace.require('ace/edit_session') - .EditSession; - var UndoManager = ace.require('ace/undomanager') - .UndoManager; - - var codiad = global.codiad; - - $(function() { - codiad.active.init(); - }); - - ////////////////////////////////////////////////////////////////// - // - // Active Files Component for Codiad - // --------------------------------- - // Track and manage EditSession instaces of files being edited. - // - ////////////////////////////////////////////////////////////////// - - codiad.active = { - - controller: 'components/active/controller.php', - - // Path to EditSession instance mapping - sessions: {}, - - // History of opened files - history: [], +( function( global, $ ) { + + var EditSession = ace.require( 'ace/edit_session' ) + .EditSession; + var UndoManager = ace.require( 'ace/undomanager' ) + .UndoManager; + + var codiad = global.codiad; + + $( function() { + codiad.active.init(); + }); + + ////////////////////////////////////////////////////////////////// + // + // Active Files Component for Codiad + // --------------------------------- + // Track and manage EditSession instaces of files being edited. + // + ////////////////////////////////////////////////////////////////// + + codiad.active = { + + controller: 'components/active/controller.php', + + // Path to EditSession instance mapping + sessions: {}, + + // History of opened files + history: [], // List of active file positions positions: {}, - - ////////////////////////////////////////////////////////////////// - // - // Check if a file is open. - // - // Parameters: - // path - {String} - // - ////////////////////////////////////////////////////////////////// - - isOpen: function(path) { - return !!this.sessions[path]; - }, - - open: function(path, content, mtime, inBackground, focus) { - - //if( this. ) { - - - //} - /* Notify listeners. */ - amplify.publish('active.onFileWillOpen', {path: path, content: content}); - - if (focus === undefined) { - focus = true; - } - - var _this = this; - - if (this.isOpen(path)) { - if(focus) this.focus(path); - return; - } - var ext = codiad.filemanager.getExtension(path); - var mode = codiad.editor.selectMode( path ); + + ////////////////////////////////////////////////////////////////// + // + // Check if a file is open. + // + // Parameters: + // path - {String} + // + ////////////////////////////////////////////////////////////////// + + isOpen: function( path ) { + return !!this.sessions[path]; + }, + + open: function( path, content, mtime, inBackground, focus ) { - var fn = function() { - - //var Mode = require('ace/mode/' + mode) - // .Mode; - - // TODO: Ask for user confirmation before recovering - // And maybe show a diff - var draft = _this.checkDraft(path); - if (draft) { - content = draft; - codiad.message.success(i18n('Recovered unsaved content for: ') + path); - } - - //var session = new EditSession(content, new Mode()); - var session = new EditSession(content); - session.setMode(mode.mode); - session.setUndoManager(new UndoManager()); - - session.path = path; - session.serverMTime = mtime; - _this.sessions[path] = session; - session.untainted = content.slice(0); - if (!inBackground && focus) { - codiad.editor.setSession(session); - } - _this.add(path, session, focus); - - if( ! ( _this.positions[`${path}`] === undefined ) && focus ) { - - _this.setPosition( _this.positions[`${path}`] ); - } - - /* Notify listeners. */ - amplify.publish('active.onOpen', path); - }; - - // Assuming the mode file has no dependencies - $.loadScript('components/editor/ace-editor/mode-' + mode.name + '.js', - fn); - }, - - init: function() { - - var _this = this; - - _this.initTabDropdownMenu(); - _this.updateTabDropdownVisibility(); - - // Focus from list. - $('#list-active-files a') - .live('click', function(e) { - e.stopPropagation(); - _this.focus($(this).parent('li').attr('data-path')); - }); - - // Focus on left button click from dropdown. - $('#dropdown-list-active-files a') - .live('click', function(e) { - if(e.which == 1) { - /* Do not stop propagation of the event, - * it will be catch by the dropdown menu - * and close it. */ - _this.focus($(this).parent('li').attr('data-path')); - } - }); - - // Focus on left button mousedown from tab. - $('#tab-list-active-files li.tab-item>a.label') - .live('mousedown', function(e) { - if(e.which == 1) { - e.stopPropagation(); - _this.focus($(this).parent('li').attr('data-path')); - } - }); + //if( this. ) { - // Remove from list. - $('#list-active-files a>span') - .live('click', function(e) { - e.stopPropagation(); - _this.remove($(this) - .parent('a') - .parent('li') - .attr('data-path')); - }); - - // Remove from dropdown. - $('#dropdown-list-active-files a>span') - .live('click', function(e) { - e.stopPropagation(); - /* Get the active editor before removing anything. Remove the - * tab, then put back the focus on the previously active - * editor if it was not removed. */ - var activePath = _this.getPath(); - var pathToRemove = $(this).parents('li').attr('data-path'); - _this.remove(pathToRemove); - if (activePath !== null && activePath !== pathToRemove) { - _this.focus(activePath); - } - _this.updateTabDropdownVisibility(); - }); - - // Remove from tab. - $('#tab-list-active-files a.close') - .live('click', function(e) { - e.stopPropagation(); - /* Get the active editor before removing anything. Remove the - * tab, then put back the focus on the previously active - * editor if it was not removed. */ - var activePath = _this.getPath(); - var pathToRemove = $(this).parent('li').attr('data-path'); - _this.remove(pathToRemove); - if (activePath !== null && activePath !== pathToRemove) { - _this.focus(activePath); - } - _this.updateTabDropdownVisibility(); - }); - - // Remove from middle button click on dropdown. - $('#dropdown-list-active-files li') - .live('mouseup', function(e) { - if (e.which == 2) { - e.stopPropagation(); - /* Get the active editor before removing anything. Remove the - * tab, then put back the focus on the previously active - * editor if it was not removed. */ - var activePath = _this.getPath(); - var pathToRemove = $(this).attr('data-path'); - _this.remove(pathToRemove); - if (activePath !== null && activePath !== pathToRemove) { - _this.focus(activePath); - } - _this.updateTabDropdownVisibility(); - } - }); - - // Remove from middle button click on tab. - $('.tab-item') - .live('mouseup', function(e) { - if (e.which == 2) { - e.stopPropagation(); - /* Get the active editor before removing anything. Remove the - * tab, then put back the focus on the previously active - * editor if it was not removed. */ - var activePath = _this.getPath(); - var pathToRemove = $(this).attr('data-path'); - _this.remove(pathToRemove); - if (activePath !== null && activePath !== pathToRemove) { - _this.focus(activePath); - } - _this.updateTabDropdownVisibility(); - } - }); - - // Make list sortable - $('#list-active-files') - .sortable({ - placeholder: 'active-sort-placeholder', - tolerance: 'intersect', - start: function(e, ui) { - ui.placeholder.height(ui.item.height()); - } - }); - - // Make dropdown sortable. - $('#dropdown-list-active-files') - .sortable({ - axis: 'y', - tolerance: 'pointer', - start: function(e, ui) { - ui.placeholder.height(ui.item.height()); - } - }); - - // Make tabs sortable. - $('#tab-list-active-files') - .sortable({ - items: '> li', - axis: 'x', - tolerance: 'pointer', - containment: 'parent', - start: function(e, ui) { - ui.placeholder.css('background', 'transparent'); - ui.helper.css('width', '200px'); - }, - stop: function(e, ui) { - // Reset css - ui.item.css('z-index', '') - ui.item.css('position', '') - } - }); - /* Woaw, so tricky! At initialization, the tab-list is empty, so - * it is not marked as float so it is not detected as an horizontal - * list by the sortable plugin. Workaround is to mark it as - * floating at initialization time. See bug report - * http://bugs.jqueryui.com/ticket/6702. */ - $('#tab-list-active-files').data('sortable').floating = true; - - // Open saved-state active files on load - $.get(_this.controller + '?action=list', function(data) { - var listResponse = codiad.jsend.parse(data); - if (listResponse !== null) { - $.each(listResponse, function(index, data) { - - codiad.active.positions[`${data.path}`] = JSON.parse( data.position ); - codiad.filemanager.openFile(data.path, data.focused); - }); - } - }); - // Prompt if a user tries to close window without saving all filess - window.onbeforeunload = function(e) { - - codiad.active.uploadPositions(); - if ($('#list-active-files li.changed') - .length > 0) { - var e = e || window.event; - var errMsg = i18n('You have unsaved files.'); - - // For IE and Firefox prior to version 4 - if (e) { - e.returnValue = errMsg; - } - - // For rest - return errMsg; - } - }; - }, - - ////////////////////////////////////////////////////////////////// - // Drafts - ////////////////////////////////////////////////////////////////// - - checkDraft: function(path) { - var draft = localStorage.getItem(path); - if (draft !== null) { - return draft; - } else { - return false; - } - }, - - removeDraft: function(path) { - localStorage.removeItem(path); - }, - - ////////////////////////////////////////////////////////////////// - // Get active editor path - ////////////////////////////////////////////////////////////////// - - getPath: function() { - try { - return codiad.editor.getActive() - .getSession() - .path; - } catch (e) { - return null; - } - }, - - ////////////////////////////////////////////////////////////////// - // Check if opened by another user - ////////////////////////////////////////////////////////////////// - - check: function(path) { - $.get(this.controller + '?action=check&path=' + encodeURIComponent(path), - - function(data) { - var checkResponse = codiad.jsend.parse(data); - }); - }, - - ////////////////////////////////////////////////////////////////// - // Add newly opened file to list - ////////////////////////////////////////////////////////////////// - - add: function(path, session, focus) { - if (focus === undefined) { - focus = true; - } - - var listThumb = this.createListThumb(path); - session.listThumb = listThumb; - $('#list-active-files').append(listThumb); - - /* If the tab list would overflow with the new tab. Move the - * first tab to dropdown, then add a new tab. */ - if (this.isTabListOverflowed(true)) { - var tab = $('#tab-list-active-files li:first-child'); - this.moveTabToDropdownMenu(tab); - } - - var tabThumb = this.createTabThumb(path); - $('#tab-list-active-files').append(tabThumb); - session.tabThumb = tabThumb; - - this.updateTabDropdownVisibility(); - - $.get(this.controller + '?action=add&path=' + encodeURIComponent(path)); - - if(focus) { - this.focus(path); - } - - // Mark draft as changed - if (this.checkDraft(path)) { - this.markChanged(path); - } - }, - - ////////////////////////////////////////////////////////////////// - // Focus on opened file - ////////////////////////////////////////////////////////////////// - - focus: function(path, moveToTabList) { - if (moveToTabList === undefined) { - moveToTabList = true; - } - - /* Notify listeners. */ - amplify.publish('active.onWillFocus', path); - - this.highlightEntry(path, moveToTabList); - - if(path != this.getPath()) { - - let _this = this; - codiad.editor.setSession(this.sessions[path]); - this.history.push(path); - $.get(this.controller, {'action':'focused', 'path':path}, function() { - - if( ! ( _this.positions[`${path}`] === undefined ) ) { - - _this.setPosition( _this.positions[`${path}`] ); - } - }); - } - - /* Check for users registered on the file. */ - this.check(path); + //} + /* Notify listeners. */ + amplify.publish( 'active.onFileWillOpen', { + path: path, + content: content + }); - /* Notify listeners. */ - amplify.publish('active.onFocus', path); - }, - - highlightEntry: function(path, moveToTabList) { - if (moveToTabList === undefined) { - moveToTabList = true; - } - - $('#list-active-files li') - .removeClass('active'); - - $('#tab-list-active-files li') - .removeClass('active'); - - $('#dropdown-list-active-files li') - .removeClass('active'); - - var session = this.sessions[path]; - - if($('#dropdown-list-active-files').has(session.tabThumb).length > 0) { - if(moveToTabList) { - /* Get the menu item as a tab, and put the last tab in - * dropdown. */ - var menuItem = session.tabThumb; - this.moveDropdownMenuItemToTab(menuItem, true); - - var tab = $('#tab-list-active-files li:last-child'); - this.moveTabToDropdownMenu(tab); - } else { - /* Show the dropdown menu if needed */ - this.showTabDropdownMenu(); - } - } - else if(this.history.length > 0) { - var prevPath = this.history[this.history.length-1]; - var prevSession = this.sessions[prevPath]; - if($('#dropdown-list-active-files').has(prevSession.tabThumb).length > 0) { - /* Hide the dropdown menu if needed */ - this.hideTabDropdownMenu(); - } - } - - session.tabThumb.addClass('active'); - session.listThumb.addClass('active'); - }, - - ////////////////////////////////////////////////////////////////// - // Mark changed - ////////////////////////////////////////////////////////////////// - - markChanged: function(path) { - this.sessions[path].listThumb.addClass('changed'); - this.sessions[path].tabThumb.addClass('changed'); - }, - - ////////////////////////////////////////////////////////////////// - // Save active editor - ////////////////////////////////////////////////////////////////// - - save: function(path, alerts=true) { - /* Notify listeners. */ - amplify.publish('active.onSave', path); - - var _this = this; - if ((path && !this.isOpen(path)) || (!path && !codiad.editor.getActive())) { - codiad.message.error(i18n('No Open Files to save')); - return; - } - var session; - if (path) session = this.sessions[path]; - else session = codiad.editor.getActive() - .getSession(); - var content = session.getValue(); - var path = session.path; - var handleSuccess = function(mtime){ - var session = codiad.active.sessions[path]; - if(typeof session != 'undefined') { - session.untainted = newContent; - session.serverMTime = mtime; - if (session.listThumb) session.listThumb.removeClass('changed'); - if (session.tabThumb) session.tabThumb.removeClass('changed'); - } - _this.removeDraft(path); - } - // Replicate the current content so as to avoid - // discrepancies due to content changes during - // computation of diff - - var newContent = content.slice(0); - if (session.serverMTime && session.untainted){ - codiad.workerManager.addTask({ - taskType: 'diff', - id: path, - original: session.untainted, - changed: newContent - }, function(success, patch){ - if (success) { - codiad.filemanager.savePatch(path, patch, session.serverMTime, { - success: handleSuccess - }, alerts); - } else { - codiad.filemanager.saveFile(path, newContent, { - success: handleSuccess - }, alerts); - } - }, this); - } else { - codiad.filemanager.saveFile(path, newContent, { - success: handleSuccess - }, alert); - } - }, - - ////////////////////////////////////////////////////////////////// - // Save all files - ////////////////////////////////////////////////////////////////// - - saveAll: function() { - var _this = this; - for(var session in _this.sessions) { - if (_this.sessions[session].listThumb.hasClass('changed')) { - codiad.active.save(session); - } - } - }, - - ////////////////////////////////////////////////////////////////// - // Remove file - ////////////////////////////////////////////////////////////////// - - remove: function(path) { - if (!this.isOpen(path)) return; - var session = this.sessions[path]; - var closeFile = true; - if (session.listThumb.hasClass('changed')) { - codiad.modal.load(450, 'components/active/dialog.php?action=confirm&path=' + encodeURIComponent(path)); - closeFile = false; - } - if (closeFile) { - this.close(path); - } - }, - - removeAll: function(discard) { - discard = discard || false; - /* Notify listeners. */ - amplify.publish('active.onRemoveAll'); - - var _this = this; - var changed = false; - - var opentabs = new Array(); - for(var session in _this.sessions) { - opentabs[session] = session; - if (_this.sessions[session].listThumb.hasClass('changed')) { - changed = true; - } - } - if(changed && !discard) { - codiad.modal.load(450, 'components/active/dialog.php?action=confirmAll'); - return; - } - - for(var tab in opentabs) { - var session = this.sessions[tab]; - - session.tabThumb.remove(); - _this.updateTabDropdownVisibility(); - - session.listThumb.remove(); - - /* Remove closed path from history */ - var history = []; - $.each(this.history, function(index) { - if(this != tab) history.push(this); - }) - this.history = history - - delete this.sessions[tab]; - this.removeDraft(tab); - } - codiad.editor.exterminate(); - $('#list-active-files').html(''); - $.get(this.controller + '?action=removeall'); - }, - - close: function(path) { - /* Notify listeners. */ - amplify.publish('active.onClose', path); - - var _this = this; - var session = this.sessions[path]; - - /* Animate only if the tabThumb if a tab, not a dropdown item. */ - if(session.tabThumb.hasClass('tab-item')) { - session.tabThumb.css({'z-index': 1}); - session.tabThumb.animate({ - top: $('#editor-top-bar').height() + 'px' - }, 300, function() { - session.tabThumb.remove(); - _this.updateTabDropdownVisibility(); - }); - } else { - session.tabThumb.remove(); - _this.updateTabDropdownVisibility(); - } - - session.listThumb.remove(); - - /* Remove closed path from history */ - var history = []; - $.each(this.history, function(index) { - if(this != path) history.push(this); - }) - this.history = history - - /* Select all the tab tumbs except the one which is to be removed. */ - var tabThumbs = $('#tab-list-active-files li[data-path!="' + path + '"]'); - - if (tabThumbs.length == 0) { - codiad.editor.exterminate(); - } else { - - var nextPath = ''; - if(this.history.length > 0) { - nextPath = this.history[this.history.length - 1]; - } else { - nextPath = $(tabThumbs[0]).attr('data-path'); - } - var nextSession = this.sessions[nextPath]; - codiad.editor.removeSession(session, nextSession); - - this.focus(nextPath); - } - delete this.sessions[path]; - $.get(this.controller + '?action=remove&path=' + encodeURIComponent(path)); - this.removeDraft(path); - }, - - ////////////////////////////////////////////////////////////////// - // Process rename - ////////////////////////////////////////////////////////////////// - - rename: function(oldPath, newPath) { - var switchSessions = function(oldPath, newPath) { - var tabThumb = this.sessions[oldPath].tabThumb; - tabThumb.attr('data-path', newPath); - var title = newPath; - if (codiad.project.isAbsPath(newPath)) { - title = newPath.substring(1); - } - tabThumb.find('.label') - .text(title); - this.sessions[newPath] = this.sessions[oldPath]; - this.sessions[newPath].path = newPath; - delete this.sessions[oldPath]; - //Rename history - for (var i = 0; i < this.history.length; i++) { - if (this.history[i] === oldPath) { - this.history[i] = newPath; - } - } - }; - if (this.sessions[oldPath]) { - // A file was renamed - switchSessions.apply(this, [oldPath, newPath]); - // pass new sessions instance to setactive - for (var k = 0; k < codiad.editor.instances.length; k++) { - if (codiad.editor.instances[k].getSession().path === newPath) { - codiad.editor.setActive(codiad.editor.instances[k]); - } - } - - var newSession = this.sessions[newPath]; - - // Change Editor Mode - var mode = codiad.editor.selectMode(newPath); + if( focus === undefined ) { + focus = true; + } + + var _this = this; + + if( this.isOpen( path ) ) { + if( focus ) this.focus( path ); + return; + } + var ext = codiad.filemanager.getExtension( path ); + var mode = codiad.editor.selectMode( path ); + + var fn = function() { - // handle async mode change - var fn = function() { - codiad.editor.setModeDisplay(newSession); - newSession.removeListener('changeMode', fn); - } - - newSession.on("changeMode", fn); - newSession.setMode( mode.mode ); - } else { - // A folder was renamed - var newKey; - for (var key in this.sessions) { - newKey = key.replace(oldPath, newPath); - if (newKey !== key) { - switchSessions.apply(this, [key, newKey]); - } - } - } - $.get(this.controller + '?action=rename&old_path=' + encodeURIComponent(oldPath) + '&new_path=' + encodeURIComponent(newPath), function() { - /* Notify listeners. */ - amplify.publish('active.onRename', {"oldPath": oldPath, "newPath": newPath}); - }); - }, - - ////////////////////////////////////////////////////////////////// - // Open in Browser - ////////////////////////////////////////////////////////////////// - - openInBrowser: function() { - var path = this.getPath(); - if (path) { - codiad.filemanager.openInBrowser(path); - } else { - codiad.message.error('No Open Files'); - } - }, - - ////////////////////////////////////////////////////////////////// - // Get Selected Text - ////////////////////////////////////////////////////////////////// - - getSelectedText: function() { - var path = this.getPath(); - var session = this.sessions[path]; - - if (path && this.isOpen(path)) { - return session.getTextRange( - codiad.editor.getActive() - .getSelectionRange()); - } else { - codiad.message.error(i18n('No Open Files or Selected Text')); - } - }, - - ////////////////////////////////////////////////////////////////// - // Insert Text - ////////////////////////////////////////////////////////////////// - - insertText: function(val) { - codiad.editor.getActive() - .insert(val); - }, - - ////////////////////////////////////////////////////////////////// - // Goto Line - ////////////////////////////////////////////////////////////////// - - gotoLine: function(line) { - codiad.editor.getActive() - .gotoLine(line, 0, true); - }, - - ////////////////////////////////////////////////////////////////// - // Move Up (Key Combo) - ////////////////////////////////////////////////////////////////// - - move: function(dir) { - - var num = $('#tab-list-active-files li').length; - if (num === 0) return; - - var newActive = null; - var active = null; - - if (dir == 'up') { - - // If active is in the tab list - active = $('#tab-list-active-files li.active'); - if(active.length > 0) { - // Previous or rotate to the end - newActive = active.prev('li'); - if (newActive.length === 0) { - newActive = $('#dropdown-list-active-files li:last-child') - if (newActive.length === 0) { - newActive = $('#tab-list-active-files li:last-child') - } - } - } - - // If active is in the dropdown list - active = $('#dropdown-list-active-files li.active'); - if(active.length > 0) { - // Previous - newActive = active.prev('li'); - if (newActive.length === 0) { - newActive = $('#tab-list-active-files li:last-child') - } - } - - } else { - - // If active is in the tab list - active = $('#tab-list-active-files li.active'); - if(active.length > 0) { - // Next or rotate to the beginning - newActive = active.next('li'); - if (newActive.length === 0) { - newActive = $('#dropdown-list-active-files li:first-child'); - if (newActive.length === 0) { - newActive = $('#tab-list-active-files li:first-child') - } - } - } - - // If active is in the dropdown list - active = $('#dropdown-list-active-files li.active'); - if(active.length > 0) { - // Next or rotate to the beginning - newActive = active.next('li'); - if (newActive.length === 0) { - newActive = $('#tab-list-active-files li:first-child') - } - } - - } - - if(newActive) this.focus(newActive.attr('data-path'), false); - }, - - ////////////////////////////////////////////////////////////////// - // Dropdown Menu - ////////////////////////////////////////////////////////////////// - - initTabDropdownMenu: function() { - var _this = this; - - var menu = $('#dropdown-list-active-files'); - var button = $('#tab-dropdown-button'); - var closebutton = $('#tab-close-button'); - - menu.appendTo($('body')); - - button.click(function(e) { - e.stopPropagation(); - _this.toggleTabDropdownMenu(); - }); - - closebutton.click(function(e) { - e.stopPropagation(); - _this.removeAll(); - }); - }, - - showTabDropdownMenu: function() { - var menu = $('#dropdown-list-active-files'); - if(!menu.is(':visible')) this.toggleTabDropdownMenu(); - }, - - hideTabDropdownMenu: function() { - var menu = $('#dropdown-list-active-files'); - if(menu.is(':visible')) this.toggleTabDropdownMenu(); - }, - - toggleTabDropdownMenu: function() { - var _this = this; - var menu = $('#dropdown-list-active-files'); - - menu.css({ - top: $("#editor-top-bar").height() + 'px', - right: '20px', - width: '200px' - }); - - menu.slideToggle('fast'); - - if(menu.is(':visible')) { - // handle click-out autoclosing - var fn = function() { - menu.hide(); - $(window).off('click', fn) - } - $(window).on('click', fn); - } - }, - - moveTabToDropdownMenu: function(tab, prepend) { - if (prepend === undefined) { - prepend = false; - } - - tab.remove(); - path = tab.attr('data-path'); - - var tabThumb = this.createMenuItemThumb(path); - if(prepend) $('#dropdown-list-active-files').prepend(tabThumb); - else $('#dropdown-list-active-files').append(tabThumb); - - if(tab.hasClass("changed")) { - tabThumb.addClass("changed"); - } - - if(tab.hasClass("active")) { - tabThumb.addClass("active"); - } - - this.sessions[path].tabThumb = tabThumb; - }, - - moveDropdownMenuItemToTab: function(menuItem, prepend) { - if (prepend === undefined) { - prepend = false; - } - - menuItem.remove(); - path = menuItem.attr('data-path'); - - var tabThumb = this.createTabThumb(path); - if(prepend) $('#tab-list-active-files').prepend(tabThumb); - else $('#tab-list-active-files').append(tabThumb); - - if(menuItem.hasClass("changed")) { - tabThumb.addClass("changed"); - } - - if(menuItem.hasClass("active")) { - tabThumb.addClass("active"); - } - - this.sessions[path].tabThumb = tabThumb; - }, - - isTabListOverflowed: function(includeFictiveTab) { - if (includeFictiveTab === undefined) { - includeFictiveTab = false; - } - - var tabs = $('#tab-list-active-files li'); - var count = tabs.length - if (includeFictiveTab) count += 1; - if (count <= 1) return false; - - var width = 0; - tabs.each(function(index) { - width += $(this).outerWidth(true); - }) - if (includeFictiveTab) { - width += $(tabs[tabs.length-1]).outerWidth(true); - } - - /* If we subtract the width of the left side bar, of the right side - * bar handle and of the tab dropdown handle to the window width, - * do we have enough room for the tab list? Its kind of complicated - * to handle all the offsets, so afterwards we add a fixed offset - * just t be sure. */ - var lsbarWidth = $(".sidebar-handle").width(); - if (codiad.sidebars.isLeftSidebarOpen) { - lsbarWidth = $("#sb-left").width(); - } - - var rsbarWidth = $(".sidebar-handle").width(); - if (codiad.sidebars.isRightSidebarOpen) { - rsbarWidth = $("#sb-right").width(); - } - - var tabListWidth = $("#tab-list-active-files").width(); - var dropdownWidth = $('#tab-dropdown').width(); - var closeWidth = $('#tab-close').width(); - var room = window.innerWidth - lsbarWidth - rsbarWidth - dropdownWidth - closeWidth - width - 30; - return (room < 0); - }, - - updateTabDropdownVisibility: function() { - while(this.isTabListOverflowed()) { - var tab = $('#tab-list-active-files li:last-child'); - if (tab.length == 1) this.moveTabToDropdownMenu(tab, true); - else break; - } - - while(!this.isTabListOverflowed(true)) { - var menuItem = $('#dropdown-list-active-files li:first-child'); - if (menuItem.length == 1) this.moveDropdownMenuItemToTab(menuItem); - else break; - } - - if ($('#dropdown-list-active-files li').length > 0) { - $('#tab-dropdown').show(); - } else { - $('#tab-dropdown').hide(); - // Be sure to hide the menu if it is opened. - $('#dropdown-list-active-files').hide(); - } - if ($('#tab-list-active-files li').length > 1) { - $('#tab-close').show(); - } else { - $('#tab-close').hide(); - } - }, + //var Mode = require('ace/mode/' + mode) + // .Mode; + + // TODO: Ask for user confirmation before recovering + // And maybe show a diff + var draft = _this.checkDraft( path ); + if( draft ) { + content = draft; + codiad.message.success( i18n( 'Recovered unsaved content for: ' ) + path ); + } + + //var session = new EditSession(content, new Mode()); + var session = new EditSession( content ); + session.setMode( mode.mode ); + session.setUndoManager( new UndoManager() ); + + session.path = path; + session.serverMTime = mtime; + _this.sessions[path] = session; + session.untainted = content.slice( 0 ); + if( !inBackground && focus ) { + codiad.editor.setSession( session ); + } + _this.add( path, session, focus ); + + if( !( _this.positions[`${path}`] === undefined ) && focus ) { + + _this.setPosition( _this.positions[`${path}`] ); + } + + /* Notify listeners. */ + amplify.publish( 'active.onOpen', path ); + }; + + // Assuming the mode file has no dependencies + $.loadScript( 'components/editor/ace-editor/mode-' + mode.name + '.js', + fn ); + }, + + init: function() { + + var _this = this; + + _this.initTabDropdownMenu(); + _this.updateTabDropdownVisibility(); + + // Focus from list. + $( '#list-active-files a' ) + .live( 'click', function( e ) { + e.stopPropagation(); + _this.focus( $( this ).parent( 'li' ).attr( 'data-path' ) ); + }); + + // Focus on left button click from dropdown. + $( '#dropdown-list-active-files a' ) + .live( 'click', function( e ) { + if( e.which == 1 ) { + /* Do not stop propagation of the event, + * it will be catch by the dropdown menu + * and close it. */ + _this.focus( $( this ).parent( 'li' ).attr( 'data-path' ) ); + } + }); + + // Focus on left button mousedown from tab. + $( '#tab-list-active-files li.tab-item>a.label' ) + .live( 'mousedown', function( e ) { + if( e.which == 1 ) { + e.stopPropagation(); + _this.focus( $( this ).parent( 'li' ).attr( 'data-path' ) ); + } + }); + + // Remove from list. + $( '#list-active-files a>span' ) + .live( 'click', function( e ) { + e.stopPropagation(); + _this.remove( $( this ) + .parent( 'a' ) + .parent( 'li' ) + .attr( 'data-path' ) ); + }); + + // Remove from dropdown. + $( '#dropdown-list-active-files a>span' ) + .live( 'click', function( e ) { + e.stopPropagation(); + /* Get the active editor before removing anything. Remove the + * tab, then put back the focus on the previously active + * editor if it was not removed. */ + var activePath = _this.getPath(); + var pathToRemove = $( this ).parents( 'li' ).attr( 'data-path' ); + _this.remove( pathToRemove ); + if( activePath !== null && activePath !== pathToRemove ) { + _this.focus( activePath ); + } + _this.updateTabDropdownVisibility(); + }); + + // Remove from tab. + $( '#tab-list-active-files a.close' ) + .live( 'click', function( e ) { + e.stopPropagation(); + /* Get the active editor before removing anything. Remove the + * tab, then put back the focus on the previously active + * editor if it was not removed. */ + var activePath = _this.getPath(); + var pathToRemove = $( this ).parent( 'li' ).attr( 'data-path' ); + _this.remove( pathToRemove ); + if( activePath !== null && activePath !== pathToRemove ) { + _this.focus( activePath ); + } + _this.updateTabDropdownVisibility(); + }); + + // Remove from middle button click on dropdown. + $( '#dropdown-list-active-files li' ) + .live( 'mouseup', function( e ) { + if( e.which == 2 ) { + e.stopPropagation(); + /* Get the active editor before removing anything. Remove the + * tab, then put back the focus on the previously active + * editor if it was not removed. */ + var activePath = _this.getPath(); + var pathToRemove = $( this ).attr( 'data-path' ); + _this.remove( pathToRemove ); + if( activePath !== null && activePath !== pathToRemove ) { + _this.focus( activePath ); + } + _this.updateTabDropdownVisibility(); + } + }); + + // Remove from middle button click on tab. + $( '.tab-item' ) + .live( 'mouseup', function( e ) { + if( e.which == 2 ) { + e.stopPropagation(); + /* Get the active editor before removing anything. Remove the + * tab, then put back the focus on the previously active + * editor if it was not removed. */ + var activePath = _this.getPath(); + var pathToRemove = $( this ).attr( 'data-path' ); + _this.remove( pathToRemove ); + if( activePath !== null && activePath !== pathToRemove ) { + _this.focus( activePath ); + } + _this.updateTabDropdownVisibility(); + } + }); + + // Make list sortable + $( '#list-active-files' ) + .sortable( { + placeholder: 'active-sort-placeholder', + tolerance: 'intersect', + start: function( e, ui ) { + ui.placeholder.height( ui.item.height() ); + } + }); + + // Make dropdown sortable. + $( '#dropdown-list-active-files' ) + .sortable( { + axis: 'y', + tolerance: 'pointer', + start: function( e, ui ) { + ui.placeholder.height( ui.item.height() ); + } + }); + + // Make tabs sortable. + $( '#tab-list-active-files' ) + .sortable( { + items: '> li', + axis: 'x', + tolerance: 'pointer', + containment: 'parent', + start: function( e, ui ) { + ui.placeholder.css( 'background', 'transparent' ); + ui.helper.css( 'width', '200px' ); + }, + stop: function( e, ui ) { + // Reset css + ui.item.css( 'z-index', '' ) + ui.item.css( 'position', '' ) + } + }); + /* Woaw, so tricky! At initialization, the tab-list is empty, so + * it is not marked as float so it is not detected as an horizontal + * list by the sortable plugin. Workaround is to mark it as + * floating at initialization time. See bug report + * http://bugs.jqueryui.com/ticket/6702. */ + $( '#tab-list-active-files' ).data( 'sortable' ).floating = true; + + // Open saved-state active files on load + $.get( _this.controller + '?action=list', function( data ) { + var listResponse = codiad.jsend.parse( data ); + if( listResponse !== null ) { + $.each( listResponse, function( index, data ) { + + codiad.active.positions[`${data.path}`] = JSON.parse( data.position ); + codiad.filemanager.openFile( data.path, data.focused ); + }); + } + }); + + // Prompt if a user tries to close window without saving all filess + window.onbeforeunload = function( e ) { + + codiad.active.uploadPositions(); + if( $( '#list-active-files li.changed' ) + .length > 0 ) { + var e = e || window.event; + var errMsg = i18n( 'You have unsaved files.' ); + + // For IE and Firefox prior to version 4 + if( e ) { + e.returnValue = errMsg; + } + + // For rest + return errMsg; + } + }; + }, + + ////////////////////////////////////////////////////////////////// + // Drafts + ////////////////////////////////////////////////////////////////// + + checkDraft: function( path ) { + var draft = localStorage.getItem( path ); + if( draft !== null ) { + return draft; + } else { + return false; + } + }, + + removeDraft: function( path ) { + localStorage.removeItem( path ); + }, + + ////////////////////////////////////////////////////////////////// + // Get active editor path + ////////////////////////////////////////////////////////////////// + + getPath: function() { + try { + return codiad.editor.getActive() + .getSession() + .path; + } catch ( e ) { + return null; + } + }, + + ////////////////////////////////////////////////////////////////// + // Check if opened by another user + ////////////////////////////////////////////////////////////////// + + check: function( path ) { + $.get( this.controller + '?action=check&path=' + encodeURIComponent( path ), + + function( data ) { + var checkResponse = codiad.jsend.parse( data ); + }); + }, + + ////////////////////////////////////////////////////////////////// + // Add newly opened file to list + ////////////////////////////////////////////////////////////////// + + add: function( path, session, focus ) { + if( focus === undefined ) { + focus = true; + } + + var listThumb = this.createListThumb( path ); + session.listThumb = listThumb; + $( '#list-active-files' ).append( listThumb ); + + /* If the tab list would overflow with the new tab. Move the + * first tab to dropdown, then add a new tab. */ + if( this.isTabListOverflowed( true ) ) { + var tab = $( '#tab-list-active-files li:first-child' ); + this.moveTabToDropdownMenu( tab ); + } + + var tabThumb = this.createTabThumb( path ); + $( '#tab-list-active-files' ).append( tabThumb ); + session.tabThumb = tabThumb; + + this.updateTabDropdownVisibility(); + + $.get( this.controller + '?action=add&path=' + encodeURIComponent( path ) ); + + if( focus ) { + this.focus( path ); + } + + // Mark draft as changed + if( this.checkDraft( path ) ) { + this.markChanged( path ); + } + }, + + ////////////////////////////////////////////////////////////////// + // Focus on opened file + ////////////////////////////////////////////////////////////////// + + focus: function( path, moveToTabList ) { + if( moveToTabList === undefined ) { + moveToTabList = true; + } + + /* Notify listeners. */ + amplify.publish( 'active.onWillFocus', path ); + + this.highlightEntry( path, moveToTabList ); + + if( path != this.getPath() ) { + + let _this = this; + codiad.editor.setSession( this.sessions[path] ); + this.history.push( path ); + $.get( this.controller, { + 'action': 'focused', + 'path': path + }, function() { + + if( !( _this.positions[`${path}`] === undefined ) ) { + + _this.setPosition( _this.positions[`${path}`] ); + } + }); + } + + /* Check for users registered on the file. */ + this.check( path ); + + /* Notify listeners. */ + amplify.publish( 'active.onFocus', path ); + }, + + highlightEntry: function( path, moveToTabList ) { + if( moveToTabList === undefined ) { + moveToTabList = true; + } + + $( '#list-active-files li' ) + .removeClass( 'active' ); + + $( '#tab-list-active-files li' ) + .removeClass( 'active' ); + + $( '#dropdown-list-active-files li' ) + .removeClass( 'active' ); + + var session = this.sessions[path]; + + if( $( '#dropdown-list-active-files' ).has( session.tabThumb ).length > 0 ) { + if( moveToTabList ) { + /* Get the menu item as a tab, and put the last tab in + * dropdown. */ + var menuItem = session.tabThumb; + this.moveDropdownMenuItemToTab( menuItem, true ); + + var tab = $( '#tab-list-active-files li:last-child' ); + this.moveTabToDropdownMenu( tab ); + } else { + /* Show the dropdown menu if needed */ + this.showTabDropdownMenu(); + } + } else if( this.history.length > 0 ) { + var prevPath = this.history[this.history.length - 1]; + var prevSession = this.sessions[prevPath]; + if( $( '#dropdown-list-active-files' ).has( prevSession.tabThumb ).length > 0 ) { + /* Hide the dropdown menu if needed */ + this.hideTabDropdownMenu(); + } + } + + session.tabThumb.addClass( 'active' ); + session.listThumb.addClass( 'active' ); + }, + + ////////////////////////////////////////////////////////////////// + // Mark changed + ////////////////////////////////////////////////////////////////// + + markChanged: function( path ) { + this.sessions[path].listThumb.addClass( 'changed' ); + this.sessions[path].tabThumb.addClass( 'changed' ); + }, + + ////////////////////////////////////////////////////////////////// + // Save active editor + ////////////////////////////////////////////////////////////////// + + save: function( path, alerts = true ) { + /* Notify listeners. */ + amplify.publish( 'active.onSave', path ); + + var _this = this; + if( ( path && !this.isOpen( path ) ) || ( !path && !codiad.editor.getActive() ) ) { + codiad.message.error( i18n( 'No Open Files to save' ) ); + return; + } + var session; + if( path ) session = this.sessions[path]; + else session = codiad.editor.getActive() + .getSession(); + var content = session.getValue(); + var path = session.path; + var handleSuccess = function( mtime ) { + var session = codiad.active.sessions[path]; + if( typeof session != 'undefined' ) { + session.untainted = newContent; + session.serverMTime = mtime; + if( session.listThumb ) session.listThumb.removeClass( 'changed' ); + if( session.tabThumb ) session.tabThumb.removeClass( 'changed' ); + } + _this.removeDraft( path ); + } + // Replicate the current content so as to avoid + // discrepancies due to content changes during + // computation of diff + + var newContent = content.slice( 0 ); + if( session.serverMTime && session.untainted ) { + codiad.workerManager.addTask( { + taskType: 'diff', + id: path, + original: session.untainted, + changed: newContent + }, function( success, patch ) { + if( success ) { + codiad.filemanager.savePatch( path, patch, session.serverMTime, { + success: handleSuccess + }, alerts ); + } else { + codiad.filemanager.saveFile( path, newContent, { + success: handleSuccess + }, alerts ); + } + }, this ); + } else { + codiad.filemanager.saveFile( path, newContent, { + success: handleSuccess + }, alert ); + } + }, + + ////////////////////////////////////////////////////////////////// + // Save all files + ////////////////////////////////////////////////////////////////// + + saveAll: function() { + var _this = this; + for( var session in _this.sessions ) { + if( _this.sessions[session].listThumb.hasClass( 'changed' ) ) { + codiad.active.save( session ); + } + } + }, + + ////////////////////////////////////////////////////////////////// + // Remove file + ////////////////////////////////////////////////////////////////// + + remove: function( path ) { + if( !this.isOpen( path ) ) return; + var session = this.sessions[path]; + var closeFile = true; + if( session.listThumb.hasClass( 'changed' ) ) { + codiad.modal.load( 450, 'components/active/dialog.php?action=confirm&path=' + encodeURIComponent( path ) ); + closeFile = false; + } + if( closeFile ) { + this.close( path ); + } + }, + + removeAll: function( discard ) { + discard = discard || false; + /* Notify listeners. */ + amplify.publish( 'active.onRemoveAll' ); + + var _this = this; + var changed = false; + + var opentabs = new Array(); + for( var session in _this.sessions ) { + opentabs[session] = session; + if( _this.sessions[session].listThumb.hasClass( 'changed' ) ) { + changed = true; + } + } + if( changed && !discard ) { + codiad.modal.load( 450, 'components/active/dialog.php?action=confirmAll' ); + return; + } + + for( var tab in opentabs ) { + var session = this.sessions[tab]; + + session.tabThumb.remove(); + _this.updateTabDropdownVisibility(); + + session.listThumb.remove(); + + /* Remove closed path from history */ + var history = []; + $.each( this.history, function( index ) { + if( this != tab ) history.push( this ); + }) + this.history = history + + delete this.sessions[tab]; + this.removeDraft( tab ); + } + codiad.editor.exterminate(); + $( '#list-active-files' ).html( '' ); + $.get( this.controller + '?action=removeall' ); + }, + + close: function( path ) { + /* Notify listeners. */ + amplify.publish( 'active.onClose', path ); + + var _this = this; + var session = this.sessions[path]; + + /* Animate only if the tabThumb if a tab, not a dropdown item. */ + if( session.tabThumb.hasClass( 'tab-item' ) ) { + session.tabThumb.css( { + 'z-index': 1 + }); + session.tabThumb.animate( { + top: $( '#editor-top-bar' ).height() + 'px' + }, 300, function() { + session.tabThumb.remove(); + _this.updateTabDropdownVisibility(); + }); + } else { + session.tabThumb.remove(); + _this.updateTabDropdownVisibility(); + } + + session.listThumb.remove(); + + /* Remove closed path from history */ + var history = []; + $.each( this.history, function( index ) { + if( this != path ) history.push( this ); + }) + this.history = history + + /* Select all the tab tumbs except the one which is to be removed. */ + var tabThumbs = $( '#tab-list-active-files li[data-path!="' + path + '"]' ); + + if( tabThumbs.length == 0 ) { + codiad.editor.exterminate(); + } else { + + var nextPath = ''; + if( this.history.length > 0 ) { + nextPath = this.history[this.history.length - 1]; + } else { + nextPath = $( tabThumbs[0] ).attr( 'data-path' ); + } + var nextSession = this.sessions[nextPath]; + codiad.editor.removeSession( session, nextSession ); + + this.focus( nextPath ); + } + delete this.sessions[path]; + $.get( this.controller + '?action=remove&path=' + encodeURIComponent( path ) ); + this.removeDraft( path ); + }, + + ////////////////////////////////////////////////////////////////// + // Process rename + ////////////////////////////////////////////////////////////////// + + rename: function( oldPath, newPath ) { + var switchSessions = function( oldPath, newPath ) { + var tabThumb = this.sessions[oldPath].tabThumb; + tabThumb.attr( 'data-path', newPath ); + var title = newPath; + if( codiad.project.isAbsPath( newPath ) ) { + title = newPath.substring( 1 ); + } + tabThumb.find( '.label' ) + .text( title ); + this.sessions[newPath] = this.sessions[oldPath]; + this.sessions[newPath].path = newPath; + delete this.sessions[oldPath]; + //Rename history + for( var i = 0; i < this.history.length; i++ ) { + if( this.history[i] === oldPath ) { + this.history[i] = newPath; + } + } + }; + if( this.sessions[oldPath] ) { + // A file was renamed + switchSessions.apply( this, [oldPath, newPath] ); + // pass new sessions instance to setactive + for( var k = 0; k < codiad.editor.instances.length; k++ ) { + if( codiad.editor.instances[k].getSession().path === newPath ) { + codiad.editor.setActive( codiad.editor.instances[k] ); + } + } + + var newSession = this.sessions[newPath]; + + // Change Editor Mode + var mode = codiad.editor.selectMode( newPath ); + + // handle async mode change + var fn = function() { + codiad.editor.setModeDisplay( newSession ); + newSession.removeListener( 'changeMode', fn ); + } + + newSession.on( "changeMode", fn ); + newSession.setMode( mode.mode ); + } else { + // A folder was renamed + var newKey; + for( var key in this.sessions ) { + newKey = key.replace( oldPath, newPath ); + if( newKey !== key ) { + switchSessions.apply( this, [key, newKey] ); + } + } + } + $.get( this.controller + '?action=rename&old_path=' + encodeURIComponent( oldPath ) + '&new_path=' + encodeURIComponent( newPath ), function() { + /* Notify listeners. */ + amplify.publish( 'active.onRename', { + "oldPath": oldPath, + "newPath": newPath + }); + }); + }, + + ////////////////////////////////////////////////////////////////// + // Open in Browser + ////////////////////////////////////////////////////////////////// + + openInBrowser: function() { + var path = this.getPath(); + if( path ) { + codiad.filemanager.openInBrowser( path ); + } else { + codiad.message.error( 'No Open Files' ); + } + }, + + ////////////////////////////////////////////////////////////////// + // Get Selected Text + ////////////////////////////////////////////////////////////////// + + getSelectedText: function() { + var path = this.getPath(); + var session = this.sessions[path]; + + if( path && this.isOpen( path ) ) { + return session.getTextRange( + codiad.editor.getActive() + .getSelectionRange() ); + } else { + codiad.message.error( i18n( 'No Open Files or Selected Text' ) ); + } + }, + + ////////////////////////////////////////////////////////////////// + // Insert Text + ////////////////////////////////////////////////////////////////// + + insertText: function( val ) { + codiad.editor.getActive() + .insert( val ); + }, + + ////////////////////////////////////////////////////////////////// + // Goto Line + ////////////////////////////////////////////////////////////////// + + gotoLine: function( line ) { + codiad.editor.getActive() + .gotoLine( line, 0, true ); + }, + + ////////////////////////////////////////////////////////////////// + // Move Up (Key Combo) + ////////////////////////////////////////////////////////////////// + + move: function( dir ) { + + var num = $( '#tab-list-active-files li' ).length; + if( num === 0 ) return; + + var newActive = null; + var active = null; + + if( dir == 'up' ) { + + // If active is in the tab list + active = $( '#tab-list-active-files li.active' ); + if( active.length > 0 ) { + // Previous or rotate to the end + newActive = active.prev( 'li' ); + if( newActive.length === 0 ) { + newActive = $( '#dropdown-list-active-files li:last-child' ) + if( newActive.length === 0 ) { + newActive = $( '#tab-list-active-files li:last-child' ) + } + } + } + + // If active is in the dropdown list + active = $( '#dropdown-list-active-files li.active' ); + if( active.length > 0 ) { + // Previous + newActive = active.prev( 'li' ); + if( newActive.length === 0 ) { + newActive = $( '#tab-list-active-files li:last-child' ) + } + } + + } else { + + // If active is in the tab list + active = $( '#tab-list-active-files li.active' ); + if( active.length > 0 ) { + // Next or rotate to the beginning + newActive = active.next( 'li' ); + if( newActive.length === 0 ) { + newActive = $( '#dropdown-list-active-files li:first-child' ); + if( newActive.length === 0 ) { + newActive = $( '#tab-list-active-files li:first-child' ) + } + } + } + + // If active is in the dropdown list + active = $( '#dropdown-list-active-files li.active' ); + if( active.length > 0 ) { + // Next or rotate to the beginning + newActive = active.next( 'li' ); + if( newActive.length === 0 ) { + newActive = $( '#tab-list-active-files li:first-child' ) + } + } + + } + + if( newActive ) this.focus( newActive.attr( 'data-path' ), false ); + }, + + ////////////////////////////////////////////////////////////////// + // Dropdown Menu + ////////////////////////////////////////////////////////////////// + + initTabDropdownMenu: function() { + var _this = this; + + var menu = $( '#dropdown-list-active-files' ); + var button = $( '#tab-dropdown-button' ); + var closebutton = $( '#tab-close-button' ); + + menu.appendTo( $( 'body' ) ); + + button.click( function( e ) { + e.stopPropagation(); + _this.toggleTabDropdownMenu(); + }); + + closebutton.click( function( e ) { + e.stopPropagation(); + _this.removeAll(); + }); + }, + + showTabDropdownMenu: function() { + var menu = $( '#dropdown-list-active-files' ); + if( !menu.is( ':visible' ) ) this.toggleTabDropdownMenu(); + }, + + hideTabDropdownMenu: function() { + var menu = $( '#dropdown-list-active-files' ); + if( menu.is( ':visible' ) ) this.toggleTabDropdownMenu(); + }, + + toggleTabDropdownMenu: function() { + var _this = this; + var menu = $( '#dropdown-list-active-files' ); + + menu.css( { + top: $( "#editor-top-bar" ).height() + 'px', + right: '20px', + width: '200px' + }); + + menu.slideToggle( 'fast' ); + + if( menu.is( ':visible' ) ) { + // handle click-out autoclosing + var fn = function() { + menu.hide(); + $( window ).off( 'click', fn ) + } + $( window ).on( 'click', fn ); + } + }, + + moveTabToDropdownMenu: function( tab, prepend ) { + if( prepend === undefined ) { + prepend = false; + } + + tab.remove(); + path = tab.attr( 'data-path' ); + + var tabThumb = this.createMenuItemThumb( path ); + if( prepend ) $( '#dropdown-list-active-files' ).prepend( tabThumb ); + else $( '#dropdown-list-active-files' ).append( tabThumb ); + + if( tab.hasClass( "changed" ) ) { + tabThumb.addClass( "changed" ); + } + + if( tab.hasClass( "active" ) ) { + tabThumb.addClass( "active" ); + } + + this.sessions[path].tabThumb = tabThumb; + }, + + moveDropdownMenuItemToTab: function( menuItem, prepend ) { + if( prepend === undefined ) { + prepend = false; + } + + menuItem.remove(); + path = menuItem.attr( 'data-path' ); + + var tabThumb = this.createTabThumb( path ); + if( prepend ) $( '#tab-list-active-files' ).prepend( tabThumb ); + else $( '#tab-list-active-files' ).append( tabThumb ); + + if( menuItem.hasClass( "changed" ) ) { + tabThumb.addClass( "changed" ); + } + + if( menuItem.hasClass( "active" ) ) { + tabThumb.addClass( "active" ); + } + + this.sessions[path].tabThumb = tabThumb; + }, + + isTabListOverflowed: function( includeFictiveTab ) { + if( includeFictiveTab === undefined ) { + includeFictiveTab = false; + } + + var tabs = $( '#tab-list-active-files li' ); + var count = tabs.length + if( includeFictiveTab ) count += 1; + if( count <= 1 ) return false; + + var width = 0; + tabs.each( function( index ) { + width += $( this ).outerWidth( true ); + }) + if( includeFictiveTab ) { + width += $( tabs[tabs.length - 1] ).outerWidth( true ); + } + + /* If we subtract the width of the left side bar, of the right side + * bar handle and of the tab dropdown handle to the window width, + * do we have enough room for the tab list? Its kind of complicated + * to handle all the offsets, so afterwards we add a fixed offset + * just t be sure. */ + var lsbarWidth = $( ".sidebar-handle" ).width(); + if( codiad.sidebars.isLeftSidebarOpen ) { + lsbarWidth = $( "#sb-left" ).width(); + } + + var rsbarWidth = $( ".sidebar-handle" ).width(); + if( codiad.sidebars.isRightSidebarOpen ) { + rsbarWidth = $( "#sb-right" ).width(); + } + + var tabListWidth = $( "#tab-list-active-files" ).width(); + var dropdownWidth = $( '#tab-dropdown' ).width(); + var closeWidth = $( '#tab-close' ).width(); + var room = window.innerWidth - lsbarWidth - rsbarWidth - dropdownWidth - closeWidth - width - 30; + return ( room < 0 ); + }, + + updateTabDropdownVisibility: function() { + while( this.isTabListOverflowed() ) { + var tab = $( '#tab-list-active-files li:last-child' ); + if( tab.length == 1 ) this.moveTabToDropdownMenu( tab, true ); + else break; + } + + while( !this.isTabListOverflowed( true ) ) { + var menuItem = $( '#dropdown-list-active-files li:first-child' ); + if( menuItem.length == 1 ) this.moveDropdownMenuItemToTab( menuItem ); + else break; + } + + if( $( '#dropdown-list-active-files li' ).length > 0 ) { + $( '#tab-dropdown' ).show(); + } else { + $( '#tab-dropdown' ).hide(); + // Be sure to hide the menu if it is opened. + $( '#dropdown-list-active-files' ).hide(); + } + if( $( '#tab-list-active-files li' ).length > 1 ) { + $( '#tab-close' ).show(); + } else { + $( '#tab-close' ).hide(); + } + }, uploadPositions: function() { - $.ajax({ + $.ajax( { type: 'POST', url: codiad.active.controller + '?action=save_positions', data: { positions: ( JSON.stringify( codiad.active.positions ) ) }, - success: function( data ) { - }, + success: function( data ) {}, }); }, @@ -1025,7 +1033,7 @@ this.positions[path] = position; }, - getPosition: function( path=null, live=false ) { + getPosition: function( path = null, live = false ) { if( path === null ) { @@ -1036,7 +1044,7 @@ let position = null; let session = codiad.active.sessions[path]; - for( let i = codiad.editor.instances.length;i--; ) { + for( let i = codiad.editor.instances.length; i--; ) { if( codiad.editor.instances[i].getSession().path == path ) { @@ -1049,7 +1057,7 @@ position = editor.getCursorPosition(); } else { - if( ! this.positions[path] === undefined ) { + if( !this.positions[path] === undefined ) { position = this.positions[path].position; } @@ -1067,7 +1075,7 @@ return position; }, - setPosition: function( cursor=null ) { + setPosition: function( cursor = null ) { let editor = codiad.editor.getActive(); @@ -1079,36 +1087,36 @@ editor.scrollToLine( cursor.row, true, true, function() {}); editor.moveCursorTo( cursor.row, cursor.column ); }, - ////////////////////////////////////////////////////////////////// - // Factory - ////////////////////////////////////////////////////////////////// - - splitDirectoryAndFileName: function(path) { - var index = path.lastIndexOf('/'); - return { - fileName: path.substring(index + 1), - directory: (path.indexOf('/') == 0)? path.substring(1, index + 1):path.substring(0, index + 1) - } - }, - - createListThumb: function(path) { - return $('
  • ' + path + '
  • '); - }, - - createTabThumb: function(path) { - split = this.splitDirectoryAndFileName(path); - return $('
  • ' - + split.directory + '' + split.fileName + '' - + 'x
  • '); - }, - - createMenuItemThumb: function(path) { - split = this.splitDirectoryAndFileName(path); - return $('
  • ' - + split.directory + '' + split.fileName + '' - + '
  • '); - }, - - }; - -})(this, jQuery); + ////////////////////////////////////////////////////////////////// + // Factory + ////////////////////////////////////////////////////////////////// + + splitDirectoryAndFileName: function( path ) { + var index = path.lastIndexOf( '/' ); + return { + fileName: path.substring( index + 1 ), + directory: ( path.indexOf( '/' ) == 0 ) ? path.substring( 1, index + 1 ) : path.substring( 0, index + 1 ) + } + }, + + createListThumb: function( path ) { + return $( '
  • ' + path + '
  • ' ); + }, + + createTabThumb: function( path ) { + split = this.splitDirectoryAndFileName( path ); + return $( '
  • ' + + split.directory + '' + split.fileName + '' + + 'x
  • ' ); + }, + + createMenuItemThumb: function( path ) { + split = this.splitDirectoryAndFileName( path ); + return $( '
  • ' + + split.directory + '' + split.fileName + '' + + '
  • ' ); + }, + + }; + +})( this, jQuery ); diff --git a/components/editor/init.js b/components/editor/init.js index aa1a700..8f3514b 100755 --- a/components/editor/init.js +++ b/components/editor/init.js @@ -1,1603 +1,1606 @@ -/* - * Copyright (c) Codiad & Kent Safranski (codiad.com), distributed - * as-is and without warranty under the MIT License. See - * [root]/license.txt for more. This information must remain intact. - */ - -(function(global, $) { - - // Classes from Ace - var VirtualRenderer = ace.require('ace/virtual_renderer').VirtualRenderer; - var Editor = ace.require('ace/editor').Editor; - var EditSession = ace.require('ace/edit_session').EditSession; - var ModeList = ace.require("ace/ext/modelist"); - var UndoManager = ace.require("ace/undomanager").UndoManager; - - // Editor modes that have been loaded - var editorModes = {}; - - var codiad = global.codiad; - codiad._cursorPoll = null; - - var separatorWidth = 3; - - $(function(){ - codiad.editor.init(); - }); - - function SplitContainer(root, children, splitType) { - var _this = this; - - this.root = root; - this.splitType = splitType; - this.childContainers = {}; - this.childElements = {}; - this.splitProp = 0.5; - - this.setChild(0, children[0]); - this.setChild(1, children[1]); - - this.splitter = $('
    ') - .addClass('splitter') - .appendTo(root) - .draggable({ - axis: (splitType === 'horizontal' ? 'x' : 'y'), - drag: function(e, ui){ - if (_this.splitType === 'horizontal') { - var w1, w2; - w1 = ui.position.left - separatorWidth/2; - w2 = _this.root.width() - ui.position.left - - separatorWidth/2; - _this.splitProp = w1 / _this.root.width(); - _this.childElements[0] - .width(w1) - .trigger('h-resize', [true, true]); - _this.childElements[1] - .width(w2) - .css('left', w1 + separatorWidth + 'px') - .trigger('h-resize', [true, true]); - _this.splitProp = ui.position.left / _this.root.width(); - } else { - var h1, h2; - h1 = ui.position.top - separatorWidth/2; - h2 = _this.root.width() - ui.position.top - - separatorWidth/2; - _this.splitProp = h1 / _this.root.height(); - _this.childElements[0] - .height(h1) - .trigger('v-resize', [true, true]); - _this.childElements[1] - .height(h2) - .css('top', h1 + separatorWidth + 'px') - .trigger('v-resize', [true, true]); - } - } - }); - - if (splitType === 'horizontal') { - this.splitter - .addClass('h-splitter') - .width(separatorWidth) - .height(root.height()); - } else if (splitType === 'vertical') { - this.splitter - .addClass('v-splitter') - .height(separatorWidth) - .width(root.width()); - } - - this.root.on('h-resize', function(e, percolateUp, percolateDown){ - e.stopPropagation(); - if (_this.splitType === 'horizontal') { - var w1, w2; - w1 = _this.root.width() * _this.splitProp - - separatorWidth / 2; - w2 = _this.root.width() * (1 - _this.splitProp) - - separatorWidth / 2; - _this.childElements[0] - .width(w1); - _this.childElements[1] - .width(w2) - .css('left', w1 + separatorWidth); - _this.splitter.css('left', w1); - } else if (_this.splitType === 'vertical') { - var w = _this.root.width(); - _this.childElements[0] - .width(w); - _this.childElements[1] - .width(w); - _this.splitter.width(w); - } - if (percolateUp) { - _this.root.parent('.editor-wrapper') - .trigger('h-resize', [true ,false]); - } - if (! percolateDown) return; - if (_this.childContainers[0]) { - _this.childContainers[0].root - .trigger('h-resize', [false, true]); - } else if (_this.childContainers[1]) { - _this.childContainers[1].root - .trigger('h-resize', [false, true]); - } - }); - - this.root.on('v-resize', function(e, percolateUp, percolateDown){ - e.stopPropagation(); - if (_this.splitType === 'horizontal') { - var h = _this.root.height(); - _this.childElements[0] - .height(h); - _this.childElements[1] - .height(h); - _this.splitter.height(h); - } else if (_this.splitType === 'vertical') { - var h1 = _this.root.height() * _this.splitProp - - separatorWidth / 2; - var h2 = _this.root.height() * (1 - _this.splitProp) - - separatorWidth / 2; - _this.childElements[0] - .height(h1); - _this.childElements[1] - .height(h2) - .css('top', h1+separatorWidth); - _this.splitter.css('top', h1); - } - if (percolateUp) { - _this.root.parent('.editor-wrapper') - .trigger('v-resize', [true ,false]); - } - if (! percolateDown) return; - if (_this.childContainers[0]) { - _this.childContainers[0].root - .trigger('v-resize', [false, true]); - } else if (_this.childContainers[1]) { - _this.childContainers[1].root - .trigger('v-resize', [false, true]); - } - }); - - this.root - .trigger('h-resize', [false, false]) - .trigger('v-resize', [false, false]); - } - - SplitContainer.prototype = { - setChild: function(idx, el){ - - if (el instanceof SplitContainer) { - this.childElements[idx] = el.root; - this.childContainers[idx] = el; - el.idx = idx; - } else { - this.childElements[idx] = el; - } - - this.childElements[idx].appendTo(this.root); - this.cssInit(this.childElements[idx], idx); - }, - cssInit: function(el, idx){ - var props = {}; - var h1, h2, w1, w2, rh, rw; - - rh = this.root.height(); - rw = this.root.width(); - - if (this.splitType === 'horizontal') { - - w1 = rw * this.splitProp - separatorWidth / 2; - w2 = rw * (1 - this.splitProp) - separatorWidth / 2; - - if (idx === 0) { - props = { - left: 0, - width: w1, - height: rh, - top: 0 - }; - } else { - props = { - left: w1 + separatorWidth, - width: w2, - height: rh, - top: 0 - }; - } - - } else if (this.splitType === 'vertical') { - - h1 = rh * this.splitProp - separatorWidth / 2; - h2 = rh * (1 - this.splitProp) - separatorWidth / 2; - - if (idx === 0) { - props = { - top: 0, - height: h1, - width: rw, - left: 0 - }; - } else { - props = { - top: h1 + separatorWidth, - height: h2, - width: rw, - left: 0 - }; - } - - } - - el.css(props); - } - }; - - ////////////////////////////////////////////////////////////////// - // - // Editor Component for Codiad - // --------------------------- - // Manage the lifecycle of Editor instances - // - ////////////////////////////////////////////////////////////////// - - codiad.editor = { - - /// Editor instances - One instance corresponds to an editor - /// pane in the user interface. Different EditSessions - /// (ace/edit_session) - instances: [], - - /// Currently focussed editor - activeInstance: null, - - // Settings for Editor instances - settings: { - autocomplete: false, - theme: 'twilight', - fontSize: '13px', - printMargin: false, - printMarginColumn: 80, - highlightLine: true, - indentGuides: true, - wrapMode: false, - softTabs: false, - persistentModal: true, - rightSidebarTrigger: false, - fileManagerTrigger: false, - tabSize: 4 - }, - - multi_line: false, - - rootContainer: null, - - fileExtensionTextMode: {}, - - init: function(){ - this.createSplitMenu(); - this.createModeMenu(); - - var er = $('#editor-region'); - - er.on('h-resize-init', function(){ - $('#editor-region > .editor-wrapper') - .width($(this).width()) - .trigger('h-resize'); - - }).on('v-resize-init', function(){ - $('#editor-region > .editor-wrapper') - .height($(this).height()) - .trigger('v-resize'); - }); - - $("#root-editor-wrapper").live("contextmenu", function( e ) { - - // Context Menu - e.preventDefault(); - _this = codiad.editor - - if( _this.getActive() ) { - - let path = codiad.active.getPath(); - $(e.target).attr('data-path', path); - codiad.filemanager.contextMenuShow( e, path, 'editor', 'editor'); - $(this).addClass('context-menu-active'); - } - }); - - $(window).resize(function(){ - $('#editor-region') - .trigger('h-resize-init') - .trigger('v-resize-init'); - }); - - //Load settings when initially starting up that way the first editor - //we open doesn't have the default settings. - this.getSettings(); - }, - - ////////////////////////////////////////////////////////////////// - // - // Retrieve editor settings from database - // - ////////////////////////////////////////////////////////////////// - - getSettings: async function() { - - let boolVal = null; - let _this = this; - let options = [ - 'editor.fontSize', - 'editor.overScroll', - 'editor.printMarginColumn', - 'editor.tabSize', - 'editor.theme', - ]; - let bool_options = [ - 'editor.autocomplete', - 'settings.autosave', - 'editor.printMargin', - 'editor.highlightLine', - 'editor.indentGuides', - 'editor.wrapMode', - 'editor.rightSidebarTrigger', - 'editor.fileManagerTrigger', - 'editor.softTabs', - 'editor.persistentModal', - ]; - - let user_settings = await codiad.settings.get_options(); - - //console.log( user_settings ); - - $.each( options, function( idx, key ) { - - let localValue = user_settings['codiad.' + key]; - if ( localValue != null ) { - - _this.settings[key.split('.').pop()] = localValue; - } - }); - - $.each( bool_options, async function(idx, key) { - - let localValue = user_settings['codiad.' + key]; - if ( localValue != null ) { - - _this.settings[key.split('.').pop()] = (localValue == 'true'); - } - }); - }, - - ///////////////////////////////////////////////////////////////// - // - // Apply configuration settings - // - // Parameters: - // i - {Editor} - // - ///////////////////////////////////////////////////////////////// - - applySettings: function(i) { - // Check user-specified settings - this.getSettings(); - - // Apply the current configuration settings: - i.setTheme('ace/theme/' + this.settings.theme); - i.setFontSize(this.settings.fontSize); - i.setPrintMarginColumn(this.settings.printMarginColumn); - i.setShowPrintMargin(this.settings.printMargin); - i.setHighlightActiveLine(this.settings.highlightLine); - i.setDisplayIndentGuides(this.settings.indentGuides); - i.getSession().setUseWrapMode(this.settings.wrapMode); - this.setTabSize(this.settings.tabSize, i); - this.setSoftTabs(this.settings.softTabs, i); - this.setOverScroll(this.settings.overScroll, i); - i.setOptions({ - enableBasicAutocompletion: true, - enableSnippets: true, - enableLiveAutocompletion: this.settings.autocomplete - }); - }, - - ////////////////////////////////////////////////////////////////// - // - // Create a new editor instance attached to given session - // - // Parameters: - // session - {EditSession} Session to be used for new Editor instance - // - ////////////////////////////////////////////////////////////////// - - addInstance: function(session, where) { - var el = $('
    '); - var chType, chArr = [], sc = null, chIdx = null; - var _this = this; - - if (this.instances.length == 0) { - // el.appendTo($('#editor-region')); - el.appendTo($('#root-editor-wrapper')); - } else { - - var ch = this.activeInstance.el; - var root; - - chIdx = (where === 'top' || where === 'left') ? 0 : 1; - chType = (where === 'top' || where === 'bottom') ? - 'vertical' : 'horizontal'; - - chArr[chIdx] = el; - chArr[1 - chIdx] = ch; - - root = $('
    ') - .height(ch.height()) - .width(ch.width()) - .addClass('editor-wrapper-' + chType) - .appendTo(ch.parent()); - - sc = new SplitContainer(root, chArr, chType); - - if (this.instances.length > 1) { - var pContainer = this.activeInstance.splitContainer; - var idx = this.activeInstance.splitIdx; - pContainer.setChild(idx, sc); - } - } - - ace.require("ace/ext/language_tools"); - var i = ace.edit(el[0]); - var resizeEditor = function(){ - i.resize(); - }; - - if (sc) { - i.splitContainer = sc; - i.splitIdx = chIdx; - - this.activeInstance.splitContainer = sc; - this.activeInstance.splitIdx = 1 - chIdx; - - sc.root - .on('h-resize', resizeEditor) - .on('v-resize', resizeEditor); - - if (this.instances.length === 1) { - var re = function(){ - _this.instances[0].resize(); - }; - sc.root - .on('h-resize', re) - .on('v-resize', re); - } - } - - i.el = el; - this.setSession(session, i); - - this.changeListener(i); - this.cursorTracking(i); - this.clickListener(i); - this.bindKeys(i); - - this.instances.push(i); - - i.on('focus', function(){ - _this.focus(i); - }); - - return i; - }, - - createSplitMenu: function(){ - var _this = this; - var _splitOptionsMenu = $('#split-options-menu'); - - this.initMenuHandler($('#split'),_splitOptionsMenu); - - $('#split-horizontally a').click(function(e){ - e.stopPropagation(); - _this.addInstance(_this.activeInstance.getSession(), 'bottom'); - _splitOptionsMenu.hide(); - }); - - $('#split-vertically a').click(function(e){ - e.stopPropagation(); - _this.addInstance(_this.activeInstance.getSession(), 'right'); - _splitOptionsMenu.hide(); - }); - - $('#merge-all a').click(function(e){ - e.stopPropagation(); - var s = _this.activeInstance.getSession(); - _this.exterminate(); - _this.addInstance(s); - _splitOptionsMenu.hide(); - }); - }, - - createModeMenu: function(){ - var _this = this; - var _thisMenu = $('#changemode-menu'); - var modeColumns = new Array(); - var modeOptions = new Array(); - var maxOptionsColumn = 15; - var firstOption = 0; - - this.initMenuHandler($('#current-mode'),_thisMenu); - - var modes = Object.keys( ModeList.modesByName ).sort(); - - $.each(modes, function(i){ - modeOptions.push('
  • '+modes[i]+'
  • '); - }); - - var html = ''; - while(true) { - html += ''; - firstOption = firstOption + maxOptionsColumn; - if(firstOption >= modeOptions.length) { - break; - } - } - - html += '
      '; - if ((modeOptions.length-firstOption) < maxOptionsColumn) { - max = modeOptions.length; - } else { - max = firstOption + maxOptionsColumn; - } - var currentcolumn = modeOptions.slice(firstOption, max); - for (var option in currentcolumn) { - html += currentcolumn[option]; - } - html += '
    '; - _thisMenu.html(html); - - $('#changemode-menu a').click(function(e){ - e.stopPropagation(); - var newMode = "ace/mode/" + $(e.currentTarget).text(); - var actSession = _this.activeInstance.getSession(); - - // handle async mode change - var fn = function(){ - _this.setModeDisplay(actSession); - actSession.removeListener('changeMode', fn); - }; - actSession.on("changeMode", fn); - - actSession.setMode(newMode); - _thisMenu.hide(); - - }); - }, - - initMenuHandler: function(button,menu){ - var _this = this; - var thisButton = button; - var thisMenu = menu; - - thisMenu.appendTo($('body')); - - thisButton.click(function(e){ - var wh = $(window).height(); - - e.stopPropagation(); - - // close other menus - _this.closeMenus(thisMenu); - - thisMenu.css({ - // display: 'block', - bottom: ( (wh - $(this).offset().top) + 8) + 'px', - left: ($(this).offset().left - 13) + 'px' - }); - - thisMenu.slideToggle('fast'); - - // handle click-out autoclosing - var fn = function(){ - thisMenu.hide(); - $(window).off('click', fn); - }; - $(window).on('click', fn); - }); - }, - - closeMenus: function(exclude){ - var menuId = exclude.attr("id"); - if(menuId != 'split-options-menu') $('#split-options-menu').hide(); - if(menuId != 'changemode-menu') $('#changemode-menu').hide(); - }, - - setModeDisplay: function(session){ - var currMode = session.getMode().$id; - if(currMode){ - currMode = currMode.substring(currMode.lastIndexOf('/') + 1); - $('#current-mode').html(currMode); - } else { - $('#current-mode').html('text'); - } - }, - - ////////////////////////////////////////////////////////////////// - // - // Remove all Editor instances and clean up the DOM - // - ////////////////////////////////////////////////////////////////// - - exterminate: function() { - $('.editor').remove(); - $('.editor-wrapper').remove(); - $('#editor-region').append($('
    ').attr('id', 'editor')); - $('#current-file').html(''); - $('#current-mode').html(''); - this.instances = []; - this.activeInstance = null; - }, - - ////////////////////////////////////////////////////////////////// - // - // Detach EditSession session from all Editor instances replacing - // them with replacementSession - // - ////////////////////////////////////////////////////////////////// - - removeSession: function(session, replacementSession) { - for (var k = 0; k < this.instances.length; k++) { - if (this.instances[k].getSession().path === session.path) { - this.instances[k].setSession(replacementSession); - } - } - if ($('#current-file').text() === session.path) { - $('#current-file').text(replacementSession.path); - } - - this.setModeDisplay(replacementSession); - }, - - isOpen: function(session){ - for (var k = 0; k < this.instances.length; k++) { - if (this.instances[k].getSession().path === session.path) { - return true; - } - } - return false; - }, - - ///////////////////////////////////////////////////////////////// - // - // Convenience function to iterate over Editor instances - // - // Parameters: - // fn - {Function} callback called with each member as an argument - // - ///////////////////////////////////////////////////////////////// - - forEach: function(fn) { - for (var k = 0; k < this.instances.length; k++) { - fn.call(this, this.instances[k]); - } - }, - - ///////////////////////////////////////////////////////////////// - // - // Get the currently active Editor instance - // - // In a multi-pane setup this would correspond to the - // editor pane user is currently working on. - // - ///////////////////////////////////////////////////////////////// - - getActive: function() { - return this.activeInstance; - }, - - ///////////////////////////////////////////////////////////////// - // - // Set an editor instance as active - // - // Parameters: - // i - {Editor} - // - ///////////////////////////////////////////////////////////////// - - setActive: function(i) { - if (! i) return; - this.activeInstance = i; - $('#current-file').text(i.getSession().path); - this.setModeDisplay(i.getSession()); - }, - - ///////////////////////////////////////////////////////////////// - // - // Change the EditSession of Editor instance - // - // Parameters: - // session - {EditSession} - // i - {Editor} - // - ///////////////////////////////////////////////////////////////// - - setSession: function(session, i) { - i = i || this.getActive(); - if (! this.isOpen(session)) { - if (! i) { - i = this.addInstance(session); - } else { - i.setSession(session); - } - } else { - // Proxy session is required because scroll-position and - // cursor position etc. are shared among sessions. - - var proxySession = new EditSession(session.getDocument(), - session.getMode()); - proxySession.setUndoManager(new UndoManager()); - proxySession.path = session.path; - proxySession.listThumb = session.listThumb; - proxySession.tabThumb = session.tabThumb; - if (! i) { - i = this.addInstance(proxySession); - } else { - i.setSession(proxySession); - } - } - this.applySettings(i); - this.cursorTracking(i); - this.setActive(i); - }, - - ///////////////////////////////////////////////////////////////// - // - // Select file mode by extension case insensitive - // - // Parameters: - // e - {String} File extension - // - ///////////////////////////////////////////////////////////////// - - selectMode: function(e) { - if(typeof(e) != 'string'){ - return 'text'; - } - e = e.toLowerCase(); - return( ModeList.getModeForPath( e ) ); - }, - - ///////////////////////////////////////////////////////////////// - // - // Add an text mode for an extension - // - // Parameters: - // extension - {String} File Extension - // mode - {String} TextMode for this extension - // - ///////////////////////////////////////////////////////////////// - - addFileExtensionTextMode: function(extension, mode){ - if(typeof(extension) != 'string' || typeof(mode) != 'string'){ - if (console){ - console.warn('wrong usage of addFileExtensionTextMode, both parameters need to be string'); - } - return; - } - mode = mode.toLowerCase(); - this.fileExtensionTextMode[extension] = mode; - }, - - ///////////////////////////////////////////////////////////////// - // - // clear all extension-text mode joins - // - ///////////////////////////////////////////////////////////////// - - clearFileExtensionTextMode: function(){ - this.fileExtensionTextMode = {}; - }, - - ///////////////////////////////////////////////////////////////// - // - // Set the editor mode - // - // Parameters: - // m - {TextMode} mode - // i - {Editor} Editor (Defaults to active editor) - // - ///////////////////////////////////////////////////////////////// - - setMode: function(m, i) { - i = i || this.getActive(); - - // Check if mode is already loaded - if (! editorModes[m]) { - - // Load the Mode - var modeFile = 'components/editor/ace-editor/mode-' + m + '.js'; - $.loadScript(modeFile, function() { - - // Mark the mode as loaded - editorModes[m] = true; - var EditorMode = ace.require('ace/mode/' + m).Mode; - i.getSession().setMode(new EditorMode()); - }, true); - } else { - - var EditorMode = ace.require('ace/mode/' + m).Mode; - i.getSession().setMode(new EditorMode()); - - } - }, - - ///////////////////////////////////////////////////////////////// - // - // Set the editor theme - // - // Parameters: - // t - {String} theme eg. twilight, cobalt etc. - // i - {Editor} Editor instance (If omitted, Defaults to all editors) - // - // For a list of themes supported by Ace - refer : - // https://github.com/ajaxorg/ace/tree/master/lib/ace/theme - // - // TODO: Provide support for custom themes - // - ///////////////////////////////////////////////////////////////// - - setTheme: function(t, i) { - if (i) { - // If a specific instance is specified, change the theme for - // this instance - i.setTheme('ace/theme/'+ t); - } else { - // Change the theme for the existing editor instances - // and make it the default for new instances - this.settings.theme = t; - for (var k = 0; k < this.instances.length; k++) { - this.instances[k].setTheme('ace/theme/'+ t); - } - } - //Database - //codiad.settings.update_option( 'codiad.editor.theme', t ); - }, - - ///////////////////////////////////////////////////////////////// - // - // Set contents of the editor - // - // Parameters: - // c - {String} content - // i - {Editor} (Defaults to active editor) - // - ///////////////////////////////////////////////////////////////// - - setContent: function(c, i) { - i = i || this.getActive(); - i.getSession().setValue(c); - }, - - ///////////////////////////////////////////////////////////////// - // - // Set Font Size - // - // Set the font for all Editor instances and remember - // the value for Editor instances to be created in - // future - // - // Parameters: - // s - {Number} font size - // i - {Editor} Editor instance (If omitted, Defaults to all editors) - // - ///////////////////////////////////////////////////////////////// - - setFontSize: function(s, i) { - if (i) { - i.setFontSize(s); - } else { - this.settings.fontSize = s; - this.forEach(function(i) { - i.setFontSize(s); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.fontSize', s ); - }, - - - ///////////////////////////////////////////////////////////////// - // - // Enable/disable Highlighting of active line - // - // Parameters: - // h - {Boolean} - // i - {Editor} Editor instance ( If left out, setting is - // applied to all editors ) - // - ///////////////////////////////////////////////////////////////// - - setHighlightLine: function(h, i) { - if (i) { - i.setHighlightActiveLine(h); - } else { - this.settings.highlightLine = h; - this.forEach(function(i) { - i.setHighlightActiveLine(h); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.highlightLine', h ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Show/Hide print margin indicator - // - // Parameters: - // p - {Number} print margin column - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setPrintMargin: function(p, i) { - if (i) { - i.setShowPrintMargin(p); - } else { - this.settings.printMargin = p; - this.forEach(function(i) { - i.setShowPrintMargin(p); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.printMargin', p ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set print margin column - // - // Parameters: - // p - {Number} print margin column - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setPrintMarginColumn: function(p, i) { - if (i) { - i.setPrintMarginColumn(p); - } else { - this.settings.printMarginColumn = p; - this.forEach(function(i) { - i.setPrintMarginColumn(p); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.printMarginColumn', p ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Show/Hide indent guides - // - // Parameters: - // g - {Boolean} - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setIndentGuides: function(g, i) { - if (i) { - i.setDisplayIndentGuides(g); - } else { - this.settings.indentGuides = g; - this.forEach(function(i) { - i.setDisplayIndentGuides(g); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.indentGuides', g ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Enable/Disable Code Folding - // - // Parameters: - // f - {Boolean} - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setCodeFolding: function(f, i) { - if (i) { - i.setFoldStyle(f); - } else { - this.forEach(function(i) { - i.setFoldStyle(f); - }); - } - }, - - ////////////////////////////////////////////////////////////////// - // - // Enable/Disable Line Wrapping - // - // Parameters: - // w - {Boolean} - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setWrapMode: function(w, i) { - if (i) { - i.getSession().setUseWrapMode(w); - } else { - this.forEach(function(i) { - i.getSession().setUseWrapMode(w); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.wrapMode', w ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set last position of modal to be saved - // - // Parameters: - // t - {Boolean} (false for Automatic Position, true for Last Position) - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setPersistentModal: function(t, i) { - this.settings.persistentModal = t; - //Database - //codiad.settings.update_option( 'codiad.editor.persistentModal', t ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set trigger for opening the right sidebar - // - // Parameters: - // t - {Boolean} (false for Hover, true for Click) - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setRightSidebarTrigger: function(t, i) { - this.settings.rightSidebarTrigger = t; - //Database - //codiad.settings.update_option( 'codiad.editor.rightSidebarTrigger', t ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set trigger for clicking on the filemanager - // - // Parameters: - // t - {Boolean} (false for Hover, true for Click) - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setFileManagerTrigger: function(t, i) { - this.settings.fileManagerTrigger = t; - //Database - //codiad.settings.update_option( 'codiad.editor.fileManagerTrigger', t ); - codiad.project.loadSide(); - }, - - - ////////////////////////////////////////////////////////////////// - // - // set Tab Size - // - // Parameters: - // s - size - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setTabSize: function(s, i) { - if (i) { - i.getSession().setTabSize(parseInt(s)); - } else { - this.forEach(function(i) { - i.getSession().setTabSize(parseInt(s)); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.tabSize', s ); - - }, - - ////////////////////////////////////////////////////////////////// - // - // Enable or disable Soft Tabs - // - // Parameters: - // t - true / false - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setSoftTabs: function(t, i) { - if (i) { - i.getSession().setUseSoftTabs(t); - } else { - this.forEach(function(i) { - i.getSession().setUseSoftTabs(t); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.softTabs', t ); - - }, - - ////////////////////////////////////////////////////////////////// - // - // Get content from editor - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - getContent: function(i) { - i = i || this.getActive(); - if (! i) return; - var content = i.getSession().getValue(); - if (!content) { - content = ' '; - } // Pass something through - return content; - }, - - ////////////////////////////////////////////////////////////////// - // - // Resize the editor - Trigger the editor to readjust its layout - // esp if the container has been resized manually. - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - resize: function(i) { - i = i || this.getActive(); - if (! i) return; - i.resize(); - }, - - ////////////////////////////////////////////////////////////////// - // - // Mark the instance as changed (in the user interface) - // upon change in the document content. - // - // Parameters: - // i - {Editor} - // - ////////////////////////////////////////////////////////////////// - - changeListener: function(i) { - - var _this = this; - i.on('change', function() { - - codiad.active.markChanged(_this.getActive().getSession().path); - codiad.active.savePosition(); - }); - }, - - clickListener: function(i) { - - var _this = this; - i.on('click', function() { - - codiad.active.savePosition(); - }); - }, - - ////////////////////////////////////////////////////////////////// - // - // Get Selected Text - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - getSelectedText: function(i) { - i = i || this.getActive(); - if (! i) return; - return i.getCopyText(); - }, - - ////////////////////////////////////////////////////////////////// - // - // Insert text - // - // Parameters: - // val - {String} Text to be inserted - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - insertText: function(val, i) { - i = i || this.getActive(); - if (! i) return; - i.insert(val); - }, - - ////////////////////////////////////////////////////////////////// - // - // Move the cursor to a particular line - // - // Parameters: - // line - {Number} Line number - // i - {Editor} Editor instance - // - ////////////////////////////////////////////////////////////////// - - gotoLine: function(line, i) { - i = i || this.getActive(); - if (! i) return; - i.gotoLine(line, 0, true); - }, - - ////////////////////////////////////////////////////////////////// - // - // Focus an editor - // - // Parameters: - // i - {Editor} Editor instance (Defaults to current editor) - // - ////////////////////////////////////////////////////////////////// - - focus: function(i) { - i = i || this.getActive(); - this.setActive(i); - if (! i) return; - i.focus(); - codiad.active.focus(i.getSession().path); - this.cursorTracking(i); - }, - - ////////////////////////////////////////////////////////////////// - // - // Setup Cursor Tracking - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - cursorTracking: function(i) { - - i = i || this.getActive(); - if (! i) return; - - /** - * Update the cursor position now so that when a new file opens, - * we do not have the old cursor data. - */ - - $('#cursor-position') - .html(i18n('Ln') + ': ' - + (i.getCursorPosition().row + 1) - + ' · ' + i18n('Col') + ': ' - + i.getCursorPosition().column - ); - - //Register the changecursor function so updates continue - i.selection.on( "changeCursor", function( e ) { - - codiad.active.savePosition(); - $('#cursor-position') - .html(i18n('Ln') + ': ' - + (i.getCursorPosition().row + 1) - + ' · ' + i18n('Col') + ': ' - + i.getCursorPosition().column - ); - }); - }, - - ////////////////////////////////////////////////////////////////// - // - // Setup Key bindings - // - // Parameters: - // i - {Editor} - // - ////////////////////////////////////////////////////////////////// - - bindKeys: function(i) { - - var _this = this; - - // Find - i.commands.addCommand({ - name: 'Find', - bindKey: { - win: 'Ctrl-F', - mac: 'Command-F' - }, - exec: function(e) { - _this.openSearch('find'); - } - }); - - // Find + Replace - i.commands.addCommand({ - name: 'Replace', - bindKey: { - win: 'Ctrl-R', - mac: 'Command-R' - }, - exec: function(e) { - _this.openSearch('replace'); - } - }); - - i.commands.addCommand({ - name: 'Move Up', - bindKey: { - win: 'Ctrl-up', - mac: 'Command-up' - }, - exec: function(e) { - codiad.active.move('up'); - } - }); - - i.commands.addCommand({ - name: 'Move Down', - bindKey: { - win: 'Ctrl-down', - mac: 'Command-up' - }, - exec: function(e) { - codiad.active.move('down'); - } - }); - - }, - - ////////////////////////////////////////////////////////////////// - // - // Present the Search (Find + Replace) dialog box - // - // Parameters: - // type - {String} Optional, defaults to find. Provide 'replace' for replace dialog. - // - ////////////////////////////////////////////////////////////////// - - openSearch: function(type) { - if (this.getActive()) { - codiad.modal.load(400, - 'components/editor/dialog.php?action=search&type=' + - type); - codiad.modal.hideOverlay(); - } else { - codiad.message.error('No Open Files'); - } - }, - - ////////////////////////////////////////////////////////////////// - // - // Perform Search (Find + Replace) operation - // - // Parameters: - // action - {String} find | replace | replaceAll - // i - {Editor} Defaults to active Editor instance - // - ////////////////////////////////////////////////////////////////// - - search: function(action, i) { - i = i || this.getActive(); - if (! i) return; - if( this.multi_line ) { - - var find = $('#modal textarea[name="find"]') - .val(); - var replace = $('#modal textarea[name="replace"]') - .val(); - } else { - - var find = $('#modal input[name="find"]') - .val(); - var replace = $('#modal input[name="replace"]') - .val(); - } - switch (action) { - case 'find': - - i.find(find, { - backwards: false, - wrap: true, - caseSensitive: false, - wholeWord: false, - regExp: false - }); - - break; - - case 'replace': - - i.find(find, { - backwards: false, - wrap: true, - caseSensitive: false, - wholeWord: false, - regExp: false - }); - i.replace(replace); - - break; - - case 'replaceAll': - - i.find(find, { - backwards: false, - wrap: true, - caseSensitive: false, - wholeWord: false, - regExp: false - }); - i.replaceAll(replace); - - break; - } - }, - - ////////////////////////////////////////////////////////////////// - // - // Enable editor - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - enableEditing: function(i) { - i = i || this.getActive(); - if (! i) return; - i.textInput.setReadOnly( false ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Disable editor - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - disableEditing: function(i) { - i = i || this.getActive(); - if (! i) return; - i.textInput.setReadOnly( true ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set Overscroll - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - setOverScroll: function( s, i ) { - - if (i) { - i.setOption( "scrollPastEnd", s ); - } else { - this.settings.overScroll = s; - this.forEach(function(i) { - i.setOption( "scrollPastEnd", s ); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.overScroll', s ); - }, - - setLiveAutocomplete: function( s, i ) { - - if (i) { - i.setOptions({ - enableLiveAutocompletion: s - }); - } else { - this.settings.autocomplete = s; - this.forEach(function(i) { - i.setOptions({ - enableLiveAutocompletion: s - }); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.autocomplete', s ); - }, - - toggleMultiLine: function( e ) { - - if( e.innerText === "Multi Line" ) { - - this.multi_line = true; - e.innerText = "Single Line"; - $('input[name="find"]').hide(); - $('textarea[name="find"]').show(); - $('textarea[name="find"]').val( $('input[name="find"]').val() ); - - $('input[name="replace"]').hide(); - $('textarea[name="replace"]').show(); - $('textarea[name="replace"]').val( $('input[name="replace"]').val() ); - } else { - - this.multi_line = false; - e.innerText = "Multi Line"; - $('input[name="find"]').show(); - $('textarea[name="find"]').hide(); - $('input[name="find"]').val( $('textarea[name="find"]').val() ); - - $('input[name="replace"]').show(); - $('textarea[name="replace"]').hide(); - $('input[name="replace"]').val( $('textarea[name="replace"]').val() ); - } - }, - - paste: function( ) { - - navigator.clipboard.readText().then(text => {codiad.editor.getActive().insert( text )}); - }, - - openSort: function() { - - if ( this.getActive() && codiad.active.getSelectedText() != "" ) { - - codiad.modal.load( 400, 'components/editor/dialog.php?action=sort' ); - codiad.modal.hideOverlay(); - } else { - - codiad.message.error('No text selected'); - } - }, - - sort: function( eol ) { - - let text = $('#modal textarea[name="sort"]').val(); - let array = text.split( eol ); - array = array.sort( codiad.editor.sort_a ); - let sorted = array.join( eol ); - - console.log( text, eol, array, sorted ); - codiad.modal.unload(); - codiad.editor.getActive().insert( sorted ); - codiad.editor.getActive().focus(); - }, - - sort_a: function( a, b ) { - - let pos = 0; - let case_sensitive = $( '#modal input[name="case_sensitive"]' ).prop( 'checked' ) - - if( ! case_sensitive ) { - - a = a.toLowerCase(); - b = b.toLowerCase(); - } - - if( a < b ) { - - pos = -1; - } else if( a > b ) { - - pos = 1; - } - - return pos; - } - }; - -})(this, jQuery); \ No newline at end of file +/* + * Copyright (c) Codiad & Kent Safranski (codiad.com), distributed + * as-is and without warranty under the MIT License. See + * [root]/license.txt for more. This information must remain intact. + */ +( function( global, $ ) { + + // Classes from Ace + var VirtualRenderer = ace.require( 'ace/virtual_renderer' ).VirtualRenderer; + var Editor = ace.require( 'ace/editor' ).Editor; + var EditSession = ace.require( 'ace/edit_session' ).EditSession; + var ModeList = ace.require( "ace/ext/modelist" ); + var UndoManager = ace.require( "ace/undomanager" ).UndoManager; + + // Editor modes that have been loaded + var editorModes = {}; + + var codiad = global.codiad; + codiad._cursorPoll = null; + + var separatorWidth = 3; + + $( function() { + codiad.editor.init(); + }); + + function SplitContainer( root, children, splitType ) { + var _this = this; + + this.root = root; + this.splitType = splitType; + this.childContainers = {}; + this.childElements = {}; + this.splitProp = 0.5; + + this.setChild( 0, children[0 ] ); + this.setChild( 1, children[1 ] ); + + this.splitter = $( '
    ' ) + .addClass( 'splitter' ) + .appendTo( root ) + .draggable( { + axis: ( splitType === 'horizontal' ? 'x' : 'y' ), + drag: function( e, ui ) { + if( _this.splitType === 'horizontal' ) { + var w1, w2; + w1 = ui.position.left - separatorWidth / 2; + w2 = _this.root.width() - ui.position.left - + separatorWidth / 2; + _this.splitProp = w1 / _this.root.width(); + _this.childElements[0 + .width( w1 ) + .trigger( 'h-resize', [true, true] ); + _this.childElements[1] + .width( w2 ) + .css( 'left', w1 + separatorWidth + 'px' ) + .trigger( 'h-resize', [true, true] ); + _this.splitProp = ui.position.left / _this.root.width(); + } else { + var h1, h2; + h1 = ui.position.top - separatorWidth / 2; + h2 = _this.root.width() - ui.position.top - + separatorWidth / 2; + _this.splitProp = h1 / _this.root.height(); + _this.childElements[0] + .height( h1 ) + .trigger( 'v-resize', [true, true] ); + _this.childElements[1] + .height( h2 ) + .css( 'top', h1 + separatorWidth + 'px' ) + .trigger( 'v-resize', [true, true] ); + } + } + }); + + if( splitType === 'horizontal' ) { + this.splitter + .addClass( 'h-splitter' ) + .width( separatorWidth ) + .height( root.height() ); + } else if( splitType === 'vertical' ) { + this.splitter + .addClass( 'v-splitter' ) + .height( separatorWidth ) + .width( root.width() ); + } + + this.root.on( 'h-resize', function( e, percolateUp, percolateDown ) { + e.stopPropagation(); + if( _this.splitType === 'horizontal' ) { + var w1, w2; + w1 = _this.root.width() * _this.splitProp - + separatorWidth / 2; + w2 = _this.root.width() * ( 1 - _this.splitProp ) - + separatorWidth / 2; + _this.childElements[0] + .width( w1 ); + _this.childElements[1] + .width( w2 ) + .css( 'left', w1 + separatorWidth ); + _this.splitter.css( 'left', w1 ); + } else if( _this.splitType === 'vertical' ) { + var w = _this.root.width(); + _this.childElements[0] + .width( w ); + _this.childElements[1] + .width( w ); + _this.splitter.width( w ); + } + if( percolateUp ) { + _this.root.parent( '.editor-wrapper' ) + .trigger( 'h-resize', [true, false] ); + } + if( !percolateDown ) return; + if( _this.childContainers[0] ) { + _this.childContainers[0].root + .trigger( 'h-resize', [false, true] ); + } else if( _this.childContainers[1] ) { + _this.childContainers[1].root + .trigger( 'h-resize', [false, true] ); + } + }); + + this.root.on( 'v-resize', function( e, percolateUp, percolateDown ) { + e.stopPropagation(); + if( _this.splitType === 'horizontal' ) { + var h = _this.root.height(); + _this.childElements[0] + .height( h ); + _this.childElements[1] + .height( h ); + _this.splitter.height( h ); + } else if( _this.splitType === 'vertical' ) { + var h1 = _this.root.height() * _this.splitProp - + separatorWidth / 2; + var h2 = _this.root.height() * ( 1 - _this.splitProp ) - + separatorWidth / 2; + _this.childElements[0] + .height( h1 ); + _this.childElements[1] + .height( h2 ) + .css( 'top', h1 + separatorWidth ); + _this.splitter.css( 'top', h1 ); + } + if( percolateUp ) { + _this.root.parent( '.editor-wrapper' ) + .trigger( 'v-resize', [true, false] ); + } + if( !percolateDown ) return; + if( _this.childContainers[0] ) { + _this.childContainers[0].root + .trigger( 'v-resize', [false, true] ); + } else if( _this.childContainers[1] ) { + _this.childContainers[1].root + .trigger( 'v-resize', [false, true] ); + } + }); + + this.root + .trigger( 'h-resize', [false, false] ) + .trigger( 'v-resize', [false, false] ); + } + + SplitContainer.prototype = { + setChild: function( idx, el ) { + + if( el instanceof SplitContainer ) { + this.childElements[idx] = el.root; + this.childContainers[idx] = el; + el.idx = idx; + } else { + this.childElements[idx] = el; + } + + this.childElements[idx].appendTo( this.root ); + this.cssInit( this.childElements[idx], idx ); + }, + cssInit: function( el, idx ) { + var props = {}; + var h1, h2, w1, w2, rh, rw; + + rh = this.root.height(); + rw = this.root.width(); + + if( this.splitType === 'horizontal' ) { + + w1 = rw * this.splitProp - separatorWidth / 2; + w2 = rw * ( 1 - this.splitProp ) - separatorWidth / 2; + + if( idx === 0 ) { + props = { + left: 0, + width: w1, + height: rh, + top: 0 + }; + } else { + props = { + left: w1 + separatorWidth, + width: w2, + height: rh, + top: 0 + }; + } + + } else if( this.splitType === 'vertical' ) { + + h1 = rh * this.splitProp - separatorWidth / 2; + h2 = rh * ( 1 - this.splitProp ) - separatorWidth / 2; + + if( idx === 0 ) { + props = { + top: 0, + height: h1, + width: rw, + left: 0 + }; + } else { + props = { + top: h1 + separatorWidth, + height: h2, + width: rw, + left: 0 + }; + } + + } + + el.css( props ); + } + }; + + ////////////////////////////////////////////////////////////////// + // + // Editor Component for Codiad + // --------------------------- + // Manage the lifecycle of Editor instances + // + ////////////////////////////////////////////////////////////////// + + codiad.editor = { + + /// Editor instances - One instance corresponds to an editor + /// pane in the user interface. Different EditSessions + /// (ace/edit_session) + instances: [], + + /// Currently focussed editor + activeInstance: null, + + // Settings for Editor instances + settings: { + autocomplete: false, + theme: 'twilight', + fontSize: '13px', + printMargin: false, + printMarginColumn: 80, + highlightLine: true, + indentGuides: true, + wrapMode: false, + softTabs: false, + persistentModal: true, + rightSidebarTrigger: false, + fileManagerTrigger: false, + tabSize: 4 + }, + + multi_line: false, + + rootContainer: null, + + fileExtensionTextMode: {}, + + init: function() { + this.createSplitMenu(); + this.createModeMenu(); + + var er = $( '#editor-region' ); + + er.on( 'h-resize-init', function() { + $( '#editor-region > .editor-wrapper' ) + .width( $( this ).width() ) + .trigger( 'h-resize' ); + + }).on( 'v-resize-init', function() { + $( '#editor-region > .editor-wrapper' ) + .height( $( this ).height() ) + .trigger( 'v-resize' ); + }); + + $( "#root-editor-wrapper" ).live( "contextmenu", function( e ) { + + // Context Menu + e.preventDefault(); + _this = codiad.editor + + if( _this.getActive() ) { + + let path = codiad.active.getPath(); + $( e.target ).attr( 'data-path', path ); + codiad.filemanager.contextMenuShow( e, path, 'editor', 'editor' ); + $( this ).addClass( 'context-menu-active' ); + } + }); + + $( window ).resize( function() { + $( '#editor-region' ) + .trigger( 'h-resize-init' ) + .trigger( 'v-resize-init' ); + }); + + //Load settings when initially starting up that way the first editor + //we open doesn't have the default settings. + this.getSettings(); + }, + + ////////////////////////////////////////////////////////////////// + // + // Retrieve editor settings from database + // + ////////////////////////////////////////////////////////////////// + + getSettings: async function() { + + let boolVal = null; + let _this = this; + let options = [ + 'editor.fontSize', + 'editor.overScroll', + 'editor.printMarginColumn', + 'editor.tabSize', + 'editor.theme', + ]; + let bool_options = [ + 'editor.autocomplete', + 'settings.autosave', + 'editor.printMargin', + 'editor.highlightLine', + 'editor.indentGuides', + 'editor.wrapMode', + 'editor.rightSidebarTrigger', + 'editor.fileManagerTrigger', + 'editor.softTabs', + 'editor.persistentModal', + ]; + + let user_settings = await codiad.settings.get_options(); + + //console.log( user_settings ); + + $.each( options, function( idx, key ) { + + let localValue = user_settings['codiad.' + key]; + if( localValue != null ) { + + _this.settings[key.split( '.' ).pop()] = localValue; + } + }); + + $.each( bool_options, async function( idx, key ) { + + let localValue = user_settings['codiad.' + key]; + if( localValue != null ) { + + _this.settings[key.split( '.' ).pop()] = ( localValue == 'true' ); + } + }); + }, + + ///////////////////////////////////////////////////////////////// + // + // Apply configuration settings + // + // Parameters: + // i - {Editor} + // + ///////////////////////////////////////////////////////////////// + + applySettings: function( i ) { + // Check user-specified settings + this.getSettings(); + + // Apply the current configuration settings: + i.setTheme( 'ace/theme/' + this.settings.theme ); + i.setFontSize( this.settings.fontSize ); + i.setPrintMarginColumn( this.settings.printMarginColumn ); + i.setShowPrintMargin( this.settings.printMargin ); + i.setHighlightActiveLine( this.settings.highlightLine ); + i.setDisplayIndentGuides( this.settings.indentGuides ); + i.getSession().setUseWrapMode( this.settings.wrapMode ); + this.setTabSize( this.settings.tabSize, i ); + this.setSoftTabs( this.settings.softTabs, i ); + this.setOverScroll( this.settings.overScroll, i ); + i.setOptions( { + enableBasicAutocompletion: true, + enableSnippets: true, + enableLiveAutocompletion: this.settings.autocomplete + }); + }, + + ////////////////////////////////////////////////////////////////// + // + // Create a new editor instance attached to given session + // + // Parameters: + // session - {EditSession} Session to be used for new Editor instance + // + ////////////////////////////////////////////////////////////////// + + addInstance: function( session, where ) { + var el = $( '
    ' ); + var chType, chArr = [], + sc = null, + chIdx = null; + var _this = this; + + if( this.instances.length == 0 ) { + // el.appendTo($('#editor-region')); + el.appendTo( $( '#root-editor-wrapper' ) ); + } else { + + var ch = this.activeInstance.el; + var root; + + chIdx = ( where === 'top' || where === 'left' ) ? 0 : 1; + chType = ( where === 'top' || where === 'bottom' ) ? + 'vertical' : 'horizontal'; + + chArr[chIdx] = el; + chArr[1 - chIdx] = ch; + + root = $( '
    ' ) + .height( ch.height() ) + .width( ch.width() ) + .addClass( 'editor-wrapper-' + chType ) + .appendTo( ch.parent() ); + + sc = new SplitContainer( root, chArr, chType ); + + if( this.instances.length > 1 ) { + var pContainer = this.activeInstance.splitContainer; + var idx = this.activeInstance.splitIdx; + pContainer.setChild( idx, sc ); + } + } + + ace.require( "ace/ext/language_tools" ); + var i = ace.edit( el[0] ); + var resizeEditor = function() { + i.resize(); + }; + + if( sc ) { + i.splitContainer = sc; + i.splitIdx = chIdx; + + this.activeInstance.splitContainer = sc; + this.activeInstance.splitIdx = 1 - chIdx; + + sc.root + .on( 'h-resize', resizeEditor ) + .on( 'v-resize', resizeEditor ); + + if( this.instances.length === 1 ) { + var re = function() { + _this.instances[0].resize(); + }; + sc.root + .on( 'h-resize', re ) + .on( 'v-resize', re ); + } + } + + i.el = el; + this.setSession( session, i ); + + this.changeListener( i ); + this.cursorTracking( i ); + this.clickListener( i ); + this.bindKeys( i ); + + this.instances.push( i ); + + i.on( 'focus', function() { + _this.focus( i ); + }); + + return i; + }, + + createSplitMenu: function() { + var _this = this; + var _splitOptionsMenu = $( '#split-options-menu' ); + + this.initMenuHandler( $( '#split' ), _splitOptionsMenu ); + + $( '#split-horizontally a' ).click( function( e ) { + e.stopPropagation(); + _this.addInstance( _this.activeInstance.getSession(), 'bottom' ); + _splitOptionsMenu.hide(); + }); + + $( '#split-vertically a' ).click( function( e ) { + e.stopPropagation(); + _this.addInstance( _this.activeInstance.getSession(), 'right' ); + _splitOptionsMenu.hide(); + }); + + $( '#merge-all a' ).click( function( e ) { + e.stopPropagation(); + var s = _this.activeInstance.getSession(); + _this.exterminate(); + _this.addInstance( s ); + _splitOptionsMenu.hide(); + }); + }, + + createModeMenu: function() { + var _this = this; + var _thisMenu = $( '#changemode-menu' ); + var modeColumns = new Array(); + var modeOptions = new Array(); + var maxOptionsColumn = 15; + var firstOption = 0; + + this.initMenuHandler( $( '#current-mode' ), _thisMenu ); + + var modes = Object.keys( ModeList.modesByName ).sort(); + + $.each( modes, function( i ) { + modeOptions.push( '
  • ' + modes[i] + '
  • ' ); + }); + + var html = ''; + while( true ) { + html += ''; + firstOption = firstOption + maxOptionsColumn; + if( firstOption >= modeOptions.length ) { + break; + } + } + + html += '
      '; + if( ( modeOptions.length - firstOption ) < maxOptionsColumn ) { + max = modeOptions.length; + } else { + max = firstOption + maxOptionsColumn; + } + var currentcolumn = modeOptions.slice( firstOption, max ); + for( var option in currentcolumn ) { + html += currentcolumn[option]; + } + html += '
    '; + _thisMenu.html( html ); + + $( '#changemode-menu a' ).click( function( e ) { + e.stopPropagation(); + var newMode = "ace/mode/" + $( e.currentTarget ).text(); + var actSession = _this.activeInstance.getSession(); + + // handle async mode change + var fn = function() { + _this.setModeDisplay( actSession ); + actSession.removeListener( 'changeMode', fn ); + }; + actSession.on( "changeMode", fn ); + + actSession.setMode( newMode ); + _thisMenu.hide(); + + }); + }, + + initMenuHandler: function( button, menu ) { + var _this = this; + var thisButton = button; + var thisMenu = menu; + + thisMenu.appendTo( $( 'body' ) ); + + thisButton.click( function( e ) { + var wh = $( window ).height(); + + e.stopPropagation(); + + // close other menus + _this.closeMenus( thisMenu ); + + thisMenu.css( { + // display: 'block', + bottom: ( ( wh - $( this ).offset().top ) + 8 ) + 'px', + left: ( $( this ).offset().left - 13 ) + 'px' + }); + + thisMenu.slideToggle( 'fast' ); + + // handle click-out autoclosing + var fn = function() { + thisMenu.hide(); + $( window ).off( 'click', fn ); + }; + $( window ).on( 'click', fn ); + }); + }, + + closeMenus: function( exclude ) { + var menuId = exclude.attr( "id" ); + if( menuId != 'split-options-menu' ) $( '#split-options-menu' ).hide(); + if( menuId != 'changemode-menu' ) $( '#changemode-menu' ).hide(); + }, + + setModeDisplay: function( session ) { + var currMode = session.getMode().$id; + if( currMode ) { + currMode = currMode.substring( currMode.lastIndexOf( '/' ) + 1 ); + $( '#current-mode' ).html( currMode ); + } else { + $( '#current-mode' ).html( 'text' ); + } + }, + + ////////////////////////////////////////////////////////////////// + // + // Remove all Editor instances and clean up the DOM + // + ////////////////////////////////////////////////////////////////// + + exterminate: function() { + $( '.editor' ).remove(); + $( '.editor-wrapper' ).remove(); + $( '#editor-region' ).append( $( '
    ' ).attr( 'id', 'editor' ) ); + $( '#current-file' ).html( '' ); + $( '#current-mode' ).html( '' ); + this.instances = []; + this.activeInstance = null; + }, + + ////////////////////////////////////////////////////////////////// + // + // Detach EditSession session from all Editor instances replacing + // them with replacementSession + // + ////////////////////////////////////////////////////////////////// + + removeSession: function( session, replacementSession ) { + for( var k = 0; k < this.instances.length; k++ ) { + if( this.instances[k].getSession().path === session.path ) { + this.instances[k].setSession( replacementSession ); + } + } + if( $( '#current-file' ).text() === session.path ) { + $( '#current-file' ).text( replacementSession.path ); + } + + this.setModeDisplay( replacementSession ); + }, + + isOpen: function( session ) { + for( var k = 0; k < this.instances.length; k++ ) { + if( this.instances[k].getSession().path === session.path ) { + return true; + } + } + return false; + }, + + ///////////////////////////////////////////////////////////////// + // + // Convenience function to iterate over Editor instances + // + // Parameters: + // fn - {Function} callback called with each member as an argument + // + ///////////////////////////////////////////////////////////////// + + forEach: function( fn ) { + for( var k = 0; k < this.instances.length; k++ ) { + fn.call( this, this.instances[k] ); + } + }, + + ///////////////////////////////////////////////////////////////// + // + // Get the currently active Editor instance + // + // In a multi-pane setup this would correspond to the + // editor pane user is currently working on. + // + ///////////////////////////////////////////////////////////////// + + getActive: function() { + return this.activeInstance; + }, + + ///////////////////////////////////////////////////////////////// + // + // Set an editor instance as active + // + // Parameters: + // i - {Editor} + // + ///////////////////////////////////////////////////////////////// + + setActive: function( i ) { + if( !i ) return; + this.activeInstance = i; + $( '#current-file' ).text( i.getSession().path ); + this.setModeDisplay( i.getSession() ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Change the EditSession of Editor instance + // + // Parameters: + // session - {EditSession} + // i - {Editor} + // + ///////////////////////////////////////////////////////////////// + + setSession: function( session, i ) { + i = i || this.getActive(); + if( !this.isOpen( session ) ) { + if( !i ) { + i = this.addInstance( session ); + } else { + i.setSession( session ); + } + } else { + // Proxy session is required because scroll-position and + // cursor position etc. are shared among sessions. + + var proxySession = new EditSession( session.getDocument(), + session.getMode() ); + proxySession.setUndoManager( new UndoManager() ); + proxySession.path = session.path; + proxySession.listThumb = session.listThumb; + proxySession.tabThumb = session.tabThumb; + if( !i ) { + i = this.addInstance( proxySession ); + } else { + i.setSession( proxySession ); + } + } + this.applySettings( i ); + this.cursorTracking( i ); + this.setActive( i ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Select file mode by extension case insensitive + // + // Parameters: + // e - {String} File extension + // + ///////////////////////////////////////////////////////////////// + + selectMode: function( e ) { + if( typeof( e ) != 'string' ) { + return 'text'; + } + e = e.toLowerCase(); + return ( ModeList.getModeForPath( e ) ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Add an text mode for an extension + // + // Parameters: + // extension - {String} File Extension + // mode - {String} TextMode for this extension + // + ///////////////////////////////////////////////////////////////// + + addFileExtensionTextMode: function( extension, mode ) { + if( typeof( extension ) != 'string' || typeof( mode ) != 'string' ) { + if( console ) { + console.warn( 'wrong usage of addFileExtensionTextMode, both parameters need to be string' ); + } + return; + } + mode = mode.toLowerCase(); + this.fileExtensionTextMode[extension] = mode; + }, + + ///////////////////////////////////////////////////////////////// + // + // clear all extension-text mode joins + // + ///////////////////////////////////////////////////////////////// + + clearFileExtensionTextMode: function() { + this.fileExtensionTextMode = {}; + }, + + ///////////////////////////////////////////////////////////////// + // + // Set the editor mode + // + // Parameters: + // m - {TextMode} mode + // i - {Editor} Editor (Defaults to active editor) + // + ///////////////////////////////////////////////////////////////// + + setMode: function( m, i ) { + i = i || this.getActive(); + + // Check if mode is already loaded + if( !editorModes[m] ) { + + // Load the Mode + var modeFile = 'components/editor/ace-editor/mode-' + m + '.js'; + $.loadScript( modeFile, function() { + + // Mark the mode as loaded + editorModes[m] = true; + var EditorMode = ace.require( 'ace/mode/' + m ).Mode; + i.getSession().setMode( new EditorMode() ); + }, true ); + } else { + + var EditorMode = ace.require( 'ace/mode/' + m ).Mode; + i.getSession().setMode( new EditorMode() ); + + } + }, + + ///////////////////////////////////////////////////////////////// + // + // Set the editor theme + // + // Parameters: + // t - {String} theme eg. twilight, cobalt etc. + // i - {Editor} Editor instance (If omitted, Defaults to all editors) + // + // For a list of themes supported by Ace - refer : + // https://github.com/ajaxorg/ace/tree/master/lib/ace/theme + // + // TODO: Provide support for custom themes + // + ///////////////////////////////////////////////////////////////// + + setTheme: function( t, i ) { + if( i ) { + // If a specific instance is specified, change the theme for + // this instance + i.setTheme( 'ace/theme/' + t ); + } else { + // Change the theme for the existing editor instances + // and make it the default for new instances + this.settings.theme = t; + for( var k = 0; k < this.instances.length; k++ ) { + this.instances[k].setTheme( 'ace/theme/' + t ); + } + } + //Database + //codiad.settings.update_option( 'codiad.editor.theme', t ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Set contents of the editor + // + // Parameters: + // c - {String} content + // i - {Editor} (Defaults to active editor) + // + ///////////////////////////////////////////////////////////////// + + setContent: function( c, i ) { + i = i || this.getActive(); + i.getSession().setValue( c ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Set Font Size + // + // Set the font for all Editor instances and remember + // the value for Editor instances to be created in + // future + // + // Parameters: + // s - {Number} font size + // i - {Editor} Editor instance (If omitted, Defaults to all editors) + // + ///////////////////////////////////////////////////////////////// + + setFontSize: function( s, i ) { + if( i ) { + i.setFontSize( s ); + } else { + this.settings.fontSize = s; + this.forEach( function( i ) { + i.setFontSize( s ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.fontSize', s ); + }, + + + ///////////////////////////////////////////////////////////////// + // + // Enable/disable Highlighting of active line + // + // Parameters: + // h - {Boolean} + // i - {Editor} Editor instance ( If left out, setting is + // applied to all editors ) + // + ///////////////////////////////////////////////////////////////// + + setHighlightLine: function( h, i ) { + if( i ) { + i.setHighlightActiveLine( h ); + } else { + this.settings.highlightLine = h; + this.forEach( function( i ) { + i.setHighlightActiveLine( h ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.highlightLine', h ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Show/Hide print margin indicator + // + // Parameters: + // p - {Number} print margin column + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setPrintMargin: function( p, i ) { + if( i ) { + i.setShowPrintMargin( p ); + } else { + this.settings.printMargin = p; + this.forEach( function( i ) { + i.setShowPrintMargin( p ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.printMargin', p ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set print margin column + // + // Parameters: + // p - {Number} print margin column + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setPrintMarginColumn: function( p, i ) { + if( i ) { + i.setPrintMarginColumn( p ); + } else { + this.settings.printMarginColumn = p; + this.forEach( function( i ) { + i.setPrintMarginColumn( p ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.printMarginColumn', p ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Show/Hide indent guides + // + // Parameters: + // g - {Boolean} + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setIndentGuides: function( g, i ) { + if( i ) { + i.setDisplayIndentGuides( g ); + } else { + this.settings.indentGuides = g; + this.forEach( function( i ) { + i.setDisplayIndentGuides( g ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.indentGuides', g ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Enable/Disable Code Folding + // + // Parameters: + // f - {Boolean} + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setCodeFolding: function( f, i ) { + if( i ) { + i.setFoldStyle( f ); + } else { + this.forEach( function( i ) { + i.setFoldStyle( f ); + }); + } + }, + + ////////////////////////////////////////////////////////////////// + // + // Enable/Disable Line Wrapping + // + // Parameters: + // w - {Boolean} + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setWrapMode: function( w, i ) { + if( i ) { + i.getSession().setUseWrapMode( w ); + } else { + this.forEach( function( i ) { + i.getSession().setUseWrapMode( w ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.wrapMode', w ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set last position of modal to be saved + // + // Parameters: + // t - {Boolean} (false for Automatic Position, true for Last Position) + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setPersistentModal: function( t, i ) { + this.settings.persistentModal = t; + //Database + //codiad.settings.update_option( 'codiad.editor.persistentModal', t ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set trigger for opening the right sidebar + // + // Parameters: + // t - {Boolean} (false for Hover, true for Click) + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setRightSidebarTrigger: function( t, i ) { + this.settings.rightSidebarTrigger = t; + //Database + //codiad.settings.update_option( 'codiad.editor.rightSidebarTrigger', t ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set trigger for clicking on the filemanager + // + // Parameters: + // t - {Boolean} (false for Hover, true for Click) + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setFileManagerTrigger: function( t, i ) { + this.settings.fileManagerTrigger = t; + //Database + //codiad.settings.update_option( 'codiad.editor.fileManagerTrigger', t ); + codiad.project.loadSide(); + }, + + + ////////////////////////////////////////////////////////////////// + // + // set Tab Size + // + // Parameters: + // s - size + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setTabSize: function( s, i ) { + if( i ) { + i.getSession().setTabSize( parseInt( s ) ); + } else { + this.forEach( function( i ) { + i.getSession().setTabSize( parseInt( s ) ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.tabSize', s ); + + }, + + ////////////////////////////////////////////////////////////////// + // + // Enable or disable Soft Tabs + // + // Parameters: + // t - true / false + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setSoftTabs: function( t, i ) { + if( i ) { + i.getSession().setUseSoftTabs( t ); + } else { + this.forEach( function( i ) { + i.getSession().setUseSoftTabs( t ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.softTabs', t ); + + }, + + ////////////////////////////////////////////////////////////////// + // + // Get content from editor + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + getContent: function( i ) { + i = i || this.getActive(); + if( !i ) return; + var content = i.getSession().getValue(); + if( !content ) { + content = ' '; + } // Pass something through + return content; + }, + + ////////////////////////////////////////////////////////////////// + // + // Resize the editor - Trigger the editor to readjust its layout + // esp if the container has been resized manually. + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + resize: function( i ) { + i = i || this.getActive(); + if( !i ) return; + i.resize(); + }, + + ////////////////////////////////////////////////////////////////// + // + // Mark the instance as changed (in the user interface) + // upon change in the document content. + // + // Parameters: + // i - {Editor} + // + ////////////////////////////////////////////////////////////////// + + changeListener: function( i ) { + + var _this = this; + i.on( 'change', function() { + + codiad.active.markChanged( _this.getActive().getSession().path ); + codiad.active.savePosition(); + }); + }, + + clickListener: function( i ) { + + var _this = this; + i.on( 'click', function() { + + codiad.active.savePosition(); + }); + }, + + ////////////////////////////////////////////////////////////////// + // + // Get Selected Text + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + getSelectedText: function( i ) { + i = i || this.getActive(); + if( !i ) return; + return i.getCopyText(); + }, + + ////////////////////////////////////////////////////////////////// + // + // Insert text + // + // Parameters: + // val - {String} Text to be inserted + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + insertText: function( val, i ) { + i = i || this.getActive(); + if( !i ) return; + i.insert( val ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Move the cursor to a particular line + // + // Parameters: + // line - {Number} Line number + // i - {Editor} Editor instance + // + ////////////////////////////////////////////////////////////////// + + gotoLine: function( line, i ) { + i = i || this.getActive(); + if( !i ) return; + i.gotoLine( line, 0, true ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Focus an editor + // + // Parameters: + // i - {Editor} Editor instance (Defaults to current editor) + // + ////////////////////////////////////////////////////////////////// + + focus: function( i ) { + i = i || this.getActive(); + this.setActive( i ); + if( !i ) return; + i.focus(); + codiad.active.focus( i.getSession().path ); + this.cursorTracking( i ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Setup Cursor Tracking + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + cursorTracking: function( i ) { + + i = i || this.getActive(); + if( !i ) return; + + /** + * Update the cursor position now so that when a new file opens, + * we do not have the old cursor data. + */ + + $( '#cursor-position' ) + .html( i18n( 'Ln' ) + ': ' + + ( i.getCursorPosition().row + 1 ) + + ' · ' + i18n( 'Col' ) + ': ' + + i.getCursorPosition().column + ); + + //Register the changecursor function so updates continue + i.selection.on( "changeCursor", function( e ) { + + codiad.active.savePosition(); + $( '#cursor-position' ) + .html( i18n( 'Ln' ) + ': ' + + ( i.getCursorPosition().row + 1 ) + + ' · ' + i18n( 'Col' ) + ': ' + + i.getCursorPosition().column + ); + }); + }, + + ////////////////////////////////////////////////////////////////// + // + // Setup Key bindings + // + // Parameters: + // i - {Editor} + // + ////////////////////////////////////////////////////////////////// + + bindKeys: function( i ) { + + var _this = this; + + // Find + i.commands.addCommand( { + name: 'Find', + bindKey: { + win: 'Ctrl-F', + mac: 'Command-F' + }, + exec: function( e ) { + _this.openSearch( 'find' ); + } + }); + + // Find + Replace + i.commands.addCommand( { + name: 'Replace', + bindKey: { + win: 'Ctrl-R', + mac: 'Command-R' + }, + exec: function( e ) { + _this.openSearch( 'replace' ); + } + }); + + i.commands.addCommand( { + name: 'Move Up', + bindKey: { + win: 'Ctrl-up', + mac: 'Command-up' + }, + exec: function( e ) { + codiad.active.move( 'up' ); + } + }); + + i.commands.addCommand( { + name: 'Move Down', + bindKey: { + win: 'Ctrl-down', + mac: 'Command-up' + }, + exec: function( e ) { + codiad.active.move( 'down' ); + } + }); + + }, + + ////////////////////////////////////////////////////////////////// + // + // Present the Search (Find + Replace) dialog box + // + // Parameters: + // type - {String} Optional, defaults to find. Provide 'replace' for replace dialog. + // + ////////////////////////////////////////////////////////////////// + + openSearch: function( type ) { + if( this.getActive() ) { + codiad.modal.load( 400, + 'components/editor/dialog.php?action=search&type=' + + type ); + codiad.modal.hideOverlay(); + } else { + codiad.message.error( 'No Open Files' ); + } + }, + + ////////////////////////////////////////////////////////////////// + // + // Perform Search (Find + Replace) operation + // + // Parameters: + // action - {String} find | replace | replaceAll + // i - {Editor} Defaults to active Editor instance + // + ////////////////////////////////////////////////////////////////// + + search: function( action, i ) { + i = i || this.getActive(); + if( !i ) return; + if( this.multi_line ) { + + var find = $( '#modal textarea[name="find"]' ) + .val(); + var replace = $( '#modal textarea[name="replace"]' ) + .val(); + } else { + + var find = $( '#modal input[name="find"]' ) + .val(); + var replace = $( '#modal input[name="replace"]' ) + .val(); + } + switch ( action ) { + case 'find': + + i.find( find, { + backwards: false, + wrap: true, + caseSensitive: false, + wholeWord: false, + regExp: false + }); + + break; + + case 'replace': + + i.find( find, { + backwards: false, + wrap: true, + caseSensitive: false, + wholeWord: false, + regExp: false + }); + i.replace( replace ); + + break; + + case 'replaceAll': + + i.find( find, { + backwards: false, + wrap: true, + caseSensitive: false, + wholeWord: false, + regExp: false + }); + i.replaceAll( replace ); + + break; + } + }, + + ////////////////////////////////////////////////////////////////// + // + // Enable editor + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + enableEditing: function( i ) { + i = i || this.getActive(); + if( !i ) return; + i.textInput.setReadOnly( false ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Disable editor + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + disableEditing: function( i ) { + i = i || this.getActive(); + if( !i ) return; + i.textInput.setReadOnly( true ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set Overscroll + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + setOverScroll: function( s, i ) { + + if( i ) { + i.setOption( "scrollPastEnd", s ); + } else { + this.settings.overScroll = s; + this.forEach( function( i ) { + i.setOption( "scrollPastEnd", s ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.overScroll', s ); + }, + + setLiveAutocomplete: function( s, i ) { + + if( i ) { + i.setOptions( { + enableLiveAutocompletion: s + }); + } else { + this.settings.autocomplete = s; + this.forEach( function( i ) { + i.setOptions( { + enableLiveAutocompletion: s + }); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.autocomplete', s ); + }, + + toggleMultiLine: function( e ) { + + if( e.innerText === "Multi Line" ) { + + this.multi_line = true; + e.innerText = "Single Line"; + $( 'input[name="find"]' ).hide(); + $( 'textarea[name="find"]' ).show(); + $( 'textarea[name="find"]' ).val( $( 'input[name="find"]' ).val() ); + + $( 'input[name="replace"]' ).hide(); + $( 'textarea[name="replace"]' ).show(); + $( 'textarea[name="replace"]' ).val( $( 'input[name="replace"]' ).val() ); + } else { + + this.multi_line = false; + e.innerText = "Multi Line"; + $( 'input[name="find"]' ).show(); + $( 'textarea[name="find"]' ).hide(); + $( 'input[name="find"]' ).val( $( 'textarea[name="find"]' ).val() ); + + $( 'input[name="replace"]' ).show(); + $( 'textarea[name="replace"]' ).hide(); + $( 'input[name="replace"]' ).val( $( 'textarea[name="replace"]' ).val() ); + } + }, + + paste: function() { + + navigator.clipboard.readText().then( text => { + codiad.editor.getActive().insert( text ) + }); + }, + + openSort: function() { + + if( this.getActive() && codiad.active.getSelectedText() != "" ) { + + codiad.modal.load( 400, 'components/editor/dialog.php?action=sort' ); + codiad.modal.hideOverlay(); + } else { + + codiad.message.error( 'No text selected' ); + } + }, + + sort: function( eol ) { + + let text = $( '#modal textarea[name="sort"]' ).val(); + let array = text.split( eol ); + array = array.sort( codiad.editor.sort_a ); + let sorted = array.join( eol ); + + console.log( text, eol, array, sorted ); + codiad.modal.unload(); + codiad.editor.getActive().insert( sorted ); + codiad.editor.getActive().focus(); + }, + + sort_a: function( a, b ) { + + let pos = 0; + let case_sensitive = $( '#modal input[name="case_sensitive"]' ).prop( 'checked' ) + + if( !case_sensitive ) { + + a = a.toLowerCase(); + b = b.toLowerCase(); + } + + if( a < b ) { + + pos = -1; + } else if( a > b ) { + + pos = 1; + } + + return pos; + } + }; + +})( this, jQuery ); diff --git a/components/filemanager/init.js b/components/filemanager/init.js index 5355a7b..42471b6 100755 --- a/components/filemanager/init.js +++ b/components/filemanager/init.js @@ -387,18 +387,18 @@ var appendage = '
      '; $.each( files, function( index ) { var ext = ''; - var name = files[ index ].name.replace( path, '' ); + var name = files[index].name.replace( path, '' ); var nodeClass = 'none'; name = name.split( '/' ) .join( ' ' ); - if( files[ index ].type == 'file' ) { + if( files[index].type == 'file' ) { var ext = ' ext-' + name.split( '.' ) .pop(); } - if( files[ index ].type == 'directory' && files[ index ].size > 0 ) { + if( files[index].type == 'directory' && files[index].size > 0 ) { nodeClass = 'plus'; } - appendage += '
    • ' + name + '
    • '; + appendage += '
    • ' + name + '
    • '; }); appendage += '
    '; if( rescan ) { @@ -416,7 +416,7 @@ } node.removeClass( 'loading' ); if( rescan && _this.rescanChildren.length > _this.rescanCounter ) { - _this.rescan( _this.rescanChildren[ _this.rescanCounter++ ] ); + _this.rescan( _this.rescanChildren[_this.rescanCounter++] ); } else { _this.rescanChildren = []; _this.rescanCounter = 0; @@ -578,7 +578,7 @@ codiad.message.error( i18n( 'File could not be saved' ) ); if( typeof callbacks.error === 'function' ) { var context = callbacks.context || _this; - callbacks.error.apply( context, [ data ] ); + callbacks.error.apply( context, [data] ); } } $.post( this.controller + '?action=modify&path=' + encodeURIComponent( path ), data, function( resp ) { @@ -611,7 +611,7 @@ } else codiad.message.error( i18n( 'File could not be saved' ) ); if( typeof callbacks.error === 'function' ) { var context = callbacks.context || _this; - callbacks.error.apply( context, [ resp.data ] ); + callbacks.error.apply( context, [resp.data] ); } } }).error( notifySaveErr ); @@ -778,7 +778,7 @@ var arr = path.split( '/' ); var temp = new Array(); for( i = 0; i < arr.length - 1; i++ ) { - temp.push( arr[ i ] ) + temp.push( arr[i] ) } var newPath = temp.join( '/' ) + '/' + newName; $.get( _this.controller, { @@ -969,12 +969,12 @@ if( searchResponse != 'error' ) { $.each( searchResponse.index, function( key, val ) { // Cleanup file format - if( val[ 'file' ].substr( -1 ) == '/' ) { - val[ 'file' ] = val[ 'file' ].substr( 0, str.length - 1 ); + if( val['file'].substr( -1 ) == '/' ) { + val['file'] = val['file'].substr( 0, str.length - 1 ); } - val[ 'file' ] = val[ 'file' ].replace( '//', '/' ); + val['file'] = val['file'].replace( '//', '/' ); // Add result - results += ''; + results += ''; }); $( '#filemanager-search-results' ) .slideDown()