From 3c38d64bdcca391d81fa638d32800db478bd7a7d Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 20 May 2025 00:16:02 +0530 Subject: [PATCH 1/7] feat: bookmarks working prototype --- src-node/package-lock.json | 4 +- .../Bookmarks/bookmarksView.js | 242 +++++++++++ .../Bookmarks/htmlContent/bookmarks-list.html | 9 + .../htmlContent/bookmarks-panel.html | 7 + src/extensionsIntegrated/Bookmarks/main.js | 392 ++++++++++++++++++ src/extensionsIntegrated/loader.js | 1 + src/styles/Extn-Bookmarks.less | 42 ++ src/styles/brackets.less | 1 + src/styles/images/bookmark.svg | 1 + 9 files changed, 697 insertions(+), 2 deletions(-) create mode 100644 src/extensionsIntegrated/Bookmarks/bookmarksView.js create mode 100644 src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-list.html create mode 100644 src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-panel.html create mode 100644 src/extensionsIntegrated/Bookmarks/main.js create mode 100644 src/styles/Extn-Bookmarks.less create mode 100644 src/styles/images/bookmark.svg diff --git a/src-node/package-lock.json b/src-node/package-lock.json index 6177376876..6183efc95f 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@phcode/node-core", - "version": "4.1.0-0", + "version": "4.1.1-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@phcode/node-core", - "version": "4.1.0-0", + "version": "4.1.1-0", "license": "GNU-AGPL3.0", "dependencies": { "@phcode/fs": "^3.0.1", diff --git a/src/extensionsIntegrated/Bookmarks/bookmarksView.js b/src/extensionsIntegrated/Bookmarks/bookmarksView.js new file mode 100644 index 0000000000..4913becb18 --- /dev/null +++ b/src/extensionsIntegrated/Bookmarks/bookmarksView.js @@ -0,0 +1,242 @@ +define(function (require, exports, module) { + var CommandManager = require("command/CommandManager"), + Commands = require("command/Commands"), + ProjectManager = require("project/ProjectManager"), + WorkspaceManager = require("view/WorkspaceManager"), + MainViewManger = require("view/MainViewManager"), + Strings = require("strings"), + bookmarksPanelTemplate = require("text!./htmlContent/bookmarks-panel.html"), + bookmarksListTemplate = require("text!./htmlContent/bookmarks-list.html"), + Mustache = require("thirdparty/mustache/mustache"); + + /** + * @const + * Debounce time for document changes updating the search results view. + * @type {number} + */ + var UPDATE_TIMEOUT = 400; + + /** + * @const + * MainViewManager events + * @type {string} + */ + var MVM_EVENTS = `workingSetAdd + workingSetAddList + workingSetMove + workingSetRemove + workingSetRemoveList + workingSetUpdate + currentFileChange + activePaneChange`; + + /** + * @constructor + * Creates a bookmarks panel + * Dispatches the following events: + * close - when the panel is closed. + * + * @typedef {Object.>} BookmarksModel + * @param {BookmarksModel} model - bookmarks model + * @param {function=} beforeRender - function to call before rendering the view + */ + function BookmarksView(model, beforeRender) { + var panelHtml = Mustache.render(bookmarksPanelTemplate, { + Strings: Strings + }); + + this._panel = WorkspaceManager.createBottomPanel("bookmarks", $(panelHtml), 100); + this._$panel = this._panel.$panel; + this._$table = this._$panel.find(".table-container"); + this._model = model; + this._beforeRender = beforeRender; + } + + /** @type {BookmarksModel} bookmarks model */ + BookmarksView.prototype._model = null; + + /** @type {Panel} Bottom panel holding the bookmarks */ + BookmarksView.prototype._panel = null; + + /** @type {$.Element} The table that holds the results */ + BookmarksView.prototype._$table = null; + + /** @type {number} The ID we use for timeouts when handling model changes. */ + BookmarksView.prototype._timeoutID = null; + + /** @type {function=} function that is called before refreshing the view */ + BookmarksView.prototype._beforeRender = null; + + /** + * @private + * Handles when model changes. Updates the view, buffering changes if necessary so as not to churn too much. + */ + BookmarksView.prototype._handleModelChange = function () { + var self = this; + if (self._ignoreModelChangeEvents) { + return; + } + if (this._timeoutID) { + window.clearTimeout(this._timeoutID); + } + this._timeoutID = window.setTimeout(function () { + // _updateResults causes the model to be recomputed + // which triggers another model change event which + // we need to ignore or we endup in a race condition + // which may lead to data loss + self._ignoreModelChangeEvents = true; + self._updateResults(); + self._timeoutID = null; + delete self._ignoreModelChangeEvents; + }, UPDATE_TIMEOUT); + }; + + /** + * @private + * Adds the listeners for close and clicking on a bookmark in the list + */ + BookmarksView.prototype._addPanelListeners = function () { + var self = this; + this._$panel + .off(".bookmarks") // Remove the old events + .on("click.bookmarks", ".close", function () { + self.close(); + }) + // Add the click event listener directly on the table parent + .on("click.bookmarks .table-container", function (e) { + var $row = $(e.target).closest("tr"); + + if ($row.length) { + if (self._$selectedRow) { + self._$selectedRow.removeClass("selected"); + } + $row.addClass("selected"); + self._$selectedRow = $row; + + var fullPathAndLineNo = $row.find(".bookmark-result").text(); + + CommandManager.execute(Commands.FILE_OPEN, { fullPath: fullPathAndLineNo }); + } + }); + MainViewManger.on(MVM_EVENTS, this._updateResults.bind(this)); + }; + + /** + * @private + * @param {!String} fullpath - path of the file to show + */ + BookmarksView.prototype._shouldShow = function (fullpath) { + if (!this._options || !this._options.show || this._options.show === "opened") { + return Boolean(MainViewManger._getPaneIdForPath(fullpath)); + } else if (this._options.show === "all") { + return true; + } else if (this._options.show === "project" && ProjectManager.getProjectRoot()) { + // show open files and any file bookmarked in the current project + return ( + Boolean(MainViewManger._getPaneIdForPath(fullpath)) || + fullpath.toLowerCase().indexOf(ProjectManager.getProjectRoot().fullPath.toLowerCase()) === 0 + ); + } + + // unknown option + return false; + }; + + /** + * @private + * Shows the current set of results. + */ + BookmarksView.prototype._render = function () { + var self = this, + bookmarks = []; + + if (this._beforeRender) { + this._beforeRender(); + } + + // Iterates throuh the files to display the results sorted by filenamess. The loop ends as soon as + // we filled the results for one page + Object.keys(this._model) + .filter(function (fullPath) { + return self._shouldShow(fullPath); + }) + .sort(function (a, b) { + return a > b; + }) + .forEach(function (fullPath) { + self._model[fullPath].forEach(function (lineNo) { + bookmarks.push({ + fullPath: fullPath, + lineNo: lineNo + 1 + }); + }); + }); + + // Insert the search results + this._$table.empty().append( + Mustache.render(bookmarksListTemplate, { + bookmarks: bookmarks, + Strings: Strings + }) + ); + + if (this._$selectedRow) { + this._$selectedRow.removeClass("selected"); + this._$selectedRow = null; + } + + this._panel.show(); + this._$table.scrollTop(0); // Otherwise scroll pos from previous contents is remembered + }; + + /** + * Updates the results view after a model change, preserving scroll position and selection. + */ + BookmarksView.prototype._updateResults = function () { + // In general this shouldn't get called if the panel is closed, but in case some + // asynchronous process kicks this (e.g. a debounced model change), we double-check. + if (this._panel.isVisible()) { + var scrollTop = this._$table.scrollTop(), + index = this._$selectedRow ? this._$selectedRow.index() : null; + this._render(); + this._$table.scrollTop(scrollTop); + if (index) { + this._$selectedRow = this._$table.find("tr:eq(" + index + ")"); + this._$selectedRow.addClass("selected"); + } + } + }; + + /** + * Opens the results panel and displays the current set of results from the model. + */ + BookmarksView.prototype.open = function (options) { + this._options = options; + this._render(); + this._addPanelListeners(); + $(this._model).on("change.BookmarksView", this._handleModelChange.bind(this)); + }; + + /** + * Hides the Search Results Panel and unregisters listeners. + */ + BookmarksView.prototype.close = function () { + if (this._panel && this._panel.isVisible()) { + this._$table.empty(); + this._panel.hide(); + this._panel.$panel.off(".bookmarks"); + $(this._model).off("change.BookmarksView"); + $(this).triggerHandler("close"); + } + }; + + /** + * Hides the Search Results Panel and unregisters listeners. + */ + BookmarksView.prototype.isOpen = function () { + return this._panel && this._panel.isVisible(); + }; + + // Public API + exports.BookmarksView = BookmarksView; +}); diff --git a/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-list.html b/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-list.html new file mode 100644 index 0000000000..02e3e4a08e --- /dev/null +++ b/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-list.html @@ -0,0 +1,9 @@ + + + {{#bookmarks}} + + + + {{/bookmarks}} + +
{{fullPath}}:{{lineNo}}
diff --git a/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-panel.html b/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-panel.html new file mode 100644 index 0000000000..264b736d2f --- /dev/null +++ b/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-panel.html @@ -0,0 +1,7 @@ +
+
+
{{Strings.BOOKMARKS_PANEL_TITLE}}
+ × +
+
+
diff --git a/src/extensionsIntegrated/Bookmarks/main.js b/src/extensionsIntegrated/Bookmarks/main.js new file mode 100644 index 0000000000..ffe25ba1f8 --- /dev/null +++ b/src/extensionsIntegrated/Bookmarks/main.js @@ -0,0 +1,392 @@ +define(function (require, exports, module) { + var PreferencesManager = require("preferences/PreferencesManager"), + CommandManager = require("command/CommandManager"), + Menus = require("command/Menus"), + DocumentManager = require("document/DocumentManager"), + EditorManager = require("editor/EditorManager"), + _ = require("thirdparty/lodash"); + + const AppInit = require("utils/AppInit"); + + var BookmarksView = require("./bookmarksView").BookmarksView; + + /** @const {string} Extension Command ID */ + var MY_MODULENAME = "bracketsEditorBookmarks"; + var CMD_TOGGLE_BOOKMARK = "bracketsEditorBookmarks.toggleBookmark", + CMD_GOTO_NEXT_BOOKMARK = "bracketsEditorBookmarks.gotoNextBookmark", + CMD_GOTO_PREV_BOOKMARK = "bracketsEditorBookmarks.gotoPrevBookmark", + CMD_TOGGLE_BOOKKMARK_VIEW = "bracketsEditorBookmarks.toggleBookmarksPanel"; + + const ExtensionStrings = { + TOGGLE_BOOKMARK: "Toggle Bookmark", + GOTO_PREV_BOOKMARK: "Go to previous Bookmark", + GOTO_NEXT_BOOKMARK: "Go to next Bookmark", + TOGGLE_BOOKMARKS_PANEL: "Toggle Bookmarks panel" + }; + + /* Our extension's preferences */ + var prefs = PreferencesManager.getExtensionPrefs(MY_MODULENAME); + + // Bookmarks Data Model + var _bookmarks = {}; + + // Bookmarks Panel + var _bookmarksPanel = null; + + /** + * Saves bookmarks to the data model for the specified editor instance + * @param {Editor=} editor - brackets editor instance. current editor if null + * @return {?Array.} array of cached bookmarked line numbers + */ + function saveBookmarks(editor) { + if (!editor) { + editor = EditorManager.getCurrentFullEditor(); + } + if (editor) { + var i, + fullPath = editor.document.file.fullPath, + cm = editor._codeMirror, + lineCount = cm.doc.lineCount(), + bookmarkedLines = []; + + for (i = 0; i < lineCount; i++) { + var lineInfo = cm.lineInfo(i); + + if (lineInfo.wrapClass && lineInfo.wrapClass.indexOf("bookmark") >= 0) { + bookmarkedLines.push(i); + } + } + + // we need to sort so that go to next bookmark works + bookmarkedLines.sort(function (a, b) { + return a > b; + }); + + _bookmarks[fullPath] = bookmarkedLines; + prefs.set("bookmarks", _bookmarks); + + $(_bookmarks).triggerHandler("change"); + + // return the bookmarks for the editor + return bookmarkedLines; + } + return null; + } + + /** + * Updates bookmarks for the current editor if necessary + * @param {Editor=} editor - brackets editor instance. current editor if null + * @return {Boolean} true if there are bookmarks for the current editor, false if not + */ + function updateBookmarksForCurrentEditor() { + var result = false, + editor = EditorManager.getCurrentFullEditor(); + if (editor) { + var fullPath = editor.document.file.fullPath, + bm = _bookmarks[fullPath]; + + // if there was already data then we + // don't need to rebuild it + result = bm && bm.length; + + if (!result) { + // there was no deta for this file so + // rebuild the model just for this file + // from what is in the editor currently + result = Boolean(saveBookmarks(editor)); + } + } + + return result; + } + + /** + * Resets the bookmarks for the file opened in the specified editor + * NOTE: When the bookmarks for the current editor are needed + * (for traversal or to update the bookmarks panel), + * updateBookmarksForCurrentEditor is called which updates + * incrementally the bookmarks for the current file + * @param {!Editor} editor - brackets editor instance + */ + function resetBookmarks(editor) { + if (editor) { + delete _bookmarks[editor.document.file.fullPath]; + $(_bookmarks).triggerHandler("change"); + } + } + + /** + * Loads the cached bookmarks into the specified editor instance + * @param {Editor=} editor - brackets editor instance. current editor if null + */ + function loadBookmarks(editor) { + if (!editor) { + editor = EditorManager.getCurrentFullEditor(); + } + if (editor) { + var cm = editor._codeMirror, + bm = _bookmarks[editor.document.file.fullPath]; + + if (bm) { + bm.forEach(function (lineNo) { + if (lineNo < cm.doc.lineCount()) { + cm.addLineClass(lineNo, "wrap", "bookmark"); + } + }); + } + } + } + + /** + * Removes all bookmarks from the editor + * @param {!Editor} editor - brackets editor instance. + */ + function clearBookmarks(editor) { + var i, + cm = editor._codeMirror, + lineCount = cm.doc.lineCount(); + + for (i = 0; i < lineCount; i++) { + cm.removeLineClass(i, "wrap", "bookmark"); + } + } + + /** + * Resets all bookmark model data so it can be re-read from prefs + */ + function resetModel() { + Object.keys(_bookmarks).forEach(function (prop) { + delete _bookmarks[prop]; + }); + } + + /** + * Loads bookmark model from prefs + */ + function loadModel() { + resetModel(); + _.assign(_bookmarks, prefs.get("bookmarks")); + } + + /** + * Reloads the bookmark model, clearing the current bookmarks + * @param {!Editor} editor - brackets editor instance. + */ + function reloadModel() { + DocumentManager.getAllOpenDocuments().forEach(function (doc) { + if (doc._masterEditor) { + clearBookmarks(doc._masterEditor); + } + }); + + loadModel(); + + DocumentManager.getAllOpenDocuments().forEach(function (doc) { + if (doc._masterEditor) { + loadBookmarks(doc._masterEditor); + } + }); + + // update the panel + $(_bookmarks).triggerHandler("change"); + } + + /** + * Moves the cursor position of the current editor to the next bookmark + * @param {!Editor} editor - brackets editor instance + */ + function gotoNextBookmark(forward) { + if (updateBookmarksForCurrentEditor()) { + var editor = EditorManager.getCurrentFullEditor(), + cursor = editor.getCursorPos(), + bm = _bookmarks[editor.document.file.fullPath]; + + var doJump = function (lineNo) { + editor.setCursorPos(lineNo, 0); + + var cm = editor._codeMirror; + cm.addLineClass(lineNo, "wrap", "bookmark-notify"); + setTimeout(function () { + cm.removeLineClass(lineNo, "wrap", "bookmark-notify"); + }, 100); + }; + + // find next bookmark + var index; + for ( + index = forward ? 0 : bm.length - 1; + forward ? index < bm.length : index >= 0; + forward ? index++ : index-- + ) { + if (forward) { + if (bm[index] > cursor.line) { + doJump(bm[index]); + return; + } + if (index === bm.length - 1) { + // wrap around just pick the first one in the list + if (bm[0] !== cursor.line) { + doJump(bm[0]); + } + return; + } + } else { + if (bm[index] < cursor.line) { + doJump(bm[index]); + return; + } + if (index === 0) { + // wrap around just pick the last one in the list + if (bm[bm.length - 1] !== cursor.line) { + doJump(bm[bm.length - 1]); + } + return; + } + } + } + } + } + + /** + * Toogles the bookmarked state of the current line of the current editor + */ + function toggleBookmark() { + var editor = EditorManager.getCurrentFullEditor(); + if (editor) { + var cursor = editor.getCursorPos(), + lineNo = cursor.line, + cm = editor._codeMirror, + lineInfo = cm.lineInfo(cursor.line); + + if (!lineInfo.wrapClass || lineInfo.wrapClass.indexOf("bookmark") === -1) { + cm.addLineClass(lineNo, "wrap", "bookmark"); + } else { + cm.removeLineClass(lineNo, "wrap", "bookmark"); + } + resetBookmarks(editor); + } + } + + /** + * Creates the bookmarks panel if it's truly needed + * @param {Boolean} panelRequired - true if panel is required. False if not + */ + function createBookmarksPanelIfNecessary(panelRequired) { + if (!_bookmarksPanel && panelRequired) { + _bookmarksPanel = new BookmarksView(_bookmarks, updateBookmarksForCurrentEditor); + + $(_bookmarksPanel).on("close", function () { + CommandManager.get(CMD_TOGGLE_BOOKKMARK_VIEW).setChecked(_bookmarksPanel.isOpen()); + }); + } + } + + /** + * Shows the bookmarks panel + * @param {Boolean} show - true to show the panel, false to hide it + * @param {{show:string=}=} options - undefined, {show: undefined|"opened"|"project"|"all"}, defaults to "opened" + */ + function showBookmarksPanel(show, options) { + // we only need to create it if we're showing it + createBookmarksPanelIfNecessary(show); + if (!_bookmarksPanel) { + // nothing to do, panel wasn't created + return; + } + + // show/hide the panel + if (show) { + _bookmarksPanel.open(options); + } else { + _bookmarksPanel.close(); + } + + // update the command state + CommandManager.get(CMD_TOGGLE_BOOKKMARK_VIEW).setChecked(_bookmarksPanel.isOpen()); + } + + /** + * Creates and/or Shows or Hides the bookmarks panel + */ + function toggleBookmarksPanel() { + // always create it + createBookmarksPanelIfNecessary(true); + showBookmarksPanel(!_bookmarksPanel.isOpen(), prefs.get("viewOptions")); + // Since this is the only user-facing command then + // we can safely update the prefs here... + // Updating it in showBookmarksPanel could result in + // a race condition so we would need a way to + // indicate whether we were initializing from prefs + // or some other method in showBookmarksPanel. + // For now, we can just assume that showBookmarksPanel + // is not a consumable API, just a helper + prefs.set("panelVisible", _bookmarksPanel.isOpen()); + } + + /** + * Updates the state from prefs + */ + function updateFromPrefs() { + reloadModel(); + showBookmarksPanel(prefs.get("panelVisible"), prefs.get("viewOptions")); + } + + AppInit.appReady(function () { + // register our commands + CommandManager.register(ExtensionStrings.TOGGLE_BOOKMARK, CMD_TOGGLE_BOOKMARK, toggleBookmark); + CommandManager.register( + ExtensionStrings.GOTO_PREV_BOOKMARK, + CMD_GOTO_PREV_BOOKMARK, + _.partial(gotoNextBookmark, false) + ); + CommandManager.register( + ExtensionStrings.GOTO_NEXT_BOOKMARK, + CMD_GOTO_NEXT_BOOKMARK, + _.partial(gotoNextBookmark, true) + ); + + // add our menu items + var menu = Menus.getMenu(Menus.AppMenuBar.NAVIGATE_MENU); + + menu.addMenuDivider(); + menu.addMenuItem(CMD_TOGGLE_BOOKMARK, "Ctrl-Shift-K"); + menu.addMenuItem(CMD_GOTO_NEXT_BOOKMARK, "Ctrl-P"); + menu.addMenuItem(CMD_GOTO_PREV_BOOKMARK, "Ctrl-Shift-P"); + + menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU); + CommandManager.register( + ExtensionStrings.TOGGLE_BOOKMARKS_PANEL, + CMD_TOGGLE_BOOKKMARK_VIEW, + toggleBookmarksPanel + ); + menu.addMenuDivider(); + menu.addMenuItem(CMD_TOGGLE_BOOKKMARK_VIEW); + + // define prefs + prefs.definePreference("bookmarks", "object", {}); + prefs.definePreference("panelVisible", "boolean", false); + prefs.definePreference("viewOptions", "object", {}); + + // Initialize + loadModel(); + showBookmarksPanel(prefs.get("panelVisible"), prefs.get("viewOptions")); + + // event handlers + // NOTE: this is an undocumented, unsupported event fired when an editor is created + // @TODO: invent a standard event + EditorManager.on("_fullEditorCreatedForDocument", function (e, document, editor) { + editor.on("beforeDestroy.bookmarks", function () { + saveBookmarks(editor); + editor.off(".bookmarks"); + document.off(".bookmarks"); + }); + document.on("change.bookmarks", function () { + resetBookmarks(editor); + }); + loadBookmarks(editor); + }); + + // prefs change handler, dump everything and reload from prefs + prefs.on("change", updateFromPrefs); + }); +}); diff --git a/src/extensionsIntegrated/loader.js b/src/extensionsIntegrated/loader.js index 2da9f10f68..33ace3a5bd 100644 --- a/src/extensionsIntegrated/loader.js +++ b/src/extensionsIntegrated/loader.js @@ -44,4 +44,5 @@ define(function (require, exports, module) { require("./indentGuides/main"); require("./CSSColorPreview/main"); require("./TabBar/main"); + require("./Bookmarks/main"); }); diff --git a/src/styles/Extn-Bookmarks.less b/src/styles/Extn-Bookmarks.less new file mode 100644 index 0000000000..415aa7705b --- /dev/null +++ b/src/styles/Extn-Bookmarks.less @@ -0,0 +1,42 @@ +.bookmark .CodeMirror-gutter-elt { + background-color: lightskyblue; +} + +.bookmark .CodeMirror-linenumber { + background-image: url('./images/bookmark.svg'); + background-size: 16px; + background-repeat: no-repeat; + color: white !important; +} + +.active-pane .bookmark .CodeMirror-gutter-elt { + background-color: dodgerblue; + display: table; +} + +.active-pane .bookmark.live-preview-sync-error .CodeMirror-linenumber { + background-color: mediumorchid !important; +} + +.bookmark-notify { + background-color: dodgerblue; +} + +.bookmark-notify .CodeMirror-activeline-background { + background-color: darkslateblue!important; +} + +.bookmark.CodeMirror-activeline .CodeMirror-gutter-elt { + background-color: rebeccapurple !important; +} + +.active-pane .bookmark.CodeMirror-activeline .CodeMirror-gutter-elt { + background-color: darkslateblue!important; +} + +.bookmark.CodeMirror-activeline .CodeMirror-linenumber { + background-image: url('./images/bookmark.svg') !important; + background-size: 16px !important; + background-repeat: no-repeat !important; + color: white !important; +} diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 6b4b43a611..4788692ccb 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -44,6 +44,7 @@ @import "Extn-TabBar.less"; @import "Extn-DisplayShortcuts.less"; @import "Extn-CSSColorPreview.less"; +@import "Extn-Bookmarks.less"; /* Overall layout */ diff --git a/src/styles/images/bookmark.svg b/src/styles/images/bookmark.svg new file mode 100644 index 0000000000..69d11e7943 --- /dev/null +++ b/src/styles/images/bookmark.svg @@ -0,0 +1 @@ + From 7ec5559134540a4ee3a7aba99ba1025045d2d03a Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 20 May 2025 00:36:44 +0530 Subject: [PATCH 2/7] refactor: move to current ES6 standards --- .../Bookmarks/bookmarksView.js | 42 +++---- src/extensionsIntegrated/Bookmarks/main.js | 112 ++++++++---------- src/nls/root/strings.js | 6 + 3 files changed, 75 insertions(+), 85 deletions(-) diff --git a/src/extensionsIntegrated/Bookmarks/bookmarksView.js b/src/extensionsIntegrated/Bookmarks/bookmarksView.js index 4913becb18..e1b30b54c5 100644 --- a/src/extensionsIntegrated/Bookmarks/bookmarksView.js +++ b/src/extensionsIntegrated/Bookmarks/bookmarksView.js @@ -1,27 +1,24 @@ define(function (require, exports, module) { - var CommandManager = require("command/CommandManager"), - Commands = require("command/Commands"), - ProjectManager = require("project/ProjectManager"), - WorkspaceManager = require("view/WorkspaceManager"), - MainViewManger = require("view/MainViewManager"), - Strings = require("strings"), - bookmarksPanelTemplate = require("text!./htmlContent/bookmarks-panel.html"), - bookmarksListTemplate = require("text!./htmlContent/bookmarks-list.html"), - Mustache = require("thirdparty/mustache/mustache"); + const CommandManager = require("command/CommandManager"); + const Commands = require("command/Commands"); + const ProjectManager = require("project/ProjectManager"); + const WorkspaceManager = require("view/WorkspaceManager"); + const MainViewManger = require("view/MainViewManager"); + const Strings = require("strings"); + const Mustache = require("thirdparty/mustache/mustache"); + + const bookmarksPanelTemplate = require("text!./htmlContent/bookmarks-panel.html"); + const bookmarksListTemplate = require("text!./htmlContent/bookmarks-list.html"); /** - * @const * Debounce time for document changes updating the search results view. - * @type {number} */ - var UPDATE_TIMEOUT = 400; + const UPDATE_TIMEOUT = 400; /** - * @const * MainViewManager events - * @type {string} */ - var MVM_EVENTS = `workingSetAdd + const MVM_EVENTS = `workingSetAdd workingSetAddList workingSetMove workingSetRemove @@ -41,7 +38,7 @@ define(function (require, exports, module) { * @param {function=} beforeRender - function to call before rendering the view */ function BookmarksView(model, beforeRender) { - var panelHtml = Mustache.render(bookmarksPanelTemplate, { + const panelHtml = Mustache.render(bookmarksPanelTemplate, { Strings: Strings }); @@ -72,7 +69,7 @@ define(function (require, exports, module) { * Handles when model changes. Updates the view, buffering changes if necessary so as not to churn too much. */ BookmarksView.prototype._handleModelChange = function () { - var self = this; + const self = this; if (self._ignoreModelChangeEvents) { return; } @@ -96,7 +93,7 @@ define(function (require, exports, module) { * Adds the listeners for close and clicking on a bookmark in the list */ BookmarksView.prototype._addPanelListeners = function () { - var self = this; + const self = this; this._$panel .off(".bookmarks") // Remove the old events .on("click.bookmarks", ".close", function () { @@ -104,7 +101,7 @@ define(function (require, exports, module) { }) // Add the click event listener directly on the table parent .on("click.bookmarks .table-container", function (e) { - var $row = $(e.target).closest("tr"); + const $row = $(e.target).closest("tr"); if ($row.length) { if (self._$selectedRow) { @@ -113,7 +110,7 @@ define(function (require, exports, module) { $row.addClass("selected"); self._$selectedRow = $row; - var fullPathAndLineNo = $row.find(".bookmark-result").text(); + const fullPathAndLineNo = $row.find(".bookmark-result").text(); CommandManager.execute(Commands.FILE_OPEN, { fullPath: fullPathAndLineNo }); } @@ -147,7 +144,7 @@ define(function (require, exports, module) { * Shows the current set of results. */ BookmarksView.prototype._render = function () { - var self = this, + const self = this, bookmarks = []; if (this._beforeRender) { @@ -196,7 +193,7 @@ define(function (require, exports, module) { // In general this shouldn't get called if the panel is closed, but in case some // asynchronous process kicks this (e.g. a debounced model change), we double-check. if (this._panel.isVisible()) { - var scrollTop = this._$table.scrollTop(), + let scrollTop = this._$table.scrollTop(), index = this._$selectedRow ? this._$selectedRow.index() : null; this._render(); this._$table.scrollTop(scrollTop); @@ -237,6 +234,5 @@ define(function (require, exports, module) { return this._panel && this._panel.isVisible(); }; - // Public API exports.BookmarksView = BookmarksView; }); diff --git a/src/extensionsIntegrated/Bookmarks/main.js b/src/extensionsIntegrated/Bookmarks/main.js index ffe25ba1f8..5e9097cd16 100644 --- a/src/extensionsIntegrated/Bookmarks/main.js +++ b/src/extensionsIntegrated/Bookmarks/main.js @@ -1,41 +1,33 @@ define(function (require, exports, module) { - var PreferencesManager = require("preferences/PreferencesManager"), - CommandManager = require("command/CommandManager"), - Menus = require("command/Menus"), - DocumentManager = require("document/DocumentManager"), - EditorManager = require("editor/EditorManager"), - _ = require("thirdparty/lodash"); - const AppInit = require("utils/AppInit"); + const PreferencesManager = require("preferences/PreferencesManager"); + const CommandManager = require("command/CommandManager"); + const Menus = require("command/Menus"); + const DocumentManager = require("document/DocumentManager"); + const EditorManager = require("editor/EditorManager"); + const Strings = require("strings"); + const _ = require("thirdparty/lodash"); - var BookmarksView = require("./bookmarksView").BookmarksView; - - /** @const {string} Extension Command ID */ - var MY_MODULENAME = "bracketsEditorBookmarks"; - var CMD_TOGGLE_BOOKMARK = "bracketsEditorBookmarks.toggleBookmark", - CMD_GOTO_NEXT_BOOKMARK = "bracketsEditorBookmarks.gotoNextBookmark", - CMD_GOTO_PREV_BOOKMARK = "bracketsEditorBookmarks.gotoPrevBookmark", - CMD_TOGGLE_BOOKKMARK_VIEW = "bracketsEditorBookmarks.toggleBookmarksPanel"; + const BookmarksView = require("./bookmarksView").BookmarksView; - const ExtensionStrings = { - TOGGLE_BOOKMARK: "Toggle Bookmark", - GOTO_PREV_BOOKMARK: "Go to previous Bookmark", - GOTO_NEXT_BOOKMARK: "Go to next Bookmark", - TOGGLE_BOOKMARKS_PANEL: "Toggle Bookmarks panel" - }; + // command IDs + const MY_MODULENAME = "bracketsEditorBookmarks"; + const CMD_TOGGLE_BOOKMARK = "bracketsEditorBookmarks.toggleBookmark"; + const CMD_GOTO_NEXT_BOOKMARK = "bracketsEditorBookmarks.gotoNextBookmark"; + const CMD_GOTO_PREV_BOOKMARK = "bracketsEditorBookmarks.gotoPrevBookmark"; + const CMD_TOGGLE_BOOKKMARK_VIEW = "bracketsEditorBookmarks.toggleBookmarksPanel"; - /* Our extension's preferences */ - var prefs = PreferencesManager.getExtensionPrefs(MY_MODULENAME); + const prefs = PreferencesManager.getExtensionPrefs(MY_MODULENAME); // Bookmarks Data Model - var _bookmarks = {}; - + const _bookmarks = {}; // Bookmarks Panel - var _bookmarksPanel = null; + let _bookmarksPanel = null; /** * Saves bookmarks to the data model for the specified editor instance - * @param {Editor=} editor - brackets editor instance. current editor if null + * + * @param {Editor} editor - the editor instance * @return {?Array.} array of cached bookmarked line numbers */ function saveBookmarks(editor) { @@ -43,14 +35,14 @@ define(function (require, exports, module) { editor = EditorManager.getCurrentFullEditor(); } if (editor) { - var i, + let i, fullPath = editor.document.file.fullPath, cm = editor._codeMirror, lineCount = cm.doc.lineCount(), bookmarkedLines = []; for (i = 0; i < lineCount; i++) { - var lineInfo = cm.lineInfo(i); + let lineInfo = cm.lineInfo(i); if (lineInfo.wrapClass && lineInfo.wrapClass.indexOf("bookmark") >= 0) { bookmarkedLines.push(i); @@ -75,14 +67,15 @@ define(function (require, exports, module) { /** * Updates bookmarks for the current editor if necessary - * @param {Editor=} editor - brackets editor instance. current editor if null + * + * @param {Editor} editor - the editor instance * @return {Boolean} true if there are bookmarks for the current editor, false if not */ function updateBookmarksForCurrentEditor() { - var result = false, + let result = false, editor = EditorManager.getCurrentFullEditor(); if (editor) { - var fullPath = editor.document.file.fullPath, + let fullPath = editor.document.file.fullPath, bm = _bookmarks[fullPath]; // if there was already data then we @@ -106,7 +99,8 @@ define(function (require, exports, module) { * (for traversal or to update the bookmarks panel), * updateBookmarksForCurrentEditor is called which updates * incrementally the bookmarks for the current file - * @param {!Editor} editor - brackets editor instance + * + * @param {!Editor} editor - the editor instance */ function resetBookmarks(editor) { if (editor) { @@ -117,14 +111,15 @@ define(function (require, exports, module) { /** * Loads the cached bookmarks into the specified editor instance - * @param {Editor=} editor - brackets editor instance. current editor if null + * + * @param {Editor} editor - brackets editor instance. current editor if null */ function loadBookmarks(editor) { if (!editor) { editor = EditorManager.getCurrentFullEditor(); } if (editor) { - var cm = editor._codeMirror, + let cm = editor._codeMirror, bm = _bookmarks[editor.document.file.fullPath]; if (bm) { @@ -139,10 +134,11 @@ define(function (require, exports, module) { /** * Removes all bookmarks from the editor + * * @param {!Editor} editor - brackets editor instance. */ function clearBookmarks(editor) { - var i, + let i, cm = editor._codeMirror, lineCount = cm.doc.lineCount(); @@ -170,6 +166,7 @@ define(function (require, exports, module) { /** * Reloads the bookmark model, clearing the current bookmarks + * * @param {!Editor} editor - brackets editor instance. */ function reloadModel() { @@ -193,18 +190,19 @@ define(function (require, exports, module) { /** * Moves the cursor position of the current editor to the next bookmark + * * @param {!Editor} editor - brackets editor instance */ function gotoNextBookmark(forward) { if (updateBookmarksForCurrentEditor()) { - var editor = EditorManager.getCurrentFullEditor(), + let editor = EditorManager.getCurrentFullEditor(), cursor = editor.getCursorPos(), bm = _bookmarks[editor.document.file.fullPath]; - var doJump = function (lineNo) { + let doJump = function (lineNo) { editor.setCursorPos(lineNo, 0); - var cm = editor._codeMirror; + let cm = editor._codeMirror; cm.addLineClass(lineNo, "wrap", "bookmark-notify"); setTimeout(function () { cm.removeLineClass(lineNo, "wrap", "bookmark-notify"); @@ -212,7 +210,7 @@ define(function (require, exports, module) { }; // find next bookmark - var index; + let index; for ( index = forward ? 0 : bm.length - 1; forward ? index < bm.length : index >= 0; @@ -251,9 +249,9 @@ define(function (require, exports, module) { * Toogles the bookmarked state of the current line of the current editor */ function toggleBookmark() { - var editor = EditorManager.getCurrentFullEditor(); + let editor = EditorManager.getCurrentFullEditor(); if (editor) { - var cursor = editor.getCursorPos(), + let cursor = editor.getCursorPos(), lineNo = cursor.line, cm = editor._codeMirror, lineInfo = cm.lineInfo(cursor.line); @@ -269,6 +267,7 @@ define(function (require, exports, module) { /** * Creates the bookmarks panel if it's truly needed + * * @param {Boolean} panelRequired - true if panel is required. False if not */ function createBookmarksPanelIfNecessary(panelRequired) { @@ -283,6 +282,7 @@ define(function (require, exports, module) { /** * Shows the bookmarks panel + * * @param {Boolean} show - true to show the panel, false to hide it * @param {{show:string=}=} options - undefined, {show: undefined|"opened"|"project"|"all"}, defaults to "opened" */ @@ -333,32 +333,20 @@ define(function (require, exports, module) { AppInit.appReady(function () { // register our commands - CommandManager.register(ExtensionStrings.TOGGLE_BOOKMARK, CMD_TOGGLE_BOOKMARK, toggleBookmark); - CommandManager.register( - ExtensionStrings.GOTO_PREV_BOOKMARK, - CMD_GOTO_PREV_BOOKMARK, - _.partial(gotoNextBookmark, false) - ); - CommandManager.register( - ExtensionStrings.GOTO_NEXT_BOOKMARK, - CMD_GOTO_NEXT_BOOKMARK, - _.partial(gotoNextBookmark, true) - ); + CommandManager.register(Strings.TOGGLE_BOOKMARK, CMD_TOGGLE_BOOKMARK, toggleBookmark); + CommandManager.register(Strings.GOTO_PREV_BOOKMARK, CMD_GOTO_PREV_BOOKMARK, _.partial(gotoNextBookmark, false)); + CommandManager.register(Strings.GOTO_NEXT_BOOKMARK, CMD_GOTO_NEXT_BOOKMARK, _.partial(gotoNextBookmark, true)); // add our menu items - var menu = Menus.getMenu(Menus.AppMenuBar.NAVIGATE_MENU); + let menu = Menus.getMenu(Menus.AppMenuBar.NAVIGATE_MENU); menu.addMenuDivider(); - menu.addMenuItem(CMD_TOGGLE_BOOKMARK, "Ctrl-Shift-K"); - menu.addMenuItem(CMD_GOTO_NEXT_BOOKMARK, "Ctrl-P"); - menu.addMenuItem(CMD_GOTO_PREV_BOOKMARK, "Ctrl-Shift-P"); + menu.addMenuItem(CMD_TOGGLE_BOOKMARK, "Ctrl-Alt-B"); + menu.addMenuItem(CMD_GOTO_NEXT_BOOKMARK, "Ctrl-Alt-N"); + menu.addMenuItem(CMD_GOTO_PREV_BOOKMARK, "Ctrl-Alt-P"); menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU); - CommandManager.register( - ExtensionStrings.TOGGLE_BOOKMARKS_PANEL, - CMD_TOGGLE_BOOKKMARK_VIEW, - toggleBookmarksPanel - ); + CommandManager.register(Strings.TOGGLE_BOOKMARKS_PANEL, CMD_TOGGLE_BOOKKMARK_VIEW, toggleBookmarksPanel); menu.addMenuDivider(); menu.addMenuItem(CMD_TOGGLE_BOOKKMARK_VIEW); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7b996f7da4..c204444b64 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -434,6 +434,12 @@ define({ "CLOSE_UNMODIFIED_TABS": "Close Unmodified Tabs", "REOPEN_CLOSED_FILE": "Reopen Closed File", + // Bookmarks extension strings + "TOGGLE_BOOKMARK": "Toggle Bookmark", + "GOTO_PREV_BOOKMARK": "Go to Previous Bookmark", + "GOTO_NEXT_BOOKMARK": "Go to Next Bookmark", + "TOGGLE_BOOKMARKS_PANEL": "Toggle Bookmarks panel", + // CodeInspection: errors/warnings "ERRORS_NO_FILE": "No File Open", "ERRORS_PANEL_TITLE_MULTIPLE": "{0} Problems - {1}", From 5e46ce5c9fcfc426d43c6d371ee665d00c43607b Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 20 May 2025 21:40:43 +0530 Subject: [PATCH 3/7] feat: bookmarks implementation --- .../Bookmarks/bookmarksView.js | 238 ----------- .../Bookmarks/htmlContent/bookmarks-list.html | 9 - .../htmlContent/bookmarks-panel.html | 7 - src/extensionsIntegrated/Bookmarks/main.js | 374 +----------------- .../Bookmarks/src/bookmarks.js | 40 ++ src/styles/Extn-Bookmarks.less | 56 +-- src/styles/images/bookmark.svg | 2 +- 7 files changed, 73 insertions(+), 653 deletions(-) delete mode 100644 src/extensionsIntegrated/Bookmarks/bookmarksView.js delete mode 100644 src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-list.html delete mode 100644 src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-panel.html create mode 100644 src/extensionsIntegrated/Bookmarks/src/bookmarks.js diff --git a/src/extensionsIntegrated/Bookmarks/bookmarksView.js b/src/extensionsIntegrated/Bookmarks/bookmarksView.js deleted file mode 100644 index e1b30b54c5..0000000000 --- a/src/extensionsIntegrated/Bookmarks/bookmarksView.js +++ /dev/null @@ -1,238 +0,0 @@ -define(function (require, exports, module) { - const CommandManager = require("command/CommandManager"); - const Commands = require("command/Commands"); - const ProjectManager = require("project/ProjectManager"); - const WorkspaceManager = require("view/WorkspaceManager"); - const MainViewManger = require("view/MainViewManager"); - const Strings = require("strings"); - const Mustache = require("thirdparty/mustache/mustache"); - - const bookmarksPanelTemplate = require("text!./htmlContent/bookmarks-panel.html"); - const bookmarksListTemplate = require("text!./htmlContent/bookmarks-list.html"); - - /** - * Debounce time for document changes updating the search results view. - */ - const UPDATE_TIMEOUT = 400; - - /** - * MainViewManager events - */ - const MVM_EVENTS = `workingSetAdd - workingSetAddList - workingSetMove - workingSetRemove - workingSetRemoveList - workingSetUpdate - currentFileChange - activePaneChange`; - - /** - * @constructor - * Creates a bookmarks panel - * Dispatches the following events: - * close - when the panel is closed. - * - * @typedef {Object.>} BookmarksModel - * @param {BookmarksModel} model - bookmarks model - * @param {function=} beforeRender - function to call before rendering the view - */ - function BookmarksView(model, beforeRender) { - const panelHtml = Mustache.render(bookmarksPanelTemplate, { - Strings: Strings - }); - - this._panel = WorkspaceManager.createBottomPanel("bookmarks", $(panelHtml), 100); - this._$panel = this._panel.$panel; - this._$table = this._$panel.find(".table-container"); - this._model = model; - this._beforeRender = beforeRender; - } - - /** @type {BookmarksModel} bookmarks model */ - BookmarksView.prototype._model = null; - - /** @type {Panel} Bottom panel holding the bookmarks */ - BookmarksView.prototype._panel = null; - - /** @type {$.Element} The table that holds the results */ - BookmarksView.prototype._$table = null; - - /** @type {number} The ID we use for timeouts when handling model changes. */ - BookmarksView.prototype._timeoutID = null; - - /** @type {function=} function that is called before refreshing the view */ - BookmarksView.prototype._beforeRender = null; - - /** - * @private - * Handles when model changes. Updates the view, buffering changes if necessary so as not to churn too much. - */ - BookmarksView.prototype._handleModelChange = function () { - const self = this; - if (self._ignoreModelChangeEvents) { - return; - } - if (this._timeoutID) { - window.clearTimeout(this._timeoutID); - } - this._timeoutID = window.setTimeout(function () { - // _updateResults causes the model to be recomputed - // which triggers another model change event which - // we need to ignore or we endup in a race condition - // which may lead to data loss - self._ignoreModelChangeEvents = true; - self._updateResults(); - self._timeoutID = null; - delete self._ignoreModelChangeEvents; - }, UPDATE_TIMEOUT); - }; - - /** - * @private - * Adds the listeners for close and clicking on a bookmark in the list - */ - BookmarksView.prototype._addPanelListeners = function () { - const self = this; - this._$panel - .off(".bookmarks") // Remove the old events - .on("click.bookmarks", ".close", function () { - self.close(); - }) - // Add the click event listener directly on the table parent - .on("click.bookmarks .table-container", function (e) { - const $row = $(e.target).closest("tr"); - - if ($row.length) { - if (self._$selectedRow) { - self._$selectedRow.removeClass("selected"); - } - $row.addClass("selected"); - self._$selectedRow = $row; - - const fullPathAndLineNo = $row.find(".bookmark-result").text(); - - CommandManager.execute(Commands.FILE_OPEN, { fullPath: fullPathAndLineNo }); - } - }); - MainViewManger.on(MVM_EVENTS, this._updateResults.bind(this)); - }; - - /** - * @private - * @param {!String} fullpath - path of the file to show - */ - BookmarksView.prototype._shouldShow = function (fullpath) { - if (!this._options || !this._options.show || this._options.show === "opened") { - return Boolean(MainViewManger._getPaneIdForPath(fullpath)); - } else if (this._options.show === "all") { - return true; - } else if (this._options.show === "project" && ProjectManager.getProjectRoot()) { - // show open files and any file bookmarked in the current project - return ( - Boolean(MainViewManger._getPaneIdForPath(fullpath)) || - fullpath.toLowerCase().indexOf(ProjectManager.getProjectRoot().fullPath.toLowerCase()) === 0 - ); - } - - // unknown option - return false; - }; - - /** - * @private - * Shows the current set of results. - */ - BookmarksView.prototype._render = function () { - const self = this, - bookmarks = []; - - if (this._beforeRender) { - this._beforeRender(); - } - - // Iterates throuh the files to display the results sorted by filenamess. The loop ends as soon as - // we filled the results for one page - Object.keys(this._model) - .filter(function (fullPath) { - return self._shouldShow(fullPath); - }) - .sort(function (a, b) { - return a > b; - }) - .forEach(function (fullPath) { - self._model[fullPath].forEach(function (lineNo) { - bookmarks.push({ - fullPath: fullPath, - lineNo: lineNo + 1 - }); - }); - }); - - // Insert the search results - this._$table.empty().append( - Mustache.render(bookmarksListTemplate, { - bookmarks: bookmarks, - Strings: Strings - }) - ); - - if (this._$selectedRow) { - this._$selectedRow.removeClass("selected"); - this._$selectedRow = null; - } - - this._panel.show(); - this._$table.scrollTop(0); // Otherwise scroll pos from previous contents is remembered - }; - - /** - * Updates the results view after a model change, preserving scroll position and selection. - */ - BookmarksView.prototype._updateResults = function () { - // In general this shouldn't get called if the panel is closed, but in case some - // asynchronous process kicks this (e.g. a debounced model change), we double-check. - if (this._panel.isVisible()) { - let scrollTop = this._$table.scrollTop(), - index = this._$selectedRow ? this._$selectedRow.index() : null; - this._render(); - this._$table.scrollTop(scrollTop); - if (index) { - this._$selectedRow = this._$table.find("tr:eq(" + index + ")"); - this._$selectedRow.addClass("selected"); - } - } - }; - - /** - * Opens the results panel and displays the current set of results from the model. - */ - BookmarksView.prototype.open = function (options) { - this._options = options; - this._render(); - this._addPanelListeners(); - $(this._model).on("change.BookmarksView", this._handleModelChange.bind(this)); - }; - - /** - * Hides the Search Results Panel and unregisters listeners. - */ - BookmarksView.prototype.close = function () { - if (this._panel && this._panel.isVisible()) { - this._$table.empty(); - this._panel.hide(); - this._panel.$panel.off(".bookmarks"); - $(this._model).off("change.BookmarksView"); - $(this).triggerHandler("close"); - } - }; - - /** - * Hides the Search Results Panel and unregisters listeners. - */ - BookmarksView.prototype.isOpen = function () { - return this._panel && this._panel.isVisible(); - }; - - exports.BookmarksView = BookmarksView; -}); diff --git a/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-list.html b/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-list.html deleted file mode 100644 index 02e3e4a08e..0000000000 --- a/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-list.html +++ /dev/null @@ -1,9 +0,0 @@ - - - {{#bookmarks}} - - - - {{/bookmarks}} - -
{{fullPath}}:{{lineNo}}
diff --git a/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-panel.html b/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-panel.html deleted file mode 100644 index 264b736d2f..0000000000 --- a/src/extensionsIntegrated/Bookmarks/htmlContent/bookmarks-panel.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
-
{{Strings.BOOKMARKS_PANEL_TITLE}}
- × -
-
-
diff --git a/src/extensionsIntegrated/Bookmarks/main.js b/src/extensionsIntegrated/Bookmarks/main.js index 5e9097cd16..1d6cef82ff 100644 --- a/src/extensionsIntegrated/Bookmarks/main.js +++ b/src/extensionsIntegrated/Bookmarks/main.js @@ -1,380 +1,40 @@ define(function (require, exports, module) { const AppInit = require("utils/AppInit"); - const PreferencesManager = require("preferences/PreferencesManager"); const CommandManager = require("command/CommandManager"); const Menus = require("command/Menus"); - const DocumentManager = require("document/DocumentManager"); - const EditorManager = require("editor/EditorManager"); const Strings = require("strings"); - const _ = require("thirdparty/lodash"); - const BookmarksView = require("./bookmarksView").BookmarksView; + const Bookmarks = require("./src/bookmarks"); - // command IDs - const MY_MODULENAME = "bracketsEditorBookmarks"; - const CMD_TOGGLE_BOOKMARK = "bracketsEditorBookmarks.toggleBookmark"; - const CMD_GOTO_NEXT_BOOKMARK = "bracketsEditorBookmarks.gotoNextBookmark"; - const CMD_GOTO_PREV_BOOKMARK = "bracketsEditorBookmarks.gotoPrevBookmark"; - const CMD_TOGGLE_BOOKKMARK_VIEW = "bracketsEditorBookmarks.toggleBookmarksPanel"; + // command ids + const CMD_TOGGLE_BOOKMARK = "bookmarks.toggleBookmark"; - const prefs = PreferencesManager.getExtensionPrefs(MY_MODULENAME); - - // Bookmarks Data Model - const _bookmarks = {}; - // Bookmarks Panel - let _bookmarksPanel = null; - - /** - * Saves bookmarks to the data model for the specified editor instance - * - * @param {Editor} editor - the editor instance - * @return {?Array.} array of cached bookmarked line numbers - */ - function saveBookmarks(editor) { - if (!editor) { - editor = EditorManager.getCurrentFullEditor(); - } - if (editor) { - let i, - fullPath = editor.document.file.fullPath, - cm = editor._codeMirror, - lineCount = cm.doc.lineCount(), - bookmarkedLines = []; - - for (i = 0; i < lineCount; i++) { - let lineInfo = cm.lineInfo(i); - - if (lineInfo.wrapClass && lineInfo.wrapClass.indexOf("bookmark") >= 0) { - bookmarkedLines.push(i); - } - } - - // we need to sort so that go to next bookmark works - bookmarkedLines.sort(function (a, b) { - return a > b; - }); - - _bookmarks[fullPath] = bookmarkedLines; - prefs.set("bookmarks", _bookmarks); - - $(_bookmarks).triggerHandler("change"); - - // return the bookmarks for the editor - return bookmarkedLines; - } - return null; - } - - /** - * Updates bookmarks for the current editor if necessary - * - * @param {Editor} editor - the editor instance - * @return {Boolean} true if there are bookmarks for the current editor, false if not - */ - function updateBookmarksForCurrentEditor() { - let result = false, - editor = EditorManager.getCurrentFullEditor(); - if (editor) { - let fullPath = editor.document.file.fullPath, - bm = _bookmarks[fullPath]; - - // if there was already data then we - // don't need to rebuild it - result = bm && bm.length; - - if (!result) { - // there was no deta for this file so - // rebuild the model just for this file - // from what is in the editor currently - result = Boolean(saveBookmarks(editor)); - } - } - - return result; - } - - /** - * Resets the bookmarks for the file opened in the specified editor - * NOTE: When the bookmarks for the current editor are needed - * (for traversal or to update the bookmarks panel), - * updateBookmarksForCurrentEditor is called which updates - * incrementally the bookmarks for the current file - * - * @param {!Editor} editor - the editor instance - */ - function resetBookmarks(editor) { - if (editor) { - delete _bookmarks[editor.document.file.fullPath]; - $(_bookmarks).triggerHandler("change"); - } - } - - /** - * Loads the cached bookmarks into the specified editor instance - * - * @param {Editor} editor - brackets editor instance. current editor if null - */ - function loadBookmarks(editor) { - if (!editor) { - editor = EditorManager.getCurrentFullEditor(); - } - if (editor) { - let cm = editor._codeMirror, - bm = _bookmarks[editor.document.file.fullPath]; - - if (bm) { - bm.forEach(function (lineNo) { - if (lineNo < cm.doc.lineCount()) { - cm.addLineClass(lineNo, "wrap", "bookmark"); - } - }); - } - } - } - - /** - * Removes all bookmarks from the editor - * - * @param {!Editor} editor - brackets editor instance. - */ - function clearBookmarks(editor) { - let i, - cm = editor._codeMirror, - lineCount = cm.doc.lineCount(); - - for (i = 0; i < lineCount; i++) { - cm.removeLineClass(i, "wrap", "bookmark"); - } - } - - /** - * Resets all bookmark model data so it can be re-read from prefs - */ - function resetModel() { - Object.keys(_bookmarks).forEach(function (prop) { - delete _bookmarks[prop]; - }); - } - - /** - * Loads bookmark model from prefs - */ - function loadModel() { - resetModel(); - _.assign(_bookmarks, prefs.get("bookmarks")); - } + // default keyboard shortcuts + const TOGGLE_BOOKMARK_KB_SHORTCUT = "Ctrl-Alt-B"; /** - * Reloads the bookmark model, clearing the current bookmarks - * - * @param {!Editor} editor - brackets editor instance. + * This function is responsible for registering all the required commands */ - function reloadModel() { - DocumentManager.getAllOpenDocuments().forEach(function (doc) { - if (doc._masterEditor) { - clearBookmarks(doc._masterEditor); - } - }); - - loadModel(); - - DocumentManager.getAllOpenDocuments().forEach(function (doc) { - if (doc._masterEditor) { - loadBookmarks(doc._masterEditor); - } - }); - - // update the panel - $(_bookmarks).triggerHandler("change"); + function _registerCommands() { + CommandManager.register(Strings.TOGGLE_BOOKMARK, CMD_TOGGLE_BOOKMARK, Bookmarks.toggleBookmark); } /** - * Moves the cursor position of the current editor to the next bookmark - * - * @param {!Editor} editor - brackets editor instance + * This function is responsible to add the bookmarks menu items to the navigate menu */ - function gotoNextBookmark(forward) { - if (updateBookmarksForCurrentEditor()) { - let editor = EditorManager.getCurrentFullEditor(), - cursor = editor.getCursorPos(), - bm = _bookmarks[editor.document.file.fullPath]; - - let doJump = function (lineNo) { - editor.setCursorPos(lineNo, 0); - - let cm = editor._codeMirror; - cm.addLineClass(lineNo, "wrap", "bookmark-notify"); - setTimeout(function () { - cm.removeLineClass(lineNo, "wrap", "bookmark-notify"); - }, 100); - }; + function _addItemsToMenu() { + const navigateMenu = Menus.getMenu(Menus.AppMenuBar.NAVIGATE_MENU); + navigateMenu.addMenuDivider(); // add a line to separate the other items from the bookmark ones - // find next bookmark - let index; - for ( - index = forward ? 0 : bm.length - 1; - forward ? index < bm.length : index >= 0; - forward ? index++ : index-- - ) { - if (forward) { - if (bm[index] > cursor.line) { - doJump(bm[index]); - return; - } - if (index === bm.length - 1) { - // wrap around just pick the first one in the list - if (bm[0] !== cursor.line) { - doJump(bm[0]); - } - return; - } - } else { - if (bm[index] < cursor.line) { - doJump(bm[index]); - return; - } - if (index === 0) { - // wrap around just pick the last one in the list - if (bm[bm.length - 1] !== cursor.line) { - doJump(bm[bm.length - 1]); - } - return; - } - } - } - } + navigateMenu.addMenuItem(CMD_TOGGLE_BOOKMARK, TOGGLE_BOOKMARK_KB_SHORTCUT); } - /** - * Toogles the bookmarked state of the current line of the current editor - */ - function toggleBookmark() { - let editor = EditorManager.getCurrentFullEditor(); - if (editor) { - let cursor = editor.getCursorPos(), - lineNo = cursor.line, - cm = editor._codeMirror, - lineInfo = cm.lineInfo(cursor.line); - - if (!lineInfo.wrapClass || lineInfo.wrapClass.indexOf("bookmark") === -1) { - cm.addLineClass(lineNo, "wrap", "bookmark"); - } else { - cm.removeLineClass(lineNo, "wrap", "bookmark"); - } - resetBookmarks(editor); - } - } - - /** - * Creates the bookmarks panel if it's truly needed - * - * @param {Boolean} panelRequired - true if panel is required. False if not - */ - function createBookmarksPanelIfNecessary(panelRequired) { - if (!_bookmarksPanel && panelRequired) { - _bookmarksPanel = new BookmarksView(_bookmarks, updateBookmarksForCurrentEditor); - - $(_bookmarksPanel).on("close", function () { - CommandManager.get(CMD_TOGGLE_BOOKKMARK_VIEW).setChecked(_bookmarksPanel.isOpen()); - }); - } - } - - /** - * Shows the bookmarks panel - * - * @param {Boolean} show - true to show the panel, false to hide it - * @param {{show:string=}=} options - undefined, {show: undefined|"opened"|"project"|"all"}, defaults to "opened" - */ - function showBookmarksPanel(show, options) { - // we only need to create it if we're showing it - createBookmarksPanelIfNecessary(show); - if (!_bookmarksPanel) { - // nothing to do, panel wasn't created - return; - } - - // show/hide the panel - if (show) { - _bookmarksPanel.open(options); - } else { - _bookmarksPanel.close(); - } - - // update the command state - CommandManager.get(CMD_TOGGLE_BOOKKMARK_VIEW).setChecked(_bookmarksPanel.isOpen()); - } - - /** - * Creates and/or Shows or Hides the bookmarks panel - */ - function toggleBookmarksPanel() { - // always create it - createBookmarksPanelIfNecessary(true); - showBookmarksPanel(!_bookmarksPanel.isOpen(), prefs.get("viewOptions")); - // Since this is the only user-facing command then - // we can safely update the prefs here... - // Updating it in showBookmarksPanel could result in - // a race condition so we would need a way to - // indicate whether we were initializing from prefs - // or some other method in showBookmarksPanel. - // For now, we can just assume that showBookmarksPanel - // is not a consumable API, just a helper - prefs.set("panelVisible", _bookmarksPanel.isOpen()); - } - - /** - * Updates the state from prefs - */ - function updateFromPrefs() { - reloadModel(); - showBookmarksPanel(prefs.get("panelVisible"), prefs.get("viewOptions")); + function init() { + _registerCommands(); + _addItemsToMenu(); } AppInit.appReady(function () { - // register our commands - CommandManager.register(Strings.TOGGLE_BOOKMARK, CMD_TOGGLE_BOOKMARK, toggleBookmark); - CommandManager.register(Strings.GOTO_PREV_BOOKMARK, CMD_GOTO_PREV_BOOKMARK, _.partial(gotoNextBookmark, false)); - CommandManager.register(Strings.GOTO_NEXT_BOOKMARK, CMD_GOTO_NEXT_BOOKMARK, _.partial(gotoNextBookmark, true)); - - // add our menu items - let menu = Menus.getMenu(Menus.AppMenuBar.NAVIGATE_MENU); - - menu.addMenuDivider(); - menu.addMenuItem(CMD_TOGGLE_BOOKMARK, "Ctrl-Alt-B"); - menu.addMenuItem(CMD_GOTO_NEXT_BOOKMARK, "Ctrl-Alt-N"); - menu.addMenuItem(CMD_GOTO_PREV_BOOKMARK, "Ctrl-Alt-P"); - - menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU); - CommandManager.register(Strings.TOGGLE_BOOKMARKS_PANEL, CMD_TOGGLE_BOOKKMARK_VIEW, toggleBookmarksPanel); - menu.addMenuDivider(); - menu.addMenuItem(CMD_TOGGLE_BOOKKMARK_VIEW); - - // define prefs - prefs.definePreference("bookmarks", "object", {}); - prefs.definePreference("panelVisible", "boolean", false); - prefs.definePreference("viewOptions", "object", {}); - - // Initialize - loadModel(); - showBookmarksPanel(prefs.get("panelVisible"), prefs.get("viewOptions")); - - // event handlers - // NOTE: this is an undocumented, unsupported event fired when an editor is created - // @TODO: invent a standard event - EditorManager.on("_fullEditorCreatedForDocument", function (e, document, editor) { - editor.on("beforeDestroy.bookmarks", function () { - saveBookmarks(editor); - editor.off(".bookmarks"); - document.off(".bookmarks"); - }); - document.on("change.bookmarks", function () { - resetBookmarks(editor); - }); - loadBookmarks(editor); - }); - - // prefs change handler, dump everything and reload from prefs - prefs.on("change", updateFromPrefs); + init(); }); }); diff --git a/src/extensionsIntegrated/Bookmarks/src/bookmarks.js b/src/extensionsIntegrated/Bookmarks/src/bookmarks.js new file mode 100644 index 0000000000..06c3b560c3 --- /dev/null +++ b/src/extensionsIntegrated/Bookmarks/src/bookmarks.js @@ -0,0 +1,40 @@ +define(function (require, exports, module) { + const EditorManager = require("editor/EditorManager"), + Editor = require("editor/Editor").Editor; + + const GUTTER_NAME = "CodeMirror-bookmarkGutter", + BOOKMARK_PRIORITY = 100; + + // the bookmark svg icon + const bookmarkSvg = require("text!styles/images/bookmark.svg"); + + // initialize the bookmark gutter + Editor.registerGutter(GUTTER_NAME, BOOKMARK_PRIORITY); + + /** + * This function is responsible to toggle a bookmark at the current cursor position + */ + function toggleBookmark() { + const editor = EditorManager.getFocusedEditor(); + if (!editor) { + return; + } + + const selection = editor.getSelection(); + const currentLine = selection.start.line; + + // check whether there's already a bookmark on this line + const marker = editor.getGutterMarker(currentLine, GUTTER_NAME); + + if (marker) { + // remove bookmark if exists + editor.setGutterMarker(currentLine, GUTTER_NAME, ""); + } else { + // add the bookmark + const $marker = $("
").addClass("bookmark-icon").html(bookmarkSvg); + editor.setGutterMarker(currentLine, GUTTER_NAME, $marker[0]); + } + } + + exports.toggleBookmark = toggleBookmark; +}); diff --git a/src/styles/Extn-Bookmarks.less b/src/styles/Extn-Bookmarks.less index 415aa7705b..1211d3e87d 100644 --- a/src/styles/Extn-Bookmarks.less +++ b/src/styles/Extn-Bookmarks.less @@ -1,42 +1,16 @@ -.bookmark .CodeMirror-gutter-elt { - background-color: lightskyblue; -} - -.bookmark .CodeMirror-linenumber { - background-image: url('./images/bookmark.svg'); - background-size: 16px; - background-repeat: no-repeat; - color: white !important; -} - -.active-pane .bookmark .CodeMirror-gutter-elt { - background-color: dodgerblue; - display: table; -} - -.active-pane .bookmark.live-preview-sync-error .CodeMirror-linenumber { - background-color: mediumorchid !important; -} - -.bookmark-notify { - background-color: dodgerblue; -} - -.bookmark-notify .CodeMirror-activeline-background { - background-color: darkslateblue!important; -} - -.bookmark.CodeMirror-activeline .CodeMirror-gutter-elt { - background-color: rebeccapurple !important; -} - -.active-pane .bookmark.CodeMirror-activeline .CodeMirror-gutter-elt { - background-color: darkslateblue!important; -} - -.bookmark.CodeMirror-activeline .CodeMirror-linenumber { - background-image: url('./images/bookmark.svg') !important; - background-size: 16px !important; - background-repeat: no-repeat !important; - color: white !important; +.bookmark-icon { + width: 12px; + height: 12px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + margin-left: 2px; + margin-top: 2px; + + svg { + width: 100%; + height: 100%; + fill: white; + } } diff --git a/src/styles/images/bookmark.svg b/src/styles/images/bookmark.svg index 69d11e7943..bbde487856 100644 --- a/src/styles/images/bookmark.svg +++ b/src/styles/images/bookmark.svg @@ -1 +1 @@ - + From f508be70ddeb62959da31c621e548ddfa3be7197 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 20 May 2025 21:56:06 +0530 Subject: [PATCH 4/7] fix: update bookmark icon size when the editor area is zoomed in and out --- src/styles/Extn-Bookmarks.less | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/styles/Extn-Bookmarks.less b/src/styles/Extn-Bookmarks.less index 1211d3e87d..42d97b7ca0 100644 --- a/src/styles/Extn-Bookmarks.less +++ b/src/styles/Extn-Bookmarks.less @@ -1,12 +1,12 @@ .bookmark-icon { - width: 12px; - height: 12px; + width: 0.75em; + height: 0.75em; display: flex; align-items: center; justify-content: center; cursor: pointer; - margin-left: 2px; - margin-top: 2px; + margin-left: 0.2em; + margin-top: 0.35em; svg { width: 100%; From 7b4f22ad48fe2b5d2676e7fed3d07b7e65783156 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 20 May 2025 22:31:28 +0530 Subject: [PATCH 5/7] fix: handle cases when multiple cursors are present --- .../Bookmarks/src/bookmarks.js | 44 +++++++++++-------- .../Bookmarks/src/helper.js | 40 +++++++++++++++++ 2 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 src/extensionsIntegrated/Bookmarks/src/helper.js diff --git a/src/extensionsIntegrated/Bookmarks/src/bookmarks.js b/src/extensionsIntegrated/Bookmarks/src/bookmarks.js index 06c3b560c3..265d0fbf10 100644 --- a/src/extensionsIntegrated/Bookmarks/src/bookmarks.js +++ b/src/extensionsIntegrated/Bookmarks/src/bookmarks.js @@ -1,18 +1,31 @@ define(function (require, exports, module) { - const EditorManager = require("editor/EditorManager"), - Editor = require("editor/Editor").Editor; + const EditorManager = require("editor/EditorManager"); + const Editor = require("editor/Editor").Editor; + + const Helper = require("./helper"); const GUTTER_NAME = "CodeMirror-bookmarkGutter", BOOKMARK_PRIORITY = 100; - // the bookmark svg icon - const bookmarkSvg = require("text!styles/images/bookmark.svg"); - // initialize the bookmark gutter Editor.registerGutter(GUTTER_NAME, BOOKMARK_PRIORITY); /** - * This function is responsible to toggle a bookmark at the current cursor position + * This function toggles a bookmark on a specific line + * + * @param {Editor} editor - The current editor instance + * @param {number} line - The line number to toggle bookmark on + */ + function toggleLineBookmark(editor, line) { + if (Helper.hasBookmark(editor, line, GUTTER_NAME)) { + editor.setGutterMarker(line, GUTTER_NAME, ""); + } else { + editor.setGutterMarker(line, GUTTER_NAME, Helper.createBookmarkMarker()); + } + } + + /** + * This function is responsible to toggle bookmarks at the current cursor position(s) */ function toggleBookmark() { const editor = EditorManager.getFocusedEditor(); @@ -20,20 +33,13 @@ define(function (require, exports, module) { return; } - const selection = editor.getSelection(); - const currentLine = selection.start.line; - - // check whether there's already a bookmark on this line - const marker = editor.getGutterMarker(currentLine, GUTTER_NAME); + const selections = editor.getSelections(); + const uniqueLines = Helper.getUniqueLines(selections); - if (marker) { - // remove bookmark if exists - editor.setGutterMarker(currentLine, GUTTER_NAME, ""); - } else { - // add the bookmark - const $marker = $("
").addClass("bookmark-icon").html(bookmarkSvg); - editor.setGutterMarker(currentLine, GUTTER_NAME, $marker[0]); - } + // process each unique line + uniqueLines.forEach((line) => { + toggleLineBookmark(editor, line); + }); } exports.toggleBookmark = toggleBookmark; diff --git a/src/extensionsIntegrated/Bookmarks/src/helper.js b/src/extensionsIntegrated/Bookmarks/src/helper.js new file mode 100644 index 0000000000..e071c60650 --- /dev/null +++ b/src/extensionsIntegrated/Bookmarks/src/helper.js @@ -0,0 +1,40 @@ +define(function (require, exports, module) { + // the bookmark svg icon + const bookmarkSvg = require("text!styles/images/bookmark.svg"); + + /** + * This function creates a bookmark marker element + * + * @returns {HTMLElement} The bookmark marker element + */ + function createBookmarkMarker() { + return $("
").addClass("bookmark-icon").html(bookmarkSvg)[0]; + } + + /** + * This function checks whether a line has a bookmark + * + * @param {Editor} editor - The current editor instance + * @param {number} line - The line number to check + * @param {string} gutterName - The name of the gutter + * @returns {boolean} True if the line has a bookmark, false otherwise + */ + function hasBookmark(editor, line, gutterName) { + return !!editor.getGutterMarker(line, gutterName); + } + + /** + * This function gets unique line numbers from all selections + * this is needed so that when multiple cursors are there at the same line, we can get the line only once + * + * @param {Array<{start: {line: number}}>} selections - Array of selections + * @returns {Array} Array of unique line numbers + */ + function getUniqueLines(selections) { + return [...new Set(selections.map((selection) => selection.start.line))]; + } + + exports.createBookmarkMarker = createBookmarkMarker; + exports.hasBookmark = hasBookmark; + exports.getUniqueLines = getUniqueLines; +}); From 669d0bea89be506efabfebb919447e4fc7e391f3 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 21 May 2025 01:47:13 +0530 Subject: [PATCH 6/7] feat: add buttons to move to prev and next bookmark --- src/extensionsIntegrated/Bookmarks/main.js | 8 ++++++++ .../Bookmarks/src/bookmarks.js | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/extensionsIntegrated/Bookmarks/main.js b/src/extensionsIntegrated/Bookmarks/main.js index 1d6cef82ff..a9724f6978 100644 --- a/src/extensionsIntegrated/Bookmarks/main.js +++ b/src/extensionsIntegrated/Bookmarks/main.js @@ -8,15 +8,21 @@ define(function (require, exports, module) { // command ids const CMD_TOGGLE_BOOKMARK = "bookmarks.toggleBookmark"; + const CMD_NEXT_BOOKMARK = "bookmarks.nextBookmark"; + const CMD_PREV_BOOKMARK = "bookmarks.prevBookmark"; // default keyboard shortcuts const TOGGLE_BOOKMARK_KB_SHORTCUT = "Ctrl-Alt-B"; + const NEXT_BOOKMARK_KB_SHORTCUT = "Ctrl-Alt-N"; + const PREV_BOOKMARK_KB_SHORTCUT = "Ctrl-Alt-P"; /** * This function is responsible for registering all the required commands */ function _registerCommands() { CommandManager.register(Strings.TOGGLE_BOOKMARK, CMD_TOGGLE_BOOKMARK, Bookmarks.toggleBookmark); + CommandManager.register(Strings.GOTO_NEXT_BOOKMARK, CMD_NEXT_BOOKMARK, Bookmarks.goToNextBookmark); + CommandManager.register(Strings.GOTO_PREV_BOOKMARK, CMD_PREV_BOOKMARK, Bookmarks.goToPrevBookmark); } /** @@ -27,6 +33,8 @@ define(function (require, exports, module) { navigateMenu.addMenuDivider(); // add a line to separate the other items from the bookmark ones navigateMenu.addMenuItem(CMD_TOGGLE_BOOKMARK, TOGGLE_BOOKMARK_KB_SHORTCUT); + navigateMenu.addMenuItem(CMD_NEXT_BOOKMARK, NEXT_BOOKMARK_KB_SHORTCUT); + navigateMenu.addMenuItem(CMD_PREV_BOOKMARK, PREV_BOOKMARK_KB_SHORTCUT); } function init() { diff --git a/src/extensionsIntegrated/Bookmarks/src/bookmarks.js b/src/extensionsIntegrated/Bookmarks/src/bookmarks.js index 265d0fbf10..a1b9e03b63 100644 --- a/src/extensionsIntegrated/Bookmarks/src/bookmarks.js +++ b/src/extensionsIntegrated/Bookmarks/src/bookmarks.js @@ -13,10 +13,11 @@ define(function (require, exports, module) { /** * This function toggles a bookmark on a specific line * + * @private * @param {Editor} editor - The current editor instance * @param {number} line - The line number to toggle bookmark on */ - function toggleLineBookmark(editor, line) { + function _toggleLineBookmark(editor, line) { if (Helper.hasBookmark(editor, line, GUTTER_NAME)) { editor.setGutterMarker(line, GUTTER_NAME, ""); } else { @@ -38,9 +39,19 @@ define(function (require, exports, module) { // process each unique line uniqueLines.forEach((line) => { - toggleLineBookmark(editor, line); + _toggleLineBookmark(editor, line); }); } + function goToNextBookmark() { + // + } + + function goToPrevBookmark() { + // + } + exports.toggleBookmark = toggleBookmark; + exports.goToNextBookmark = goToNextBookmark; + exports.goToPrevBookmark = goToPrevBookmark; }); From 3d69ed7fb17a2c092ca4315a4424b4c067dce0d9 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 21 May 2025 02:51:26 +0530 Subject: [PATCH 7/7] feat: implement moving to next and previous bookmark functionality --- .../Bookmarks/src/bookmarks.js | 126 +++++++++++++++++- 1 file changed, 124 insertions(+), 2 deletions(-) diff --git a/src/extensionsIntegrated/Bookmarks/src/bookmarks.js b/src/extensionsIntegrated/Bookmarks/src/bookmarks.js index a1b9e03b63..8bcefcf350 100644 --- a/src/extensionsIntegrated/Bookmarks/src/bookmarks.js +++ b/src/extensionsIntegrated/Bookmarks/src/bookmarks.js @@ -7,9 +7,53 @@ define(function (require, exports, module) { const GUTTER_NAME = "CodeMirror-bookmarkGutter", BOOKMARK_PRIORITY = 100; + /** + * This is where all the bookmarks will be stored + * it is an array of objects where each object will be stored firstly based on the file and then as per line no. + * the sorting is done to make sure we need not access the whole complete list when trying to move back and forth + * + * @type {[{file: {String}, line: {Number}}]} + */ + const BookmarksList = []; + // initialize the bookmark gutter Editor.registerGutter(GUTTER_NAME, BOOKMARK_PRIORITY); + /** + * This function is responsible to remove the bookmark from the bookmarks list + * + * @private + * @param {String} file - the file path + * @param {Number} line - the line number + */ + function _removeFromBookmarksList(file, line) { + for (let i = 0; i < BookmarksList.length; i++) { + if (BookmarksList[i].file === file && BookmarksList[i].line === line) { + BookmarksList.splice(i, 1); + break; + } + } + } + + /** + * This function is responsible to add the bookmark to the bookmarks list + * after adding that we also sort that first by file path and then by line number to make accessing efficient + * + * @private + * @param {String} file - the file path + * @param {Number} line - the line number + */ + function _addToBookmarksList(file, line) { + BookmarksList.push({ file: file, line: line }); + + BookmarksList.sort((a, b) => { + if (a.file === b.file) { + return a.line - b.line; + } + return a.file.localeCompare(b.file); + }); + } + /** * This function toggles a bookmark on a specific line * @@ -18,10 +62,16 @@ define(function (require, exports, module) { * @param {number} line - The line number to toggle bookmark on */ function _toggleLineBookmark(editor, line) { + const file = editor.document.file.fullPath; // this file path will be used when storing in the bookmarks list + + // remove bookmark if (Helper.hasBookmark(editor, line, GUTTER_NAME)) { editor.setGutterMarker(line, GUTTER_NAME, ""); + _removeFromBookmarksList(file, line); } else { + // add bookmark editor.setGutterMarker(line, GUTTER_NAME, Helper.createBookmarkMarker()); + _addToBookmarksList(file, line); } } @@ -43,12 +93,84 @@ define(function (require, exports, module) { }); } + /** + * This function gets executed when users click on the go to next bookmark button in the navigate menu, + * or its keyboard shortcut + * This finds the next bookmark in the current file and moves the cursor there + */ function goToNextBookmark() { - // + const editor = EditorManager.getFocusedEditor(); + if (!editor) { + return; + } + + // get the file path and line as these values are needed when searching in the bookmarks list + const currentFile = editor.document.file.fullPath; + const currentLine = editor.getCursorPos().line; + + // get all the bookmarks in current file (this is already sorted by line number) + const fileBookmarks = BookmarksList.filter((bookmark) => bookmark.file === currentFile); + if (fileBookmarks.length === 0) { + return; + } + + // find the next bookmark after current position + let nextBookmark = null; + + // find the first bookmark after current line + for (let i = 0; i < fileBookmarks.length; i++) { + if (fileBookmarks[i].line > currentLine) { + nextBookmark = fileBookmarks[i]; + break; + } + } + + // If no next bookmark found, we wrap around to get the first bookmark in this file + if (!nextBookmark && fileBookmarks.length > 0) { + nextBookmark = fileBookmarks[0]; + } + + // take the cursor to the bookmark + if (nextBookmark) { + editor.setCursorPos(nextBookmark.line, 0); + } } + /** + * This function gets executed when users click on the go to previous bookmark button in the navigate menu, + * or its keyboard shortcut + * This finds the previous bookmark in the current file and moves the cursor there + */ function goToPrevBookmark() { - // + const editor = EditorManager.getFocusedEditor(); + if (!editor) { + return; + } + + const currentFile = editor.document.file.fullPath; + const currentLine = editor.getCursorPos().line; + + const fileBookmarks = BookmarksList.filter((bookmark) => bookmark.file === currentFile); + if (fileBookmarks.length === 0) { + return; + } + + let prevBookmark = null; + + for (let i = fileBookmarks.length - 1; i >= 0; i--) { + if (fileBookmarks[i].line < currentLine) { + prevBookmark = fileBookmarks[i]; + break; + } + } + + if (!prevBookmark && fileBookmarks.length > 0) { + prevBookmark = fileBookmarks[fileBookmarks.length - 1]; + } + + if (prevBookmark) { + editor.setCursorPos(prevBookmark.line, 0); + } } exports.toggleBookmark = toggleBookmark;