ep_hljs 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +10 -0
- package/.github/workflows/automerge.yml +45 -0
- package/.github/workflows/backend-tests.yml +75 -0
- package/.github/workflows/codeql.yml +41 -0
- package/.github/workflows/frontend-tests.yml +72 -0
- package/.github/workflows/npmpublish.yml +124 -0
- package/.github/workflows/test-and-release.yml +32 -0
- package/CLAUDE.md +141 -0
- package/LICENSE +201 -0
- package/README.md +56 -0
- package/demo.gif +0 -0
- package/demo.png +0 -0
- package/ep.json +26 -0
- package/index.js +109 -0
- package/lib/exportRenderer.js +39 -0
- package/lib/languageAllowlist.js +16 -0
- package/lib/padLanguageStore.js +27 -0
- package/locales/en.json +10 -0
- package/package.json +59 -0
- package/pnpm-workspace.yaml +2 -0
- package/scripts/build-vendor.js +45 -0
- package/static/css/editor.css +94 -0
- package/static/css/themes/github-dark.css +118 -0
- package/static/css/themes/github.css +118 -0
- package/static/js/codeIndent.js +107 -0
- package/static/js/constants.js +7 -0
- package/static/js/domOverlay.js +16 -0
- package/static/js/highlightRegistry.js +109 -0
- package/static/js/hljsAdapter.js +80 -0
- package/static/js/index.js +124 -0
- package/static/js/lruCache.js +28 -0
- package/static/js/syntaxRenderer.js +201 -0
- package/static/js/themeBridge.js +76 -0
- package/static/js/vendor/hljs.min.js +5 -0
- package/static/tests/backend/specs/codeIndent.test.js +144 -0
- package/static/tests/backend/specs/export.test.js +47 -0
- package/static/tests/backend/specs/highlightRegistry.test.js +59 -0
- package/static/tests/backend/specs/hljsAdapter.test.js +43 -0
- package/static/tests/backend/specs/lruCache.test.js +45 -0
- package/static/tests/backend/specs/padLanguageStore.test.js +63 -0
- package/static/tests/backend/specs/socket.test.js +54 -0
- package/static/tests/frontend-new/helper/highlights.ts +64 -0
- package/static/tests/frontend-new/specs/caret-stability.spec.ts +106 -0
- package/static/tests/frontend-new/specs/code-indent.spec.ts +78 -0
- package/static/tests/frontend-new/specs/collaboration.spec.ts +59 -0
- package/static/tests/frontend-new/specs/content-sync.spec.ts +45 -0
- package/static/tests/frontend-new/specs/dark-mode.spec.ts +87 -0
- package/static/tests/frontend-new/specs/export.spec.ts +31 -0
- package/static/tests/frontend-new/specs/initial-paint.spec.ts +49 -0
- package/static/tests/frontend-new/specs/language-picker.spec.ts +54 -0
- package/static/tests/frontend-new/specs/large-pad.spec.ts +36 -0
- package/static/tests/frontend-new/specs/lifecycle.spec.ts +27 -0
- package/static/tests/frontend-new/specs/multi-user-caret.spec.ts +167 -0
- package/static/tests/frontend-new/specs/single-line-while.spec.ts +50 -0
- package/templates/editbarButtons.ejs +29 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* CSS Custom Highlights — paint syntax tokens by Range registration in
|
|
2
|
+
highlightRegistry.js. Light palette tuned for visibility against the
|
|
3
|
+
default white-ish editor background; dark palette below mirrors GitHub
|
|
4
|
+
dark and is scoped via the super-dark-editor skin variant Etherpad
|
|
5
|
+
sets on the inner iframe's <html>. */
|
|
6
|
+
::highlight(hljs-keyword) { color: #d73a49; font-weight: bold; }
|
|
7
|
+
::highlight(hljs-doctag) { color: #d73a49; }
|
|
8
|
+
::highlight(hljs-template-tag) { color: #d73a49; }
|
|
9
|
+
::highlight(hljs-template-variable) { color: #d73a49; }
|
|
10
|
+
::highlight(hljs-type) { color: #d73a49; }
|
|
11
|
+
::highlight(hljs-title) { color: #6f42c1; }
|
|
12
|
+
::highlight(hljs-attr) { color: #3b82f6; }
|
|
13
|
+
::highlight(hljs-attribute) { color: #3b82f6; }
|
|
14
|
+
::highlight(hljs-literal) { color: #3b82f6; }
|
|
15
|
+
::highlight(hljs-meta) { color: #3b82f6; }
|
|
16
|
+
::highlight(hljs-number) { color: #3b82f6; }
|
|
17
|
+
::highlight(hljs-operator) { color: #3b82f6; }
|
|
18
|
+
::highlight(hljs-variable) { color: #3b82f6; }
|
|
19
|
+
::highlight(hljs-selector-attr) { color: #3b82f6; }
|
|
20
|
+
::highlight(hljs-selector-class) { color: #3b82f6; }
|
|
21
|
+
::highlight(hljs-selector-id) { color: #3b82f6; }
|
|
22
|
+
::highlight(hljs-regexp) { color: #1d4ed8; }
|
|
23
|
+
::highlight(hljs-string) { color: #1d4ed8; }
|
|
24
|
+
::highlight(hljs-built_in) { color: #e36209; }
|
|
25
|
+
::highlight(hljs-symbol) { color: #e36209; }
|
|
26
|
+
::highlight(hljs-comment) { color: #6a737d; font-style: italic; }
|
|
27
|
+
::highlight(hljs-code) { color: #6a737d; }
|
|
28
|
+
::highlight(hljs-formula) { color: #6a737d; }
|
|
29
|
+
::highlight(hljs-name) { color: #22863a; }
|
|
30
|
+
::highlight(hljs-quote) { color: #22863a; }
|
|
31
|
+
::highlight(hljs-selector-tag) { color: #22863a; }
|
|
32
|
+
::highlight(hljs-selector-pseudo) { color: #22863a; }
|
|
33
|
+
::highlight(hljs-tag) { color: #22863a; }
|
|
34
|
+
::highlight(hljs-section) { color: #3b82f6; font-weight: bold; }
|
|
35
|
+
::highlight(hljs-bullet) { color: #735c0f; }
|
|
36
|
+
::highlight(hljs-link) { color: #1d4ed8; text-decoration: underline; }
|
|
37
|
+
::highlight(hljs-emphasis) { font-style: italic; }
|
|
38
|
+
::highlight(hljs-strong) { font-weight: bold; }
|
|
39
|
+
::highlight(hljs-addition) { color: #22863a; background: #f0fff4; }
|
|
40
|
+
::highlight(hljs-deletion) { color: #b31d28; background: #ffeef0; }
|
|
41
|
+
|
|
42
|
+
/* Authorship background colors fight syntax foreground colors and make
|
|
43
|
+
tokens hard to read. Suppress them only when this plugin is *actively*
|
|
44
|
+
highlighting (state.language is a real grammar AND user has highlighting
|
|
45
|
+
enabled). Plain pads — and pads where this plugin loaded but is not
|
|
46
|
+
currently painting — keep their author backgrounds untouched, so core
|
|
47
|
+
Etherpad tests for authorship rendering pass unaffected. */
|
|
48
|
+
.ep-syntax-highlighting-active [class*="author-a"] {
|
|
49
|
+
background-color: transparent !important;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#ep_hljs_paused_badge {
|
|
53
|
+
margin-left: 6px;
|
|
54
|
+
font-style: italic;
|
|
55
|
+
opacity: 0.7;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Dark mode (Etherpad's super-dark-editor skin variant). Palette taken
|
|
59
|
+
from highlight.js's github-dark.css. */
|
|
60
|
+
.super-dark-editor ::highlight(hljs-keyword) { color: #ff7b72; font-weight: bold; }
|
|
61
|
+
.super-dark-editor ::highlight(hljs-doctag) { color: #ff7b72; }
|
|
62
|
+
.super-dark-editor ::highlight(hljs-template-tag) { color: #ff7b72; }
|
|
63
|
+
.super-dark-editor ::highlight(hljs-template-variable) { color: #ff7b72; }
|
|
64
|
+
.super-dark-editor ::highlight(hljs-type) { color: #ff7b72; }
|
|
65
|
+
.super-dark-editor ::highlight(hljs-title) { color: #d2a8ff; }
|
|
66
|
+
.super-dark-editor ::highlight(hljs-attr) { color: #79c0ff; }
|
|
67
|
+
.super-dark-editor ::highlight(hljs-attribute) { color: #79c0ff; }
|
|
68
|
+
.super-dark-editor ::highlight(hljs-literal) { color: #79c0ff; }
|
|
69
|
+
.super-dark-editor ::highlight(hljs-meta) { color: #79c0ff; }
|
|
70
|
+
.super-dark-editor ::highlight(hljs-number) { color: #79c0ff; }
|
|
71
|
+
.super-dark-editor ::highlight(hljs-operator) { color: #79c0ff; }
|
|
72
|
+
.super-dark-editor ::highlight(hljs-variable) { color: #79c0ff; }
|
|
73
|
+
.super-dark-editor ::highlight(hljs-selector-attr) { color: #79c0ff; }
|
|
74
|
+
.super-dark-editor ::highlight(hljs-selector-class) { color: #79c0ff; }
|
|
75
|
+
.super-dark-editor ::highlight(hljs-selector-id) { color: #79c0ff; }
|
|
76
|
+
.super-dark-editor ::highlight(hljs-regexp) { color: #a5d6ff; }
|
|
77
|
+
.super-dark-editor ::highlight(hljs-string) { color: #a5d6ff; }
|
|
78
|
+
.super-dark-editor ::highlight(hljs-built_in) { color: #ffa657; }
|
|
79
|
+
.super-dark-editor ::highlight(hljs-symbol) { color: #ffa657; }
|
|
80
|
+
.super-dark-editor ::highlight(hljs-comment) { color: #8b949e; font-style: italic; }
|
|
81
|
+
.super-dark-editor ::highlight(hljs-code) { color: #8b949e; }
|
|
82
|
+
.super-dark-editor ::highlight(hljs-formula) { color: #8b949e; }
|
|
83
|
+
.super-dark-editor ::highlight(hljs-name) { color: #7ee787; }
|
|
84
|
+
.super-dark-editor ::highlight(hljs-quote) { color: #7ee787; }
|
|
85
|
+
.super-dark-editor ::highlight(hljs-selector-tag) { color: #7ee787; }
|
|
86
|
+
.super-dark-editor ::highlight(hljs-selector-pseudo) { color: #7ee787; }
|
|
87
|
+
.super-dark-editor ::highlight(hljs-tag) { color: #7ee787; }
|
|
88
|
+
.super-dark-editor ::highlight(hljs-section) { color: #1f6feb; font-weight: bold; }
|
|
89
|
+
.super-dark-editor ::highlight(hljs-bullet) { color: #f2cc60; }
|
|
90
|
+
.super-dark-editor ::highlight(hljs-link) { color: #a5d6ff; text-decoration: underline; }
|
|
91
|
+
.super-dark-editor ::highlight(hljs-emphasis) { font-style: italic; }
|
|
92
|
+
.super-dark-editor ::highlight(hljs-strong) { font-weight: bold; }
|
|
93
|
+
.super-dark-editor ::highlight(hljs-addition) { color: #aff5b4; background: #033a16; }
|
|
94
|
+
.super-dark-editor ::highlight(hljs-deletion) { color: #ffdcd7; background: #67060c; }
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
pre code.hljs {
|
|
2
|
+
display: block;
|
|
3
|
+
overflow-x: auto;
|
|
4
|
+
padding: 1em
|
|
5
|
+
}
|
|
6
|
+
code.hljs {
|
|
7
|
+
padding: 3px 5px
|
|
8
|
+
}
|
|
9
|
+
/*!
|
|
10
|
+
Theme: GitHub Dark
|
|
11
|
+
Description: Dark theme as seen on github.com
|
|
12
|
+
Author: github.com
|
|
13
|
+
Maintainer: @Hirse
|
|
14
|
+
Updated: 2021-05-15
|
|
15
|
+
|
|
16
|
+
Outdated base version: https://github.com/primer/github-syntax-dark
|
|
17
|
+
Current colors taken from GitHub's CSS
|
|
18
|
+
*/
|
|
19
|
+
.hljs {
|
|
20
|
+
color: #c9d1d9;
|
|
21
|
+
background: #0d1117
|
|
22
|
+
}
|
|
23
|
+
.hljs-doctag,
|
|
24
|
+
.hljs-keyword,
|
|
25
|
+
.hljs-meta .hljs-keyword,
|
|
26
|
+
.hljs-template-tag,
|
|
27
|
+
.hljs-template-variable,
|
|
28
|
+
.hljs-type,
|
|
29
|
+
.hljs-variable.language_ {
|
|
30
|
+
/* prettylights-syntax-keyword */
|
|
31
|
+
color: #ff7b72
|
|
32
|
+
}
|
|
33
|
+
.hljs-title,
|
|
34
|
+
.hljs-title.class_,
|
|
35
|
+
.hljs-title.class_.inherited__,
|
|
36
|
+
.hljs-title.function_ {
|
|
37
|
+
/* prettylights-syntax-entity */
|
|
38
|
+
color: #d2a8ff
|
|
39
|
+
}
|
|
40
|
+
.hljs-attr,
|
|
41
|
+
.hljs-attribute,
|
|
42
|
+
.hljs-literal,
|
|
43
|
+
.hljs-meta,
|
|
44
|
+
.hljs-number,
|
|
45
|
+
.hljs-operator,
|
|
46
|
+
.hljs-variable,
|
|
47
|
+
.hljs-selector-attr,
|
|
48
|
+
.hljs-selector-class,
|
|
49
|
+
.hljs-selector-id {
|
|
50
|
+
/* prettylights-syntax-constant */
|
|
51
|
+
color: #79c0ff
|
|
52
|
+
}
|
|
53
|
+
.hljs-regexp,
|
|
54
|
+
.hljs-string,
|
|
55
|
+
.hljs-meta .hljs-string {
|
|
56
|
+
/* prettylights-syntax-string */
|
|
57
|
+
color: #a5d6ff
|
|
58
|
+
}
|
|
59
|
+
.hljs-built_in,
|
|
60
|
+
.hljs-symbol {
|
|
61
|
+
/* prettylights-syntax-variable */
|
|
62
|
+
color: #ffa657
|
|
63
|
+
}
|
|
64
|
+
.hljs-comment,
|
|
65
|
+
.hljs-code,
|
|
66
|
+
.hljs-formula {
|
|
67
|
+
/* prettylights-syntax-comment */
|
|
68
|
+
color: #8b949e
|
|
69
|
+
}
|
|
70
|
+
.hljs-name,
|
|
71
|
+
.hljs-quote,
|
|
72
|
+
.hljs-selector-tag,
|
|
73
|
+
.hljs-selector-pseudo {
|
|
74
|
+
/* prettylights-syntax-entity-tag */
|
|
75
|
+
color: #7ee787
|
|
76
|
+
}
|
|
77
|
+
.hljs-subst {
|
|
78
|
+
/* prettylights-syntax-storage-modifier-import */
|
|
79
|
+
color: #c9d1d9
|
|
80
|
+
}
|
|
81
|
+
.hljs-section {
|
|
82
|
+
/* prettylights-syntax-markup-heading */
|
|
83
|
+
color: #1f6feb;
|
|
84
|
+
font-weight: bold
|
|
85
|
+
}
|
|
86
|
+
.hljs-bullet {
|
|
87
|
+
/* prettylights-syntax-markup-list */
|
|
88
|
+
color: #f2cc60
|
|
89
|
+
}
|
|
90
|
+
.hljs-emphasis {
|
|
91
|
+
/* prettylights-syntax-markup-italic */
|
|
92
|
+
color: #c9d1d9;
|
|
93
|
+
font-style: italic
|
|
94
|
+
}
|
|
95
|
+
.hljs-strong {
|
|
96
|
+
/* prettylights-syntax-markup-bold */
|
|
97
|
+
color: #c9d1d9;
|
|
98
|
+
font-weight: bold
|
|
99
|
+
}
|
|
100
|
+
.hljs-addition {
|
|
101
|
+
/* prettylights-syntax-markup-inserted */
|
|
102
|
+
color: #aff5b4;
|
|
103
|
+
background-color: #033a16
|
|
104
|
+
}
|
|
105
|
+
.hljs-deletion {
|
|
106
|
+
/* prettylights-syntax-markup-deleted */
|
|
107
|
+
color: #ffdcd7;
|
|
108
|
+
background-color: #67060c
|
|
109
|
+
}
|
|
110
|
+
.hljs-char.escape_,
|
|
111
|
+
.hljs-link,
|
|
112
|
+
.hljs-params,
|
|
113
|
+
.hljs-property,
|
|
114
|
+
.hljs-punctuation,
|
|
115
|
+
.hljs-tag {
|
|
116
|
+
/* purposely ignored */
|
|
117
|
+
|
|
118
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
pre code.hljs {
|
|
2
|
+
display: block;
|
|
3
|
+
overflow-x: auto;
|
|
4
|
+
padding: 1em
|
|
5
|
+
}
|
|
6
|
+
code.hljs {
|
|
7
|
+
padding: 3px 5px
|
|
8
|
+
}
|
|
9
|
+
/*!
|
|
10
|
+
Theme: GitHub
|
|
11
|
+
Description: Light theme as seen on github.com
|
|
12
|
+
Author: github.com
|
|
13
|
+
Maintainer: @Hirse
|
|
14
|
+
Updated: 2021-05-15
|
|
15
|
+
|
|
16
|
+
Outdated base version: https://github.com/primer/github-syntax-light
|
|
17
|
+
Current colors taken from GitHub's CSS
|
|
18
|
+
*/
|
|
19
|
+
.hljs {
|
|
20
|
+
color: #24292e;
|
|
21
|
+
background: #ffffff
|
|
22
|
+
}
|
|
23
|
+
.hljs-doctag,
|
|
24
|
+
.hljs-keyword,
|
|
25
|
+
.hljs-meta .hljs-keyword,
|
|
26
|
+
.hljs-template-tag,
|
|
27
|
+
.hljs-template-variable,
|
|
28
|
+
.hljs-type,
|
|
29
|
+
.hljs-variable.language_ {
|
|
30
|
+
/* prettylights-syntax-keyword */
|
|
31
|
+
color: #d73a49
|
|
32
|
+
}
|
|
33
|
+
.hljs-title,
|
|
34
|
+
.hljs-title.class_,
|
|
35
|
+
.hljs-title.class_.inherited__,
|
|
36
|
+
.hljs-title.function_ {
|
|
37
|
+
/* prettylights-syntax-entity */
|
|
38
|
+
color: #6f42c1
|
|
39
|
+
}
|
|
40
|
+
.hljs-attr,
|
|
41
|
+
.hljs-attribute,
|
|
42
|
+
.hljs-literal,
|
|
43
|
+
.hljs-meta,
|
|
44
|
+
.hljs-number,
|
|
45
|
+
.hljs-operator,
|
|
46
|
+
.hljs-variable,
|
|
47
|
+
.hljs-selector-attr,
|
|
48
|
+
.hljs-selector-class,
|
|
49
|
+
.hljs-selector-id {
|
|
50
|
+
/* prettylights-syntax-constant */
|
|
51
|
+
color: #005cc5
|
|
52
|
+
}
|
|
53
|
+
.hljs-regexp,
|
|
54
|
+
.hljs-string,
|
|
55
|
+
.hljs-meta .hljs-string {
|
|
56
|
+
/* prettylights-syntax-string */
|
|
57
|
+
color: #032f62
|
|
58
|
+
}
|
|
59
|
+
.hljs-built_in,
|
|
60
|
+
.hljs-symbol {
|
|
61
|
+
/* prettylights-syntax-variable */
|
|
62
|
+
color: #e36209
|
|
63
|
+
}
|
|
64
|
+
.hljs-comment,
|
|
65
|
+
.hljs-code,
|
|
66
|
+
.hljs-formula {
|
|
67
|
+
/* prettylights-syntax-comment */
|
|
68
|
+
color: #6a737d
|
|
69
|
+
}
|
|
70
|
+
.hljs-name,
|
|
71
|
+
.hljs-quote,
|
|
72
|
+
.hljs-selector-tag,
|
|
73
|
+
.hljs-selector-pseudo {
|
|
74
|
+
/* prettylights-syntax-entity-tag */
|
|
75
|
+
color: #22863a
|
|
76
|
+
}
|
|
77
|
+
.hljs-subst {
|
|
78
|
+
/* prettylights-syntax-storage-modifier-import */
|
|
79
|
+
color: #24292e
|
|
80
|
+
}
|
|
81
|
+
.hljs-section {
|
|
82
|
+
/* prettylights-syntax-markup-heading */
|
|
83
|
+
color: #005cc5;
|
|
84
|
+
font-weight: bold
|
|
85
|
+
}
|
|
86
|
+
.hljs-bullet {
|
|
87
|
+
/* prettylights-syntax-markup-list */
|
|
88
|
+
color: #735c0f
|
|
89
|
+
}
|
|
90
|
+
.hljs-emphasis {
|
|
91
|
+
/* prettylights-syntax-markup-italic */
|
|
92
|
+
color: #24292e;
|
|
93
|
+
font-style: italic
|
|
94
|
+
}
|
|
95
|
+
.hljs-strong {
|
|
96
|
+
/* prettylights-syntax-markup-bold */
|
|
97
|
+
color: #24292e;
|
|
98
|
+
font-weight: bold
|
|
99
|
+
}
|
|
100
|
+
.hljs-addition {
|
|
101
|
+
/* prettylights-syntax-markup-inserted */
|
|
102
|
+
color: #22863a;
|
|
103
|
+
background-color: #f0fff4
|
|
104
|
+
}
|
|
105
|
+
.hljs-deletion {
|
|
106
|
+
/* prettylights-syntax-markup-deleted */
|
|
107
|
+
color: #b31d28;
|
|
108
|
+
background-color: #ffeef0
|
|
109
|
+
}
|
|
110
|
+
.hljs-char.escape_,
|
|
111
|
+
.hljs-link,
|
|
112
|
+
.hljs-params,
|
|
113
|
+
.hljs-property,
|
|
114
|
+
.hljs-punctuation,
|
|
115
|
+
.hljs-tag {
|
|
116
|
+
/* purposely ignored */
|
|
117
|
+
|
|
118
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
let indentSize = 2;
|
|
4
|
+
let getLanguage = () => null;
|
|
5
|
+
let getAutoDetect = () => true;
|
|
6
|
+
|
|
7
|
+
exports.start = (opts) => {
|
|
8
|
+
if (opts && typeof opts.indentSize === 'number' && opts.indentSize > 0) {
|
|
9
|
+
indentSize = opts.indentSize;
|
|
10
|
+
}
|
|
11
|
+
if (opts && typeof opts.getLanguage === 'function') {
|
|
12
|
+
getLanguage = opts.getLanguage;
|
|
13
|
+
}
|
|
14
|
+
if (opts && typeof opts.getAutoDetect === 'function') {
|
|
15
|
+
getAutoDetect = opts.getAutoDetect;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
exports.setIndentSize = (n) => {
|
|
20
|
+
if (typeof n === 'number' && n > 0) indentSize = n;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
exports.getIndentSize = () => indentSize;
|
|
24
|
+
|
|
25
|
+
const inCodeMode = () => {
|
|
26
|
+
const lang = getLanguage();
|
|
27
|
+
if (!lang || lang === 'auto' || lang === 'off') return false;
|
|
28
|
+
// Only intercept Tab / Enter / Shift+Tab when the user has *explicitly*
|
|
29
|
+
// picked a language. Auto-detect picking a language under the hood does
|
|
30
|
+
// not enable keystroke interception — otherwise core Etherpad tests that
|
|
31
|
+
// paste code-shaped text into a plain pad (e.g. indentation.spec.ts)
|
|
32
|
+
// would have their Tab/Enter overridden when auto-detect kicks in.
|
|
33
|
+
return getAutoDetect() === false;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const indentStr = (n) => ' '.repeat(n);
|
|
37
|
+
|
|
38
|
+
const leadingIndent = (text) => {
|
|
39
|
+
const m = /^([ \t]*)/.exec(text);
|
|
40
|
+
return m ? m[1] : '';
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleEnter = (rep, editorInfo, evt) => {
|
|
44
|
+
const line = rep.selStart[0];
|
|
45
|
+
const col = rep.selStart[1];
|
|
46
|
+
const lineEntry = rep.lines.atIndex(line);
|
|
47
|
+
const text = (lineEntry && lineEntry.text) || '';
|
|
48
|
+
const beforeCaret = text.slice(0, col);
|
|
49
|
+
let indent = leadingIndent(text);
|
|
50
|
+
if (/[{[(]\s*$/.test(beforeCaret)) indent += indentStr(indentSize);
|
|
51
|
+
editorInfo.ace_replaceRange(rep.selStart, rep.selEnd, `\n${indent}`);
|
|
52
|
+
evt.preventDefault();
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleTab = (rep, editorInfo, evt) => {
|
|
57
|
+
const a = rep.selStart;
|
|
58
|
+
const b = rep.selEnd;
|
|
59
|
+
if (a[0] === b[0]) {
|
|
60
|
+
editorInfo.ace_replaceRange(a, b, indentStr(indentSize));
|
|
61
|
+
} else {
|
|
62
|
+
const startLine = Math.min(a[0], b[0]);
|
|
63
|
+
const endLine = Math.max(a[0], b[0]);
|
|
64
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
65
|
+
editorInfo.ace_replaceRange([i, 0], [i, 0], indentStr(indentSize));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
evt.preventDefault();
|
|
69
|
+
return true;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handleShiftTab = (rep, editorInfo, evt) => {
|
|
73
|
+
const a = rep.selStart;
|
|
74
|
+
const b = rep.selEnd;
|
|
75
|
+
const startLine = Math.min(a[0], b[0]);
|
|
76
|
+
const endLine = Math.max(a[0], b[0]);
|
|
77
|
+
let didAnything = false;
|
|
78
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
79
|
+
const lineEntry = rep.lines.atIndex(i);
|
|
80
|
+
const text = (lineEntry && lineEntry.text) || '';
|
|
81
|
+
const m = /^([ \t]*)/.exec(text);
|
|
82
|
+
if (!m || !m[1].length) continue;
|
|
83
|
+
const removeCount = Math.min(m[1].length, indentSize);
|
|
84
|
+
editorInfo.ace_replaceRange([i, 0], [i, removeCount], '');
|
|
85
|
+
didAnything = true;
|
|
86
|
+
}
|
|
87
|
+
if (!didAnything) return false;
|
|
88
|
+
evt.preventDefault();
|
|
89
|
+
return true;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
exports.handleKey = (hookName, ctx) => {
|
|
93
|
+
if (!inCodeMode()) return false;
|
|
94
|
+
const evt = ctx && ctx.evt;
|
|
95
|
+
if (!evt || evt.type !== 'keydown') return false;
|
|
96
|
+
const rep = ctx.rep;
|
|
97
|
+
if (!rep || !rep.selStart || !rep.lines) return false;
|
|
98
|
+
const editorInfo = ctx.editorInfo;
|
|
99
|
+
if (!editorInfo || typeof editorInfo.ace_replaceRange !== 'function') return false;
|
|
100
|
+
if (evt.keyCode === 13 && !evt.ctrlKey && !evt.altKey && !evt.metaKey && !evt.shiftKey) {
|
|
101
|
+
return handleEnter(rep, editorInfo, evt);
|
|
102
|
+
}
|
|
103
|
+
if (evt.keyCode === 9 && !evt.ctrlKey && !evt.altKey && !evt.metaKey) {
|
|
104
|
+
return evt.shiftKey ? handleShiftTab(rep, editorInfo, evt) : handleTab(rep, editorInfo, evt);
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.showPausedBadge = (visible) => {
|
|
4
|
+
const sel = document.getElementById('ep_hljs_select');
|
|
5
|
+
if (!sel) return;
|
|
6
|
+
let badge = document.getElementById('ep_hljs_paused_badge');
|
|
7
|
+
if (visible && !badge) {
|
|
8
|
+
badge = document.createElement('span');
|
|
9
|
+
badge.id = 'ep_hljs_paused_badge';
|
|
10
|
+
badge.setAttribute('data-l10n-id', 'ep_hljs.paused');
|
|
11
|
+
badge.textContent = 'Highlighting paused';
|
|
12
|
+
sel.insertAdjacentElement('afterend', badge);
|
|
13
|
+
} else if (!visible && badge) {
|
|
14
|
+
badge.remove();
|
|
15
|
+
}
|
|
16
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const lineRanges = new WeakMap();
|
|
4
|
+
const classHighlights = new Map();
|
|
5
|
+
|
|
6
|
+
const supports = (win) => {
|
|
7
|
+
if (!win || !win.Highlight) return false;
|
|
8
|
+
if (!win.CSS || !win.CSS.highlights) return false;
|
|
9
|
+
return typeof win.CSS.highlights.set === 'function';
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getOrCreateHighlight = (win, cls) => {
|
|
13
|
+
let h = classHighlights.get(cls);
|
|
14
|
+
if (h) return h;
|
|
15
|
+
h = new win.Highlight();
|
|
16
|
+
win.CSS.highlights.set(cls, h);
|
|
17
|
+
classHighlights.set(cls, h);
|
|
18
|
+
return h;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const buildSegments = (lineEl, win) => {
|
|
22
|
+
const doc = lineEl.ownerDocument;
|
|
23
|
+
const walker = doc.createTreeWalker(lineEl, win.NodeFilter.SHOW_TEXT);
|
|
24
|
+
const segs = [];
|
|
25
|
+
let pos = 0;
|
|
26
|
+
let n;
|
|
27
|
+
while ((n = walker.nextNode())) {
|
|
28
|
+
const len = n.nodeValue.length;
|
|
29
|
+
segs.push({node: n, start: pos, len});
|
|
30
|
+
pos += len;
|
|
31
|
+
}
|
|
32
|
+
return segs;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const buildRange = (doc, segs, start, end) => {
|
|
36
|
+
let startNode = null;
|
|
37
|
+
let startOff = 0;
|
|
38
|
+
let endNode = null;
|
|
39
|
+
let endOff = 0;
|
|
40
|
+
for (const seg of segs) {
|
|
41
|
+
const segEnd = seg.start + seg.len;
|
|
42
|
+
if (!startNode && start >= seg.start && start <= segEnd) {
|
|
43
|
+
startNode = seg.node;
|
|
44
|
+
startOff = start - seg.start;
|
|
45
|
+
}
|
|
46
|
+
// We break the moment endNode is set, so by the time we reach this
|
|
47
|
+
// condition again endNode is guaranteed null — no `!endNode` needed.
|
|
48
|
+
if (end > seg.start && end <= segEnd) {
|
|
49
|
+
endNode = seg.node;
|
|
50
|
+
endOff = end - seg.start;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!startNode || !endNode) return null;
|
|
55
|
+
const range = doc.createRange();
|
|
56
|
+
try {
|
|
57
|
+
range.setStart(startNode, startOff);
|
|
58
|
+
range.setEnd(endNode, endOff);
|
|
59
|
+
} catch (_e) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return range;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const removeLineRanges = (lineEl) => {
|
|
66
|
+
const map = lineRanges.get(lineEl);
|
|
67
|
+
if (!map) return;
|
|
68
|
+
for (const [cls, arr] of map) {
|
|
69
|
+
const h = classHighlights.get(cls);
|
|
70
|
+
if (!h) continue;
|
|
71
|
+
for (const r of arr) {
|
|
72
|
+
try { h.delete(r); } catch (_e) { /* stale range */ }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
lineRanges.delete(lineEl);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const setLineRanges = (lineEl, tokenRanges) => {
|
|
79
|
+
if (!lineEl) return;
|
|
80
|
+
const win = lineEl.ownerDocument && lineEl.ownerDocument.defaultView;
|
|
81
|
+
if (!supports(win)) {
|
|
82
|
+
removeLineRanges(lineEl);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
removeLineRanges(lineEl);
|
|
86
|
+
if (!tokenRanges || !tokenRanges.length) return;
|
|
87
|
+
const segs = buildSegments(lineEl, win);
|
|
88
|
+
if (!segs.length) return;
|
|
89
|
+
const newMap = new Map();
|
|
90
|
+
for (const tr of tokenRanges) {
|
|
91
|
+
if (!tr || tr.start >= tr.end || !tr.cls) continue;
|
|
92
|
+
const range = buildRange(lineEl.ownerDocument, segs, tr.start, tr.end);
|
|
93
|
+
if (!range) continue;
|
|
94
|
+
const h = getOrCreateHighlight(win, tr.cls);
|
|
95
|
+
h.add(range);
|
|
96
|
+
let arr = newMap.get(tr.cls);
|
|
97
|
+
if (!arr) { arr = []; newMap.set(tr.cls, arr); }
|
|
98
|
+
arr.push(range);
|
|
99
|
+
}
|
|
100
|
+
if (newMap.size > 0) lineRanges.set(lineEl, newMap);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const clearAll = () => {
|
|
104
|
+
for (const h of classHighlights.values()) {
|
|
105
|
+
try { h.clear(); } catch (_e) { /* ignore */ }
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
module.exports = {setLineRanges, removeLineRanges, clearAll, buildRange, buildSegments};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Parses hljs's HTML output into character ranges. hljs emits nested
|
|
4
|
+
// <span class="hljs-…">…</span> markup; we walk it linearly, accumulating
|
|
5
|
+
// the plain-text length and producing {start, end, cls} ranges for every
|
|
6
|
+
// closing </span>.
|
|
7
|
+
const SPAN_RE = /<span class="([^"]+)">|<\/span>/g;
|
|
8
|
+
|
|
9
|
+
// Single-pass entity decoder. A naive sequence of .replace(&, &) followed
|
|
10
|
+
// by .replace(<, <) double-decodes input like "&lt;" into "<", which
|
|
11
|
+
// is wrong (it should stay as "<"). One regex with a dispatch table
|
|
12
|
+
// processes each entity exactly once.
|
|
13
|
+
const ENTITIES = {
|
|
14
|
+
'&': '&', '<': '<', '>': '>',
|
|
15
|
+
'"': '"', ''': "'", ' ': ' ',
|
|
16
|
+
};
|
|
17
|
+
const ENTITY_RE = /&(?:amp|lt|gt|quot|#39|nbsp);/g;
|
|
18
|
+
const decodeEntities = (s) => s.replace(ENTITY_RE, (m) => ENTITIES[m]);
|
|
19
|
+
|
|
20
|
+
// hljs sometimes emits multi-class spans (e.g. `<span class="hljs-meta hljs-string">`).
|
|
21
|
+
// CSS Highlights names are <custom-ident>, which can't contain spaces, so we
|
|
22
|
+
// emit one range per class and let the cascade overlay them.
|
|
23
|
+
const parseHljsHtml = (html) => {
|
|
24
|
+
const ranges = [];
|
|
25
|
+
const stack = [];
|
|
26
|
+
let pos = 0;
|
|
27
|
+
let plain = 0;
|
|
28
|
+
let m;
|
|
29
|
+
SPAN_RE.lastIndex = 0;
|
|
30
|
+
while ((m = SPAN_RE.exec(html)) != null) {
|
|
31
|
+
const before = decodeEntities(html.slice(pos, m.index));
|
|
32
|
+
plain += before.length;
|
|
33
|
+
if (m[1] != null) {
|
|
34
|
+
stack.push({classes: m[1].split(/\s+/).filter(Boolean), start: plain});
|
|
35
|
+
} else {
|
|
36
|
+
const top = stack.pop();
|
|
37
|
+
if (top) {
|
|
38
|
+
for (const cls of top.classes) {
|
|
39
|
+
ranges.push({start: top.start, end: plain, cls});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
pos = m.index + m[0].length;
|
|
44
|
+
}
|
|
45
|
+
return ranges;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Returns:
|
|
49
|
+
// Array<{start,end,cls}> — token ranges (possibly empty if no tokens)
|
|
50
|
+
// null — hljs not yet loaded; caller should skip + retry
|
|
51
|
+
const tokenize = (text, language) => {
|
|
52
|
+
if (!text) return [];
|
|
53
|
+
if (!language || language === 'auto' || language === 'off') return [];
|
|
54
|
+
const hljs = (typeof window !== 'undefined') ? window.hljs : null;
|
|
55
|
+
if (!hljs) return null;
|
|
56
|
+
if (!hljs.getLanguage(language)) return [];
|
|
57
|
+
let result;
|
|
58
|
+
try {
|
|
59
|
+
result = hljs.highlight(text, {language, ignoreIllegals: true});
|
|
60
|
+
} catch (_e) { return []; }
|
|
61
|
+
return parseHljsHtml(result.value);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const detect = (text) => {
|
|
65
|
+
const hljs = (typeof window !== 'undefined') ? window.hljs : null;
|
|
66
|
+
if (!hljs) return null;
|
|
67
|
+
let result;
|
|
68
|
+
try {
|
|
69
|
+
result = hljs.highlightAuto(text);
|
|
70
|
+
} catch (_e) { return null; }
|
|
71
|
+
if (!result || !result.language) return null;
|
|
72
|
+
// hljs scores by token-match count. A single short line of real code
|
|
73
|
+
// (e.g. `const foo = "bar"; // note`) typically scores 2-4. The previous
|
|
74
|
+
// threshold of 5 silently rejected most short pads. 2 is sensitive
|
|
75
|
+
// enough for real code without triggering on a single English word.
|
|
76
|
+
if ((result.relevance || 0) < 2) return null;
|
|
77
|
+
return result.language;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
module.exports = {tokenize, detect, parseHljsHtml};
|