Skip to content

Commit

Permalink
feat(code-textarea): add line numbers (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrBulanek authored Jan 21, 2025
1 parent 02fd490 commit 83f03f2
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,40 @@
grid-column: 1 /-1;
border-radius: inherit;
border: 0;
word-break: break-all;
overflow: hidden;
overflow-y: hidden;
overflow-x: auto;
padding: $spacing-05;
white-space: pre;
}

> .textarea {
padding-inline-start: calc(var(--line-number-width) + #{$spacing-05});
}

code {
display: block;
inline-size: 100%;
white-space: pre-wrap !important;
min-inline-size: 100%;
inline-size: max-content;
white-space: pre !important;
}

:global(.linenumber) {
min-inline-size: var(--line-number-width) !important;
white-space: nowrap;
text-align: start !important;
padding-inline-end: $spacing-07 !important;
position: sticky;
inset-inline-start: 0;
&::before {
content: '';
inline-size: 100%;
background-color: var(--highlight-background);
position: absolute;
inset-block: 0;
inset-inline: -$spacing-05;
z-index: -1;
border-inline-end: 1px solid $border-subtle-00;
}
}

&.invalid {
Expand All @@ -76,6 +101,13 @@
padding-inline-end: $spacing-10 !important;
}
}

&:focus-within :global(.linenumber) {
&::before {
inline-size: calc(100% - 2px);
inset-inline-start: calc(2px - $spacing-05);
}
}
}

.textarea {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@
import { FormLabel } from '@carbon/react';
import { WarningFilled } from '@carbon/react/icons';
import clsx from 'clsx';
import { CSSProperties, ReactNode, TextareaHTMLAttributes } from 'react';
import {
CSSProperties,
ReactNode,
TextareaHTMLAttributes,
useEffect,
useRef,
useState,
} from 'react';
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
import python from 'react-syntax-highlighter/dist/cjs/languages/hljs/python';
import defaultStyle from 'react-syntax-highlighter/dist/cjs/styles/hljs/default-style';
import { useResizeObserver } from 'usehooks-ts';
import classes from './EditableSyntaxHighlighter.module.scss';

const style: { [key: string]: CSSProperties } = {
Expand Down Expand Up @@ -122,7 +130,7 @@ interface Props
onChange?: (value: string) => void;
invalid?: boolean;
readOnly?: boolean;
// showLineNumbers?: boolean;
showLineNumbers?: boolean;
}

export function EditableSyntaxHighlighter({
Expand All @@ -132,27 +140,92 @@ export function EditableSyntaxHighlighter({
onChange,
invalid,
readOnly,
showLineNumbers,
className,
// showLineNumbers,
...props
}: Props) {
const [lineNumberWidth, setLineNumberWidth] = useState<number>(0);
const rootRef = useRef<HTMLDivElement>(null);
const preRef = useRef<HTMLPreElement>(null);
const textAreaRef = useRef<HTMLTextAreaElement>(null);

const PreTag = (preProps: any) => <pre {...preProps} ref={preRef} />;

const syncScroll = () => {
requestAnimationFrame(() => {
const preElement = preRef.current;
const textAreaElement = textAreaRef.current;

if (preElement && textAreaElement) {
preElement.scrollLeft = textAreaElement.scrollLeft;
}
});
};

const checkLineNumberWidth = () => {
if (!preRef.current) {
return;
}

const lineNumberElement = [
...preRef.current.querySelectorAll('.linenumber'),
].at(-1);

setLineNumberWidth(
lineNumberElement ? (lineNumberElement as HTMLElement).offsetWidth : 0,
);
};

useEffect(() => {
const textAreaElement = textAreaRef.current;

if (textAreaElement) {
textAreaElement.addEventListener('scroll', syncScroll);
textAreaElement.addEventListener('input', syncScroll);
textAreaElement.addEventListener('paste', syncScroll);
}

return () => {
if (textAreaElement) {
textAreaElement.removeEventListener('scroll', syncScroll);
textAreaElement.removeEventListener('input', syncScroll);
textAreaElement.removeEventListener('paste', syncScroll);
}
};
}, []);

useEffect(() => {
checkLineNumberWidth();
}, [value]);

useResizeObserver({
ref: rootRef,
onResize: checkLineNumberWidth,
});

return (
<div className={clsx(classes.root, className)}>
<div className={clsx(classes.root, className)} ref={rootRef}>
{labelText && <FormLabel id={id}>{labelText}</FormLabel>}

<div className={clsx(classes.wrapper, { [classes.invalid]: invalid })}>
<div
className={clsx(classes.wrapper, { [classes.invalid]: invalid })}
style={
{ [`--line-number-width`]: `${lineNumberWidth}px` } as CSSProperties
}
>
<SyntaxHighlighter
PreTag={PreTag}
language="python"
style={style}
// TODO: resolve text wrapping
// showLineNumbers={showLineNumbers}
showLineNumbers={showLineNumbers}
>
{value.at(-1) === '\n' ? `${value} ` : value}
</SyntaxHighlighter>

{!readOnly && (
<textarea
{...props}
ref={textAreaRef}
id={id}
className={classes.textarea}
value={value}
Expand Down
2 changes: 1 addition & 1 deletion src/modules/apps/builder/SourceCodeEditor.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
block-size: 100%;
inline-size: 100%;
overflow-y: auto;
padding-block-end: $spacing-08;
padding-block-end: $spacing-10;
background-color: $layer-02;

> div {
Expand Down
7 changes: 4 additions & 3 deletions src/modules/apps/builder/SourceCodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
*/

import { EditableSyntaxHighlighter } from '@/components/EditableSyntaxHighlighter/EditableSyntaxHighlighter';
import classes from './SourceCodeEditor.module.scss';
import { useChat } from '@/modules/chat/providers/chat-context';
import { Button } from '@carbon/react';
import { useEffect, useId, useRef, useState } from 'react';
import { useAppBuilder, useAppBuilderApi } from './AppBuilderProvider';
import { Button } from '@carbon/react';
import { useChat } from '@/modules/chat/providers/chat-context';
import classes from './SourceCodeEditor.module.scss';

interface Props {
onSaveCode: () => void;
Expand Down Expand Up @@ -52,6 +52,7 @@ export function SourceCodeEditor({ onSaveCode }: Props) {
required
rows={16}
readOnly={status === 'fetching'}
showLineNumbers
/>
</div>
<div className={classes.buttons}>
Expand Down
3 changes: 3 additions & 0 deletions src/modules/tools/manage/UserToolModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ export function UserToolModal({
invalid={errors.api?.schema != null}
rows={16}
className={classes.apiSchemaField}
showLineNumbers
/>
</>
)}
Expand Down Expand Up @@ -309,6 +310,7 @@ export function UserToolModal({
required
invalid={errors.sourceCode != null}
rows={16}
showLineNumbers
/>
)}
/>
Expand Down Expand Up @@ -469,6 +471,7 @@ UserToolModal.View = function ViewUserToolModal({
readOnly
rows={16}
className={type === 'api' ? classes.apiSchemaField : undefined}
showLineNumbers
/>
</ModalBody>
</Modal>
Expand Down

0 comments on commit 83f03f2

Please sign in to comment.