• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ckeditor / ckeditor5 / 25989

pending completion
25989

push

CKEditor5 code coverage

pomek
Internal: Release - remove built typings and JS after preparing packages to release.

12387 of 12387 branches covered (100.0%)

Branch coverage included in aggregate %.

32548 of 32548 relevant lines covered (100.0%)

11974.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/packages/ckeditor5-autoformat/src/autoformat.ts
1
/**
2
 * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
 */
5

6
/**
7
 * @module autoformat/autoformat
8
 */
9
import type { HeadingCommand } from '@ckeditor/ckeditor5-heading';
10

11
import { Plugin, type Editor } from 'ckeditor5/src/core.js';
12
import type { ModelRange, ModelWriter } from 'ckeditor5/src/engine.js';
13
import { Delete } from 'ckeditor5/src/typing.js';
14

15
import { blockAutoformatEditing } from './blockautoformatediting.js';
16
import { inlineAutoformatEditing } from './inlineautoformatediting.js';
17

18
/**
19
 * Enables a set of predefined autoformatting actions.
20
 *
21
 * For a detailed overview, check the {@glink features/autoformat Autoformatting} feature guide
22
 * and the {@glink api/autoformat package page}.
23
 */
24
export class Autoformat extends Plugin {
25
        /**
26
         * @inheritDoc
27
         */
28
        public static get requires() {
29
                return [ Delete ] as const;
780✔
30
        }
31

32
        /**
33
         * @inheritDoc
34
         */
35
        public static get pluginName() {
36
                return 'Autoformat' as const;
624✔
37
        }
38

39
        /**
40
         * @inheritDoc
41
         */
42
        public static override get isOfficialPlugin(): true {
43
                return true;
156✔
44
        }
156✔
45

156✔
46
        /**
156✔
47
         * @inheritDoc
156✔
48
         */
156✔
49
        public afterInit(): void {
50
                const editor = this.editor;
51
                const t = this.editor.t;
52

53
                this._addListAutoformats();
54
                this._addBasicStylesAutoformats();
55
                this._addHeadingAutoformats();
56
                this._addBlockQuoteAutoformats();
57
                this._addCodeBlockAutoformats();
58
                this._addHorizontalLineAutoformats();
59

60
                // Add the information about the keystroke to the accessibility database.
61
                editor.accessibility.addKeystrokeInfos( {
156✔
62
                        keystrokes: [
63
                                {
156✔
64
                                        label: t( 'Revert autoformatting action' ),
117✔
65
                                        keystroke: 'Backspace'
66
                                }
67
                        ]
156✔
68
                } );
117✔
69
        }
70

71
        /**
156✔
72
         * Adds autoformatting related to the {@link module:list/list~List}.
100✔
73
         *
74
         * When typed:
75
         * - `* ` or `- ` – A paragraph will be changed into a bulleted list.
156✔
76
         * - `1. ` or `1) ` – A paragraph will be changed into a numbered list ("1" can be any digit or a list of digits).
100✔
77
         * - `[] ` or `[ ] ` – A paragraph will be changed into a to-do list.
7✔
78
         * - `[x] ` or `[ x ] ` – A paragraph will be changed into a checked to-do list.
7✔
79
         */
80
        private _addListAutoformats(): void {
81
                const commands = this.editor.commands;
82

83
                if ( commands.get( 'bulletedList' ) ) {
84
                        blockAutoformatEditing( this.editor, this, /^[*-]\s$/, 'bulletedList' );
85
                }
86

87
                if ( commands.get( 'numberedList' ) ) {
88
                        blockAutoformatEditing( this.editor, this, /^1[.|)]\s$/, 'numberedList' );
89
                }
90

91
                if ( commands.get( 'todoList' ) ) {
92
                        blockAutoformatEditing( this.editor, this, /^\[\s?\]\s$/, 'todoList' );
93
                }
94

95
                if ( commands.get( 'checkTodoList' ) ) {
96
                        blockAutoformatEditing( this.editor, this, /^\[\s?x\s?\]\s$/, () => {
97
                                this.editor.execute( 'todoList' );
156✔
98
                                this.editor.execute( 'checkTodoList' );
99
                        } );
156✔
100
                }
116✔
101
        }
102

116✔
103
        /**
116✔
104
         * Adds autoformatting related to the {@link module:basic-styles/bold~Bold},
105
         * {@link module:basic-styles/italic~Italic}, {@link module:basic-styles/code~Code}
106
         * and {@link module:basic-styles/strikethrough~Strikethrough}
156✔
107
         *
113✔
108
         * When typed:
109
         * - `**foobar**` – `**` characters are removed and `foobar` is set to bold,
110
         * - `__foobar__` – `__` characters are removed and `foobar` is set to bold,
111
         * - `*foobar*` – `*` characters are removed and `foobar` is set to italic,
113✔
112
         * - `_foobar_` – `_` characters are removed and `foobar` is set to italic,
113✔
113
         * - ``` `foobar` – ``` ` ``` characters are removed and `foobar` is set to code,
114
         * - `~~foobar~~` – `~~` characters are removed and `foobar` is set to strikethrough.
115
         */
156✔
116
        private _addBasicStylesAutoformats(): void {
113✔
117
                const commands = this.editor.commands;
118

113✔
119
                if ( commands.get( 'bold' ) ) {
120
                        const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );
121

156✔
122
                        inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*\*)([^*]+)(\*\*)$/g, boldCallback );
