Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code Block: add keyboard shortcut to indent lines inside code block #39509

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions packages/block-library/src/code/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,49 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { RichText, useBlockProps } from '@wordpress/block-editor';
import {
RichText,
useBlockProps,
RichTextShortcut,
} from '@wordpress/block-editor';
import { __unstableIndentCode } from '@wordpress/rich-text';

export default function CodeEdit( { attributes, setAttributes, onRemove } ) {
export default function CodeEdit( {
attributes,
setAttributes,
onRemove,
onReplace,
} ) {
const blockProps = useBlockProps();

const controls = ( { value, onChange } ) => {
return (
<>
<RichTextShortcut
type="primary"
character="]"
onUse={ () => {
onChange( __unstableIndentCode( value ) );
} }
/>
</>
);
};
return (
<pre { ...blockProps }>
<RichText
tagName="code"
value={ attributes.content }
onChange={ ( content ) => setAttributes( { content } ) }
onReplace={ onReplace }
onRemove={ onRemove }
placeholder={ __( 'Write code…' ) }
aria-label={ __( 'Code' ) }
preserveWhiteSpace
__unstablePastePlainText
/>
>
{ controls }
</RichText>
</pre>
);
}
31 changes: 31 additions & 0 deletions packages/rich-text/src/can-indent-code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Internal dependencies
*/
import { getLineIndex } from './get-line-index';

/** @typedef {import('./create').RichTextValue} RichTextValue */

/**
* Checks if the selected list item can be indented.
*
* @param {RichTextValue} value Value to check.
*
* @return {boolean} Whether or not the selected list item can be indented.
*/
export function canIndentCode( value ) {
const lineIndex = getLineIndex( value );

// There is only one line, so the line cannot be indented.
if ( lineIndex === undefined ) {
return false;
}

const { replacements } = value;
const previousLineIndex = getLineIndex( value, lineIndex );
const formatsAtLineIndex = replacements[ lineIndex ] || [];
const formatsAtPreviousLineIndex = replacements[ previousLineIndex ] || [];

// If the indentation of the current line is greater than previous line,
// then the line cannot be furter indented.
return formatsAtLineIndex.length <= formatsAtPreviousLineIndex.length;
}
6 changes: 5 additions & 1 deletion packages/rich-text/src/get-line-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import { LINE_SEPARATOR } from './special-characters';
* @return {number|void} The line index. Undefined if not found.
*/
export function getLineIndex( { start, text }, startIndex = start ) {
const NEW_LINE_CHARACTER = '\n';
let index = startIndex;

while ( index-- ) {
if ( text[ index ] === LINE_SEPARATOR ) {
if (
text[ index ] === LINE_SEPARATOR ||
text[ index ] === NEW_LINE_CHARACTER
) {
return index;
}
}
Expand Down
42 changes: 42 additions & 0 deletions packages/rich-text/src/indent-code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Internal dependencies
*/

import { canIndentCode } from './can-indent-code';
import { insert } from './insert';

/** @typedef {import('./create').RichTextValue} RichTextValue */

/**
* Indents any selected list items if possible.
*
* @param {RichTextValue} value Value to change.
*
* @return {RichTextValue} The changed value.
*/
export function indentCode( value ) {
if ( ! canIndentCode( value ) ) {
return value;
}

const { start, end, text } = value;

const selectedText = text.slice( start, end );
// The first line should be indented, even if it starts with `\n`
// The last line should only be indented if includes any character after `\n`
const lineBreakCount = /\n/g.exec( selectedText )?.length;

if ( lineBreakCount > 0 ) {
// Select full first line to replace everything at once
const firstLineStart = text.lastIndexOf( '\n', start - 1 ) + 1;

const newSelection = text.slice( firstLineStart, end - 1 );
const indentedText = newSelection.replace(
/^|\n/g, // Match all line starts
'$&\t'
);

return insert( value, indentedText, firstLineStart, end - 1 );
}
return insert( value, '\t' );
}
1 change: 1 addition & 0 deletions packages/rich-text/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export { indentListItems as __unstableIndentListItems } from './indent-list-item
export { outdentListItems as __unstableOutdentListItems } from './outdent-list-items';
export { changeListType as __unstableChangeListType } from './change-list-type';
export { createElement as __unstableCreateElement } from './create-element';
export { indentCode as __unstableIndentCode } from './indent-code';

export { useAnchorRef } from './component/use-anchor-ref';

Expand Down