mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
feat: Add markdown toggle (alt+m) to switch between rendered and raw… (#10383)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -48,6 +48,7 @@ export enum Command {
|
||||
SHOW_ERROR_DETAILS = 'showErrorDetails',
|
||||
TOGGLE_TOOL_DESCRIPTIONS = 'toggleToolDescriptions',
|
||||
TOGGLE_IDE_CONTEXT_DETAIL = 'toggleIDEContextDetail',
|
||||
TOGGLE_MARKDOWN = 'toggleMarkdown',
|
||||
QUIT = 'quit',
|
||||
EXIT = 'exit',
|
||||
SHOW_MORE_LINES = 'showMoreLines',
|
||||
@@ -158,6 +159,7 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
||||
[Command.SHOW_ERROR_DETAILS]: [{ key: 'o', ctrl: true }],
|
||||
[Command.TOGGLE_TOOL_DESCRIPTIONS]: [{ key: 't', ctrl: true }],
|
||||
[Command.TOGGLE_IDE_CONTEXT_DETAIL]: [{ key: 'g', ctrl: true }],
|
||||
[Command.TOGGLE_MARKDOWN]: [{ key: 'm', command: true }],
|
||||
[Command.QUIT]: [{ key: 'c', ctrl: true }],
|
||||
[Command.EXIT]: [{ key: 'd', ctrl: true }],
|
||||
[Command.SHOW_MORE_LINES]: [{ key: 's', ctrl: true }],
|
||||
|
||||
@@ -11,6 +11,7 @@ import { KeypressProvider } from '../ui/contexts/KeypressContext.js';
|
||||
import { SettingsContext } from '../ui/contexts/SettingsContext.js';
|
||||
import { ShellFocusContext } from '../ui/contexts/ShellFocusContext.js';
|
||||
import { UIStateContext, type UIState } from '../ui/contexts/UIStateContext.js';
|
||||
import { StreamingState } from '../ui/types.js';
|
||||
import { ConfigContext } from '../ui/contexts/ConfigContext.js';
|
||||
import { calculateMainAreaWidth } from '../ui/utils/ui-sizing.js';
|
||||
import { VimModeProvider } from '../ui/contexts/VimModeContext.js';
|
||||
@@ -59,6 +60,8 @@ export const createMockSettings = (
|
||||
// A minimal mock UIState to satisfy the context provider.
|
||||
// Tests that need specific UIState values should provide their own.
|
||||
const baseMockUiState = {
|
||||
renderMarkdown: true,
|
||||
streamingState: StreamingState.Idle,
|
||||
mainAreaWidth: 100,
|
||||
terminalWidth: 120,
|
||||
};
|
||||
|
||||
@@ -793,6 +793,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
|
||||
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(false);
|
||||
const [showToolDescriptions, setShowToolDescriptions] =
|
||||
useState<boolean>(false);
|
||||
const [renderMarkdown, setRenderMarkdown] = useState<boolean>(true);
|
||||
|
||||
const [ctrlCPressedOnce, setCtrlCPressedOnce] = useState(false);
|
||||
const ctrlCTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -973,6 +974,13 @@ Logging in with Google... Please restart Gemini CLI to continue.
|
||||
if (Object.keys(mcpServers || {}).length > 0) {
|
||||
handleSlashCommand(newValue ? '/mcp desc' : '/mcp nodesc');
|
||||
}
|
||||
} else if (keyMatchers[Command.TOGGLE_MARKDOWN](key)) {
|
||||
setRenderMarkdown((prev) => {
|
||||
const newValue = !prev;
|
||||
// Force re-render of static content
|
||||
refreshStatic();
|
||||
return newValue;
|
||||
});
|
||||
} else if (
|
||||
keyMatchers[Command.TOGGLE_IDE_CONTEXT_DETAIL](key) &&
|
||||
config.getIdeMode() &&
|
||||
@@ -1011,6 +1019,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
|
||||
activePtyId,
|
||||
embeddedShellFocused,
|
||||
settings.merged.general?.debugKeystrokeLogging,
|
||||
refreshStatic,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1139,6 +1148,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
|
||||
filteredConsoleMessages,
|
||||
ideContextState,
|
||||
showToolDescriptions,
|
||||
renderMarkdown,
|
||||
ctrlCPressedOnce,
|
||||
ctrlDPressedOnce,
|
||||
showEscapePrompt,
|
||||
@@ -1221,6 +1231,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
|
||||
filteredConsoleMessages,
|
||||
ideContextState,
|
||||
showToolDescriptions,
|
||||
renderMarkdown,
|
||||
ctrlCPressedOnce,
|
||||
ctrlDPressedOnce,
|
||||
showEscapePrompt,
|
||||
|
||||
@@ -112,6 +112,7 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
|
||||
ideContextState: null,
|
||||
geminiMdFileCount: 0,
|
||||
showToolDescriptions: false,
|
||||
renderMarkdown: true,
|
||||
filteredConsoleMessages: [],
|
||||
sessionStats: {
|
||||
lastPromptTokenCount: 0,
|
||||
@@ -403,6 +404,26 @@ describe('Composer', () => {
|
||||
|
||||
expect(lastFrame()).toContain('ShellModeIndicator');
|
||||
});
|
||||
|
||||
it('shows RawMarkdownIndicator when renderMarkdown is false', () => {
|
||||
const uiState = createMockUIState({
|
||||
renderMarkdown: false,
|
||||
});
|
||||
|
||||
const { lastFrame } = renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).toContain('raw markdown mode');
|
||||
});
|
||||
|
||||
it('does not show RawMarkdownIndicator when renderMarkdown is true', () => {
|
||||
const uiState = createMockUIState({
|
||||
renderMarkdown: true,
|
||||
});
|
||||
|
||||
const { lastFrame } = renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).not.toContain('raw markdown mode');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Details Display', () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ContextSummaryDisplay } from './ContextSummaryDisplay.js';
|
||||
import { AutoAcceptIndicator } from './AutoAcceptIndicator.js';
|
||||
import { ShellModeIndicator } from './ShellModeIndicator.js';
|
||||
import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
|
||||
import { RawMarkdownIndicator } from './RawMarkdownIndicator.js';
|
||||
import { InputPrompt } from './InputPrompt.js';
|
||||
import { Footer } from './Footer.js';
|
||||
import { ShowMoreLines } from './ShowMoreLines.js';
|
||||
@@ -110,6 +111,7 @@ export const Composer = () => {
|
||||
<AutoAcceptIndicator approvalMode={showAutoAcceptIndicator} />
|
||||
)}
|
||||
{uiState.shellModeActive && <ShellModeIndicator />}
|
||||
{!uiState.renderMarkdown && <RawMarkdownIndicator />}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
21
packages/cli/src/ui/components/RawMarkdownIndicator.tsx
Normal file
21
packages/cli/src/ui/components/RawMarkdownIndicator.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
export const RawMarkdownIndicator: React.FC = () => {
|
||||
const modKey = process.platform === 'darwin' ? 'option+m' : 'alt+m';
|
||||
return (
|
||||
<Box>
|
||||
<Text>
|
||||
raw markdown mode
|
||||
<Text color={theme.text.secondary}> ({modKey} to toggle) </Text>
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { GeminiMessage } from './GeminiMessage.js';
|
||||
import { StreamingState } from '../../types.js';
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
|
||||
describe('<GeminiMessage /> - Raw Markdown Display Snapshots', () => {
|
||||
const baseProps = {
|
||||
text: 'Test **bold** and `code` markdown\n\n```javascript\nconst x = 1;\n```',
|
||||
isPending: false,
|
||||
terminalWidth: 80,
|
||||
};
|
||||
|
||||
it.each([
|
||||
{ renderMarkdown: true, description: '(default)' },
|
||||
{
|
||||
renderMarkdown: false,
|
||||
description: '(raw markdown with syntax highlighting, no line numbers)',
|
||||
},
|
||||
])(
|
||||
'renders with renderMarkdown=$renderMarkdown $description',
|
||||
({ renderMarkdown }) => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<GeminiMessage {...baseProps} />,
|
||||
{
|
||||
uiState: { renderMarkdown, streamingState: StreamingState.Idle },
|
||||
},
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
|
||||
it.each([{ renderMarkdown: true }, { renderMarkdown: false }])(
|
||||
'renders pending state with renderMarkdown=$renderMarkdown',
|
||||
({ renderMarkdown }) => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<GeminiMessage {...baseProps} isPending={true} />,
|
||||
{
|
||||
uiState: { renderMarkdown, streamingState: StreamingState.Idle },
|
||||
},
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -9,6 +9,7 @@ import { Text, Box } from 'ink';
|
||||
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js';
|
||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
|
||||
interface GeminiMessageProps {
|
||||
text: string;
|
||||
@@ -23,6 +24,7 @@ export const GeminiMessage: React.FC<GeminiMessageProps> = ({
|
||||
availableTerminalHeight,
|
||||
terminalWidth,
|
||||
}) => {
|
||||
const { renderMarkdown } = useUIState();
|
||||
const prefix = '✦ ';
|
||||
const prefixWidth = prefix.length;
|
||||
|
||||
@@ -39,6 +41,7 @@ export const GeminiMessage: React.FC<GeminiMessageProps> = ({
|
||||
isPending={isPending}
|
||||
availableTerminalHeight={availableTerminalHeight}
|
||||
terminalWidth={terminalWidth}
|
||||
renderMarkdown={renderMarkdown}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import type React from 'react';
|
||||
import { Box } from 'ink';
|
||||
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
|
||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
|
||||
interface GeminiMessageContentProps {
|
||||
text: string;
|
||||
@@ -27,6 +28,7 @@ export const GeminiMessageContent: React.FC<GeminiMessageContentProps> = ({
|
||||
availableTerminalHeight,
|
||||
terminalWidth,
|
||||
}) => {
|
||||
const { renderMarkdown } = useUIState();
|
||||
const originalPrefix = '✦ ';
|
||||
const prefixWidth = originalPrefix.length;
|
||||
|
||||
@@ -37,6 +39,7 @@ export const GeminiMessageContent: React.FC<GeminiMessageContentProps> = ({
|
||||
isPending={isPending}
|
||||
availableTerminalHeight={availableTerminalHeight}
|
||||
terminalWidth={terminalWidth}
|
||||
renderMarkdown={renderMarkdown}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from 'ink-testing-library';
|
||||
import type { ToolMessageProps } from './ToolMessage.js';
|
||||
import { ToolMessage } from './ToolMessage.js';
|
||||
import { StreamingState, ToolCallStatus } from '../../types.js';
|
||||
import { Text } from 'ink';
|
||||
import { StreamingContext } from '../../contexts/StreamingContext.js';
|
||||
import type { AnsiOutput } from '@google/gemini-cli-core';
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
|
||||
vi.mock('../TerminalOutput.js', () => ({
|
||||
TerminalOutput: function MockTerminalOutput({
|
||||
@@ -72,7 +72,7 @@ const renderWithContext = (
|
||||
streamingState: StreamingState,
|
||||
) => {
|
||||
const contextValue: StreamingState = streamingState;
|
||||
return render(
|
||||
return renderWithProviders(
|
||||
<StreamingContext.Provider value={contextValue}>
|
||||
{ui}
|
||||
</StreamingContext.Provider>,
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '../../constants.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import type { AnsiOutput, Config } from '@google/gemini-cli-core';
|
||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
|
||||
const STATIC_HEIGHT = 1;
|
||||
const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc.
|
||||
@@ -56,6 +57,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
||||
ptyId,
|
||||
config,
|
||||
}) => {
|
||||
const { renderMarkdown } = useUIState();
|
||||
const isThisShellFocused =
|
||||
(name === SHELL_COMMAND_NAME || name === 'Shell') &&
|
||||
status === ToolCallStatus.Executing &&
|
||||
@@ -149,6 +151,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
||||
isPending={false}
|
||||
availableTerminalHeight={availableHeight}
|
||||
terminalWidth={childWidth}
|
||||
renderMarkdown={renderMarkdown}
|
||||
/>
|
||||
</Box>
|
||||
) : typeof resultDisplay === 'string' && !renderOutputAsMarkdown ? (
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { ToolMessageProps } from './ToolMessage.js';
|
||||
import { ToolMessage } from './ToolMessage.js';
|
||||
import { StreamingState, ToolCallStatus } from '../../types.js';
|
||||
import { StreamingContext } from '../../contexts/StreamingContext.js';
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
|
||||
describe('<ToolMessage /> - Raw Markdown Display Snapshots', () => {
|
||||
const baseProps: ToolMessageProps = {
|
||||
callId: 'tool-123',
|
||||
name: 'test-tool',
|
||||
description: 'A tool for testing',
|
||||
resultDisplay: 'Test **bold** and `code` markdown',
|
||||
status: ToolCallStatus.Success,
|
||||
terminalWidth: 80,
|
||||
confirmationDetails: undefined,
|
||||
emphasis: 'medium',
|
||||
};
|
||||
|
||||
it.each([
|
||||
{ renderMarkdown: true, description: '(default)' },
|
||||
{
|
||||
renderMarkdown: false,
|
||||
description: '(raw markdown with syntax highlighting, no line numbers)',
|
||||
},
|
||||
])(
|
||||
'renders with renderMarkdown=$renderMarkdown $description',
|
||||
({ renderMarkdown }) => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<StreamingContext.Provider value={StreamingState.Idle}>
|
||||
<ToolMessage {...baseProps} />
|
||||
</StreamingContext.Provider>,
|
||||
{
|
||||
uiState: { renderMarkdown, streamingState: StreamingState.Idle },
|
||||
},
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<GeminiMessage /> - Raw Markdown Display Snapshots > renders pending state with renderMarkdown=false 1`] = `
|
||||
"✦ Test **bold** and \`code\` markdown
|
||||
|
||||
\`\`\`javascript
|
||||
const x = 1;
|
||||
\`\`\`"
|
||||
`;
|
||||
|
||||
exports[`<GeminiMessage /> - Raw Markdown Display Snapshots > renders pending state with renderMarkdown=true 1`] = `
|
||||
"✦ Test bold and code markdown
|
||||
|
||||
1 const x = 1;"
|
||||
`;
|
||||
|
||||
exports[`<GeminiMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=false '(raw markdown with syntax highlightin…' 1`] = `
|
||||
"✦ Test **bold** and \`code\` markdown
|
||||
|
||||
\`\`\`javascript
|
||||
const x = 1;
|
||||
\`\`\`"
|
||||
`;
|
||||
|
||||
exports[`<GeminiMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true '(default)' 1`] = `
|
||||
"✦ Test bold and code markdown
|
||||
|
||||
1 const x = 1;"
|
||||
`;
|
||||
@@ -0,0 +1,13 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=false '(raw markdown with syntax highlightin…' 1`] = `
|
||||
" ✓ test-tool A tool for testing
|
||||
|
||||
Test **bold** and \`code\` markdown"
|
||||
`;
|
||||
|
||||
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true '(default)' 1`] = `
|
||||
" ✓ test-tool A tool for testing
|
||||
|
||||
Test bold and code markdown"
|
||||
`;
|
||||
@@ -4,10 +4,10 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from 'ink-testing-library';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ToolsList } from './ToolsList.js';
|
||||
import { type ToolDefinition } from '../../types.js';
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
|
||||
const mockTools: ToolDefinition[] = [
|
||||
{
|
||||
@@ -32,7 +32,7 @@ const mockTools: ToolDefinition[] = [
|
||||
|
||||
describe('<ToolsList />', () => {
|
||||
it('renders correctly with descriptions', () => {
|
||||
const { lastFrame } = render(
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<ToolsList
|
||||
tools={mockTools}
|
||||
showDescriptions={true}
|
||||
@@ -43,7 +43,7 @@ describe('<ToolsList />', () => {
|
||||
});
|
||||
|
||||
it('renders correctly without descriptions', () => {
|
||||
const { lastFrame } = render(
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<ToolsList
|
||||
tools={mockTools}
|
||||
showDescriptions={false}
|
||||
@@ -54,7 +54,7 @@ describe('<ToolsList />', () => {
|
||||
});
|
||||
|
||||
it('renders correctly with no tools', () => {
|
||||
const { lastFrame } = render(
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<ToolsList tools={[]} showDescriptions={true} terminalWidth={40} />,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
|
||||
@@ -82,6 +82,7 @@ export interface UIState {
|
||||
filteredConsoleMessages: ConsoleMessageItem[];
|
||||
ideContextState: IdeContext | undefined;
|
||||
showToolDescriptions: boolean;
|
||||
renderMarkdown: boolean;
|
||||
ctrlCPressedOnce: boolean;
|
||||
ctrlDPressedOnce: boolean;
|
||||
showEscapePrompt: boolean;
|
||||
|
||||
@@ -55,6 +55,7 @@ describe('keyMatchers', () => {
|
||||
key.ctrl && key.name === 't',
|
||||
[Command.TOGGLE_IDE_CONTEXT_DETAIL]: (key: Key) =>
|
||||
key.ctrl && key.name === 'g',
|
||||
[Command.TOGGLE_MARKDOWN]: (key: Key) => key.meta && key.name === 'm',
|
||||
[Command.QUIT]: (key: Key) => key.ctrl && key.name === 'c',
|
||||
[Command.EXIT]: (key: Key) => key.ctrl && key.name === 'd',
|
||||
[Command.SHOW_MORE_LINES]: (key: Key) => key.ctrl && key.name === 's',
|
||||
@@ -225,6 +226,11 @@ describe('keyMatchers', () => {
|
||||
positive: [createKey('g', { ctrl: true })],
|
||||
negative: [createKey('g'), createKey('t', { ctrl: true })],
|
||||
},
|
||||
{
|
||||
command: Command.TOGGLE_MARKDOWN,
|
||||
positive: [createKey('m', { meta: true })],
|
||||
negative: [createKey('m'), createKey('m', { shift: true })],
|
||||
},
|
||||
{
|
||||
command: Command.QUIT,
|
||||
positive: [createKey('c', { ctrl: true })],
|
||||
|
||||
@@ -132,10 +132,13 @@ export function colorizeCode(
|
||||
maxWidth?: number,
|
||||
theme?: Theme,
|
||||
settings?: LoadedSettings,
|
||||
hideLineNumbers?: boolean,
|
||||
): React.ReactNode {
|
||||
const codeToHighlight = code.replace(/\n$/, '');
|
||||
const activeTheme = theme || themeManager.getActiveTheme();
|
||||
const showLineNumbers = settings?.merged.ui?.showLineNumbers ?? true;
|
||||
const showLineNumbers = hideLineNumbers
|
||||
? false
|
||||
: (settings?.merged.ui?.showLineNumbers ?? true);
|
||||
|
||||
try {
|
||||
// Render the HAST tree using the adapted theme
|
||||
|
||||
@@ -17,6 +17,7 @@ interface MarkdownDisplayProps {
|
||||
isPending: boolean;
|
||||
availableTerminalHeight?: number;
|
||||
terminalWidth: number;
|
||||
renderMarkdown?: boolean;
|
||||
}
|
||||
|
||||
// Constants for Markdown parsing and rendering
|
||||
@@ -31,9 +32,31 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
|
||||
isPending,
|
||||
availableTerminalHeight,
|
||||
terminalWidth,
|
||||
renderMarkdown = true,
|
||||
}) => {
|
||||
const settings = useSettings();
|
||||
|
||||
if (!text) return <></>;
|
||||
|
||||
// Raw markdown mode - display syntax-highlighted markdown without rendering
|
||||
if (!renderMarkdown) {
|
||||
// Hide line numbers in raw markdown mode as they are confusing due to chunked output
|
||||
const colorizedMarkdown = colorizeCode(
|
||||
text,
|
||||
'markdown',
|
||||
availableTerminalHeight,
|
||||
terminalWidth - CODE_BLOCK_PREFIX_PADDING,
|
||||
undefined,
|
||||
settings,
|
||||
true, // hideLineNumbers
|
||||
);
|
||||
return (
|
||||
<Box paddingLeft={CODE_BLOCK_PREFIX_PADDING} flexDirection="column">
|
||||
{colorizedMarkdown}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const lines = text.split(/\r?\n/);
|
||||
const headerRegex = /^ *(#{1,4}) +(.*)/;
|
||||
const codeFenceRegex = /^ *(`{3,}|~{3,}) *(\w*?) *$/;
|
||||
|
||||
Reference in New Issue
Block a user