Skip to content

Commit

Permalink
Add syntax highlighting for placeholders and tags (mozilla#2879)
Browse files Browse the repository at this point in the history
Co-authored-by: Matjaž Horvat <matjaz.horvat@gmail.com>
  • Loading branch information
eemeli and mathjazz authored Jun 19, 2023
1 parent 69a2076 commit 9220827
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 2 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions translate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@fluent/langneg": "^0.7.0",
"@fluent/react": "^0.15.1",
"@fluent/syntax": "^0.19.0",
"@lezer/highlight": "^1.1.6",
"@messageformat/fluent": "^0.4.1",
"@reduxjs/toolkit": "^1.6.1",
"classnames": "^2.3.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Locale } from '~/context/Locale';
import { useReadonlyEditor } from '~/hooks/useReadonlyEditor';

import { getExtensions, useKeyHandlers } from '../utils/editFieldExtensions';
import { EntityView } from '~/context/EntityView';

export type EditFieldProps = {
index: number;
Expand All @@ -38,13 +39,14 @@ export const EditField = memo(
const { l10n } = useLocalization();
const locale = useContext(Locale);
const readOnly = useReadonlyEditor();
const { entity } = useContext(EntityView);
const { setResultFromInput } = useContext(EditorActions);
const keyHandlers = useKeyHandlers();
const [view, setView] = useState<EditorView | null>(null);

const initView = useCallback((parent: HTMLDivElement | null) => {
if (parent) {
const extensions = getExtensions(keyHandlers);
const extensions = getExtensions(entity.format, keyHandlers);
if (readOnly) {
extensions.push(
EditorState.readOnly.of(true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
insertNewlineAndIndent,
standardKeymap,
} from '@codemirror/commands';
import { bracketMatching } from '@codemirror/language';
import {
HighlightStyle,
StreamLanguage,
bracketMatching,
syntaxHighlighting,
} from '@codemirror/language';
import { Extension } from '@codemirror/state';
import { EditorView, keymap } from '@codemirror/view';
import { useContext, useEffect, useRef } from 'react';
Expand All @@ -17,6 +22,8 @@ import {
useHandleEnter,
useHandleEscape,
} from './editFieldShortcuts';
import { fluentMode, commonMode } from './editFieldModes';
import { tags } from '@lezer/highlight';

/**
* Key handlers depend on application state,
Expand Down Expand Up @@ -50,13 +57,23 @@ export function useKeyHandlers() {
return ref;
}

const style = HighlightStyle.define([
{ tag: tags.keyword, color: '#872bff', fontFamily: 'monospace' }, // printf
{ tag: tags.tagName, color: '#3e9682', fontFamily: 'monospace' }, // <...>
{ tag: tags.brace, color: '#872bff', fontWeight: 'bold' }, // {...}
{ tag: tags.name, color: '#872bff' }, // {...}
]);

export const getExtensions = (
format: string,
ref: ReturnType<typeof useKeyHandlers>,
): Extension[] => [
history(),
bracketMatching(),
closeBrackets(),
EditorView.lineWrapping,
StreamLanguage.define<any>(format === 'ftl' ? fluentMode : commonMode),
syntaxHighlighting(style),
keymap.of([
{
key: 'Enter',
Expand Down
64 changes: 64 additions & 0 deletions translate/src/modules/translationform/utils/editFieldModes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { StreamParser } from '@codemirror/language';

export const fluentMode: StreamParser<{ expression: boolean; tag: boolean }> = {
name: 'fluent',
startState: () => ({ expression: false, tag: false }),
token(stream, state) {
const ch = stream.next();
if (state.expression) {
if (ch === '}') {
state.expression = false;
return 'brace';
}
stream.skipTo('}');
return 'name';
} else {
if (ch === '{') {
state.expression = true;
return 'brace';
}
if (ch === '<') {
state.tag = true;
}
if (state.tag) {
if (ch === '>') {
state.tag = false;
} else {
stream.eatWhile(/[^>{]+/);
}
return 'tagName';
}
stream.eatWhile(/[^<{]+/);
return 'string';
}
},
};

const printf =
/^%(\d\$|\(.*?\))?[-+ 0'#]*[\d*]*(\.[\d*])?(hh?|ll?|[jLtz])?[%@AacdEeFfGginopSsuXx]/;

const pythonFormat = /^{[\w.[\]]*(![rsa])?(:.*?)?}/;

export const commonMode: StreamParser<{ tag: boolean }> = {
name: 'common',
startState: () => ({ tag: false }),
token(stream, state) {
if (stream.match(printf) || stream.match(pythonFormat)) {
return 'keyword';
}
const ch = stream.next();
if (ch === '<') {
state.tag = true;
}
if (state.tag) {
if (ch === '>') {
state.tag = false;
} else {
stream.eatWhile(/[^>%{]+/);
}
return 'tagName';
}
stream.eatWhile(/[^%{<]+/);
return 'string';
},
};

0 comments on commit 9220827

Please sign in to comment.