Add a keyboard shortcut for beautify request body (#2733)

This commit is contained in:
Eiman
2021-09-08 23:16:03 +04:30
committed by GitHub
parent 381cf74a19
commit 972770731c
4 changed files with 86 additions and 53 deletions

View File

@@ -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'),
@@ -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),

View File

@@ -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';
@@ -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';
@@ -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';
@@ -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) {
@@ -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) {
@@ -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
*/
@@ -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;
}
@@ -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 = '') {
@@ -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>,
@@ -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}
@@ -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}

View File

@@ -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: {},

View File

@@ -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;
@@ -74,7 +74,7 @@ class KeydownBinder extends PureComponent<Props> {
}
render() {
return this.props.children;
return this.props.children ?? null;
}
}