Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

Sequential navigation in edit history #13418

Merged
merged 15 commits into from
Jun 28, 2017
Merged
Next Next commit
First commit - sequential navigation in edit history
  • Loading branch information
swmitra committed Jun 2, 2017
commit 43df078a8b20e4f7df53756fdd3f772887ac6e5a
3 changes: 3 additions & 0 deletions src/editor/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,9 @@ define(function (require, exports, module) {
this._codeMirror.on("cursorActivity", function (instance) {
self.trigger("cursorActivity", self);
});
this._codeMirror.on("beforeSelectionChange", function (instance, selectionObj) {
self.trigger("beforeSelectionChange", selectionObj);
});
this._codeMirror.on("scroll", function (instance) {
// If this editor is visible, close all dropdowns on scroll.
// (We don't want to do this if we're just scrolling in a non-visible editor
Expand Down
215 changes: 215 additions & 0 deletions src/extensions/default/NavigationAndHistory/NavigationProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
* Copyright (c) 2016 - present Adobe Systems Incorporated. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/

define(function (require, exports, module) {
"use strict";

var Strings = brackets.getModule("strings"),
MainViewManager = brackets.getModule("view/MainViewManager"),
DocumentManager = brackets.getModule("document/DocumentManager"),
DocumentCommandHandlers = brackets.getModule("document/DocumentCommandHandlers"),
EditorManager = brackets.getModule("editor/EditorManager"),
Editor = brackets.getModule("editor/Editor"),
ProjectManager = brackets.getModule("project/ProjectManager"),
CommandManager = brackets.getModule("command/CommandManager"),
Commands = brackets.getModule("command/Commands"),
Menus = brackets.getModule("command/Menus"),
KeyBindingManager = brackets.getModule("command/KeyBindingManager");

var KeyboardPrefs = JSON.parse(require("text!keyboard.json"));

// Command constants for navigation history
var NAVIGATION_JUMP_BACK = "navigation.jump.back",
NAVIGATION_JUMP_FWD = "navigation.jump.fwd";

var NAV_FRAME_CAPTURE_LATENCY = 3000;


/*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: missing * (/**)

* Contains list of most recently opened files and their last known cursor position
* @private
* @type {Array.<Object>}
*/
var jumpToPosStack = [],
jumpedPosStack = [],
captureTimer,
activePosNotSynched = false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: values with defaults first

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: activePosNotSynced

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

jumpInProgress,
command_JumpBack,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The camelcase + underscore hybrid is a bit weird, would prefer just commandJumpBack.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

command_JumpFwd,
cmMarkers = {};


function NavigationFrame(editor, selectionObj) {
this.cm = editor._codeMirror;
this.file = editor.document.file._path;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might call this filepath or just path to make it clear that this is not a file handle.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. "filePath" would be more logical.

this.paneId = editor._paneId;
this.uId = (new Date()).getTime() + "";
this.selections = [];
this.bookMarkIds = [];
this._createMarkers(selectionObj.ranges);
this._bindEditor(editor);
}

NavigationFrame.prototype._bindEditor = function (editor) {
var self = this;
editor.on("beforeDestroy", function () {
self._backupSelectionRanges();
self.cm = null;
self.bookMarkIds = null;
});
}

NavigationFrame.prototype._createMarkers = function (ranges) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createMarkers and reinstateMarkers share a lot of code. Maybe we can consolidate them a little more?

As I see it, this should work:

NavigationFrame.prototype._reinstateMarkers = function (editor) {
    this.cm = editor._codeMirror;
    this.paneId = editor._paneId;
    this._createMarkers(this.selections)
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem was with range start and end placeholder names but handled it now to make use of same function.

var range, index, bookMark;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: new variables on new lines

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

this.bookMarkIds = [];
for (index in ranges) {
range = ranges[index];
if (range.anchor.line === range.head.line && range.anchor.ch === range.head.ch) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: can anchor or head be null?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a cursor or selection, it can never be null and if there are no selections/cursor then the ranges would be an empty array.

bookMark = this.cm.setBookmark(range.anchor, range.head);
this.bookMarkIds.push(bookMark.id);
} else {
this.cm.markText(range.anchor, range.head, {className: (this.uId + "")});
}
}
};

NavigationFrame.prototype._backupSelectionRanges = function () {
if (!this.cm) {
return;
}

this.selections = [];
var marker, selection, index;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: new variables on new lines

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

var self = this;
var markers = this.cm.getAllMarks().filter(function (entry) {
if (entry.className === self.uId || self.bookMarkIds.indexOf(entry.id) !== -1) {
return entry;
}
})
for (index in markers) {
marker = markers[index];
selection = marker.find();
if (marker.type === "bookmark") {
this.selections.push({start: selection, end: selection});
} else {
this.selections.push({start: selection.from, end: selection.to});
}
}
};

NavigationFrame.prototype.goTo = function () {
var self = this;
this._backupSelectionRanges();
jumpInProgress = true;
CommandManager.execute(Commands.FILE_OPEN, {fullPath: this.file, paneId: this.paneId}).done(function () {
EditorManager.getCurrentFullEditor().setSelections(self.selections, true);
command_JumpFwd.setEnabled(true);
}).always(function () {
jumpInProgress = false;
});
};

function _recordJumpDef(event, selectionObj) {
if (jumpInProgress) {
return;
}
jumpedPosStack = [];
if (selectionObj.origin !== "+move" && (window.event && window.event.type !== "input")) {
if (captureTimer) {
window.clearTimeout(captureTimer);
captureTimer = null;
}
captureTimer = window.setTimeout(function () {
jumpToPosStack.push(new NavigationFrame(event.target, selectionObj));
command_JumpFwd.setEnabled(false);
if (jumpToPosStack.length > 1) {
command_JumpBack.setEnabled(true);
}
activePosNotSynched = false;
}, NAV_FRAME_CAPTURE_LATENCY);
} else {
activePosNotSynched = true;
}
}

function _jumpToPosBack() {
if (!jumpedPosStack.length) {
if (activePosNotSynched) {
jumpToPosStack.push(new NavigationFrame(EditorManager.getCurrentFullEditor(), {ranges: EditorManager.getCurrentFullEditor()._codeMirror.listSelections()}));
} else {
jumpedPosStack.push(jumpToPosStack.pop());
}
}
var navFrame = jumpToPosStack.pop();
if (navFrame) {
jumpedPosStack.push(navFrame);
navFrame.goTo();
}
}

function _jumpToPosFwd() {
var navFrame = jumpedPosStack.pop();
if (navFrame) {
jumpToPosStack.push(navFrame);
navFrame.goTo();
}
}

/**
* Handle Active Editor change to update navigation information
* @private
*/
function _handleActiveEditorChange(event, current, previous) {
if (current && current._paneId) { // Handle only full editors
current.on("beforeSelectionChange", _recordJumpDef);
}

if (previous && previous._paneId) {
previous.off("beforeSelectionChange", _recordJumpDef);
}
}

function _handleProjectOpen() {
jumpToPosStack = [];
jumpedPosStack = [];
}

function init() {
CommandManager.register(Strings.CMD_NAVIGATE_BACKWARD, NAVIGATION_JUMP_BACK, _jumpToPosBack);
CommandManager.register(Strings.CMD_NAVIGATE_FORWARD, NAVIGATION_JUMP_FWD, _jumpToPosFwd);
command_JumpBack = CommandManager.get(NAVIGATION_JUMP_BACK);
command_JumpFwd = CommandManager.get(NAVIGATION_JUMP_FWD);
command_JumpBack.setEnabled(false);
command_JumpFwd.setEnabled(false);
KeyBindingManager.addBinding(NAVIGATION_JUMP_BACK, KeyboardPrefs[NAVIGATION_JUMP_BACK]);
KeyBindingManager.addBinding(NAVIGATION_JUMP_FWD, KeyboardPrefs[NAVIGATION_JUMP_FWD]);
var menu = Menus.getMenu(Menus.AppMenuBar.NAVIGATE_MENU);
menu.addMenuItem(NAVIGATION_JUMP_BACK, "", Menus.AFTER, Commands.NAVIGATE_PREV_DOC);
menu.addMenuItem(NAVIGATION_JUMP_FWD, "", Menus.AFTER, NAVIGATION_JUMP_BACK);
EditorManager.on("activeEditorChange", _handleActiveEditorChange);
ProjectManager.on("projectOpen", _handleProjectOpen);
}

exports.init = init;
});
10 changes: 10 additions & 0 deletions src/extensions/default/NavigationAndHistory/keyboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,15 @@
"key": "Cmd-Shift-[",
"platform": "mac"
}
],
"navigation.jump.back": [
{
"key": "Alt-I"
}
],
"navigation.jump.fwd": [
{
"key": "Alt-Shift-I"
}
]
}
4 changes: 3 additions & 1 deletion src/extensions/default/NavigationAndHistory/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ define(function (require, exports, module) {
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
KeyBindingManager = brackets.getModule("command/KeyBindingManager"),
ExtensionUtils = brackets.getModule("utils/ExtensionUtils"),
Mustache = brackets.getModule("thirdparty/mustache/mustache");
Mustache = brackets.getModule("thirdparty/mustache/mustache"),
NavigationProvider = require("NavigationProvider");

var KeyboardPrefs = JSON.parse(require("text!keyboard.json"));

Expand Down Expand Up @@ -829,5 +830,6 @@ define(function (require, exports, module) {

AppInit.appReady(function () {
ExtensionUtils.loadStyleSheet(module, "styles/recent-files.css");
NavigationProvider.init();
});
});
2 changes: 2 additions & 0 deletions src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ define({
"CMD_CSS_QUICK_EDIT_NEW_RULE" : "New Rule",
"CMD_NEXT_DOC" : "Next Document",
"CMD_PREV_DOC" : "Previous Document",
"CMD_NAVIGATE_BACKWARD" : "Navigate Backward",
"CMD_NAVIGATE_FORWARD" : "Navigate Forward",
"CMD_NEXT_DOC_LIST_ORDER" : "Next Document in List",
"CMD_PREV_DOC_LIST_ORDER" : "Previous Document in List",
"CMD_SHOW_IN_TREE" : "Show in File Tree",
Expand Down