diff --git a/packages/browser-repl/src/components/editor.spec.tsx b/packages/browser-repl/src/components/editor.spec.tsx index 1e4fbe5cfd..7228ec86f2 100644 --- a/packages/browser-repl/src/components/editor.spec.tsx +++ b/packages/browser-repl/src/components/editor.spec.tsx @@ -11,19 +11,22 @@ describe('', () => { return aceEditor.instance().editor as any; }; - const execCommandBoundTo = (aceEditor: any, key: string): void => { + const execCommandBoundTo = ( + aceEditor: any, + key: { win: string; mac: string } + ): void => { const commands = Object.values(aceEditor.commands.commands); - const command: any = commands.find(({ bindKey }) => { + const command: any = commands.find(({ name, bindKey }) => { if (!bindKey) { return false; } - - if (typeof bindKey === 'string') { - return key === bindKey; + if (name === 'gotoline') { + // Ignore gotoline - our command overrides. + return false; } - const { win, mac } = bindKey as {win: string; mac: string}; - return win === key && mac === key; + const { win, mac } = bindKey as { win: string; mac: string }; + return win === key.win && mac === key.mac; }); if (!command) { @@ -58,7 +61,24 @@ describe('', () => { const aceEditor = getAceEditorInstance(wrapper); expect(spy).not.to.have.been.called; - execCommandBoundTo(aceEditor, 'Return'); + execCommandBoundTo(aceEditor, { + win: 'Return', + mac: 'Return' + }); + expect(spy).to.have.been.calledOnce; + }); + + it('calls onClearCommand when command/ctrl+L is pressed', () => { + const spy = sinon.spy(); + const wrapper = mount(); + + const aceEditor = getAceEditorInstance(wrapper); + + expect(spy).not.to.have.been.called; + execCommandBoundTo(aceEditor, { + win: 'Ctrl-L', + mac: 'Command-L' + }); expect(spy).to.have.been.calledOnce; }); @@ -69,7 +89,10 @@ describe('', () => { const aceEditor = getAceEditorInstance(wrapper); expect(spy).not.to.have.been.called; - execCommandBoundTo(aceEditor, 'Up'); + execCommandBoundTo(aceEditor, { + win: 'Up', + mac: 'Up' + }); expect(spy).to.have.been.calledOnce; }); @@ -82,7 +105,10 @@ describe('', () => { aceEditor.moveCursorToPosition({ row: 1, column: 0 }); aceEditor.clearSelection(); - execCommandBoundTo(aceEditor, 'Up'); + execCommandBoundTo(aceEditor, { + win: 'Up', + mac: 'Up' + }); expect(spy).not.to.have.been.called; }); @@ -96,7 +122,10 @@ describe('', () => { aceEditor.clearSelection(); expect(spy).not.to.have.been.called; - execCommandBoundTo(aceEditor, 'Down'); + execCommandBoundTo(aceEditor, { + win: 'Down', + mac: 'Down' + }); expect(spy).to.have.been.calledOnce; }); @@ -107,7 +136,10 @@ describe('', () => { const aceEditor = getAceEditorInstance(wrapper); aceEditor.setValue('row 0\nrow 1'); - execCommandBoundTo(aceEditor, 'Down'); + execCommandBoundTo(aceEditor, { + win: 'Down', + mac: 'Down' + }); expect(spy).not.to.have.been.called; }); @@ -119,7 +151,10 @@ describe('', () => { aceEditor.setValue('text'); aceEditor.selectAll(); - execCommandBoundTo(aceEditor, 'Up'); + execCommandBoundTo(aceEditor, { + win: 'Up', + mac: 'Up' + }); expect(spy).not.to.have.been.called; }); @@ -131,7 +166,10 @@ describe('', () => { aceEditor.setValue('text'); aceEditor.selectAll(); - execCommandBoundTo(aceEditor, 'Down'); + execCommandBoundTo(aceEditor, { + win: 'Down', + mac: 'Down' + }); expect(spy).not.to.have.been.called; }); diff --git a/packages/browser-repl/src/components/editor.tsx b/packages/browser-repl/src/components/editor.tsx index acf4744604..7e5db05633 100644 --- a/packages/browser-repl/src/components/editor.tsx +++ b/packages/browser-repl/src/components/editor.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import AceEditor from 'react-ace'; import { Autocompleter } from '@mongosh/browser-runtime-core'; import { AceAutocompleterAdapter } from './ace-autocompleter-adapter'; @@ -11,35 +10,26 @@ import './ace-theme'; import ace from 'brace'; const tools = ace.acequire('ace/ext/language_tools'); -const noop = (): void => { - // -}; +const noop = (): void => {}; interface EditorProps { onEnter?(): void | Promise; onArrowUpOnFirstLine?(): void | Promise; onArrowDownOnLastLine?(): void | Promise; onChange?(value: string): void | Promise; + onClearCommand?(): void | Promise; autocompleter?: Autocompleter; setInputRef?(ref): void; value?: string; } export class Editor extends Component { - static propTypes = { - onEnter: PropTypes.func, - onArrowUpOnFirstLine: PropTypes.func, - onArrowDownOnLastLine: PropTypes.func, - onChange: PropTypes.func, - setInputRef: PropTypes.func, - value: PropTypes.string - }; - static defaultProps = { onEnter: noop, onArrowUpOnFirstLine: noop, onArrowDownOnLastLine: noop, onChange: noop, + onClearCommand: noop, value: '' }; @@ -60,6 +50,8 @@ export class Editor extends Component { }; render(): JSX.Element { + const { onClearCommand } = this.props; + return ( { this.props.onArrowDownOnLastLine(); } + }, + { + name: 'clearShell', + bindKey: { win: 'Ctrl-L', mac: 'Command-L' }, + exec: onClearCommand } ]} width="100%" diff --git a/packages/browser-repl/src/components/shell-input.tsx b/packages/browser-repl/src/components/shell-input.tsx index 002ba4dda2..cd05a44d41 100644 --- a/packages/browser-repl/src/components/shell-input.tsx +++ b/packages/browser-repl/src/components/shell-input.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import classnames from 'classnames'; import { Editor } from './editor'; import { Autocompleter } from '@mongosh/browser-runtime-core'; @@ -9,9 +8,10 @@ import Icon from '@leafygreen-ui/icon'; const styles = require('./shell-input.less'); interface ShellInputProps { - onInput?(code: string): void | Promise; - history?: readonly string[]; autocompleter?: Autocompleter; + history?: readonly string[]; + onClearCommand?(): void | Promise; + onInput?(code: string): void | Promise; setInputRef?(ref): void; } @@ -20,13 +20,6 @@ interface ShellInputState { } export class ShellInput extends Component { - static propTypes = { - onInput: PropTypes.func, - history: PropTypes.arrayOf(PropTypes.string), - autocompleter: PropTypes.object, - setInputRef: PropTypes.func - }; - readonly state: ShellInputState = { currentValue: '' }; @@ -116,13 +109,14 @@ export class ShellInput extends Component { />); const editor = (); const className = classnames(styles['shell-input']); diff --git a/packages/browser-repl/src/components/shell.tsx b/packages/browser-repl/src/components/shell.tsx index 00d63a8b1b..c799d37d91 100644 --- a/packages/browser-repl/src/components/shell.tsx +++ b/packages/browser-repl/src/components/shell.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import classnames from 'classnames'; import { ShellInput } from './shell-input'; import { ShellOutput, ShellOutputEntry } from './shell-output'; @@ -72,22 +71,6 @@ const noop = (): void => { * The browser-repl Shell component */ export class Shell extends Component { - static propTypes = { - runtime: PropTypes.shape({ - evaluate: PropTypes.func.isRequired - }).isRequired, - onOutputChanged: PropTypes.func, - onHistoryChanged: PropTypes.func, - redactInfo: PropTypes.bool, - maxOutputLength: PropTypes.number, - maxHistoryLength: PropTypes.number, - initialOutput: PropTypes.arrayOf(PropTypes.shape({ - format: PropTypes.string.isRequired, - value: PropTypes.any.isRequired - })), - initialHistory: PropTypes.arrayOf(PropTypes.string) - }; - static defaultProps = { onHistoryChanged: noop, onOutputChanged: noop, @@ -162,6 +145,15 @@ export class Shell extends Component { return output; } + private onClearCommand = (): void => { + const output = []; + + Object.freeze(output); + + this.setState({ output }); + this.props.onOutputChanged(output); + }; + private onInput = async(code: string): Promise => { const inputLine: ShellOutputEntry = { format: 'input', @@ -212,6 +204,7 @@ export class Shell extends Component {
{ this.shellInputElement = el; }}> { + this.shellOutput = output; + } + lastOpenHeight = defaultShellHeightOpened; + resizableRef = null; saveHistory = async(history) => { if (!this.props.historyStorage) { @@ -143,11 +150,15 @@ export class CompassShell extends Component { onShellToggleClicked={this.shellToggleClicked} /> {isExpanded && ( -
+
)} diff --git a/packages/compass-shell/src/components/compass-shell/compass-shell.spec.js b/packages/compass-shell/src/components/compass-shell/compass-shell.spec.js index 7c9fab6193..c34e260716 100644 --- a/packages/compass-shell/src/components/compass-shell/compass-shell.spec.js +++ b/packages/compass-shell/src/components/compass-shell/compass-shell.spec.js @@ -63,6 +63,29 @@ describe('CompassShell', () => { top: , }); }); + + it('renders the Shell with an output change handler', () => { + const fakeRuntime = {}; + const wrapper = shallow(); + expect(!!wrapper.find(Shell).prop('onOutputChanged')).to.equal(true); + }); + + it('passes saved shell output', () => { + const fakeRuntime = {}; + const wrapper = shallow(); + + expect(wrapper.find(Shell).prop('initialOutput')).to.deep.equal([{ + type: 'output', + value: 'pineapple' + }]); + }); }); context('when historyStorage is not present', () => { @@ -187,5 +210,19 @@ describe('CompassShell', () => { ).to.equal(true); }); }); + + it('sets shellOutput on onShellOutputChanged', () => { + const shell = new CompassShell({ isExpanded: true }); + + shell.onShellOutputChanged([{ + type: 'output', + value: 'some output' + }]); + + expect(shell.shellOutput).to.deep.equal([{ + type: 'output', + value: 'some output' + }]); + }); }); diff --git a/packages/compass-shell/src/components/info-modal/info-modal.jsx b/packages/compass-shell/src/components/info-modal/info-modal.jsx index 1a37a7c25f..4da2ad2428 100644 --- a/packages/compass-shell/src/components/info-modal/info-modal.jsx +++ b/packages/compass-shell/src/components/info-modal/info-modal.jsx @@ -26,6 +26,9 @@ const hotkeys = [{ }, { key: 'Ctrl+H', description: 'Erases one character. Similar to hitting backspace.' +}, { + key: 'Ctrl+L', + description: 'Clears the screen, similar to the clear command.' }, { key: 'Ctrl+T', description: 'Swap the last two characters before the cursor.' @@ -75,7 +78,10 @@ export class InfoModal extends PureComponent {
{hotkeys.map(shortcut => ( -
+
{shortcut.key}{shortcut.description} diff --git a/packages/shell-api/src/decorators.ts b/packages/shell-api/src/decorators.ts index 9f3349c81f..7b62c09016 100644 --- a/packages/shell-api/src/decorators.ts +++ b/packages/shell-api/src/decorators.ts @@ -45,7 +45,12 @@ interface TypeSignature { interface Signatures { [key: string]: TypeSignature; } -const signatures = {} as Signatures; +const signaturesGlobalIdentifier = '@@@mdb.signatures@@@'; +if (!global[signaturesGlobalIdentifier]) { + global[signaturesGlobalIdentifier] = {}; +} + +const signatures: Signatures = global[signaturesGlobalIdentifier]; export const toIgnore = [asShellResult, 'asPrintable', 'constructor']; export function shellApiClassDefault(constructor: Function): void {