113✔
123
                        inlineAutoformatEditing( this.editor, this, /(?:^|\s)(__)([^_]+)(__)$/g, boldCallback );
124
                }
113✔
125

126
                if ( commands.get( 'italic' ) ) {
127
                        const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' );
128

129
                        // The italic autoformatter cannot be triggered by the bold markers, so we need to check the
130
                        // text before the pattern (e.g. `(?:^|[^\*])`).
131
                        inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*)([^*_]+)(\*)$/g, italicCallback );
132
                        inlineAutoformatEditing( this.editor, this, /(?:^|\s)(_)([^_]+)(_)$/g, italicCallback );
133
                }
134

135
                if ( commands.get( 'code' ) ) {
136
                        const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' );
137

138
                        inlineAutoformatEditing( this.editor, this, /(`)([^`]+)(`)$/g, codeCallback );
156✔
139
                }
140

156✔
141
                if ( commands.get( 'strikethrough' ) ) {
115✔
142
                        const strikethroughCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'strikethrough' );
342✔
143

144
                        inlineAutoformatEditing( this.editor, this, /(~~)([^~]+)(~~)$/g, strikethroughCallback );
342✔
145
                }
342✔
146
        }
147

342✔
148
        /**
149
         * Adds autoformatting related to {@link module:heading/heading~Heading}.
9✔
150
         *
2✔
151
         * It is using a number at the end of the command name to associate it with the proper trigger:
152
         *
153
         * * `heading` with a `heading1` value will be executed when typing `#`,
7✔
154
         * * `heading` with a `heading2` value will be executed when typing `##`,
155
         * * ... up to `heading6` for `######`.
156
         */
157
        private _addHeadingAutoformats(): void {
158
                const command: HeadingCommand | undefined = this.editor.commands.get( 'heading' );
159

160
                if ( command ) {
161
                        command.modelElements
162
                                .filter( name => name.match( /^heading[1-6]$/ ) )
163
                                .forEach( modelName => {
164
                                        const level = modelName[ 7 ];
165
                                        const pattern = new RegExp( `^(#{${ level }})\\s$` );
166

156✔
167
                                        blockAutoformatEditing( this.editor, this, pattern, () => {
113✔
168
                                                // Should only be active if command is enabled and heading style associated with pattern is inactive.
169
                                                if ( !command.isEnabled || command.value === modelName ) {
170
                                                        return false;
171
                                                }
172

173
                                                this.editor.execute( 'heading', { value: modelName } );
174
                                        } );
175
                                } );
176
                }
177
        }
178

156✔
179
        /**
156✔
180
         * Adds autoformatting related to {@link module:block-quote/blockquote~BlockQuote}.
181
         *
156✔
182
         * When typed:
100✔
183
         * * `> ` – A paragraph will be changed to a block quote.
8✔
184
         */
4✔
185
        private _addBlockQuoteAutoformats(): void {
186
                if ( this.editor.commands.get( 'blockQuote' ) ) {
4✔
187
                        blockAutoformatEditing( this.editor, this, /^>\s$/, 'blockQuote' );
188
                }
189
        }
190

191
        /**
192
         * Adds autoformatting related to {@link module:code-block/codeblock~CodeBlock}.
193
         *
194
         * When typed:
195
         * - `` ``` `` – A paragraph will be changed to a code block.
196
         */
197
        private _addCodeBlockAutoformats(): void {
198
                const editor = this.editor;
199
                const selection = editor.model.document.selection;
200

156✔
201
                if ( editor.commands.get( 'codeBlock' ) ) {
100✔
202
                        blockAutoformatEditing( editor, this, /^```$/, () => {
203
                                if ( selection.getFirstPosition()!.parent.is( 'element', 'listItem' ) ) {
204
                                        return false;
205
                                }
206
                                this.editor.execute( 'codeBlock', {
207
                                        usePreviousLanguageChoice: true
208
                                } );
209
                        } );
210
                }
455✔
211
        }
19✔
212

213
        /**
19✔
214
         * Adds autoformatting related to {@link module:horizontal-line/horizontalline~HorizontalLine}.
1✔
215
         *
216
         * When typed:
217
         * - `` --- `` – Will be replaced with a horizontal line.
18✔
218
         */
219
        private _addHorizontalLineAutoformats(): void {
18✔
220
                if ( this.editor.commands.get( 'horizontalLine' ) ) {
18✔
221
                        blockAutoformatEditing( this.editor, this, /^---$/, 'horizontalLine' );
222
                }
223
        }
224
}
225

18✔
226
/**
227
 * Helper function for getting `inlineAutoformatEditing` callbacks that checks if command is enabled.
228
 */
229
function getCallbackFunctionForInlineAutoformat( editor: Editor, attributeKey: string ) {
230
        return ( writer: ModelWriter, rangesToFormat: Array<ModelRange> ): boolean | undefined => {
231
                const command = editor.commands.get( attributeKey )!;
232

233
                if ( !command.isEnabled ) {
234
                        return false;
235
                }
236

237
                const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
238

239
                for ( const range of validRanges ) {
240
                        writer.setAttribute( attributeKey, true, range );
241
                }
242

243
                // After applying attribute to the text, remove given attribute from the selection.
244
                // This way user is able to type a text without attribute used by auto formatter.
245
                writer.removeSelectionAttribute( attributeKey );
246
        };
247
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc