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

fix(EuiCode): fix code block copy button not including the last character #6794

Merged
merged 8 commits into from
Jun 2, 2023
4 changes: 1 addition & 3 deletions src/components/code/code_block_copy.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ describe('EuiCodeBlock copy UX', () => {
cy.realMount(<EuiCodeBlock isCopyable>{questionContent}</EuiCodeBlock>);

cy.get('[data-test-subj="euiCodeBlockCopy"]').realClick();
// TODO: Remove incorrect assertion and uncomment correct assertion once bug is fixed
assertClipboardContentEquals('hello\n\nworld');
// assertClipboardContentEquals(questionContent);
assertClipboardContentEquals(questionContent);
});
});
10 changes: 9 additions & 1 deletion src/components/code/code_block_copy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useInnerText } from '../inner_text';
import { EuiCopy } from '../copy';
import { useEuiI18n } from '../i18n';
import { EuiButtonIcon } from '../button';
import { NEW_LINE_REGEX_GLOBAL } from './utils';

/**
* Hook that returns copy-related state/logic/utils
Expand All @@ -26,7 +27,14 @@ export const useCopy = ({
}) => {
const [innerTextRef, _innerText] = useInnerText('');
const innerText = useMemo(
() => _innerText?.replace(/[\r\n?]{2}|\n\n/g, '\n') || '',
() =>
_innerText
// Normalize line terminations to match native JS format
?.replace(NEW_LINE_REGEX_GLOBAL, '\n')
// Reduce two or more consecutive new line characters to a single one
// This is needed primarily because of how syntax highlighting
// generated DOM elements affect `innerText` output.
.replace(/\n{2,}/g, '\n') || '',
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
[_innerText]
);
const textToCopy = isVirtualized ? `${children}` : innerText; // Virtualized code blocks do not have inner text
Expand Down
20 changes: 18 additions & 2 deletions src/components/code/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ export type EuiCodeSharedProps = CommonProps &
export const SUPPORTED_LANGUAGES = listLanguages();
export const DEFAULT_LANGUAGE = 'text';

/**
* Platform-agnostic new line regex that safely matches all standard
* line termination conventions:
* - LF: Unix-based platforms and JS-native sources like text areas
* - CRLF: Windows
* - CR: Mac Classic; to support files saved a long time ago
*/
export const NEW_LINE_REGEX = /\r\n|\r|\n/;

/**
* Platform-agnostic global new line regex that safely matches all standard
* line termination conventions.
* See [NEW_LINE_REGEX]{@link NEW_LINE_REGEX} for more details.
*/
export const NEW_LINE_REGEX_GLOBAL = new RegExp(NEW_LINE_REGEX, 'g');

export const checkSupportedLanguage = (language: string): string => {
return SUPPORTED_LANGUAGES.includes(language) ? language : DEFAULT_LANGUAGE;
};
Expand Down Expand Up @@ -143,12 +159,12 @@ const addLineData = (
return nodes.reduce<ExtendedRefractorNode[]>((result, node) => {
const lineStart = data.lineNumber;
if (node.type === 'text') {
if (!node.value.match(/\r\n?|\n/)) {
if (!node.value.match(NEW_LINE_REGEX)) {
node.lineStart = lineStart;
node.lineEnd = lineStart;
result.push(node);
} else {
const lines = node.value.split(/\r\n?|\n/);
const lines = node.value.split(NEW_LINE_REGEX);
lines.forEach((line, i) => {
const num = i === 0 ? data.lineNumber : ++data.lineNumber;
result.push({
Expand Down
3 changes: 3 additions & 0 deletions upcoming_changelogs/6794.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**Bug fixes**

- Fixed `EuiCodeBlock` potentially incorrectly ignoring lines ending with a question mark when using the Copy button.