import {theme, vars} from 'app/client/ui2018/cssVars'; import {Theme} from 'app/common/ThemePrefs'; import {getGristConfig} from 'app/common/urlUtils'; import * as ace from 'brace'; import {BindableValue, Computed, dom, DomElementArg, Observable, styled, subscribeElem} from 'grainjs'; // tslint:disable:no-var-requires require('brace/ext/static_highlight'); require("brace/mode/python"); require("brace/theme/chrome"); require('brace/theme/dracula'); export interface ICodeOptions { gristTheme: Computed; placeholder?: string; maxLines?: number; } export function buildHighlightedCode( code: BindableValue, options: ICodeOptions, ...args: DomElementArg[] ): HTMLElement { const {gristTheme, placeholder, maxLines} = options; const {enableCustomCss} = getGristConfig(); const highlighter = ace.acequire('ace/ext/static_highlight'); const PythonMode = ace.acequire('ace/mode/python').Mode; const chrome = ace.acequire('ace/theme/chrome'); const dracula = ace.acequire('ace/theme/dracula'); const mode = new PythonMode(); const codeText = Observable.create(null, ''); const codeTheme = Observable.create(null, gristTheme.get()); function updateHighlightedCode(elem: HTMLElement) { let text = codeText.get(); if (text) { if (maxLines) { // If requested, trim to maxLines, and add an ellipsis at the end. // (Long lines are also truncated with an ellpsis via text-overflow style.) const lines = text.split(/\n/); if (lines.length > maxLines) { text = lines.slice(0, maxLines).join("\n") + " \u2026"; // Ellipsis } } const aceTheme = codeTheme.get().appearance === 'dark' && !enableCustomCss ? dracula : chrome; elem.innerHTML = highlighter.render(text, mode, aceTheme, 1, true).html; } else { elem.textContent = placeholder || ''; } } return cssHighlightedCode( dom.autoDispose(codeText), dom.autoDispose(codeTheme), elem => subscribeElem(elem, code, (newCodeText) => { codeText.set(newCodeText); updateHighlightedCode(elem); }), elem => subscribeElem(elem, gristTheme, (newCodeTheme) => { codeTheme.set(newCodeTheme); updateHighlightedCode(elem); }), ...args, ); } // Use a monospace font, a subset of what ACE editor seems to use. export const cssCodeBlock = styled('div', ` font-family: 'Monaco', 'Menlo', monospace; font-size: ${vars.smallFontSize}; background-color: ${theme.highlightedCodeBlockBg}; &[disabled], &.disabled { background-color: ${theme.highlightedCodeBlockBgDisabled}; } `); const cssHighlightedCode = styled(cssCodeBlock, ` position: relative; white-space: pre; overflow: hidden; text-overflow: ellipsis; border: 1px solid ${theme.highlightedCodeBorder}; border-radius: 3px; min-height: 28px; padding: 5px 6px; color: ${theme.highlightedCodeFg}; &.disabled, &.disabled .ace-chrome, &.disabled .ace-dracula { background-color: ${theme.highlightedCodeBgDisabled}; } & .ace_line { overflow: hidden; text-overflow: ellipsis; } `); export const cssFieldFormula = styled(buildHighlightedCode, ` flex: auto; cursor: pointer; margin-top: 4px; padding-left: 24px; --icon-color: ${theme.accentIcon}; &-disabled-icon.formula_field_sidepane::before { --icon-color: ${theme.iconDisabled}; } &-disabled { pointer-events: none; } `);