Skip to content

Commit

Permalink
Add a keyboard shortcut for beautify request body (Kong#2733)
Browse files Browse the repository at this point in the history
  • Loading branch information
eimanip authored Sep 8, 2021
1 parent 381cf74 commit 9727707
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 53 deletions.
5 changes: 5 additions & 0 deletions packages/insomnia-app/app/common/hotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export const hotKeyRefs: Record<string, HotKeyDefinition> = {
CLOSE_DROPDOWN: defineHotKey('closeDropdown', 'Close Dropdown'),
CLOSE_MODAL: defineHotKey('closeModal', 'Close Modal'),
ENVIRONMENT_UNCOVER_VARIABLES: defineHotKey('environment_uncoverVariables', 'Uncover Variables'),
BEAUTIFY_REQUEST_BODY: defineHotKey('beautifyRequestBody', 'Beautify Active Code Editors'),
GRAPHQL_EXPLORER_FOCUS_FILTER: defineHotKey('graphql_explorer_focus_filter', 'Focus GraphQL Explorer Filter'),
// Designer-specific
SHOW_SPEC_EDITOR: defineHotKey('activity_specEditor', 'Show Spec Activity'),
Expand Down Expand Up @@ -278,6 +279,10 @@ const defaultRegistry: HotKeyRegistry = {
keyComb(false, false, true, true, keyboardKeys.f.keyCode),
keyComb(true, false, true, false, keyboardKeys.f.keyCode),
),
[hotKeyRefs.BEAUTIFY_REQUEST_BODY.id]: keyBinds(
keyComb(false, false, true, true, keyboardKeys.i.keyCode),
keyComb(true, false, true, false, keyboardKeys.i.keyCode),
),
[hotKeyRefs.SHOW_SPEC_EDITOR.id]: keyBinds(
keyComb(false, false, true, true, keyboardKeys.s.keyCode),
keyComb(true, false, true, false, keyboardKeys.s.keyCode),
Expand Down
60 changes: 34 additions & 26 deletions packages/insomnia-app/app/ui/components/codemirror/code-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { json as jsonPrettify } from 'insomnia-prettify';
import { query as queryXPath } from 'insomnia-xpath';
import jq from 'jsonpath';
import React, { Component, CSSProperties, ReactNode } from 'react';
import { unreachable } from 'ts-assert-unreachable';
import vkBeautify from 'vkbeautify';
import zprint from 'zprint-clj';

Expand All @@ -20,6 +21,8 @@ import {
EDITOR_KEY_MAP_VIM,
isMac,
} from '../../../common/constants';
import { hotKeyRefs } from '../../../common/hotkeys';
import { executeHotKey } from '../../../common/hotkeys-listener';
import { keyboardKeys as keyCodes } from '../../../common/keyboard-keys';
import * as misc from '../../../common/misc';
import { HandleGetRenderContext, HandleRender } from '../../../common/render';
Expand All @@ -28,6 +31,7 @@ import { NunjucksParsedTag } from '../../../templating/utils';
import Dropdown from '../base/dropdown/dropdown';
import DropdownButton from '../base/dropdown/dropdown-button';
import DropdownItem from '../base/dropdown/dropdown-item';
import KeydownBinder from '../keydown-binder';
import FilterHelpModal from '../modals/filter-help-modal';
import { showModal } from '../modals/index';
import { normalizeIrregularWhitespace } from './normalizeIrregularWhitespace';
Expand Down Expand Up @@ -162,7 +166,6 @@ class CodeEditor extends Component<Props, State> {
codeMirror?: CodeMirror.EditorFromTextArea;
private _filterInput: HTMLInputElement;
private _autocompleteDebounce: NodeJS.Timeout | null = null;
private _ignoreNextChange: boolean;
private _filterTimeout: NodeJS.Timeout | null = null;

constructor(props: Props) {
Expand Down Expand Up @@ -641,12 +644,14 @@ class CodeEditor extends Component<Props, State> {
: new Array((this.codeMirror?.getOption?.('indentUnit') || 0) + 1).join(' ');
}

_handleBeautify() {
this._prettify(this.codeMirror?.getValue());
}
_prettify() {
const canPrettify = this._canPrettify();
if (!canPrettify) {
return;
}

_prettify(code?: string) {
this._codemirrorSetValue(code, true);
const code = this.codeMirror?.getValue();
this._codemirrorSetValue(code, canPrettify);
}

_prettifyJSON(code: string) {
Expand Down Expand Up @@ -698,6 +703,10 @@ class CodeEditor extends Component<Props, State> {
}
}

async _handleKeyDown(event: KeyboardEvent) {
executeHotKey(event, hotKeyRefs.BEAUTIFY_REQUEST_BODY, this._prettify);
}

/**
* Sets options on the CodeMirror editor while also sanitizing them
*/
Expand Down Expand Up @@ -1047,9 +1056,7 @@ class CodeEditor extends Component<Props, State> {
* Wrapper function to add extra behaviour to our onChange event
*/
_codemirrorValueChanged() {
// Don't trigger change event if we're ignoring changes
if (this._ignoreNextChange || !this.props.onChange) {
this._ignoreNextChange = false;
if (!this.props.onChange) {
return;
}

Expand All @@ -1074,33 +1081,34 @@ class CodeEditor extends Component<Props, State> {
* @param code the code to set in the editor
* @param forcePrettify
*/
_codemirrorSetValue(code = '', forcePrettify = false) {
_codemirrorSetValue(code?: string, forcePrettify?: boolean) {
if (typeof code !== 'string') {
console.warn('Code editor was passed non-string value', code);
return;
}

const { autoPrettify, mode } = this.props;
this._originalCode = code;

// If we're setting initial value, don't trigger onChange because the
// user hasn't done anything yet
if (!forcePrettify) {
this._ignoreNextChange = true;
}

const shouldPrettify = forcePrettify || this.props.autoPrettify;
const shouldPrettify = forcePrettify || autoPrettify;

if (shouldPrettify && this._canPrettify()) {
if (CodeEditor._isXML(this.props.mode)) {
if (CodeEditor._isXML(mode)) {
code = this._prettifyXML(code);
} else if (CodeEditor._isEDN(this.props.mode)) {
} else if (CodeEditor._isEDN(mode)) {
code = CodeEditor._prettifyEDN(code);
} else {
} else if (CodeEditor._isJSON(mode)) {
code = this._prettifyJSON(code);
} else {
unreachable('attempted to prettify in a mode that should not support prettifying');
}
}

this.codeMirror?.setValue(code);
// this prevents codeMirror from needlessly setting the same thing repeatedly (which has the effect of moving the user's cursor and resetting the viewport scroll: a bad user experience)
const currentCode = this.codeMirror?.getValue();
if (currentCode === code) {
return;
}

this.codeMirror?.setValue(code || '');
}

_handleFilterHistorySelect(filter = '') {
Expand Down Expand Up @@ -1219,7 +1227,7 @@ class CodeEditor extends Component<Props, State> {
key="prettify"
className="btn btn--compact"
title="Auto-format request body whitespace"
onClick={this._handleBeautify}
onClick={this._prettify}
>
Beautify {contentTypeName}
</button>,
Expand All @@ -1244,6 +1252,7 @@ class CodeEditor extends Component<Props, State> {

return (
<div className={classes} style={style} data-editor-type={type}>
<KeydownBinder onKeydown={this._handleKeyDown} />
<div
className={classnames('editor__container', 'input', className)}
style={styles}
Expand All @@ -1259,8 +1268,7 @@ class CodeEditor extends Component<Props, State> {
}}
readOnly={readOnly}
autoComplete="off"
// NOTE: When setting this to empty string, it breaks the _ignoreNextChange logic on initial component mount
defaultValue=" "
defaultValue=""
/>
</div>
{toolbar}
Expand Down
70 changes: 45 additions & 25 deletions packages/insomnia-app/app/ui/components/codemirror/modes/curl.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,53 @@
import 'codemirror/addon/mode/simple';

import CodeMirror from 'codemirror';

/** regular key-value header tokens */
const keyValueHeaders = [
{
regex: /^(> )([^:]*:)(.*)$/,
token: ['curl-prefix curl-out', 'curl-out', 'curl-out curl-value'],
},
{
regex: /^(< )([^:]*:)(.*)$/,
token: ['curl-prefix curl-in', 'curl-in', 'curl-in curl-value'],
},
];

/**
* @example POST /foo/bar HTTP/1.1
*/
const headerFields = [
{
regex: /^(> )([^:]+ .*)$/,
token: ['curl-prefix curl-out curl-header', 'curl-out curl-header'],
},
{
regex: /^(< )([^:]+ .*)$/,
token: ['curl-prefix curl-in curl-header', 'curl-in curl-header'],
},
];

const data = [
{
regex: /^(\| )(.*)$/,
token: ['curl-prefix curl-data', 'curl-data'],
},
];

const informationalText = [
{
regex: /^(\* )(.*)$/,
token: ['curl-prefix curl-comment', 'curl-comment'],
},
];

CodeMirror.defineSimpleMode('curl', {
start: [
// Regular key-value header tokens
{
regex: /^(> )([^:]*:)(.*)$/,
token: ['curl-prefix curl-out', 'curl-out', 'curl-out curl-value'],
},
{
regex: /^(< )([^:]*:)(.*)$/,
token: ['curl-prefix curl-in', 'curl-in', 'curl-in curl-value'],
}, // Header fields ("POST /foo/bar HTTP/1.1")
{
regex: /^(> )([^:]+ .*)$/,
token: ['curl-prefix curl-out curl-header', 'curl-out curl-header'],
},
{
regex: /^(< )([^:]+ .*)$/,
token: ['curl-prefix curl-in curl-header', 'curl-in curl-header'],
}, // Data
{
regex: /^(\| )(.*)$/,
token: ['curl-prefix curl-data', 'curl-data'],
}, // Informational text
{
regex: /^(\* )(.*)$/,
token: ['curl-prefix curl-comment', 'curl-comment'],
},
...keyValueHeaders,
...headerFields,
...data,
...informationalText,
],
comment: [],
meta: {},
Expand Down
4 changes: 2 additions & 2 deletions packages/insomnia-app/app/ui/components/keydown-binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ReactDOM from 'react-dom';
import { AUTOBIND_CFG, isMac } from '../../common/constants';

interface Props {
children: ReactNode;
children?: ReactNode;
onKeydown?: (...args: any[]) => any;
onKeyup?: (...args: any[]) => any;
disabled?: boolean;
Expand Down Expand Up @@ -74,7 +74,7 @@ class KeydownBinder extends PureComponent<Props> {
}

render() {
return this.props.children;
return this.props.children ?? null;
}
}

Expand Down

0 comments on commit 9727707

Please sign in to comment.