Skip to content

Commit bf1e2be

Browse files
devvaannshabose
authored andcommitted
feat: add showHintsAtTop api inside codehintmanager
1 parent 7507955 commit bf1e2be

File tree

4 files changed

+206
-132
lines changed

4 files changed

+206
-132
lines changed

src/editor/CodeHintManager.js

Lines changed: 51 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -266,23 +266,8 @@ define(function (require, exports, module) {
266266
codeHintsEnabled = true,
267267
codeHintOpened = false;
268268

269-
// custom snippets integration
270-
// this is to check whether user has set any custom snippets as we need to show it at the first
271-
let customSnippetsDriver = null;
272-
let customSnippetsGlobal = null;
273-
let customSnippetsCursorManager = null;
274-
275-
// load custom snippets driver and global
276-
try {
277-
customSnippetsDriver = require("../extensionsIntegrated/CustomSnippets/driver");
278-
customSnippetsGlobal = require("../extensionsIntegrated/CustomSnippets/global");
279-
customSnippetsCursorManager = require("../extensionsIntegrated/CustomSnippets/snippetCursorManager");
280-
} catch (e) {
281-
// if unable to load we just set it to null to prevent other parts of the code from breaking
282-
customSnippetsDriver = null;
283-
customSnippetsGlobal = null;
284-
customSnippetsCursorManager = null;
285-
}
269+
// API for extensions to show hints at the top
270+
let hintsAtTopHandler = null;
286271

287272
PreferencesManager.definePreference("showCodeHints", "boolean", true, {
288273
description: Strings.DESCRIPTION_SHOW_CODE_HINTS
@@ -479,9 +464,13 @@ define(function (require, exports, module) {
479464
_endSession();
480465
_beginSession(previousEditor);
481466
} else if (response.hasOwnProperty("hints")) { // a synchronous response
482-
// prepend custom snippets to the response
483-
if(customSnippetsDriver && customSnippetsGlobal && customSnippetsGlobal.SnippetHintsList) {
484-
response = customSnippetsDriver.prependCustomSnippets(response, sessionEditor);
467+
// allow extensions to modify the response by adding hints at the top
468+
if (hintsAtTopHandler) {
469+
if (typeof hintsAtTopHandler === 'function') {
470+
response = hintsAtTopHandler(response, sessionEditor);
471+
} else if (hintsAtTopHandler.prepend) {
472+
response = hintsAtTopHandler.prepend(response, sessionEditor);
473+
}
485474
}
486475

487476
if (hintList.isOpen()) {
@@ -500,9 +489,13 @@ define(function (require, exports, module) {
500489
if (!hintList) {
501490
return;
502491
}
503-
// prepend custom snippets to the response
504-
if (customSnippetsDriver && customSnippetsGlobal && customSnippetsGlobal.SnippetHintsList) {
505-
response = customSnippetsDriver.prependCustomSnippets(response, sessionEditor);
492+
// allow extensions to modify the response by adding hints at the top
493+
if (hintsAtTopHandler) {
494+
if (typeof hintsAtTopHandler === 'function') {
495+
hints = hintsAtTopHandler(hints, sessionEditor);
496+
} else if (hintsAtTopHandler.prepend) {
497+
hints = hintsAtTopHandler.prepend(hints, sessionEditor);
498+
}
506499
}
507500

508501
if (hintList.isOpen()) {
@@ -573,43 +566,20 @@ define(function (require, exports, module) {
573566
}
574567
});
575568
hintList.onSelect(function (hint) {
576-
// check if the hint is a custom snippet
577-
if (hint && hint.jquery && hint.attr("data-isCustomSnippet")) {
578-
// handle custom snippet insertion
579-
const abbreviation = hint.attr("data-val");
580-
if (customSnippetsDriver && customSnippetsGlobal && customSnippetsGlobal.SnippetHintsList) {
581-
const matchedSnippet = customSnippetsGlobal.SnippetHintsList.find(
582-
(snippet) => snippet.abbreviation === abbreviation
583-
);
584-
if (matchedSnippet) {
585-
// replace the typed abbreviation with the template text using cursor manager
586-
const wordInfo = customSnippetsDriver.getWordBeforeCursor();
587-
const start = { line: wordInfo.line, ch: wordInfo.ch + 1 };
588-
const end = sessionEditor.getCursorPos();
589-
590-
if (customSnippetsCursorManager) {
591-
customSnippetsCursorManager.insertSnippetWithTabStops(
592-
sessionEditor,
593-
matchedSnippet.templateText,
594-
start,
595-
end
596-
);
597-
} else {
598-
// insert snippet just by replacing range if cursor manager is not available
599-
sessionEditor.document.replaceRange(matchedSnippet.templateText, start, end);
600-
}
601-
_endSession();
602-
return;
603-
}
604-
}
569+
// allow extensions to handle special hint selections
570+
var handled = false;
571+
if (hintsAtTopHandler && hintsAtTopHandler.handleHintSelection) {
572+
handled = hintsAtTopHandler.handleHintSelection(hint, sessionEditor, _endSession);
605573
}
606574

607-
// Regular hint provider handling
608-
var restart = sessionProvider.insertHint(hint),
609-
previousEditor = sessionEditor;
610-
_endSession();
611-
if (restart) {
612-
_beginSession(previousEditor);
575+
if (!handled) {
576+
// Regular hint provider handling
577+
var restart = sessionProvider.insertHint(hint),
578+
previousEditor = sessionEditor;
579+
_endSession();
580+
if (restart) {
581+
_beginSession(previousEditor);
582+
}
613583
}
614584
});
615585
hintList.onClose(()=>{
@@ -756,6 +726,27 @@ define(function (require, exports, module) {
756726
return (hintList && hintList.isOpen());
757727
}
758728

729+
/**
730+
* Register a handler to modify hints at the top of the hint list.
731+
* This API allows extensions to prepend their own hints to the standard hint list.
732+
*
733+
* @param {Function|Object} handler - Either a function that takes (response, editor) and returns
734+
* a modified response, or an object with:
735+
* - prepend: function(response, editor) - modify the hint response
736+
* - handleHintSelection: function(hint, editor, endSession) - handle hint selection
737+
* returns true if handled, false otherwise
738+
*/
739+
function showHintsAtTop(handler) {
740+
hintsAtTopHandler = handler;
741+
}
742+
743+
/**
744+
* Unregister the hints at top handler.
745+
*/
746+
function clearHintsAtTop() {
747+
hintsAtTopHandler = null;
748+
}
749+
759750
/**
760751
* Explicitly start a new session. If we have an existing session,
761752
* then close the current one and restart a new one.
@@ -839,6 +830,8 @@ define(function (require, exports, module) {
839830
exports.isOpen = isOpen;
840831
exports.registerHintProvider = registerHintProvider;
841832
exports.hasValidExclusion = hasValidExclusion;
833+
exports.showHintsAtTop = showHintsAtTop;
834+
exports.clearHintsAtTop = clearHintsAtTop;
842835

843836
exports.SELECTION_REASON = CodeHintListModule._SELECTION_REASON;
844837
});
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* GNU AGPL-3.0 License
3+
*
4+
* Copyright (c) 2021 - present core.ai . All rights reserved.
5+
*
6+
* This program is free software: you can redistribute it and/or modify it
7+
* under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
14+
* for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
18+
*
19+
*/
20+
21+
define(function (require, exports, module) {
22+
const CodeHintManager = require("editor/CodeHintManager");
23+
24+
const Global = require("./global");
25+
const Driver = require("./driver");
26+
const SnippetCursorManager = require("./snippetCursorManager");
27+
28+
/**
29+
* Handler object for the CodeHintManager API
30+
* This provides the interface for showing custom snippets at the top of hint lists
31+
*/
32+
const CustomSnippetsHandler = {
33+
/**
34+
* Prepend custom snippets to the hint response
35+
* @param {Object} response - The original hint response from the current provider
36+
* @param {Editor} editor - The current editor instance
37+
* @return {Object} - The modified response with custom snippets added to the front
38+
*/
39+
prepend: function (response, editor) {
40+
if (!response || !response.hints) {
41+
return response;
42+
}
43+
44+
try {
45+
// check if the response already contains custom snippet hints to avoid duplicates
46+
// this is needed because sometimes when there are no default hints present then the
47+
// SnippetCodeHints.js shows some hints, so we don't want to duplicate hints
48+
if (Array.isArray(response.hints) && response.hints.length > 0) {
49+
const hasCustomSnippets = response.hints.some((hint) => {
50+
return (
51+
(hint && hint.hasClass && hint.hasClass("emmet-hint")) ||
52+
(hint && hint.attr && hint.attr("data-isCustomSnippet"))
53+
);
54+
});
55+
56+
if (hasCustomSnippets) {
57+
return response; // already has custom snippets, don't need to add again
58+
}
59+
}
60+
61+
const wordInfo = Driver.getWordBeforeCursor();
62+
if (!wordInfo || !wordInfo.word) {
63+
return response;
64+
}
65+
66+
const needle = wordInfo.word.toLowerCase();
67+
const Helper = require("./helper");
68+
69+
// check if there's at least one exact match using language context detection
70+
if (!Helper.hasExactMatchingSnippet(needle, editor)) {
71+
return response;
72+
}
73+
74+
// get all matching snippets using language context detection
75+
const matchingSnippets = Helper.getMatchingSnippets(needle, editor);
76+
77+
// if we have matching snippets, prepend them to the hints
78+
if (matchingSnippets.length > 0) {
79+
const customSnippetHints = matchingSnippets.map((snippet) => {
80+
return Helper.createHintItem(snippet.abbreviation, needle, snippet.description);
81+
});
82+
83+
// create a new response with custom snippets at the top
84+
const newResponse = $.extend({}, response);
85+
if (Array.isArray(response.hints)) {
86+
newResponse.hints = customSnippetHints.concat(response.hints);
87+
} else {
88+
newResponse.hints = customSnippetHints.concat([response.hints]);
89+
}
90+
91+
return newResponse;
92+
}
93+
} catch (e) {
94+
console.log("Error checking custom snippets:", e);
95+
}
96+
97+
return response;
98+
},
99+
100+
/**
101+
* Handle selection of custom snippet hints
102+
* @param {jQuery} hint - The selected hint element
103+
* @param {Editor} editor - The current editor instance
104+
* @param {Function} endSession - Function to end the current hint session
105+
* @return {boolean} - true if handled, false otherwise
106+
*/
107+
handleHintSelection: function (hint, editor, endSession) {
108+
// check if the hint is a custom snippet
109+
if (hint && hint.jquery && hint.attr("data-isCustomSnippet")) {
110+
// handle custom snippet insertion
111+
const abbreviation = hint.attr("data-val");
112+
if (Global.SnippetHintsList) {
113+
const matchedSnippet = Global.SnippetHintsList.find(
114+
(snippet) => snippet.abbreviation === abbreviation
115+
);
116+
if (matchedSnippet) {
117+
// replace the typed abbreviation with the template text using cursor manager
118+
const wordInfo = Driver.getWordBeforeCursor();
119+
const start = { line: wordInfo.line, ch: wordInfo.ch + 1 };
120+
const end = editor.getCursorPos();
121+
122+
SnippetCursorManager.insertSnippetWithTabStops(editor, matchedSnippet.templateText, start, end);
123+
124+
endSession();
125+
return true; // handled
126+
}
127+
}
128+
}
129+
130+
return false; // not handled
131+
}
132+
};
133+
134+
/**
135+
* Initialize the code hint integration
136+
* This should be called during extension initialization
137+
*/
138+
function init() {
139+
// Register our handler with the CodeHintManager API
140+
CodeHintManager.showHintsAtTop(CustomSnippetsHandler);
141+
}
142+
143+
/**
144+
* Clean up the integration
145+
* This should be called when the extension is being disabled/unloaded
146+
*/
147+
function cleanup() {
148+
CodeHintManager.clearHintsAtTop();
149+
}
150+
151+
exports.init = init;
152+
exports.cleanup = cleanup;
153+
});

src/extensionsIntegrated/CustomSnippets/driver.js

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -164,84 +164,10 @@ define(function (require, exports, module) {
164164
};
165165
}
166166

167-
/**
168-
* This function is called from CodeHintManager.js because of how Phoenix handles hinting.
169-
*
170-
* Here's the problem:
171-
* When a provider returns true for `hasHints`, it locks in that provider for the entire hinting session
172-
* until it returns false. If the user types something like 'clg', and the default JavaScript provider
173-
* is already active, the CodeHintManager won't even ask the custom snippets provider if it has hints.
174-
*
175-
* To fix that, what we did is that when hints are shown we just ask the custom snippets if it has any relevant hint
176-
* If it does, this function prepends them to the existing list of hints.
177-
*
178-
* @param {Object} response - The original hint response from the current provider
179-
* @param {Editor} editor - The current editor instance
180-
* @return {Object} - The modified response with custom snippets added to the front
181-
*/
182-
function prependCustomSnippets(response, editor) {
183-
if (!response || !response.hints) {
184-
return response;
185-
}
186-
187-
try {
188-
// check if the response already contains custom snippet hints to avoid duplicates
189-
// this is needed because sometimes when there are no default hints present then the
190-
// SnippetCodeHints.js shows some hints, so we don't want to duplicate hints
191-
if (Array.isArray(response.hints) && response.hints.length > 0) {
192-
const hasCustomSnippets = response.hints.some((hint) => {
193-
return (
194-
(hint && hint.hasClass && hint.hasClass("emmet-hint")) ||
195-
(hint && hint.attr && hint.attr("data-isCustomSnippet"))
196-
);
197-
});
198-
199-
if (hasCustomSnippets) {
200-
return response; // already has custom snippets, don't need to add again
201-
}
202-
}
203167

204-
const wordInfo = getWordBeforeCursor();
205-
if (!wordInfo || !wordInfo.word) {
206-
return response;
207-
}
208-
209-
const needle = wordInfo.word.toLowerCase();
210-
211-
// check if there's at least one exact match using language context detection
212-
if (!Helper.hasExactMatchingSnippet(needle, editor)) {
213-
return response;
214-
}
215-
216-
// get all matching snippets using language context detection
217-
const matchingSnippets = Helper.getMatchingSnippets(needle, editor);
218-
219-
// if we have matching snippets, prepend them to the hints
220-
if (matchingSnippets.length > 0) {
221-
const customSnippetHints = matchingSnippets.map((snippet) => {
222-
return Helper.createHintItem(snippet.abbreviation, needle, snippet.description);
223-
});
224-
225-
// create a new response with custom snippets at the top
226-
const newResponse = $.extend({}, response);
227-
if (Array.isArray(response.hints)) {
228-
newResponse.hints = customSnippetHints.concat(response.hints);
229-
} else {
230-
newResponse.hints = customSnippetHints.concat([response.hints]);
231-
}
232-
233-
return newResponse;
234-
}
235-
} catch (e) {
236-
console.log("Error checking custom snippets:", e);
237-
}
238-
239-
return response;
240-
}
241168

242169
exports.getWordBeforeCursor = getWordBeforeCursor;
243170
exports.handleSaveBtnClick = handleSaveBtnClick;
244171
exports.handleEditSaveBtnClick = handleEditSaveBtnClick;
245172
exports.handleResetBtnClick = handleResetBtnClick;
246-
exports.prependCustomSnippets = prependCustomSnippets;
247173
});

0 commit comments

Comments
 (0)