| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- /**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
- /**
- * @module code-block/outdentcodeblockcommand
- */
- import Command from '@ckeditor/ckeditor5-core/src/command';
- import {
- getLeadingWhiteSpaces,
- getIndentOutdentPositions,
- isModelSelectionInCodeBlock
- } from './utils';
- /**
- * The code block indentation decrease command plugin.
- *
- * @extends module:core/command~Command
- */
- export default class OutdentCodeBlockCommand extends Command {
- constructor( editor ) {
- super( editor );
- /**
- * A sequence of characters removed from the line when the command is executed.
- *
- * @readonly
- * @private
- * @member {String}
- */
- this._indentSequence = editor.config.get( 'codeBlock.indentSequence' );
- }
- /**
- * @inheritDoc
- */
- refresh() {
- this.isEnabled = this._checkEnabled();
- }
- /**
- * Executes the command. When the command {@link #isEnabled is enabled}, the indentation of the
- * code lines in the selection will be decreased.
- *
- * @fires execute
- */
- execute() {
- const editor = this.editor;
- const model = editor.model;
- model.change( writer => {
- const positions = getIndentOutdentPositions( model );
- // Outdent all positions, for instance assuming the indent sequence is 4x space (" "):
- //
- // <codeBlock>^foo</codeBlock> -> <codeBlock>foo</codeBlock>
- //
- // <codeBlock> ^bar</codeBlock> -> <codeBlock>bar</codeBlock>
- //
- // Also, when there is more than one position:
- //
- // <codeBlock>
- // ^foobar
- // <softBreak></softBreak>
- // ^bazqux
- // </codeBlock>
- //
- // ->
- //
- // <codeBlock>
- // foobar
- // <softBreak></softBreak>
- // bazqux
- // </codeBlock>
- for ( const position of positions ) {
- const range = getLastOutdentableSequenceRange( this.editor.model, position, this._indentSequence );
- if ( range ) {
- writer.remove( range );
- }
- }
- } );
- }
- /**
- * Checks whether the command can be enabled in the current context.
- *
- * @private
- * @returns {Boolean} Whether the command should be enabled.
- */
- _checkEnabled() {
- if ( !this._indentSequence ) {
- return false;
- }
- const model = this.editor.model;
- if ( !isModelSelectionInCodeBlock( model.document.selection ) ) {
- return false;
- }
- // Outdent command can execute only when there is an indent character sequence
- // in some of the lines.
- return getIndentOutdentPositions( model ).some( position => {
- return getLastOutdentableSequenceRange( model, position, this._indentSequence );
- } );
- }
- }
- // For a position coming from `getIndentOutdentPositions()`, it returns the range representing
- // the last occurrence of the indent sequence among the leading whitespaces of the code line the
- // position represents.
- //
- // For instance, assuming the indent sequence is 4x space (" "):
- //
- // <codeBlock>foo^</codeBlock> -> null
- // <codeBlock>foo^<softBreak></softBreak>bar</codeBlock> -> null
- // <codeBlock> ^foo</codeBlock> -> null
- // <codeBlock> ^foo</codeBlock> -> <codeBlock> [ ]foo</codeBlock>
- // <codeBlock> ^foo bar</codeBlock> -> <codeBlock>[ ]foo bar</codeBlock>
- //
- // @param {<module:engine/model/model~Model>} model
- // @param {<module:engine/model/position~Position>} position
- // @param {String} sequence
- // @returns {<module:engine/model/range~Range>|null}
- function getLastOutdentableSequenceRange( model, position, sequence ) {
- // Positions start before each text node (code line). Get the node corresponding to the position.
- const nodeAtPosition = getCodeLineTextNodeAtPosition( position );
- if ( !nodeAtPosition ) {
- return null;
- }
- const leadingWhiteSpaces = getLeadingWhiteSpaces( nodeAtPosition );
- const lastIndexOfSequence = leadingWhiteSpaces.lastIndexOf( sequence );
- // For instance, assuming the indent sequence is 4x space (" "):
- //
- // <codeBlock> ^foo</codeBlock> -> null
- //
- if ( lastIndexOfSequence + sequence.length !== leadingWhiteSpaces.length ) {
- return null;
- }
- // For instance, assuming the indent sequence is 4x space (" "):
- //
- // <codeBlock> ^foo</codeBlock> -> null
- //
- if ( lastIndexOfSequence === -1 ) {
- return null;
- }
- const { parent, startOffset } = nodeAtPosition;
- // Create a range that contains the **last** indent sequence among the leading whitespaces
- // of the line.
- //
- // For instance, assuming the indent sequence is 4x space (" "):
- //
- // <codeBlock> ^foo</codeBlock> -> <codeBlock> [ ]foo</codeBlock>
- //
- return model.createRange(
- model.createPositionAt( parent, startOffset + lastIndexOfSequence ),
- model.createPositionAt( parent, startOffset + lastIndexOfSequence + sequence.length )
- );
- }
- function getCodeLineTextNodeAtPosition( position ) {
- // Positions start before each text node (code line). Get the node corresponding to the position.
- let nodeAtPosition = position.parent.getChild( position.index );
- // <codeBlock>foo^</codeBlock>
- // <codeBlock>foo^<softBreak></softBreak>bar</codeBlock>
- if ( !nodeAtPosition || nodeAtPosition.is( 'element', 'softBreak' ) ) {
- nodeAtPosition = position.nodeBefore;
- }
- // <codeBlock>^</codeBlock>
- // <codeBlock>foo^<softBreak></softBreak>bar</codeBlock>
- if ( !nodeAtPosition || nodeAtPosition.is( 'element', 'softBreak' ) ) {
- return null;
- }
- return nodeAtPosition;
- }
|