Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import get from 'lodash/get';
import debounce from 'lodash/debounce';
import isEmpty from 'lodash/isEmpty';
import { editor } from '@bfcomposer/monaco-editor/esm/vs/editor/editor.api';
import { lgIndexer, combineMessage, isValid, filterTemplateDiagnostics } from '@bfc/indexers';
import { lgIndexer, filterTemplateDiagnostics } from '@bfc/indexers';
import { RouteComponentProps } from '@reach/router';
import querystring from 'query-string';

Expand Down Expand Up @@ -56,13 +56,7 @@ const CodeEditor: React.FC<CodeEditorProps> = props => {
setContent(value);
}, [fileId, templateId, projectId]);

useEffect(() => {
const currentDiagnostics = inlineMode && template ? filterTemplateDiagnostics(diagnostics, template) : diagnostics;

const isInvalid = !isValid(currentDiagnostics);
const text = isInvalid ? combineMessage(currentDiagnostics) : '';
setErrorMsg(text);
}, [diagnostics]);
const currentDiagnostics = inlineMode && template ? filterTemplateDiagnostics(diagnostics, template) : diagnostics;

const editorDidMount = (lgEditor: editor.IStandaloneCodeEditor) => {
setLgEditor(lgEditor);
Expand Down Expand Up @@ -163,6 +157,7 @@ const CodeEditor: React.FC<CodeEditorProps> = props => {
editorDidMount={editorDidMount}
value={content}
errorMsg={errorMsg}
diagnostics={currentDiagnostics}
lgOption={lgOption}
languageServer={{
path: lspServerPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import get from 'lodash/get';
import debounce from 'lodash/debounce';
import isEmpty from 'lodash/isEmpty';
import { editor } from '@bfcomposer/monaco-editor/esm/vs/editor/editor.api';
import { luIndexer, combineMessage, isValid, filterTemplateDiagnostics } from '@bfc/indexers';
import { luIndexer, filterTemplateDiagnostics } from '@bfc/indexers';
import { RouteComponentProps } from '@reach/router';
import querystring from 'query-string';

Expand Down Expand Up @@ -55,11 +55,7 @@ const CodeEditor: React.FC<CodeEditorProps> = props => {
setContent(value);
}, [file, sectionId, projectId]);

const errorMsg = useMemo(() => {
const currentDiagnostics = inlineMode && intent ? filterTemplateDiagnostics(diagnostics, intent) : diagnostics;
const isInvalid = !isValid(currentDiagnostics);
return isInvalid ? combineMessage(diagnostics) : httpErrorMsg;
}, [diagnostics, httpErrorMsg]);
const currentDiagnostics = inlineMode && intent ? filterTemplateDiagnostics(diagnostics, intent) : diagnostics;

const editorDidMount = (luEditor: editor.IStandaloneCodeEditor) => {
setLuEditor(luEditor);
Expand Down Expand Up @@ -161,7 +157,8 @@ const CodeEditor: React.FC<CodeEditorProps> = props => {
hidePlaceholder={inlineMode}
editorDidMount={editorDidMount}
value={content}
errorMsg={errorMsg}
errorMsg={httpErrorMsg}
diagnostics={currentDiagnostics}
luOption={luOption}
languageServer={{
path: lspServerPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,8 @@ export const LgEditorWidget: React.FC<LgEditorWidgetProps> = props => {
},
};

const diagnostic = lgFile && filterTemplateDiagnostics(lgFile.diagnostics, template)[0];
const diagnostics = lgFile ? filterTemplateDiagnostics(lgFile.diagnostics, template) : [];

const errorMsg = diagnostic
? diagnostic.message.split('error message: ')[diagnostic.message.split('error message: ').length - 1]
: '';
const [localValue, setLocalValue] = useState(template.body);
const sync = useRef(
debounce((shellData: any, localData: any) => {
Expand Down Expand Up @@ -120,7 +117,7 @@ export const LgEditorWidget: React.FC<LgEditorWidgetProps> = props => {
onChange={onChange}
value={localValue}
lgOption={lgOption}
errorMsg={errorMsg}
diagnostics={diagnostics}
hidePlaceholder={true}
helpURL={LG_HELP}
languageServer={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,7 @@ export class LuEditorWidget extends React.Component<LuEditorWidgetProps> {
render() {
const { height = 250 } = this.props;
const { luFile, luFileId, luIntent, name, formContext } = this;
const diagnostic = luFile && filterSectionDiagnostics(luFile.diagnostics, luIntent)[0];

const errorMsg = diagnostic
? diagnostic.message.split('error message: ')[diagnostic.message.split('error message: ').length - 1]
: '';
const diagnostics = luFile ? filterSectionDiagnostics(luFile.diagnostics, luIntent) : [];

const label = prompt
? formatMessage('Expected responses (intent: {name})', { name })
Expand All @@ -94,7 +90,7 @@ export class LuEditorWidget extends React.Component<LuEditorWidgetProps> {
<LuEditor
onChange={this.onChange}
value={this.state.localValue}
errorMsg={errorMsg}
diagnostics={diagnostics}
hidePlaceholder={true}
luOption={{
projectId: formContext.projectId,
Expand Down
4 changes: 4 additions & 0 deletions Composer/packages/lib/code-editor/demo/src/richEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,25 @@ const content = `# Greeting
export default function App() {
const [value, setValue] = useState<string>(content);
const [showError, setShowError] = useState(true);
const [showWarning, setShowWarning] = useState(false);

const placeholder = `> To learn more about the LU file format, read the documentation at
> ${LU_HELP}`;
const errorMsg = showError ? 'example error' : undefined;
const warningMsg = showWarning ? 'example warning' : undefined;

return (
<div style={{ height: '99vh', width: '100%' }}>
<div style={{ marginBottom: '10px' }}>
<button onClick={() => setShowError(!showError)}>Toggle Error</button>
<button onClick={() => setShowWarning(!showWarning)}>Toggle Warning</button>
</div>
<RichEditor
onChange={newVal => setValue(newVal)}
value={value}
placeholder={placeholder}
errorMsg={errorMsg}
warningMsg={warningMsg}
helpURL="https://dev.botframework.com"
height={500}
/>
Expand Down
48 changes: 39 additions & 9 deletions Composer/packages/lib/code-editor/src/RichEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import React, { Fragment, useState, useEffect } from 'react';
import React, { Fragment, useState, useEffect, useMemo } from 'react';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { SharedColors, NeutralColors } from '@uifabric/fluent-theme';
import formatMessage from 'format-message';
import { EditorDidMount } from '@bfcomposer/react-monaco-editor';
import * as monacoEditor from '@bfcomposer/monaco-editor/esm/vs/editor/editor.api';
import { Diagnostic, findErrors, findWarnings, combineSimpleMessage } from '@bfc/indexers';

import { BaseEditor, BaseEditorProps } from './BaseEditor';
import { processSize } from './utils/common';
Expand All @@ -15,17 +16,42 @@ export interface RichEditorProps extends BaseEditorProps {
hidePlaceholder?: boolean; // default false
placeholder?: string; // empty placeholder
errorMsg?: string; // error text show below editor
warningMsg?: string; // warning text show below editor
diagnostics?: Diagnostic[]; // indexer generic diagnostic
helpURL?: string; // help link show below editor
height?: number | string;
}

export function RichEditor(props: RichEditorProps) {
const { errorMsg, helpURL, placeholder, hidePlaceholder = false, height, editorDidMount, ...rest } = props;
const isInvalid = !!errorMsg;
const {
errorMsg,
warningMsg,
diagnostics = [],
helpURL,
placeholder,
hidePlaceholder = false,
height,
editorDidMount,
...rest
} = props;

const [editor, setEditor] = useState<monacoEditor.editor.IStandaloneCodeEditor | null>(null);
const [hovered, setHovered] = useState(false);
const [focused, setFocused] = useState(false);

const errorMsgFromDiagnostics = useMemo(() => {
const errors = findErrors(diagnostics);
return errors.length ? combineSimpleMessage(errors) : '';
}, [diagnostics]);

const warningMsgFromDiagnostics = useMemo(() => {
const warnings = findWarnings(diagnostics);
return warnings.length ? combineSimpleMessage(warnings) : '';
}, [diagnostics]);

const hasError = !!errorMsg || !!errorMsgFromDiagnostics;
const hasWarning = !!warningMsg || !!warningMsgFromDiagnostics;

const onEditorMount: EditorDidMount = (editor, monaco) => {
setEditor(editor);

Expand All @@ -51,8 +77,8 @@ export function RichEditor(props: RichEditorProps) {
}
}, [editor]);

const errorHelp = formatMessage.rich('{errorMsg}. Refer to the syntax documentation<a>here</a>.', {
errorMsg,
const messageHelp = formatMessage.rich('{msg}. Refer to the syntax documentation<a>here</a>.', {
msg: errorMsg || errorMsgFromDiagnostics || warningMsg || warningMsgFromDiagnostics,
a: ({ children }) => (
<a key="a" href={helpURL} target="_blank" rel="noopener noreferrer">
{children}
Expand All @@ -78,7 +104,11 @@ export function RichEditor(props: RichEditorProps) {
borderColor = SharedColors.cyanBlue10;
}

if (isInvalid) {
if (hasWarning) {
borderColor = SharedColors.yellow10;
}

if (hasError) {
borderColor = SharedColors.red20;
}

Expand All @@ -99,15 +129,15 @@ export function RichEditor(props: RichEditorProps) {
>
<BaseEditor {...rest} editorDidMount={onEditorMount} placeholder={hidePlaceholder ? undefined : placeholder} />
</div>
{isInvalid && (
{(hasError || hasWarning) && (
<MessageBar
messageBarType={MessageBarType.error}
messageBarType={hasError ? MessageBarType.error : hasWarning ? MessageBarType.warning : MessageBarType.info}
isMultiline={false}
dismissButtonAriaLabel={formatMessage('Close')}
truncated
overflowButtonAriaLabel={formatMessage('See more')}
>
{errorHelp}
{messageHelp}
</MessageBar>
)}
</Fragment>
Expand Down
19 changes: 19 additions & 0 deletions Composer/packages/lib/indexers/src/utils/diagnosticUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ export function combineMessage(diagnostics: Diagnostic[]): string {
}, '');
}

export function combineSimpleMessage(diagnostics: Diagnostic[]): string {
const diagnostic = diagnostics[0];
if (diagnostic) {
let msg = '';
if (diagnostic.range) {
const { start, end } = diagnostic.range;
const position = `L${start.line}:${start.character} - L${end.line}:${end.character} `;
msg += position;
}
const [, errorInfo] = diagnostic.message.split('error message: ');
return msg + errorInfo;
}
return '';
}

export function offsetRange(range: Range, offset: number): Range {
return new Range(
new Position(range.start.line - offset, range.start.character),
Expand Down Expand Up @@ -73,6 +88,10 @@ export function findErrors(diagnostics: Diagnostic[]): Diagnostic[] {
return diagnostics.filter(d => d.severity === DiagnosticSeverity.Error);
}

export function findWarnings(diagnostics: Diagnostic[]): Diagnostic[] {
return diagnostics.filter(d => d.severity === DiagnosticSeverity.Warning);
}

export function isValid(diagnostics: Diagnostic[]): boolean {
return diagnostics.every(d => d.severity !== DiagnosticSeverity.Error);
}