Skip to content

Commit 0dc96b2

Browse files
committed
feature(gutter): add ability to replace fold icon with custom for a specific row
1 parent 78ad9c7 commit 0dc96b2

File tree

5 files changed

+278
-1
lines changed

5 files changed

+278
-1
lines changed

src/css/editor-css.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ module.exports = `
507507
padding-right: 13px;
508508
}
509509
510-
.ace_fold-widget {
510+
.ace_fold-widget, .ace_custom-widget {
511511
box-sizing: border-box;
512512
513513
margin: 0 -12px 0 1px;

src/edit_session.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,27 @@ class EditSession {
585585
this._signal("changeBreakpoint", {});
586586
}
587587

588+
/**
589+
* Replaces the custom icon with the fold widget if present from a specific row in the gutter
590+
* @param {number} row The row number for which to hide the custom icon
591+
*/
592+
hideGutterCustomWidget(row) {
593+
this.$editor.renderer.$gutterLayer.hideCustomWidget(row);
594+
}
595+
596+
/**
597+
* Replaces the fold widget if present with the custom icon from a specific row in the gutter
598+
* @param {number} row - The row number where the widget will be displayed
599+
* @param {Object} options - Configuration options for the widget
600+
* @param {string} options.className - CSS class name for styling the widget
601+
* @param {string} options.label - Text label to display in the widget
602+
* @param {string} options.title - Tooltip text for the widget
603+
* @returns {void}
604+
*/
605+
showGutterCustomWidget(row,attributes) {
606+
this.$editor.renderer.$gutterLayer.showCustomWidget(row,attributes);
607+
}
608+
588609
/**
589610
* Removes `className` from the `row`.
590611
* @param {Number} row The row number

src/layer/gutter.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ class Gutter{
331331

332332
var textNode = element.childNodes[0];
333333
var foldWidget = element.childNodes[1];
334+
var customWidget = element.childNodes[3];
334335
var annotationNode = element.childNodes[2];
335336
var annotationIconNode = annotationNode.firstChild;
336337

@@ -418,6 +419,10 @@ class Gutter{
418419
// Set a11y properties.
419420
foldWidget.setAttribute("role", "button");
420421
foldWidget.setAttribute("tabindex", "-1");
422+
423+
if(customWidget && window.getComputedStyle(customWidget).getPropertyValue('display') !== 'none'){
424+
this.hideFoldWidget(row);
425+
}
421426
var foldRange = session.getFoldWidgetRange(row);
422427

423428
// getFoldWidgetRange is optional to be implemented by fold modes, if not available we fall-back.
@@ -588,6 +593,89 @@ class Gutter{
588593
getShowFoldWidgets() {
589594
return this.$showFoldWidgets;
590595
}
596+
597+
/**
598+
* Hides the fold widget/icon from a specific row in the gutter
599+
* @param {number} row The row number from which to hide the fold icon
600+
*/
601+
hideFoldWidget(row) {
602+
const cells = this.$lines.cells;
603+
const cell = cells[row];
604+
if (cell && cell.element) {
605+
const foldWidget = cell.element.childNodes[1];
606+
if (foldWidget) {
607+
dom.setStyle(foldWidget.style, "display", "none");
608+
}
609+
}
610+
}
611+
612+
/**
613+
* Shows the fold widget/icon from a specific row in the gutter
614+
* @param {number} row The row number from which to show the fold icon
615+
*/
616+
showFoldWidget(row) {
617+
const cells = this.$lines.cells;
618+
const cell = cells[row];
619+
if (cell && cell.element) {
620+
const foldWidget = cell.element.childNodes[1];
621+
if (foldWidget) {
622+
dom.setStyle(foldWidget.style, "display", "inline-block");
623+
}
624+
}
625+
}
626+
627+
/**
628+
* Displays a custom widget for a specific row
629+
* @param {number} row - The row number where the widget will be displayed
630+
* @param {Object} options - Configuration options for the widget
631+
* @param {string} options.className - CSS class name for styling the widget
632+
* @param {string} options.label - Text label to display in the widget
633+
* @param {string} options.title - Tooltip text for the widget
634+
* @returns {void}
635+
*/
636+
showCustomWidget(row, {className, label, title}) {
637+
const cells = this.$lines.cells;
638+
const cell = cells[row];
639+
if (cell && cell.element) {
640+
var customWidget = cell.element.querySelector(".ace_custom-widget");
641+
this.hideFoldWidget(row);
642+
// if customWidget already exists show it.
643+
if (customWidget) {
644+
dom.setStyle(customWidget.style, "display", "inline-block");
645+
}
646+
else {
647+
// if customWidget doesn't exists create it.
648+
customWidget = dom.createElement("span");
649+
650+
customWidget.className = `ace_custom-widget ${className}`;
651+
customWidget.setAttribute("tabindex", "-1");
652+
customWidget.setAttribute("role", 'button');
653+
customWidget.setAttribute("aria-label", label);
654+
customWidget.setAttribute("title", title);
655+
dom.setStyle(customWidget.style, "display", "inline-block");
656+
dom.setStyle(customWidget.style, "height", "inherit");
657+
658+
cell.element.appendChild(customWidget);
659+
}
660+
}
661+
}
662+
663+
/**
664+
* Hides a custom widget for a specific row
665+
* @param {number} row - The row number where the widget will be hidden
666+
* @returns {void}
667+
*/
668+
hideCustomWidget(row){
669+
const cells = this.$lines.cells;
670+
const cell = cells[row];
671+
if (cell && cell.element) {
672+
const customWidget = cell.element.querySelector(".ace_custom-widget");
673+
if (customWidget) {
674+
dom.setStyle(customWidget.style, "display", "none");
675+
}
676+
this.showFoldWidget(row);
677+
}
678+
}
591679

592680
$computePadding() {
593681
if (!this.element.firstChild)

src/layer/gutter_test.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
if (typeof process !== "undefined") {
2+
require("amd-loader");
3+
require("../test/mockdom");
4+
}
5+
6+
var keys = require('../lib/keys');
7+
8+
"use strict";
9+
10+
require("../multi_select");
11+
require("../theme/textmate");
12+
var Editor = require("../editor").Editor;
13+
var Mode = require("../mode/java").Mode;
14+
var VirtualRenderer = require("../virtual_renderer").VirtualRenderer;
15+
var assert = require("../test/assertions");
16+
17+
function emit(keyCode) {
18+
var data = {bubbles: true, keyCode};
19+
var event = new KeyboardEvent("keydown", data);
20+
21+
var el = document.activeElement;
22+
el.dispatchEvent(event);
23+
}
24+
25+
module.exports = {
26+
setUp : function(done) {
27+
this.editor = new Editor(new VirtualRenderer());
28+
this.editor.container.style.position = "absolute";
29+
this.editor.container.style.height = "500px";
30+
this.editor.container.style.width = "500px";
31+
this.editor.container.style.left = "50px";
32+
this.editor.container.style.top = "10px";
33+
document.body.appendChild(this.editor.container);
34+
done();
35+
},
36+
"test: custom icon replaces the fold icon sucessfully" : function(done) {
37+
var editor = this.editor;
38+
var value = "x {" + "\n".repeat(50) + "}\n";
39+
value = value.repeat(50);
40+
editor.session.setMode(new Mode());
41+
editor.setValue(value, -1);
42+
editor.setOption("enableKeyboardAccessibility", true);
43+
editor.renderer.$loop._flush();
44+
45+
var lines = editor.renderer.$gutterLayer.$lines;
46+
47+
// Set focus to the gutter div.
48+
editor.renderer.$gutter.focus();
49+
assert.equal(document.activeElement, editor.renderer.$gutter);
50+
51+
// Focus on the fold widget.
52+
emit(keys["enter"]);
53+
editor.renderer.$gutterLayer.showCustomWidget(13, {
54+
className: "ace_users_css",
55+
label: "Open_label",
56+
title: "Open_title"
57+
});
58+
59+
setTimeout(function() {
60+
setTimeout(function() {
61+
// Check that custom widget is shown
62+
editor.renderer.$loop._flush();
63+
console.log(lines.cells[13].element.children[2].className);
64+
assert.ok(/ace_users_css/.test(lines.cells[13].element.children[3].className));
65+
// fold widget is not shown
66+
assert.equal(lines.cells[13].element.children[1].style.display, "none");
67+
68+
// After escape focus should be back to the gutter.
69+
emit(keys["escape"]);
70+
assert.equal(document.activeElement, editor.renderer.$gutter);
71+
72+
done();
73+
}, 20);
74+
}, 20);
75+
},
76+
77+
"test: folding is kept consistent when custom widget is shown first and then hidden" : function(done) {
78+
var editor = this.editor;
79+
var value = "x {" + "\n".repeat(50) + "}\n";
80+
value = value.repeat(50);
81+
editor.session.setMode(new Mode());
82+
editor.setValue(value, -1);
83+
editor.setOption("enableKeyboardAccessibility", true);
84+
editor.renderer.$loop._flush();
85+
86+
var lines = editor.renderer.$gutterLayer.$lines;
87+
88+
// Set focus to the gutter div.
89+
editor.renderer.$gutter.focus();
90+
assert.equal(document.activeElement, editor.renderer.$gutter);
91+
92+
// Focus on the custom widget.
93+
emit(keys["enter"]);
94+
// Click the fold widget.
95+
emit(keys["enter"]);
96+
editor.renderer.$gutterLayer.showCustomWidget(0, {
97+
className: "ace_users_css",
98+
label: "Open_label",
99+
title: "Open_title"
100+
});
101+
editor.renderer.$gutterLayer.hideCustomWidget(0);
102+
103+
setTimeout(function() {
104+
assert.equal(document.activeElement, lines.cells[0].element.childNodes[1]);
105+
106+
setTimeout(function() {
107+
// Check that custom widget is hidden.
108+
editor.renderer.$loop._flush();
109+
assert.ok(/ace_users_css/.test(lines.cells[0].element.children[3].className));
110+
assert.equal(lines.cells[0].element.children[3].style.display, "none");
111+
assert.ok(/ace_closed/.test(lines.cells[0].element.children[1].className));
112+
113+
// After escape focus should be back to the gutter.
114+
emit(keys["escape"]);
115+
assert.equal(document.activeElement, editor.renderer.$gutter);
116+
117+
done();
118+
}, 20);
119+
}, 20);
120+
},
121+
122+
tearDown : function() {
123+
this.editor.destroy();
124+
document.body.removeChild(this.editor.container);
125+
}
126+
};
127+
128+
if (typeof module !== "undefined" && module === require.main) {
129+
require("asyncjs").test.testcase(module.exports).exec();
130+
}

types/ace-modules.d.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,30 @@ declare module "ace-code/src/layer/gutter" {
422422
getShowLineNumbers(): boolean;
423423
setShowFoldWidgets(show?: boolean): void;
424424
getShowFoldWidgets(): boolean;
425+
/**
426+
* Hides the fold widget/icon from a specific row in the gutter
427+
* @param {number} row The row number from which to hide the fold icon
428+
*/
429+
hideFoldWidget(row: number): void;
430+
/**
431+
* Shows the fold widget/icon from a specific row in the gutter
432+
* @param {number} row The row number from which to show the fold icon
433+
*/
434+
showFoldWidget(row: number): void;
435+
/**
436+
* Displays a custom widget for a specific row
437+
* @param {number} row - The row number where the widget will be displayed
438+
* @param {Object} options - Configuration options for the widget
439+
* @param {string} options.className - CSS class name for styling the widget
440+
* @param {string} options.label - Text label to display in the widget
441+
* @param {string} options.title - Tooltip text for the widget
442+
*/
443+
showCustomWidget(row: number, { className, label, title }: {
444+
className: string;
445+
label: string;
446+
title: string;
447+
}): void;
448+
hideCustomWidget(row: any): void;
425449
getRegion(point: {
426450
x: number;
427451
}): "markers" | "foldWidgets";
@@ -3948,6 +3972,20 @@ declare module "ace-code/src/edit_session" {
39483972
* @param {String} className The class to add
39493973
**/
39503974
addGutterDecoration(row: number, className: string): void;
3975+
/**
3976+
* Replaces the custom icon with the fold widget if present from a specific row in the gutter
3977+
* @param {number} row The row number for which to hide the custom icon
3978+
*/
3979+
hideGutterCustomWidget(row: number): void;
3980+
/**
3981+
* Replaces the fold widget if present with the custom icon from a specific row in the gutter
3982+
* @param {number} row - The row number where the widget will be displayed
3983+
* @param {Object} options - Configuration options for the widget
3984+
* @param {string} options.className - CSS class name for styling the widget
3985+
* @param {string} options.label - Text label to display in the widget
3986+
* @param {string} options.title - Tooltip text for the widget
3987+
*/
3988+
showGutterCustomWidget(row: number, attributes: any): void;
39513989
/**
39523990
* Removes `className` from the `row`.
39533991
* @param {Number} row The row number

0 commit comments

Comments
 (0)