From 3c0be624dd907e94e49a0401f2bda220fc2e2789 Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 19 May 2025 13:06:33 +0530 Subject: [PATCH 01/61] feat: custom snippets working prototype --- .../htmlContent/snippets-panel.html | 82 +++++++++++ .../CustomSnippets/main.js | 132 ++++++++++++++++++ .../CustomSnippets/src/UIHelper.js | 115 +++++++++++++++ .../CustomSnippets/src/codeHints.js | 120 ++++++++++++++++ .../CustomSnippets/src/driver.js | 85 +++++++++++ .../CustomSnippets/src/helper.js | 76 ++++++++++ .../CustomSnippets/src/snippetsList.js | 110 +++++++++++++++ src/extensionsIntegrated/loader.js | 1 + src/styles/Extn-CustomSnippets.less | 110 +++++++++++++++ src/styles/brackets.less | 1 + 10 files changed, 832 insertions(+) create mode 100644 src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html create mode 100644 src/extensionsIntegrated/CustomSnippets/main.js create mode 100644 src/extensionsIntegrated/CustomSnippets/src/UIHelper.js create mode 100644 src/extensionsIntegrated/CustomSnippets/src/codeHints.js create mode 100644 src/extensionsIntegrated/CustomSnippets/src/driver.js create mode 100644 src/extensionsIntegrated/CustomSnippets/src/helper.js create mode 100644 src/extensionsIntegrated/CustomSnippets/src/snippetsList.js create mode 100644 src/styles/Extn-CustomSnippets.less diff --git a/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html b/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html new file mode 100644 index 0000000000..66cdc260fc --- /dev/null +++ b/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html @@ -0,0 +1,82 @@ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+ + +
+
+
No custom snippets added yet!
+
+ +
+
+ + +
+ + + +
diff --git a/src/extensionsIntegrated/CustomSnippets/main.js b/src/extensionsIntegrated/CustomSnippets/main.js new file mode 100644 index 0000000000..9bd3e97c9b --- /dev/null +++ b/src/extensionsIntegrated/CustomSnippets/main.js @@ -0,0 +1,132 @@ +define(function (require, exports, module) { + const AppInit = require("utils/AppInit"); + const CommandManager = require("command/CommandManager"); + const Menus = require("command/Menus"); + const Commands = require("command/Commands"); + const WorkspaceManager = require("view/WorkspaceManager"); + + const Driver = require("./src/driver"); + const SnippetsList = require("./src/snippetsList"); + const CodeHints = require("./src/codeHints"); + const Helper = require("./src/helper"); + const UIHelper = require("./src/UIHelper"); + + const snippetsPanelTpl = require("text!./htmlContent/snippets-panel.html"); + // the html content of the panel will be stored in this variable + let $snippetsPanel; + + const MY_COMMAND_ID = "custom_snippets"; + const PANEL_ID = "customSnippets.panel"; + const MENU_ITEM_NAME = "Custom Snippets..."; // this name will appear as the menu item + const PANEL_MIN_SIZE = 100; // the minimum size more than which its height cannot be decreased + + // this is to store the panel reference, + // as we only need to create this once. rest of the time we can just toggle the visibility of the panel + let customSnippetsPanel; + + /** + * This function is called when the first time the custom snippets panel button is clicked + * this is responsible to create the custom snippets bottom panel and show that + * @private + */ + function _createPanel() { + customSnippetsPanel = WorkspaceManager.createBottomPanel(PANEL_ID, $snippetsPanel, PANEL_MIN_SIZE); + customSnippetsPanel.show(); + + // also register the handlers + _registerHandlers(); + } + + /** + * This function is responsible to toggle the visibility of the panel + * this is called every time (after the panel is created) to show/hide the panel + * @private + */ + function _togglePanelVisibility() { + if (customSnippetsPanel.isVisible()) { + customSnippetsPanel.hide(); + } else { + customSnippetsPanel.show(); + } + } + + /** + * This function is responsible to hide the panel + * this is called when user clicks on the 'cross' icon inside the panel itself and that is the reason, + * why we don't need to check whether the panel is visible or not + * @private + */ + function _hidePanel() { + customSnippetsPanel.hide(); + } + + /** + * This function is responsible to create the bottom panel, if not created + * if panel is already created, we just toggle its visibility + * this will be called when the custom snippets menu item is clicked from the menu bar + */ + function showCustomSnippetsPanel() { + // make sure that the panel is not created, + // if it is then we can just toggle its visibility + if (!customSnippetsPanel) { + _createPanel(); + } else { + _togglePanelVisibility(); + } + } + + /** + * This function is responsible to add the Custom Snippets menu item to the menu bar + * @private + */ + function _addToMenu() { + const menu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU); + menu.addMenuItem(MY_COMMAND_ID, "", Menus.BEFORE, Commands.FILE_EXTENSION_MANAGER); + } + + /** + * This function is responsible to register all the required handlers + * @private + */ + function _registerHandlers() { + const $closePanelBtn = $("#close-custom-snippets-panel-btn"); + const $saveCustomSnippetBtn = $("#save-custom-snippet-btn"); + const $abbrInput = $("#abbr-box"); + const $templateInput = $("#template-text-box"); + const $addSnippetBtn = $("#add-snippet-btn"); + const $addNewSnippetBtn = $("#add-new-snippet-btn"); + const $backToListMenuBtn = $("#back-to-list-menu-btn"); + + $addSnippetBtn.on("click", function () { + UIHelper.showAddSnippetMenu(); + }); + + $addNewSnippetBtn.on("click", function () { + UIHelper.showAddSnippetMenu(); + }); + + $backToListMenuBtn.on("click", function () { + UIHelper.showSnippetListMenu(); + SnippetsList.showSnippetsList(); + }); + + $closePanelBtn.on("click", function () { + _hidePanel(); + }); + + $saveCustomSnippetBtn.on("click", function () { + Driver.handleSaveBtnClick(); + }); + + $abbrInput.on("input", Helper.toggleSaveButtonDisability); + $templateInput.on("input", Helper.toggleSaveButtonDisability); + } + + AppInit.appReady(function () { + CommandManager.register(MENU_ITEM_NAME, MY_COMMAND_ID, showCustomSnippetsPanel); + $snippetsPanel = $(snippetsPanelTpl); + _addToMenu(); + CodeHints.init(); + SnippetsList.showSnippetsList(); + }); +}); diff --git a/src/extensionsIntegrated/CustomSnippets/src/UIHelper.js b/src/extensionsIntegrated/CustomSnippets/src/UIHelper.js new file mode 100644 index 0000000000..9601043f72 --- /dev/null +++ b/src/extensionsIntegrated/CustomSnippets/src/UIHelper.js @@ -0,0 +1,115 @@ +/* eslint-disable no-invalid-this */ +define(function (require, exports, module) { + /** + * This function is called when there are no available snippets to display + * this is called inside the 'showSnippetsList' function inside the snippetsList.js file + * in that case we need to show the empty snippet message + */ + function showEmptySnippetMessage() { + const $emptySnippet = $("#no-snippets-wrapper"); + const $snippetsList = $("#snippets-list-wrapper"); + + $emptySnippet.removeClass("hidden"); + $snippetsList.addClass("hidden"); + } + + /** + * This function is called when there are snippets to display. + * Note: this function just updates the hidden state from the wrapper divs + * so this just unhides the wrapper. it doesn't add the snippet items + * this is called inside the 'showSnippetsList' function inside the snippetsList.js file + */ + function showSnippetsList() { + const $emptySnippet = $("#no-snippets-wrapper"); + const $snippetsList = $("#snippets-list-wrapper"); + + $emptySnippet.addClass("hidden"); + $snippetsList.removeClass("hidden"); + } + + /** + * This function clears all the existing items inside the snippets list wrapper + * this is called everytime users switches back to the snippets list view, + * because we rebuild the snippets list menu everytime + */ + function clearSnippetsList() { + const $snippetsListWrapper = $("#snippets-list-wrapper"); + $snippetsListWrapper.empty(); + } + + /** + * This function is responsible to show the add snippet menu + * add snippet menu is the menu which allows users to create a new snippet + * this is called when user clicks on the plus button at the toolbar or the add new snippet button + */ + function showAddSnippetMenu() { + const $addSnippetMenu = $("#custom-snippets-add-new"); + const $snippetListMenu = $("#custom-snippets-list"); + const $backToListMenuBtn = $("#back-to-list-menu-btn"); + const $addNewSnippetBtn = $("#add-new-snippet-btn"); + + $addSnippetMenu.removeClass("hidden"); + $snippetListMenu.addClass("hidden"); + + $backToListMenuBtn.removeClass("hidden"); + $addNewSnippetBtn.addClass("hidden"); + } + + /** + * This function is to show the snippets list menu + * this menu is loaded by default when user opens up the panel + * it displays the list of all the snippets available + */ + function showSnippetListMenu() { + const $addSnippetMenu = $("#custom-snippets-add-new"); + const $snippetListMenu = $("#custom-snippets-list"); + const $backToListMenuBtn = $("#back-to-list-menu-btn"); + const $addNewSnippetBtn = $("#add-new-snippet-btn"); + + $addSnippetMenu.addClass("hidden"); + $snippetListMenu.removeClass("hidden"); + + $backToListMenuBtn.addClass("hidden"); + $addNewSnippetBtn.removeClass("hidden"); + } + + /** + * Shows an error message when a snippet with the same abbreviation already exists + * and user is trying to add a new one + * @param {string} abbreviation - The abbreviation that's duplicated + */ + function showDuplicateAbbreviationError(abbreviation) { + // just make sure that the error message is not already displaying + if ($("#abbreviation-error-message").length === 0) { + const $errorMessage = $("
") + .attr("id", "abbreviation-error-message") + .addClass("error-message") + .text(`A snippet with abbreviation "${abbreviation}" already exists.`); + + $("#abbr-box-wrapper").append($errorMessage); + + // highlight the abbreviation input with error + $("#abbr-box").addClass("error-input"); + + // automatically remove it after 5 seconds + setTimeout(function () { + $("#abbreviation-error-message").fadeOut(function () { + $(this).remove(); + }); + $("#abbr-box").removeClass("error-input"); + }, 5000); + + $("#abbr-box").one("input", function () { + $("#abbreviation-error-message").remove(); + $(this).removeClass("error-input"); + }); + } + } + + exports.showEmptySnippetMessage = showEmptySnippetMessage; + exports.showSnippetsList = showSnippetsList; + exports.clearSnippetsList = clearSnippetsList; + exports.showAddSnippetMenu = showAddSnippetMenu; + exports.showSnippetListMenu = showSnippetListMenu; + exports.showDuplicateAbbreviationError = showDuplicateAbbreviationError; +}); diff --git a/src/extensionsIntegrated/CustomSnippets/src/codeHints.js b/src/extensionsIntegrated/CustomSnippets/src/codeHints.js new file mode 100644 index 0000000000..5f5c8bbd2c --- /dev/null +++ b/src/extensionsIntegrated/CustomSnippets/src/codeHints.js @@ -0,0 +1,120 @@ +define(function (require, exports, module) { + const CodeHintManager = require("editor/CodeHintManager"); + + const Driver = require("./driver"); + const Helper = require("./helper"); + + /** + * Constructor + */ + function SnippetHints() {} + + /** + * Determines whether any custom snippets hints are available + * + * @param {Editor} editor - the editor instance + * @param {string} implicitChar - null if the hinting request was explicit, + * or a single character that represents the last insertion and that indicates an implicit + * hinting request. + * + * @return {boolean} - true if hints are available else false + */ + SnippetHints.prototype.hasHints = function (editor, implicitChar) { + this.editor = editor; + + // we don't need to show any hints if user explicitly asks for it + if (implicitChar === null) { + return false; + } + + // extract the actual word, because getWordBeforeCursor returns an object + const word = Driver.getWordBeforeCursor().word; + + const matchedItem = Driver.SnippetHintsList.find((snippet) => snippet.abbreviation === word); + + if (matchedItem) { + // If fileExtension is "all", we need to show the hint in all the files + if (matchedItem.fileExtension.toLowerCase() === "all") { + return true; + } + + // get current file extension + const filePath = editor.document?.file?.fullPath; + if (filePath) { + const fileExtension = filePath.substring(filePath.lastIndexOf(".")).toLowerCase(); + + // Check if snippet's fileExtension includes current file extension + // Snippet's fileExtension is expected to be comma-separated list like ".js, .html" + const supportedExtensions = matchedItem.fileExtension + .toLowerCase() + .split(",") + .map((ext) => ext.trim()); + + // this returns true if current file's extension is in the list of supported extensions + return supportedExtensions.some((ext) => ext === fileExtension); + } + } + + return false; + }; + + /** + * Returns a list of all the available snippet hints + * this only gets executed if 'hasHints' returned true + * + * @returns {Object} - this object is what is displayed by the code hint manager. it is in the format + * { + * hints: , + * selectInitial: true, + * handleWideResults: false + * } + * 1. hints: array of hints, each element is a jquery + * 2. selectInital: true because we want that the first hint should be selected by default + * 3. handleWideResults: false as we don't want the result string to stretch width of display + */ + SnippetHints.prototype.getHints = function (implicitChar) { + const result = []; + + const word = Driver.getWordBeforeCursor().word; + const matchedItem = Driver.SnippetHintsList.find((snippet) => snippet.abbreviation === word); + + if (matchedItem) { + const $hintItem = Helper.createHintItem(matchedItem.abbreviation); + result.push($hintItem); + return { + hints: result, + selectInitial: true, + handleWideResults: false + }; + } + + return null; + }; + + /** + * Inserts the given hint into the current editor context. + * @param {string} hint - unused variable because we fetch the abbreviation again + */ + SnippetHints.prototype.insertHint = function (hint) { + const cursor = this.editor.getCursorPos(); + const word = Driver.getWordBeforeCursor(); + const start = { line: word.line, ch: word.ch }; + const end = cursor; + + const matchedItem = Driver.SnippetHintsList.find((snippet) => snippet.abbreviation === word.word); + this.editor.document.replaceRange(matchedItem.templateText, start, end); + + return false; + }; + + /** + * the initialization function + * called inside the appInit inside the main.js + */ + function init() { + const snippetHints = new SnippetHints(); + CodeHintManager.registerHintProvider(snippetHints, ["all"], 10); + } + + exports.init = init; +}); diff --git a/src/extensionsIntegrated/CustomSnippets/src/driver.js b/src/extensionsIntegrated/CustomSnippets/src/driver.js new file mode 100644 index 0000000000..66bfe94257 --- /dev/null +++ b/src/extensionsIntegrated/CustomSnippets/src/driver.js @@ -0,0 +1,85 @@ +define(function (require, exports, module) { + const EditorManager = require("editor/EditorManager"); + + const Helper = require("./helper"); + const UIHelper = require("./UIHelper"); + + /** + * This is an array of objects. this will store the list of all the snippets + * it is an array of objects stored in the format + * [{ + * abbreviation: 'clg', + * description: 'console log shortcut', + * templateText: 'console.log()', + * fileExtension: '.js, .css' + * }] + */ + const SnippetHintsList = []; + + + /** + * This function handles the save button click handler + * it does all the chores like fetching the data from the required fields, adding it to snippet list and all that + */ + function handleSaveBtnClick() { + const snippetData = Helper.getSnippetData(); + if (shouldAddSnippetToList(snippetData)) { + SnippetHintsList.push(snippetData); + Helper.clearAllInputFields(); + Helper.toggleSaveButtonDisability(); + } else { + UIHelper.showDuplicateAbbreviationError(snippetData.abbreviation); + } + } + + /** + * This function is to check whether we can add the new snippet to the snippets list + * because we don't want to add the new snippet if a snippet already exists with the same abbreviation + * @param {object} snippetData - the snippet data object + * @returns {boolean} - true if we can add the new snippet to the list otherwise false + */ + function shouldAddSnippetToList(snippetData) { + const matchedItem = SnippetHintsList.find((snippet) => snippet.abbreviation === snippetData.abbreviation); + + if (matchedItem) { + return false; + } + return true; + } + + /** + * This function is responsible to get the word before the cursor + * this is required to check whether something matches the snippet list + * @returns {object} - an object in the format {word: 'pluto', line: 10, ch: 2} + */ + function getWordBeforeCursor() { + const editor = EditorManager.getActiveEditor(); + if (!editor) { + return; + } + + const pos = editor.getCursorPos(); + let word = ""; // this will store the actual word before the cursor + let i = pos.ch - 1; // index of the char right before the cursor + const breakWordAt = ["", " "]; // we need to break the loop when we encounter this char's + + while (i >= 0) { + const char = editor.getCharacterAtPosition({ line: pos.line, ch: i }); + if (breakWordAt.includes(char)) { + break; + } + word = char + word; + i--; + } + + return { + word: word, + line: pos.line, + ch: i + }; + } + + exports.SnippetHintsList = SnippetHintsList; + exports.getWordBeforeCursor = getWordBeforeCursor; + exports.handleSaveBtnClick = handleSaveBtnClick; +}); diff --git a/src/extensionsIntegrated/CustomSnippets/src/helper.js b/src/extensionsIntegrated/CustomSnippets/src/helper.js new file mode 100644 index 0000000000..9a5b95428f --- /dev/null +++ b/src/extensionsIntegrated/CustomSnippets/src/helper.js @@ -0,0 +1,76 @@ +define(function (require, exports, module) { + /** + * This function is responsible to get the snippet data from all the required input fields + * it is called when the save button is clicked + * @private + * @returns {object} - a snippet object + */ + function getSnippetData() { + // get the values from all the input fields + const abbreviation = $("#abbr-box").val().trim(); + const description = $("#desc-box").val().trim() || "No Description"; + const templateText = $("#template-text-box").val().trim(); + const fileExtension = $("#file-extn-box").val().trim() || "all"; + + return { + abbreviation: abbreviation, + description: description, + templateText: templateText, + fileExtension: fileExtension + }; + } + + /** + * This function is responsible to enable/disable the save button + * when all the required input fields are not filled up as required then we need to disable the save button + * otherwise we enable it + * this is called inside the '_registerHandlers' function in the main.js file + */ + function toggleSaveButtonDisability() { + // these are the required fields + const $abbrInput = $("#abbr-box"); + const $templateInput = $("#template-text-box"); + + const $saveBtn = $("#save-custom-snippet-btn").find("button"); + + // make sure that the required fields has some value + const hasAbbr = $abbrInput.val().trim().length > 0; + const hasTemplate = $templateInput.val().trim().length > 0; + $saveBtn.prop("disabled", !(hasAbbr && hasTemplate)); + } + + /** + * this function is responsible to create a hint item + * this is needed because along with the abbr in the code hint, we also want to show an icon saying 'Snippet', + * to give users an idea that this hint is coming from snippets + * this function is called inside the 'getHints' method in the codeHints.js file + * @param {String} abbr - the abbreviation text that is to be displayed in the code hint + * @returns {JQuery} - the jquery item that has the abbr text and the Snippet icon + */ + function createHintItem(abbr) { + var $hint = $("").addClass("emmet-hint").text(abbr); + + // style in brackets_patterns_override.less file + // using the same style as the emmet one + let $icon = $(`Snippet`); + $hint.append($icon); + + return $hint; + } + + /** + * This function is responsible to clear all the input fields. + * when the save button is clicked we get the data from the input fields and then clear all of them + */ + function clearAllInputFields() { + $("#abbr-box").val(""); + $("#desc-box").val(""); + $("#template-text-box").val(""); + $("#file-extn-box").val(""); + } + + exports.toggleSaveButtonDisability = toggleSaveButtonDisability; + exports.createHintItem = createHintItem; + exports.clearAllInputFields = clearAllInputFields; + exports.getSnippetData = getSnippetData; +}); diff --git a/src/extensionsIntegrated/CustomSnippets/src/snippetsList.js b/src/extensionsIntegrated/CustomSnippets/src/snippetsList.js new file mode 100644 index 0000000000..95b8aa31b6 --- /dev/null +++ b/src/extensionsIntegrated/CustomSnippets/src/snippetsList.js @@ -0,0 +1,110 @@ + +/* + * This file handles the display and management of the snippets list that is shown in the UI + * Note: when there are no snippets present, a message like no snippets are added yet is shown. refer to the html file + */ + +/* eslint-disable no-invalid-this */ +define(function (require, exports, module) { + const Driver = require("./driver"); + const UIHelper = require("./UIHelper"); + + /** + * This function is responsible to create a snippet item + * refer to html file for the structure of the snippet item + * @private + * @param {object} snippetItem + */ + function _createSnippetItem(snippetItem) { + // the main snippet item container, + // all the items like abbr, description and all that will be appended into this + const $snippetItem = $("
").attr("data-abbr", snippetItem.abbreviation).attr("id", "snippet-item"); + + const $snippetAbbr = $("
").text(snippetItem.abbreviation).attr("id", "snippet-abbr"); + + const $snippetTemplate = $("
").text(snippetItem.templateText).attr("id", "snippet-template"); + + const $snippetDescription = $("
") + .text(snippetItem.description || "No description") + .attr("id", "snippet-description"); + + const $snippetFiles = $("
") + .text(snippetItem.fileExtension || "all") + .attr("id", "snippet-files"); + + const $deleteSnippet = $("
") + .html(``) + .attr("id", "delete-snippet-btn") + .addClass("delete-snippet-btn"); + + $snippetItem.append($snippetAbbr, $snippetTemplate, $snippetDescription, $snippetFiles, $deleteSnippet); + $snippetItem.data("snippet", snippetItem); // store full object. this is needed for deletion purposes + + // finally when the snippet item is ready, we append it to the snippets-list-wrapper + $("#snippets-list-wrapper").append($snippetItem); + + // here we register the delete button click handler + _registerHandlers(); + } + + /** + * This function is called when the user clicks on the custom snippets button from the file menu + * this also gets called when user clicks on the 'back' button to move back to the snippets list menu + * refer to '_registerHandlers' function inside the main.js file + */ + function showSnippetsList() { + UIHelper.clearSnippetsList(); // to clear existing snippets list, as we'll rebuild it + const snippetList = Driver.SnippetHintsList; // gets the list of the snippets, this is an array of objects + + // if there are no snippets available, we show the message that no snippets are present + // refer to html file + if (snippetList.length === 0) { + UIHelper.showEmptySnippetMessage(); + } else { + UIHelper.showSnippetsList(); // to remove the hidden class from the snippets list wrapper + + // rebuild the snippets menu + for (let i = 0; i < snippetList.length; i++) { + const snippetItem = snippetList[i]; + _createSnippetItem(snippetItem); + } + } + } + + /** + * This function is responsible to delete the snippet for which delete button was clicked + */ + function deleteSnippet() { + // get the element + const $snippetItem = $(this).closest("#snippet-item"); + const snippetItem = $snippetItem.data("snippet"); // this gives the actual object with all the keys and vals + + const index = Driver.SnippetHintsList.findIndex((s) => s.abbreviation === snippetItem.abbreviation); + + if (index !== -1) { + Driver.SnippetHintsList.splice(index, 1); // removes it from the actual array + $snippetItem.remove(); // remove from the dom + + // if snippetHintsList is now empty we need to show the empty snippet message + if (Driver.SnippetHintsList.length === 0) { + UIHelper.showEmptySnippetMessage(); + } + } + } + + /** + * This function is responsible to register the delete snippet button handler + * @private + */ + function _registerHandlers() { + const $deleteSnippetBtn = $(".delete-snippet-btn"); + + $deleteSnippetBtn.off("click"); + $deleteSnippetBtn.on("click", function () { + deleteSnippet.call(this); + }); + } + + exports.showSnippetsList = showSnippetsList; + exports.deleteSnippet = deleteSnippet; +}); diff --git a/src/extensionsIntegrated/loader.js b/src/extensionsIntegrated/loader.js index 2da9f10f68..776a81dd0f 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("./CustomSnippets/main"); }); diff --git a/src/styles/Extn-CustomSnippets.less b/src/styles/Extn-CustomSnippets.less new file mode 100644 index 0000000000..6325b75754 --- /dev/null +++ b/src/styles/Extn-CustomSnippets.less @@ -0,0 +1,110 @@ +#custom-snippets-panel { + background-color: @bc-panel-bg; + position: relative; + + .dark & { + background-color: @dark-bc-panel-bg; + } +} + +#custom-snippets-toolbar { + background-color: @bc-panel-bg-promoted; + padding: 5px 8px; + + .dark & { + background-color: @dark-bc-panel-bg-promoted; + } +} + + +#custom-snippets-toolbar .custom-snippets-btn-group.right button { + height: 26px; + border-radius: 4px; + font-size: 12px; + background-color: @bc-btn-bg; + color: @bc-text; + border: 1px solid @bc-btn-border; + box-shadow: inset 0 1px @bc-highlight; + + .dark & { + background-color: @dark-bc-btn-bg; + color: @dark-bc-text; + border: 1px solid @dark-bc-btn-border; + box-shadow: inset 0 1px @dark-bc-highlight; + } +} + +#close-custom-snippets-panel-btn { + position: absolute; + top: 9px; + right: 7px; +} + +#close-custom-snippets-panel-btn button { + // remove all the default styling + background: transparent; + border: none; + outline: none; + + font-size: 13px; + opacity: 0.7; + color: @bc-text; + + .dark & { + color: @dark-bc-text; + } +} + +#close-custom-panel-btn button:hover { + opacity: 0.9; +} + +#custom-snippets-list.hidden { + display: none; +} + +#custom-snippets-add-new.hidden { + display: none; +} + +.custom-snippet-btn.hidden { + display: none; +} + +#no-snippets-wrapper.hidden { + display: none; +} + +#snippets-list-wrapper.hidden { + display: none; +} + +#snippet-item { + background-color: burlywood; + display: flex; + color: black; + justify-content: space-between; + padding: 4px 10px; + margin-bottom: 10px; +} + +#abbreviation-error-message { + color: #f44336; + font-size: 12px; + margin-top: 3px; + margin-left: 5px; + padding: 3px 5px; + background-color: rgba(244, 67, 54, 0.1); + border-radius: 3px; + animation: fadeIn 0.3s ease; +} + +.error-input { + border-color: #f44336 !important; + box-shadow: 0 0 3px rgba(244, 67, 54, 0.5) !important; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 502ece88fb..5150753c88 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-CustomSnippets.less"; @import "UserProfile.less"; /* Overall layout */ From 207b2c677dcbd249c6c8ce61d25af2b80b52d423 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 24 May 2025 21:43:19 +0530 Subject: [PATCH 02/61] feat: integrated custom snippets with code hint manager --- src/editor/CodeHintManager.js | 42 +++++++ .../CustomSnippets/src/driver.js | 116 ++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/src/editor/CodeHintManager.js b/src/editor/CodeHintManager.js index 74823f5e87..c022d01dc5 100644 --- a/src/editor/CodeHintManager.js +++ b/src/editor/CodeHintManager.js @@ -266,6 +266,18 @@ define(function (require, exports, module) { codeHintsEnabled = true, codeHintOpened = false; + // custom snippets integration + // this is to check whether user has set any custom snippets as we need to show it at the first + let customSnippetsDriver = null; + + // load custom snippets driver + try { + customSnippetsDriver = require("../extensionsIntegrated/CustomSnippets/src/driver"); + } catch (e) { + // if unable to load we just set it to null to prevent other parts of the code from breaking + customSnippetsDriver = null; + } + PreferencesManager.definePreference("showCodeHints", "boolean", true, { description: Strings.DESCRIPTION_SHOW_CODE_HINTS }); @@ -461,6 +473,11 @@ define(function (require, exports, module) { _endSession(); _beginSession(previousEditor); } else if (response.hasOwnProperty("hints")) { // a synchronous response + // prepend custom snippets to the response + if(customSnippetsDriver && customSnippetsDriver.SnippetHintsList) { + response = customSnippetsDriver.prependCustomSnippets(response, sessionEditor); + } + if (hintList.isOpen()) { // the session is open hintList.update(response); @@ -477,6 +494,10 @@ define(function (require, exports, module) { if (!hintList) { return; } + // prepend custom snippets to the response + if (customSnippetsDriver && customSnippetsDriver.SnippetHintsList) { + response = customSnippetsDriver.prependCustomSnippets(response, sessionEditor); + } if (hintList.isOpen()) { // the session is open @@ -546,6 +567,27 @@ define(function (require, exports, module) { } }); hintList.onSelect(function (hint) { + // check if the hint is a custom snippet + if (hint && hint.jquery && hint.attr("data-isCustomSnippet")) { + // handle custom snippet insertion + const abbreviation = hint.attr("data-val"); + if (customSnippetsDriver && customSnippetsDriver.SnippetHintsList) { + const matchedSnippet = customSnippetsDriver.SnippetHintsList.find( + (snippet) => snippet.abbreviation === abbreviation + ); + if (matchedSnippet) { + // replace the typed abbreviation with the template text + const wordInfo = customSnippetsDriver.getWordBeforeCursor(); + const start = { line: wordInfo.line, ch: wordInfo.ch + 1 }; + const end = sessionEditor.getCursorPos(); + sessionEditor.document.replaceRange(matchedSnippet.templateText, start, end); + _endSession(); + return; + } + } + } + + // Regular hint provider handling var restart = sessionProvider.insertHint(hint), previousEditor = sessionEditor; _endSession(); diff --git a/src/extensionsIntegrated/CustomSnippets/src/driver.js b/src/extensionsIntegrated/CustomSnippets/src/driver.js index 66bfe94257..af3599b6bb 100644 --- a/src/extensionsIntegrated/CustomSnippets/src/driver.js +++ b/src/extensionsIntegrated/CustomSnippets/src/driver.js @@ -79,7 +79,123 @@ define(function (require, exports, module) { }; } + /** + * This function is called from CodeHintManager.js because of how Phoenix handles hinting. + * + * Here’s the problem: + * When a provider returns true for `hasHints`, it locks in that provider for the entire hinting session + * until it returns false. If the user types something like ‘clg’, and the default JavaScript provider + * is already active, the CodeHintManager won’t even ask the custom snippets provider if it has hints. + * + * To fix that, what we did is that when hints are shown we just ask the custom snippets if it has any relevant hint + * If it does, this function prepends them to the existing list of hints. + * + * @param {Object} response - The original hint response from the current provider + * @param {Editor} editor - The current editor instance + * @return {Object} - The modified response with custom snippets added to the front + */ + function prependCustomSnippets(response, editor) { + if (!response || !response.hints) { + return response; + } + + try { + const wordInfo = getWordBeforeCursor(); + if (!wordInfo || !wordInfo.word) { + return response; + } + + const needle = wordInfo.word.toLowerCase(); + + // get the current file extension + const filePath = editor.document?.file?.fullPath; + let fileExtension = null; + if (filePath) { + fileExtension = filePath.substring(filePath.lastIndexOf(".")).toLowerCase(); + } + + // first, check if there's at least one exact match - only show snippets if there is + // the logic is: + // lets say we have 2 snippets 'clg' ang 'clgi' + // now if user types 'cl' in the editor then we don't show the snippets + // but when user types 'clg' then we show the clg snippet and we also check if there are any more snippets starting with this and we show all of them + const hasExactMatch = SnippetHintsList.some((snippet) => { + if (snippet.abbreviation.toLowerCase() === needle) { + if (snippet.fileExtension.toLowerCase() === "all") { + return true; + } + + // check if current file extension is supported + if (fileExtension) { + const supportedExtensions = snippet.fileExtension + .toLowerCase() + .split(",") + .map((ext) => ext.trim()); + return supportedExtensions.some((ext) => ext === fileExtension); + } + } + return false; + }); + + if (!hasExactMatch) { + return response; + } + + // now find all matching snippets (including prefix matches) + const matchingSnippets = SnippetHintsList.filter((snippet) => { + if (snippet.abbreviation.toLowerCase().startsWith(needle)) { + if (snippet.fileExtension.toLowerCase() === "all") { + return true; + } + + if (fileExtension) { + const supportedExtensions = snippet.fileExtension + .toLowerCase() + .split(",") + .map((ext) => ext.trim()); + return supportedExtensions.some((ext) => ext === fileExtension); + } + } + return false; + }); + + // if we have matching snippets, prepend them to the hints + if (matchingSnippets.length > 0) { + const customSnippetHints = matchingSnippets.map((snippet) => { + const $snippetHintObj = $("") + .addClass("brackets-snippets-hints brackets-hints") + .attr("data-val", snippet.abbreviation) + .attr("data-isCustomSnippet", true); + + // add the abbreviation text + $snippetHintObj.append(snippet.abbreviation); + + // add custom snippet icon (using emmet class as we need it exactly how Emmet does it) + let $icon = $(`Snippet`); + $snippetHintObj.append($icon); + + return $snippetHintObj; + }); + + // create a new response with custom snippets at the top + const newResponse = $.extend({}, response); + if (Array.isArray(response.hints)) { + newResponse.hints = customSnippetHints.concat(response.hints); + } else { + newResponse.hints = customSnippetHints.concat([response.hints]); + } + + return newResponse; + } + } catch (e) { + console.log("Error checking custom snippets:", e); + } + + return response; + } + exports.SnippetHintsList = SnippetHintsList; exports.getWordBeforeCursor = getWordBeforeCursor; exports.handleSaveBtnClick = handleSaveBtnClick; + exports.prependCustomSnippets = prependCustomSnippets; }); From d983f29ebcfc92237ea2c2a8da17739051f1b7b3 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 24 May 2025 22:14:03 +0530 Subject: [PATCH 03/61] fix: avoid duplicate snippet hints --- src/extensionsIntegrated/CustomSnippets/main.js | 4 ++-- .../CustomSnippets/src/driver.js | 17 ++++++++++++++++- .../src/{codeHints.js => snippetCodeHints.js} | 0 3 files changed, 18 insertions(+), 3 deletions(-) rename src/extensionsIntegrated/CustomSnippets/src/{codeHints.js => snippetCodeHints.js} (100%) diff --git a/src/extensionsIntegrated/CustomSnippets/main.js b/src/extensionsIntegrated/CustomSnippets/main.js index 9bd3e97c9b..ee4221ac08 100644 --- a/src/extensionsIntegrated/CustomSnippets/main.js +++ b/src/extensionsIntegrated/CustomSnippets/main.js @@ -7,7 +7,7 @@ define(function (require, exports, module) { const Driver = require("./src/driver"); const SnippetsList = require("./src/snippetsList"); - const CodeHints = require("./src/codeHints"); + const SnippetCodeHints = require("./src/snippetCodeHints"); const Helper = require("./src/helper"); const UIHelper = require("./src/UIHelper"); @@ -126,7 +126,7 @@ define(function (require, exports, module) { CommandManager.register(MENU_ITEM_NAME, MY_COMMAND_ID, showCustomSnippetsPanel); $snippetsPanel = $(snippetsPanelTpl); _addToMenu(); - CodeHints.init(); + SnippetCodeHints.init(); SnippetsList.showSnippetsList(); }); }); diff --git a/src/extensionsIntegrated/CustomSnippets/src/driver.js b/src/extensionsIntegrated/CustomSnippets/src/driver.js index af3599b6bb..e8f2321887 100644 --- a/src/extensionsIntegrated/CustomSnippets/src/driver.js +++ b/src/extensionsIntegrated/CustomSnippets/src/driver.js @@ -100,6 +100,20 @@ define(function (require, exports, module) { } try { + // check if the response already contains custom snippet hints to avoid duplicates + // this is needed because sometimes when there are no default hints present then the + // SnippetCodeHints.js shows some hints, so we don't want to duplicate hints + if (Array.isArray(response.hints) && response.hints.length > 0) { + const hasCustomSnippets = response.hints.some(hint => { + return (hint && hint.hasClass && hint.hasClass('emmet-hint')) || + (hint && hint.attr && hint.attr('data-isCustomSnippet')); + }); + + if (hasCustomSnippets) { + return response; // already has custom snippets, don't need to add again + } + } + const wordInfo = getWordBeforeCursor(); if (!wordInfo || !wordInfo.word) { return response; @@ -118,7 +132,8 @@ define(function (require, exports, module) { // the logic is: // lets say we have 2 snippets 'clg' ang 'clgi' // now if user types 'cl' in the editor then we don't show the snippets - // but when user types 'clg' then we show the clg snippet and we also check if there are any more snippets starting with this and we show all of them + // but when user types 'clg' then we show the clg snippet + // and we also check if there are any more snippets starting with this and we show all of them const hasExactMatch = SnippetHintsList.some((snippet) => { if (snippet.abbreviation.toLowerCase() === needle) { if (snippet.fileExtension.toLowerCase() === "all") { diff --git a/src/extensionsIntegrated/CustomSnippets/src/codeHints.js b/src/extensionsIntegrated/CustomSnippets/src/snippetCodeHints.js similarity index 100% rename from src/extensionsIntegrated/CustomSnippets/src/codeHints.js rename to src/extensionsIntegrated/CustomSnippets/src/snippetCodeHints.js From bda71a5e0350c06a445d376c86f620dfcb4ee7b0 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 25 May 2025 00:20:57 +0530 Subject: [PATCH 04/61] fix: show snippet icon at the side of the hint properly --- .../CustomSnippets/src/driver.js | 14 +------------- .../CustomSnippets/src/helper.js | 8 ++++++-- src/styles/brackets_patterns_override.less | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/extensionsIntegrated/CustomSnippets/src/driver.js b/src/extensionsIntegrated/CustomSnippets/src/driver.js index e8f2321887..f21d1e3448 100644 --- a/src/extensionsIntegrated/CustomSnippets/src/driver.js +++ b/src/extensionsIntegrated/CustomSnippets/src/driver.js @@ -177,19 +177,7 @@ define(function (require, exports, module) { // if we have matching snippets, prepend them to the hints if (matchingSnippets.length > 0) { const customSnippetHints = matchingSnippets.map((snippet) => { - const $snippetHintObj = $("") - .addClass("brackets-snippets-hints brackets-hints") - .attr("data-val", snippet.abbreviation) - .attr("data-isCustomSnippet", true); - - // add the abbreviation text - $snippetHintObj.append(snippet.abbreviation); - - // add custom snippet icon (using emmet class as we need it exactly how Emmet does it) - let $icon = $(`Snippet`); - $snippetHintObj.append($icon); - - return $snippetHintObj; + return Helper.createHintItem(snippet.abbreviation); }); // create a new response with custom snippets at the top diff --git a/src/extensionsIntegrated/CustomSnippets/src/helper.js b/src/extensionsIntegrated/CustomSnippets/src/helper.js index 9a5b95428f..a45693b4d5 100644 --- a/src/extensionsIntegrated/CustomSnippets/src/helper.js +++ b/src/extensionsIntegrated/CustomSnippets/src/helper.js @@ -48,11 +48,15 @@ define(function (require, exports, module) { * @returns {JQuery} - the jquery item that has the abbr text and the Snippet icon */ function createHintItem(abbr) { - var $hint = $("").addClass("emmet-hint").text(abbr); + var $hint = $("") + .addClass("brackets-css-hints brackets-hints") + .attr("data-val", abbr) + .attr("data-isCustomSnippet", true) + .text(abbr); // style in brackets_patterns_override.less file // using the same style as the emmet one - let $icon = $(`Snippet`); + let $icon = $(`Snippet`); $hint.append($icon); return $hint; diff --git a/src/styles/brackets_patterns_override.less b/src/styles/brackets_patterns_override.less index 5b99c5e46c..1687b7602b 100644 --- a/src/styles/brackets_patterns_override.less +++ b/src/styles/brackets_patterns_override.less @@ -756,6 +756,23 @@ a:focus { white-space: nowrap; vertical-align: top; line-height: inherit; + +.custom-snippet-code-hint { + visibility: hidden; +} + +.codehint-menu .dropdown-menu li .highlight .custom-snippet-code-hint { + visibility: visible; + position: absolute; + right: 0; + margin-top: -2px; + font-size: 0.85em !important; + font-weight: @font-weight-semibold; + letter-spacing: 0.3px; + color: @css-codehint-icon !important; + .dark& { + color: @dark-css-codehint-icon !important; + } } #codehint-desc { From 3685255991dadb9b8026c929e4815c86674b6362 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 25 May 2025 04:02:41 +0530 Subject: [PATCH 05/61] feat: save snippets to prefs so that it stays even on reloads --- src/editor/CodeHintManager.js | 13 +++--- .../CustomSnippets/main.js | 5 ++- .../CustomSnippets/src/driver.js | 27 ++++------- .../CustomSnippets/src/global.js | 15 +++++++ .../CustomSnippets/src/snippetCodeHints.js | 7 +-- .../CustomSnippets/src/snippetsList.js | 14 +++--- .../CustomSnippets/src/snippetsState.js | 45 +++++++++++++++++++ 7 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 src/extensionsIntegrated/CustomSnippets/src/global.js create mode 100644 src/extensionsIntegrated/CustomSnippets/src/snippetsState.js diff --git a/src/editor/CodeHintManager.js b/src/editor/CodeHintManager.js index c022d01dc5..7e95ecdcd8 100644 --- a/src/editor/CodeHintManager.js +++ b/src/editor/CodeHintManager.js @@ -269,13 +269,16 @@ define(function (require, exports, module) { // custom snippets integration // this is to check whether user has set any custom snippets as we need to show it at the first let customSnippetsDriver = null; + let customSnippetsGlobal = null; - // load custom snippets driver + // load custom snippets driver and global try { customSnippetsDriver = require("../extensionsIntegrated/CustomSnippets/src/driver"); + customSnippetsGlobal = require("../extensionsIntegrated/CustomSnippets/src/global"); } catch (e) { // if unable to load we just set it to null to prevent other parts of the code from breaking customSnippetsDriver = null; + customSnippetsGlobal = null; } PreferencesManager.definePreference("showCodeHints", "boolean", true, { @@ -474,7 +477,7 @@ define(function (require, exports, module) { _beginSession(previousEditor); } else if (response.hasOwnProperty("hints")) { // a synchronous response // prepend custom snippets to the response - if(customSnippetsDriver && customSnippetsDriver.SnippetHintsList) { + if(customSnippetsDriver && customSnippetsGlobal && customSnippetsGlobal.SnippetHintsList) { response = customSnippetsDriver.prependCustomSnippets(response, sessionEditor); } @@ -495,7 +498,7 @@ define(function (require, exports, module) { return; } // prepend custom snippets to the response - if (customSnippetsDriver && customSnippetsDriver.SnippetHintsList) { + if (customSnippetsDriver && customSnippetsGlobal && customSnippetsGlobal.SnippetHintsList) { response = customSnippetsDriver.prependCustomSnippets(response, sessionEditor); } @@ -571,8 +574,8 @@ define(function (require, exports, module) { if (hint && hint.jquery && hint.attr("data-isCustomSnippet")) { // handle custom snippet insertion const abbreviation = hint.attr("data-val"); - if (customSnippetsDriver && customSnippetsDriver.SnippetHintsList) { - const matchedSnippet = customSnippetsDriver.SnippetHintsList.find( + if (customSnippetsDriver && customSnippetsGlobal && customSnippetsGlobal.SnippetHintsList) { + const matchedSnippet = customSnippetsGlobal.SnippetHintsList.find( (snippet) => snippet.abbreviation === abbreviation ); if (matchedSnippet) { diff --git a/src/extensionsIntegrated/CustomSnippets/main.js b/src/extensionsIntegrated/CustomSnippets/main.js index ee4221ac08..e8192975ee 100644 --- a/src/extensionsIntegrated/CustomSnippets/main.js +++ b/src/extensionsIntegrated/CustomSnippets/main.js @@ -10,6 +10,7 @@ define(function (require, exports, module) { const SnippetCodeHints = require("./src/snippetCodeHints"); const Helper = require("./src/helper"); const UIHelper = require("./src/UIHelper"); + const SnippetsState = require("./src/snippetsState"); const snippetsPanelTpl = require("text!./htmlContent/snippets-panel.html"); // the html content of the panel will be stored in this variable @@ -35,6 +36,7 @@ define(function (require, exports, module) { // also register the handlers _registerHandlers(); + SnippetsList.showSnippetsList(); // to show the snippets list in the snippets panel } /** @@ -47,6 +49,7 @@ define(function (require, exports, module) { customSnippetsPanel.hide(); } else { customSnippetsPanel.show(); + SnippetsList.showSnippetsList(); // we just remake the snippets list UI to make sure it is always on point } } @@ -127,6 +130,6 @@ define(function (require, exports, module) { $snippetsPanel = $(snippetsPanelTpl); _addToMenu(); SnippetCodeHints.init(); - SnippetsList.showSnippetsList(); + SnippetsState.loadSnippetsFromState(); }); }); diff --git a/src/extensionsIntegrated/CustomSnippets/src/driver.js b/src/extensionsIntegrated/CustomSnippets/src/driver.js index f21d1e3448..2fc18be539 100644 --- a/src/extensionsIntegrated/CustomSnippets/src/driver.js +++ b/src/extensionsIntegrated/CustomSnippets/src/driver.js @@ -1,21 +1,10 @@ define(function (require, exports, module) { const EditorManager = require("editor/EditorManager"); + const Global = require("./global"); const Helper = require("./helper"); const UIHelper = require("./UIHelper"); - - /** - * This is an array of objects. this will store the list of all the snippets - * it is an array of objects stored in the format - * [{ - * abbreviation: 'clg', - * description: 'console log shortcut', - * templateText: 'console.log()', - * fileExtension: '.js, .css' - * }] - */ - const SnippetHintsList = []; - + const SnippetsState = require("./snippetsState"); /** * This function handles the save button click handler @@ -24,9 +13,10 @@ define(function (require, exports, module) { function handleSaveBtnClick() { const snippetData = Helper.getSnippetData(); if (shouldAddSnippetToList(snippetData)) { - SnippetHintsList.push(snippetData); + Global.SnippetHintsList.push(snippetData); Helper.clearAllInputFields(); Helper.toggleSaveButtonDisability(); + SnippetsState.saveSnippetsToState(); } else { UIHelper.showDuplicateAbbreviationError(snippetData.abbreviation); } @@ -39,7 +29,9 @@ define(function (require, exports, module) { * @returns {boolean} - true if we can add the new snippet to the list otherwise false */ function shouldAddSnippetToList(snippetData) { - const matchedItem = SnippetHintsList.find((snippet) => snippet.abbreviation === snippetData.abbreviation); + const matchedItem = Global.SnippetHintsList.find( + (snippet) => snippet.abbreviation === snippetData.abbreviation + ); if (matchedItem) { return false; @@ -134,7 +126,7 @@ define(function (require, exports, module) { // now if user types 'cl' in the editor then we don't show the snippets // but when user types 'clg' then we show the clg snippet // and we also check if there are any more snippets starting with this and we show all of them - const hasExactMatch = SnippetHintsList.some((snippet) => { + const hasExactMatch = Global.SnippetHintsList.some((snippet) => { if (snippet.abbreviation.toLowerCase() === needle) { if (snippet.fileExtension.toLowerCase() === "all") { return true; @@ -157,7 +149,7 @@ define(function (require, exports, module) { } // now find all matching snippets (including prefix matches) - const matchingSnippets = SnippetHintsList.filter((snippet) => { + const matchingSnippets = Global.SnippetHintsList.filter((snippet) => { if (snippet.abbreviation.toLowerCase().startsWith(needle)) { if (snippet.fileExtension.toLowerCase() === "all") { return true; @@ -197,7 +189,6 @@ define(function (require, exports, module) { return response; } - exports.SnippetHintsList = SnippetHintsList; exports.getWordBeforeCursor = getWordBeforeCursor; exports.handleSaveBtnClick = handleSaveBtnClick; exports.prependCustomSnippets = prependCustomSnippets; diff --git a/src/extensionsIntegrated/CustomSnippets/src/global.js b/src/extensionsIntegrated/CustomSnippets/src/global.js new file mode 100644 index 0000000000..aa1bf593f2 --- /dev/null +++ b/src/extensionsIntegrated/CustomSnippets/src/global.js @@ -0,0 +1,15 @@ +define(function (require, exports, module) { + /** + * This is an array of objects. this will store the list of all the snippets + * it is an array of objects stored in the format + * [{ + * abbreviation: 'clg', + * description: 'console log shortcut', + * templateText: 'console.log()', + * fileExtension: '.js, .css' + * }] + */ + const SnippetHintsList = []; + + exports.SnippetHintsList = SnippetHintsList; +}); diff --git a/src/extensionsIntegrated/CustomSnippets/src/snippetCodeHints.js b/src/extensionsIntegrated/CustomSnippets/src/snippetCodeHints.js index 5f5c8bbd2c..e6159acf45 100644 --- a/src/extensionsIntegrated/CustomSnippets/src/snippetCodeHints.js +++ b/src/extensionsIntegrated/CustomSnippets/src/snippetCodeHints.js @@ -1,6 +1,7 @@ define(function (require, exports, module) { const CodeHintManager = require("editor/CodeHintManager"); + const Global = require("./global"); const Driver = require("./driver"); const Helper = require("./helper"); @@ -30,7 +31,7 @@ define(function (require, exports, module) { // extract the actual word, because getWordBeforeCursor returns an object const word = Driver.getWordBeforeCursor().word; - const matchedItem = Driver.SnippetHintsList.find((snippet) => snippet.abbreviation === word); + const matchedItem = Global.SnippetHintsList.find((snippet) => snippet.abbreviation === word); if (matchedItem) { // If fileExtension is "all", we need to show the hint in all the files @@ -76,7 +77,7 @@ define(function (require, exports, module) { const result = []; const word = Driver.getWordBeforeCursor().word; - const matchedItem = Driver.SnippetHintsList.find((snippet) => snippet.abbreviation === word); + const matchedItem = Global.SnippetHintsList.find((snippet) => snippet.abbreviation === word); if (matchedItem) { const $hintItem = Helper.createHintItem(matchedItem.abbreviation); @@ -101,7 +102,7 @@ define(function (require, exports, module) { const start = { line: word.line, ch: word.ch }; const end = cursor; - const matchedItem = Driver.SnippetHintsList.find((snippet) => snippet.abbreviation === word.word); + const matchedItem = Global.SnippetHintsList.find((snippet) => snippet.abbreviation === word.word); this.editor.document.replaceRange(matchedItem.templateText, start, end); return false; diff --git a/src/extensionsIntegrated/CustomSnippets/src/snippetsList.js b/src/extensionsIntegrated/CustomSnippets/src/snippetsList.js index 95b8aa31b6..5cd12b9200 100644 --- a/src/extensionsIntegrated/CustomSnippets/src/snippetsList.js +++ b/src/extensionsIntegrated/CustomSnippets/src/snippetsList.js @@ -6,7 +6,8 @@ /* eslint-disable no-invalid-this */ define(function (require, exports, module) { - const Driver = require("./driver"); + const Global = require("./global"); + const SnippetsState = require("./snippetsState"); const UIHelper = require("./UIHelper"); /** @@ -54,7 +55,7 @@ define(function (require, exports, module) { */ function showSnippetsList() { UIHelper.clearSnippetsList(); // to clear existing snippets list, as we'll rebuild it - const snippetList = Driver.SnippetHintsList; // gets the list of the snippets, this is an array of objects + const snippetList = Global.SnippetHintsList; // gets the list of the snippets, this is an array of objects // if there are no snippets available, we show the message that no snippets are present // refer to html file @@ -79,14 +80,17 @@ define(function (require, exports, module) { const $snippetItem = $(this).closest("#snippet-item"); const snippetItem = $snippetItem.data("snippet"); // this gives the actual object with all the keys and vals - const index = Driver.SnippetHintsList.findIndex((s) => s.abbreviation === snippetItem.abbreviation); + const index = Global.SnippetHintsList.findIndex((s) => s.abbreviation === snippetItem.abbreviation); if (index !== -1) { - Driver.SnippetHintsList.splice(index, 1); // removes it from the actual array + Global.SnippetHintsList.splice(index, 1); // removes it from the actual array $snippetItem.remove(); // remove from the dom + // save to preferences after deleting snippet + SnippetsState.saveSnippetsToState(); + // if snippetHintsList is now empty we need to show the empty snippet message - if (Driver.SnippetHintsList.length === 0) { + if (Global.SnippetHintsList.length === 0) { UIHelper.showEmptySnippetMessage(); } } diff --git a/src/extensionsIntegrated/CustomSnippets/src/snippetsState.js b/src/extensionsIntegrated/CustomSnippets/src/snippetsState.js new file mode 100644 index 0000000000..31ec813d38 --- /dev/null +++ b/src/extensionsIntegrated/CustomSnippets/src/snippetsState.js @@ -0,0 +1,45 @@ +define(function (require, exports, module) { + const PreferencesManager = require("preferences/PreferencesManager"); + + const Global = require("./global"); + + // create extension preferences + const prefs = PreferencesManager.getExtensionPrefs("CustomSnippets"); + + // define preference for storing snippets + prefs.definePreference("snippetsList", "array", [], { + description: "List of custom code snippets" + }); + + /** + * Load snippets from preferences + * This is called on startup to restore previously saved snippets + */ + function loadSnippetsFromState() { + try { + const savedSnippets = prefs.get("snippetsList"); + if (Array.isArray(savedSnippets)) { + // clear existing snippets and load from saved state + Global.SnippetHintsList.length = 0; + Global.SnippetHintsList.push(...savedSnippets); + } + } catch (e) { + console.error("something went wrong when trying to load custom snippets from preferences:", e); + } + } + + /** + * Save snippets to preferences + * This is called whenever snippets are modified + */ + function saveSnippetsToState() { + try { + prefs.set("snippetsList", [...Global.SnippetHintsList]); + } catch (e) { + console.error("something went wrong when saving custom snippets to preferences:", e); + } + } + + exports.loadSnippetsFromState = loadSnippetsFromState; + exports.saveSnippetsToState = saveSnippetsToState; +}); From 96213189af99c5aebafdb4683d9332a5b774ea28 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 25 May 2025 04:07:42 +0530 Subject: [PATCH 06/61] fix: remove popup auto-complete suggestions from appearing when input boxes are focused --- .../CustomSnippets/htmlContent/snippets-panel.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html b/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html index 66cdc260fc..1af4b83bfa 100644 --- a/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html +++ b/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html @@ -57,22 +57,22 @@