diff --git a/packages/recorder/src/recorder.css b/packages/recorder/src/recorder.css index 571755dd2f..895c51e6a8 100644 --- a/packages/recorder/src/recorder.css +++ b/packages/recorder/src/recorder.css @@ -49,3 +49,13 @@ .recorder .selector-input { flex: auto; } + +.recorder .toolbar .cm-wrapper { + margin-left: 8px; + display: flex; + padding: 8px; +} + +.recorder .toolbar .CodeMirror { + height: auto; +} diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index a9078d3199..dd5e425727 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -15,6 +15,7 @@ */ import type { CallLog, Mode, Source } from './recorderTypes'; +import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper'; import { Source as SourceView } from '@web/components/source'; import { SplitView } from '@web/components/splitView'; import { Toolbar } from '@web/components/toolbar'; @@ -45,14 +46,6 @@ export const Recorder: React.FC = ({ log, mode, }) => { - const [locator, setLocator] = React.useState(''); - const [focusSelectorInput, setFocusSelectorInput] = React.useState(false); - window.playwrightSetSelector = (selector: string, focus?: boolean) => { - const language = sources[0]?.language || 'javascript'; - setLocator(asLocator(language, selector)); - setFocusSelectorInput(!!focus); - }; - const [fileId, setFileId] = React.useState(); React.useEffect(() => { @@ -68,6 +61,13 @@ export const Recorder: React.FC = ({ label: '', highlight: [] }; + + const [locator, setLocator] = React.useState(''); + window.playwrightSetSelector = (selector: string, focus?: boolean) => { + const language = source.language; + setLocator(asLocator(language, selector)); + }; + window.playwrightSetFileIfNeeded = (value: string) => { const newSource = sources.find(s => s.id === value); // Do not forcefully switch between two recorded sources, because @@ -81,14 +81,6 @@ export const Recorder: React.FC = ({ messagesEndRef.current?.scrollIntoView({ block: 'center', inline: 'nearest' }); }, [messagesEndRef]); - const selectorInputRef = React.createRef(); - React.useLayoutEffect(() => { - if (focusSelectorInput && selectorInputRef.current) { - selectorInputRef.current.select(); - selectorInputRef.current.focus(); - setFocusSelectorInput(false); - } - }, [focusSelectorInput, selectorInputRef]); React.useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { @@ -145,12 +137,12 @@ export const Recorder: React.FC = ({ { window.dispatch({ event: 'setMode', params: { mode: mode === 'inspecting' ? 'none' : 'inspecting' } }).catch(() => { }); }}>Explore - { - setLocator(event.target.value); - window.dispatch({ event: 'selectorUpdated', params: { selector: event.target.value } }); - }} /> + { + setLocator(text); + window.dispatch({ event: 'selectorUpdated', params: { selector: text } }); + }}> { - copy(selectorInputRef.current?.value || ''); + copy(locator); }}> diff --git a/packages/web/src/components/codeMirrorWrapper.css b/packages/web/src/components/codeMirrorWrapper.css new file mode 100644 index 0000000000..523a47d82a --- /dev/null +++ b/packages/web/src/components/codeMirrorWrapper.css @@ -0,0 +1,127 @@ +/* + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +@import '../third_party/vscode/colors.css'; + +.cm-wrapper, .cm-wrapper > div { + width: 100%; + height: 100%; +} + +.CodeMirror span.cm-meta { + color: var(--vscode-editor-foreground); +} + +.CodeMirror span.cm-number { + color: var(--vscode-debugTokenExpression-number); +} + +.CodeMirror span.cm-keyword { + color: var(--vscode-debugTokenExpression-name); +} + +.CodeMirror span.cm-operator { + color: var(--vscode-editor-foreground); +} + +.CodeMirror span.cm-string { + color: var(--vscode-debugTokenExpression-string); +} + +.CodeMirror span.cm-string-2 { + color: var(--vscode-debugTokenExpression-string); +} + +.CodeMirror span.cm-error { + color: var(--vscode-errorForeground); +} + +.CodeMirror span.cm-def, .CodeMirror span.cm-tag { + color: #0070c1; +} + +.CodeMirror span.cm-comment, .CodeMirror span.cm-link { + color: #008000; +} + +.CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom { + color: #0070c1; +} + +.CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute { + color: #001080; +} + +.CodeMirror span.cm-variable-3, +.CodeMirror span.cm-type { + color: #267f99; +} + +@media(prefers-color-scheme: dark) { + + .CodeMirror span.cm-def, .CodeMirror span.cm-tag { + color: var(--vscode-debugView-valueChangedHighlight); + } + + .CodeMirror span.cm-comment, .CodeMirror span.cm-link { + color: #6a9955; + } + + .CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom { + color: #4fc1ff; + } + + .CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute { + color: #9cdcfe; + } + + .CodeMirror span.cm-variable-3, + .CodeMirror span.cm-type { + color: #4ec9b0; + } + +} + +.CodeMirror span.cm-bracket { + color: var(--vscode-editorBracketHighlight-foreground3); +} + +.CodeMirror-cursor { + border-left: 1px solid #bebebe; +} + +.CodeMirror div.CodeMirror-selected { + background: var(--vscode-terminal-inactiveSelectionBackground); +} + +.CodeMirror .CodeMirror-gutters { + background: var(--vscode-editor-background); + border-right: 1px solid var(--vscode-editorGroup-border); + color: var(--vscode-editorLineNumber-foreground); +} + +.CodeMirror .CodeMirror-matchingbracket { + background-color: var(--vscode-editorBracketPairGuide-background1); + color: var(--vscode-editorBracketHighlight-foreground1) !important; +} + +.CodeMirror { + font-family: var(--vscode-editor-font-family) !important; + color: var(--vscode-editor-foreground) !important; + background-color: var(--vscode-editor-background) !important; + font-weight: var(--vscode-editor-font-weight) !important; + font-size: var(--vscode-editor-font-size) !important; +} diff --git a/packages/web/src/components/codeMirrorWrapper.tsx b/packages/web/src/components/codeMirrorWrapper.tsx new file mode 100644 index 0000000000..1ac346502a --- /dev/null +++ b/packages/web/src/components/codeMirrorWrapper.tsx @@ -0,0 +1,109 @@ +/* + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import './source.css'; +import * as React from 'react'; +import CodeMirror from 'codemirror'; +import 'codemirror/mode/javascript/javascript'; +import 'codemirror/mode/python/python'; +import 'codemirror/mode/clike/clike'; +import 'codemirror/lib/codemirror.css'; + +export type SourceHighlight = { + line: number; + type: 'running' | 'paused' | 'error'; +}; + +export interface SourceProps { + text: string; + language: string; + readOnly: boolean; + // 1-based + highlight?: SourceHighlight[]; + revealLine?: number; + lineNumbers?: boolean; + focusOnChange?: boolean; + wrapLines?: boolean; + onChange?: (text: string) => void; +} + +export const CodeMirrorWrapper: React.FC = ({ + text, + language, + readOnly, + highlight = [], + revealLine, + lineNumbers, + focusOnChange, + wrapLines, + onChange, +}) => { + const codemirrorElement = React.createRef(); + const [codemirror, setCodemirror] = React.useState(); + + React.useEffect(() => { + let mode; + if (language === 'javascript') + mode = 'javascript'; + if (language === 'python') + mode = 'python'; + if (language === 'java') + mode = 'text/x-java'; + if (language === 'csharp') + mode = 'text/x-csharp'; + + if (codemirror && codemirror.getOption('mode') === mode) + return; + + if (!codemirrorElement.current) + return; + if (codemirror) + codemirror.getWrapperElement().remove(); + + const cm = CodeMirror(codemirrorElement.current, { + value: '', + mode, + readOnly, + lineNumbers, + lineWrapping: wrapLines, + }); + if (onChange) + cm.on('change', () => onChange(cm.getValue())); + setCodemirror(cm); + updateEditor(cm, text, highlight, revealLine, focusOnChange); + }, [codemirror, codemirrorElement, text, language, highlight, revealLine, focusOnChange, lineNumbers, wrapLines, readOnly, onChange]); + + if (codemirror) + updateEditor(codemirror, text, highlight, revealLine, focusOnChange); + + return
; +}; + +function updateEditor(cm: CodeMirror.Editor, text: string, highlight: SourceHighlight[], revealLine?: number, focusOnChange?: boolean) { + if (cm.getValue() !== text) { + cm.setValue(text); + if (focusOnChange) { + cm.execCommand('selectAll'); + cm.focus(); + } + } + for (let i = 0; i < cm.lineCount(); ++i) + cm.removeLineClass(i, 'wrap'); + for (const h of highlight) + cm.addLineClass(h.line - 1, 'wrap', `source-line-${h.type}`); + if (revealLine) + cm.scrollIntoView({ line: revealLine - 1, ch: 0 }, 50); +} diff --git a/packages/web/src/components/source.css b/packages/web/src/components/source.css index 10f83268e2..e13a4f280c 100644 --- a/packages/web/src/components/source.css +++ b/packages/web/src/components/source.css @@ -46,118 +46,3 @@ outline: 1px solid #ff5656; z-index: 2; } - -.cm-wrapper, .cm-wrapper > div { - width: 100%; - height: 100%; -} - -.CodeMirror span.cm-meta { - color: var(--vscode-editor-foreground); -} - -.CodeMirror span.cm-number { - color: var(--vscode-debugTokenExpression-number); -} - -.CodeMirror span.cm-keyword { - color: var(--vscode-debugTokenExpression-name); -} - -.CodeMirror span.cm-operator { - color: var(--vscode-editor-foreground); -} - -.CodeMirror span.cm-string { - color: var(--vscode-debugTokenExpression-string); -} - -.CodeMirror span.cm-string-2 { - color: var(--vscode-debugTokenExpression-string); -} - -.CodeMirror span.cm-error { - color: var(--vscode-errorForeground); -} - -.CodeMirror span.cm-def, .CodeMirror span.cm-tag { - color: #0070c1; -} - -.CodeMirror span.cm-comment, .CodeMirror span.cm-link { - color: #008000; -} - -.CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom { - color: #0070c1; -} - -.CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute { - color: #001080; -} - -.CodeMirror span.cm-variable-3, -.CodeMirror span.cm-type { - color: #267f99; -} - -@media(prefers-color-scheme: dark) { - - .CodeMirror span.cm-def, .CodeMirror span.cm-tag { - color: var(--vscode-debugView-valueChangedHighlight); - } - - .CodeMirror span.cm-comment, .CodeMirror span.cm-link { - color: #6a9955; - } - - .CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom { - color: #4fc1ff; - } - - .CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute { - color: #9cdcfe; - } - - .CodeMirror span.cm-variable-3, - .CodeMirror span.cm-type { - color: #4ec9b0; - } - -} - -.CodeMirror span.cm-bracket { - color: var(--vscode-editorBracketHighlight-foreground3); -} - -.CodeMirror-cursor { - border-left: 1px solid #bebebe; -} - -.CodeMirror div.CodeMirror-selected { - background: var(--vscode-terminal-inactiveSelectionBackground); -} - -.CodeMirror .CodeMirror-gutters { - background: var(--vscode-editor-background); - border-right: 1px solid var(--vscode-editorGroup-border); - color: var(--vscode-editorLineNumber-foreground); -} - -.CodeMirror .CodeMirror-matchingbracket { - background-color: var(--vscode-editorBracketPairGuide-background1); - color: var(--vscode-editorBracketHighlight-foreground1) !important; -} - - /* .CodeMirror-line, - .CodeMirror-line-like { - color: var(--vscode-editor-foreground) !important; - } */ - -.CodeMirror { - font-family: var(--vscode-editor-font-family) !important; - color: var(--vscode-editor-foreground) !important; - background-color: var(--vscode-editor-background) !important; - font-weight: var(--vscode-editor-font-weight) !important; - font-size: var(--vscode-editor-font-size) !important; -} diff --git a/packages/web/src/components/source.tsx b/packages/web/src/components/source.tsx index 1c4801fb69..cae68a8451 100644 --- a/packages/web/src/components/source.tsx +++ b/packages/web/src/components/source.tsx @@ -14,13 +14,9 @@ limitations under the License. */ -import './source.css'; import * as React from 'react'; -import CodeMirror from 'codemirror'; -import 'codemirror/mode/javascript/javascript'; -import 'codemirror/mode/python/python'; -import 'codemirror/mode/clike/clike'; -import 'codemirror/lib/codemirror.css'; +import './codeMirrorWrapper.css'; +import { CodeMirrorWrapper } from './codeMirrorWrapper'; export type SourceHighlight = { line: number; @@ -41,51 +37,5 @@ export const Source: React.FC = ({ highlight = [], revealLine }) => { - const codemirrorElement = React.createRef(); - const [codemirror, setCodemirror] = React.useState(); - - React.useEffect(() => { - let mode; - if (language === 'javascript') - mode = 'javascript'; - if (language === 'python') - mode = 'python'; - if (language === 'java') - mode = 'text/x-java'; - if (language === 'csharp') - mode = 'text/x-csharp'; - - if (codemirror && codemirror.getOption('mode') === mode) - return; - - if (!codemirrorElement.current) - return; - if (codemirror) - codemirror.getWrapperElement().remove(); - - const cm = CodeMirror(codemirrorElement.current, { - value: '', - mode, - readOnly: true, - lineNumbers: true, - }); - setCodemirror(cm); - updateEditor(cm, text, highlight, revealLine); - }, [codemirror, codemirrorElement, text, language, highlight, revealLine]); - - if (codemirror) - updateEditor(codemirror, text, highlight, revealLine); - - return
; + return ; }; - -function updateEditor(cm: CodeMirror.Editor, text: string, highlight: SourceHighlight[], revealLine: number | undefined) { - if (cm.getValue() !== text) - cm.setValue(text); - for (let i = 0; i < cm.lineCount(); ++i) - cm.removeLineClass(i, 'wrap'); - for (const h of highlight) - cm.addLineClass(h.line - 1, 'wrap', `source-line-${h.type}`); - if (revealLine) - cm.scrollIntoView({ line: revealLine - 1, ch: 0 }, 50); -} diff --git a/packages/web/src/components/toolbar.css b/packages/web/src/components/toolbar.css index 5e3f1ecd79..0cc56677b3 100644 --- a/packages/web/src/components/toolbar.css +++ b/packages/web/src/components/toolbar.css @@ -19,7 +19,7 @@ box-shadow: var(--box-shadow); background-color: var(--vscode-sideBar-background); color: var(--vscode-sideBarTitle-foreground); - height: 40px; + min-height: 40px; align-items: center; padding-right: 10px; flex: none; diff --git a/packages/web/src/components/toolbarButton.css b/packages/web/src/components/toolbarButton.css index f50cbb23be..7afe5a803b 100644 --- a/packages/web/src/components/toolbarButton.css +++ b/packages/web/src/components/toolbarButton.css @@ -15,6 +15,7 @@ */ .toolbar-button { + flex: none; border: none; outline: none; color: var(--vscode-sideBarTitle-foreground); diff --git a/tests/library/inspector/pause.spec.ts b/tests/library/inspector/pause.spec.ts index cc046c3633..4e2c65ddb5 100644 --- a/tests/library/inspector/pause.spec.ts +++ b/tests/library/inspector/pause.spec.ts @@ -359,10 +359,12 @@ it.describe('pause', () => { await page.pause(); })(); const recorderPage = await recorderPageGetter(); - const [box1] = await Promise.all([ - waitForTestLog(page, 'Highlight box for test: '), - recorderPage.fill('input[placeholder="Playwright Selector"]', 'text=Submit'), - ]); + + const box1Promise = waitForTestLog(page, 'Highlight box for test: '); + await recorderPage.click('.toolbar .CodeMirror'); + await recorderPage.keyboard.type('getByText(\'Submit\')'); + const box1 = await box1Promise; + const button = await page.$('text=Submit'); const box2 = await button.boundingBox(); expect(roundBox(box1)).toEqual(roundBox(box2));