Skip to content
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5a1a26b
fix: profile button text not centralized
devvaannsh Jul 9, 2025
4252e76
feat: add cancel button in add snippet panel
devvaannsh Jul 9, 2025
ae7dbc4
fix: save button always remain disabled
devvaannsh Jul 9, 2025
4004cfd
feat: auto update API docs
devvaannsh Jul 10, 2025
e5d2653
fix: increase panel min size so that the save, cancel buttons are alw…
devvaannsh Jul 10, 2025
078502c
fix: update text in the no snippets present panel
devvaannsh Jul 10, 2025
f4db68f
fix: back button text not properly centralized
devvaannsh Jul 10, 2025
4236acd
feat: add check state on menu item when panel is open
devvaannsh Jul 10, 2025
f985b8b
fix: alignment of plus icon in the add snippet button
devvaannsh Jul 10, 2025
c619e32
fix: back button alignment
devvaannsh Jul 10, 2025
1ff336d
fix: move the content a bit right to maintain hierarchy structure
devvaannsh Jul 10, 2025
e7d59a3
fix: made close button styling consistent to other panels close button
devvaannsh Jul 10, 2025
a5932f5
fix: make filter input consistent to other panels filter input
devvaannsh Jul 10, 2025
5ad4a12
fix: set min width to the template textare to keep it consistent to o…
devvaannsh Jul 10, 2025
41bc07d
feat: disable reset button in edit panel when nothing to reset
devvaannsh Jul 10, 2025
11552b6
fix: ui layout issues in snippets list header
devvaannsh Jul 12, 2025
90a8a51
fix: font weight issue for snippet list
devvaannsh Jul 12, 2025
99c819a
feat: allow users to write file extension in any format
devvaannsh Jul 12, 2025
f941f01
fix: mutliple commas appearing when parsing failed
devvaannsh Jul 12, 2025
4148d56
fix: remove trailing dot from result
devvaannsh Jul 12, 2025
3191f02
feat: save snippets to file storage instead of prefs manager
devvaannsh Jul 12, 2025
2a12fd3
feat: replace reset button with cancel button in the edit snippets panel
devvaannsh Jul 12, 2025
ae58526
fix: prevent processing file extension where two dots are there to su…
devvaannsh Jul 12, 2025
589b6d1
refactor: replace template with Click to edit template and so on
devvaannsh Jul 12, 2025
8068629
refactor: store snippets directly in file instead of preferencesBase
devvaannsh Jul 13, 2025
206daed
fix: snippets 3 dots in the menu item was not consistent with the 3 d…
devvaannsh Jul 13, 2025
77a2391
fix: remove redundant then blocks
devvaannsh Jul 14, 2025
8c502ab
fix: remove unnecessary pretty printing when saving file
devvaannsh Jul 14, 2025
2947c2d
fix: directly return jsPromise for writeText instead of creating a ne…
devvaannsh Jul 14, 2025
d4491e1
feat: report unexpected errors to bugsnag
devvaannsh Jul 14, 2025
63d1a7b
feat: move all strings to strings.js file
devvaannsh Jul 14, 2025
81da0d5
fix: strings in html not appearing
devvaannsh Jul 14, 2025
1350ded
feat: add metrics to track snippets usage
devvaannsh Jul 14, 2025
10ecd71
fix: remove redundant console error as logger is already present
devvaannsh Jul 14, 2025
f280ab1
fix: replace reject with resolve as reject might break boot
devvaannsh Jul 14, 2025
3dc6e1d
fix: instead of hard-coding file names use language manager
devvaannsh Jul 14, 2025
76d6aae
fix: use stringUtils to replace strings
devvaannsh Jul 14, 2025
818ef69
fix: change cursor to pointer when close button for snippets panel is…
devvaannsh Jul 14, 2025
9c0b25a
fix: return a "file" type if the snippet is enabled only for specific…
devvaannsh Jul 15, 2025
be92f9a
fix: optimize the snippet code hints to prevent checking for hints on…
devvaannsh Jul 15, 2025
d518c53
fix: improve readability of isSnippetSupportedInLanguageContext function
devvaannsh Jul 15, 2025
862131d
fix: remove redundant try catch block from getCurrentLanguageContext …
devvaannsh Jul 15, 2025
d27e2b2
refactor: replace ?. with x && y
devvaannsh Jul 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/API-Reference/editor/CodeHintManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ insertHintOnTab Preference.
* [.registerHintProvider(provider, languageIds, priority)](#module_CodeHintManager..registerHintProvider)
* [.hasValidExclusion(exclusion, textAfterCursor)](#module_CodeHintManager..hasValidExclusion) ⇒ <code>boolean</code>
* [.isOpen()](#module_CodeHintManager..isOpen) ⇒ <code>boolean</code>
* [.showHintsAtTop(handler)](#module_CodeHintManager..showHintsAtTop)
* [.clearHintsAtTop()](#module_CodeHintManager..clearHintsAtTop)

<a name="module_CodeHintManager..registerHintProvider"></a>

Expand Down Expand Up @@ -263,3 +265,21 @@ Test if a hint popup is open.

**Kind**: inner method of [<code>CodeHintManager</code>](#module_CodeHintManager)
**Returns**: <code>boolean</code> - - true if the hints are open, false otherwise.
<a name="module_CodeHintManager..showHintsAtTop"></a>

### CodeHintManager.showHintsAtTop(handler)
Register a handler to show hints at the top of the hint list.
This API allows extensions to add their own hints at the top of the standard hint list.

**Kind**: inner method of [<code>CodeHintManager</code>](#module_CodeHintManager)

| Param | Type | Description |
| --- | --- | --- |
| handler | <code>Object</code> | A hint provider object with standard methods: - hasHints: function(editor, implicitChar) - returns true if hints are available - getHints: function(editor, implicitChar) - returns hint response object with hints array - insertHint: function(hint) - handles hint insertion, returns true if handled |

<a name="module_CodeHintManager..clearHintsAtTop"></a>

### CodeHintManager.clearHintsAtTop()
Unregister the hints at top handler.

**Kind**: inner method of [<code>CodeHintManager</code>](#module_CodeHintManager)
3 changes: 2 additions & 1 deletion src/extensionsIntegrated/CustomSnippets/UIHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
/* eslint-disable no-invalid-this */
define(function (require, exports, module) {
const Global = require("./global");
const Strings = require("strings");

/**
* this is a generic function to show error messages for input fields
Expand Down Expand Up @@ -187,7 +188,7 @@ define(function (require, exports, module) {
const wrapperId = isEditForm ? "edit-abbr-box-wrapper" : "abbr-box-wrapper";
const errorId = isEditForm ? "edit-abbreviation-duplicate-error" : "abbreviation-duplicate-error";

showError(inputId, wrapperId, `A snippet with abbreviation "${abbreviation}" already exists.`, errorId);
showError(inputId, wrapperId, Strings.CUSTOM_SNIPPETS_DUPLICATE_ERROR.replace("{0}", abbreviation), errorId);
Comment thread
devvaannsh marked this conversation as resolved.
Outdated
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
define(function (require, exports, module) {
const CodeHintManager = require("editor/CodeHintManager");
const EditorManager = require("editor/EditorManager");
const Metrics = require("utils/Metrics");

const Global = require("./global");
const Driver = require("./driver");
Expand Down Expand Up @@ -118,6 +119,10 @@ define(function (require, exports, module) {
const editor = EditorManager.getFocusedEditor();

if (editor) {
// to track the usage metrics
const fileCategory = Helper.categorizeFileExtensionForMetrics(matchedSnippet.fileExtension);
Metrics.countEvent(Metrics.EVENT_TYPE.EDITOR, "snipt", `use.${fileCategory}`);

// replace the typed abbreviation with the template text using cursor manager
const wordInfo = Driver.getWordBeforeCursor();
const start = { line: wordInfo.line, ch: wordInfo.ch + 1 };
Expand Down
43 changes: 32 additions & 11 deletions src/extensionsIntegrated/CustomSnippets/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
*
*/

/* global logger */
define(function (require, exports, module) {
const EditorManager = require("editor/EditorManager");
const Metrics = require("utils/Metrics");

const Global = require("./global");
const Helper = require("./helper");
Expand All @@ -45,7 +47,17 @@ define(function (require, exports, module) {
Global.SnippetHintsList.push(snippetData);
Helper.clearAllInputFields();
Helper.toggleSaveButtonDisability();
SnippetsState.saveSnippetsToState();

// snippet creating metrics
const fileCategory = Helper.categorizeFileExtensionForMetrics(snippetData.fileExtension);
Metrics.countEvent(Metrics.EVENT_TYPE.EDITOR, "snipt", `add.${fileCategory}`);

// save to file storage
SnippetsState.saveSnippetsToState()
.catch(function (error) {
console.error("failed to save custom snippet correctly:", error);
Comment thread
devvaannsh marked this conversation as resolved.
Outdated
logger.reportError(error, "Custom Snippets: failed to save new snippet to file storage");
});

// we need to move back to snippets list view after a snippet is saved
UIHelper.showSnippetListMenu();
Expand Down Expand Up @@ -87,7 +99,13 @@ define(function (require, exports, module) {
// update the snippet in the list
if (snippetIndex !== -1) {
Global.SnippetHintsList[snippetIndex] = editedData;
SnippetsState.saveSnippetsToState();

// save to file storage
SnippetsState.saveSnippetsToState()
.catch(function (error) {
console.error("failed to save custom snippet correctly:", error);
Comment thread
devvaannsh marked this conversation as resolved.
Outdated
Comment thread
devvaannsh marked this conversation as resolved.
Outdated
logger.reportError(error, "Custom Snippets: failed to save edited snippet to file storage");
});

// clear the stored data
$editView.removeData("originalSnippet");
Expand All @@ -100,19 +118,24 @@ define(function (require, exports, module) {
}

/**
* This function handles the reset button click for editing a snippet
* It restores the original snippet data in the edit form
* This function is responsible to handle the cancel button click in the edit-snippet panel
* this resets the format to the last saved values and then moves back to the snippets-list panel
*/
function handleResetBtnClick() {
function handleCancelEditBtnClick() {
const $editView = $("#custom-snippets-edit");
const originalSnippet = $editView.data("originalSnippet");

if (originalSnippet) {
// restore original data in the form
// restore original data in the form to reset any changes
Helper.populateEditForm(originalSnippet);
// update save button state
Helper.toggleEditSaveButtonDisability();
}

$editView.removeData("originalSnippet");
$editView.removeData("snippetIndex");

// navigate back to snippets list
UIHelper.showSnippetListMenu();
SnippetsList.showSnippetsList();
}

/**
Expand Down Expand Up @@ -164,10 +187,8 @@ define(function (require, exports, module) {
};
}



exports.getWordBeforeCursor = getWordBeforeCursor;
exports.handleSaveBtnClick = handleSaveBtnClick;
exports.handleEditSaveBtnClick = handleEditSaveBtnClick;
exports.handleResetBtnClick = handleResetBtnClick;
exports.handleCancelEditBtnClick = handleCancelEditBtnClick;
});
117 changes: 102 additions & 15 deletions src/extensionsIntegrated/CustomSnippets/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ define(function (require, exports, module) {
const StringMatch = require("utils/StringMatch");
const Global = require("./global");
const UIHelper = require("./UIHelper");
const Strings = require("strings");

// list of all the navigation and function keys that are allowed inside the input fields
const ALLOWED_NAVIGATION_KEYS = [
Expand Down Expand Up @@ -95,6 +96,67 @@ define(function (require, exports, module) {
return languageMap[languageId] || languageId;
}

/**
* This function is to make sure file extensions are properly formatted with leading dots
* because user may provide values in not very consistent manner, we need to handle all those cases
* For ex: what we expect: `.js, .html, .css`
* what user may provide: `js, html, css` or: `js html css` etc
*
* This function processes file extensions in various formats and ensures they:
* - Have a leading dot (if not empty or "all")
* - Are properly separated with commas and spaces
* - Don't contain empty or standalone dots
* - No consecutive commas
*
* @param {string} extension - The file extension(s) to process
* @returns {string} - The properly formatted file extension(s)
*/
function processFileExtensionInput(extension) {
if (!extension || extension === "all") {
return extension;
}

// Step 1: normalize the input by converting spaces to commas if no commas exist
if (extension.includes(" ")) {
extension = extension.replace(/\s+/g, ",");
}

let result = "";

// Step 2: process comma-separated extensions FIRST (before dot-separated)
// this prevents issues with inputs like ".js,.html,." or ".js,,.html"
if (extension.includes(",")) {
result = extension
.split(",")
.map((ext) => {
ext = ext.trim();
// skip all the standalone dots or empty entries
if (ext === "." || ext === "") {
return "";
}
// Add leading dot if missing
return ext.startsWith(".") ? ext : "." + ext;
})
.filter((ext) => ext !== "") // Remove empty entries
.join(", ");
} else {
// Step 3: Handle single extension
if (extension === ".") {
result = ""; // remove standalone dot
} else {
// Add leading dot if missing
result = extension.startsWith(".") ? extension : "." + extension;
}
}

// this is just the final safeguard to remove any consecutive commas and clean up spacing
result = result.replace(/,\s*,+/g, ",").replace(/,\s*$/, "").replace(/^\s*,/, "").trim();
// remove trailing dots (like .css. -> .css)
result = result.endsWith('.') ? result.slice(0, -1) : result;

return result;
}

/**
* This function is responsible to get the snippet data from all the required input fields
* it is called when the save button is clicked
Expand All @@ -108,11 +170,14 @@ define(function (require, exports, module) {
const templateText = $("#template-text-box").val().trim();
const fileExtension = $("#file-extn-box").val().trim();

// process the file extension so that we can get the value in the required format
const processedFileExtension = processFileExtensionInput(fileExtension);

return {
abbreviation: abbreviation,
description: description || "", // allow empty description
templateText: templateText,
fileExtension: fileExtension || "all" // default to "all" if empty
fileExtension: processedFileExtension || "all" // default to "all" if empty
};
}

Expand All @@ -128,7 +193,7 @@ define(function (require, exports, module) {
const $abbrInput = $("#abbr-box");
const $templateInput = $("#template-text-box");

const $saveBtn = $("#save-custom-snippet-btn button");
const $saveBtn = $("#save-custom-snippet-btn");

// make sure that the required fields has some value
const hasAbbr = $abbrInput.val().trim().length > 0;
Expand Down Expand Up @@ -325,7 +390,7 @@ define(function (require, exports, module) {
}

// the codehints related style is written in brackets_patterns_override.less file
let $icon = $(`<a href="#" class="custom-snippet-code-hint" style="text-decoration: none">Snippet</a>`);
let $icon = $(`<a href="#" class="custom-snippet-code-hint" style="text-decoration: none">${Strings.CUSTOM_SNIPPETS_HINT_LABEL}</a>`);
$hint.append($icon);

if (description && description.trim() !== "") {
Expand Down Expand Up @@ -374,11 +439,14 @@ define(function (require, exports, module) {
const templateText = $("#edit-template-text-box").val().trim();
const fileExtension = $("#edit-file-extn-box").val().trim();

// process the file extension so that we can get the value in the required format
const processedFileExtension = processFileExtensionInput(fileExtension);

return {
abbreviation: abbreviation,
description: description || "", // allow empty description
templateText: templateText,
fileExtension: fileExtension || "all" // default to "all" if empty
fileExtension: processedFileExtension || "all" // default to "all" if empty
};
}

Expand Down Expand Up @@ -553,7 +621,7 @@ define(function (require, exports, module) {
const wrapperId = isEditForm ? "edit-abbr-box-wrapper" : "abbr-box-wrapper";
const errorId = isEditForm ? "edit-abbreviation-space-error" : "abbreviation-space-error";

UIHelper.showError(inputId, wrapperId, "Space is not accepted as a valid abbreviation character.", errorId);
UIHelper.showError(inputId, wrapperId, Strings.CUSTOM_SNIPPETS_SPACE_ERROR, errorId);
return;
}

Expand All @@ -571,7 +639,7 @@ define(function (require, exports, module) {
const wrapperId = isEditForm ? "edit-abbr-box-wrapper" : "abbr-box-wrapper";
const errorId = isEditForm ? "edit-abbreviation-length-error" : "abbreviation-length-error";

UIHelper.showError(inputId, wrapperId, "Abbreviation cannot be more than 30 characters.", errorId);
UIHelper.showError(inputId, wrapperId, Strings.CUSTOM_SNIPPETS_ABBR_LENGTH_ERROR, errorId);
}
}

Expand Down Expand Up @@ -600,7 +668,7 @@ define(function (require, exports, module) {
const wrapperId = isEditForm ? "edit-desc-box-wrapper" : "desc-box-wrapper";
const errorId = isEditForm ? "edit-description-length-error" : "description-length-error";

UIHelper.showError(inputId, wrapperId, "Description cannot be more than 80 characters.", errorId);
UIHelper.showError(inputId, wrapperId, Strings.CUSTOM_SNIPPETS_DESC_LENGTH_ERROR, errorId);
}
}

Expand Down Expand Up @@ -663,15 +731,10 @@ define(function (require, exports, module) {
// Prioritize length error over space error if both occurred
if (wasTruncated) {
const errorId = isEditForm ? "edit-abbreviation-paste-length-error" : "abbreviation-paste-length-error";
UIHelper.showError(inputId, wrapperId, "Abbreviation cannot be more than 30 characters.", errorId);
UIHelper.showError(inputId, wrapperId, Strings.CUSTOM_SNIPPETS_ABBR_LENGTH_ERROR, errorId);
} else if (hadSpaces) {
const errorId = isEditForm ? "edit-abbreviation-paste-space-error" : "abbreviation-paste-space-error";
UIHelper.showError(
inputId,
wrapperId,
"Space is not accepted as a valid abbreviation character.",
errorId
);
UIHelper.showError(inputId, wrapperId, Strings.CUSTOM_SNIPPETS_SPACE_ERROR, errorId);
}
}

Expand Down Expand Up @@ -739,7 +802,7 @@ define(function (require, exports, module) {
const wrapperId = isEditForm ? "edit-desc-box-wrapper" : "desc-box-wrapper";
const errorId = isEditForm ? "edit-description-paste-length-error" : "description-paste-length-error";

UIHelper.showError(inputId, wrapperId, "Description cannot be more than 80 characters.", errorId);
UIHelper.showError(inputId, wrapperId, Strings.CUSTOM_SNIPPETS_DESC_LENGTH_ERROR, errorId);
}

// Determine which save button to toggle based on input field
Expand All @@ -750,6 +813,29 @@ define(function (require, exports, module) {
}
}

/**
* Categorize file extension for metrics tracking
* @param {string} fileExtension - The file extension from snippet
* @returns {string} - Categorized extension for metrics
*/
function categorizeFileExtensionForMetrics(fileExtension) {
if (!fileExtension || fileExtension === "all") {
return "all";
}

const ext = fileExtension.toLowerCase();
Comment thread
devvaannsh marked this conversation as resolved.
Outdated
if (ext.includes(".js") || ext.includes(".ts")) {
return "js";
}
if (ext.includes("html") || ext.includes("htm")) {
return "html";
}
if (ext.includes("css") || ext.includes("less") || ext.includes("scss") || ext.includes("sass")) {
return "css";
}
return "other";
}

exports.toggleSaveButtonDisability = toggleSaveButtonDisability;
exports.createHintItem = createHintItem;
exports.clearAllInputFields = clearAllInputFields;
Expand All @@ -769,6 +855,7 @@ define(function (require, exports, module) {
exports.populateEditForm = populateEditForm;
exports.getEditSnippetData = getEditSnippetData;
exports.toggleEditSaveButtonDisability = toggleEditSaveButtonDisability;
exports.categorizeFileExtensionForMetrics = categorizeFileExtensionForMetrics;
exports.clearEditInputFields = clearEditInputFields;
exports.handleTextareaTabKey = handleTextareaTabKey;
exports.validateAbbrInput = validateAbbrInput;
Expand Down
Loading
Loading