Files
bruno/packages/bruno-app/src/utils/common/codemirror.js
Abhishek S Lal a798b32f25 feat: add response data type selector in response viewer (#6100)
* feat: add response data type selector in response viewer

* chore: fixed lint issue

* test: add test for resonse format change and preview.

* refactor: streamline response format tests with utility functions for navigation and format switching

* refactor: simplify ButtonDropdown component and enhance QueryResultTypeSelector with header and toggle switch

* feat: enhance ButtonDropdown with prefix and suffix props; implement content type detection and update QueryResult for improved format handling

* fix: lint errors resolved

* fix: remove unnecessary blank line to resolve lint issues

* fix: update response format tests

* refactor: remove preview tab locator from response format tests

* fix: update dependency in useEffect to include previewFormatOptions for accurate format handling

* refactor: reorganize imports and enhance QueryResult component for improved format handling and error display

* fix: update error messages in response format preview tests and adjust version in JSON fixture

* feat: add drag detection to HtmlPreview component and update structure for improved user interaction

* refactor: update ResponsePane components for improved structure and functionality;

replace QueryResult with QueryResponse, enhance layout handling, and streamline response actions

* refactor: remove ButtonDropdown component and associated styles;

* refactor: moved ErrorAlert to ui folder

* fix: lint error

* feat: add data-testid attributes to Collection and CollectionItem components for improved testability

* feat: hide dropdown on select in response selector

* fix: update QueryResult component to use detectedContentType for format handling

* test: update ResponseLayoutToggle tests to use data-testid for button selection

* feat: add data-testid attribute to ResponseClear component for improved testability

* refactor: implement clickResponseAction utility for streamlined response action handling in tests

* feat: add data-testid attribute to ResponseCopy component for enhanced testability

* fix: unwanted code in test
2025-12-09 23:45:01 +05:30

124 lines
4.7 KiB
JavaScript

import get from 'lodash/get';
import { mockDataFunctions } from '@usebruno/common';
import { PROMPT_VARIABLE_TEXT_PATTERN } from '@usebruno/common/utils';
const CodeMirror = require('codemirror');
const pathFoundInVariables = (path, obj) => {
const value = get(obj, path);
return value !== undefined;
};
/**
* Defines a custom CodeMirror mode for Bruno variables highlighting.
* This function creates a specialized mode that can highlight both Bruno template
* variables (in the format {{variable}}) and URL path parameters (in the format /:param).
*
* @param {Object} _variables - The variables object containing data to validate against
* @param {string} mode - The base CodeMirror mode to extend (e.g., 'javascript', 'application/json')
* @param {boolean} highlightPathParams - Whether to highlight URL path parameters
* @param {boolean} highlightVariables - Whether to highlight template variables
* @returns {void} - Registers the mode with CodeMirror for later use
*/
export const defineCodeMirrorBrunoVariablesMode = (_variables, mode, highlightPathParams, highlightVariables) => {
CodeMirror.defineMode('brunovariables', function (config, parserConfig) {
const { pathParams = {}, ...variables } = _variables || {};
const variablesOverlay = {
token: function (stream) {
if (stream.match('{{', true)) {
let ch;
let word = '';
while ((ch = stream.next()) != null) {
if (ch === '}' && stream.peek() === '}') {
stream.eat('}');
// Prompt variable: starts with '?', no leading/trailing spaces, no braces
if (PROMPT_VARIABLE_TEXT_PATTERN.test(word)) {
return `variable-prompt`;
}
// Check if it's a mock variable (starts with $) and exists in mockDataFunctions
const isMockVariable = word.startsWith('$') && mockDataFunctions.hasOwnProperty(word.substring(1));
const found = isMockVariable || pathFoundInVariables(word, variables);
const status = found ? 'valid' : 'invalid';
const randomClass = `random-${(Math.random() + 1).toString(36).substring(9)}`;
return `variable-${status} ${randomClass}`;
}
word += ch;
}
}
stream.skipTo('{{') || stream.skipToEnd();
return null;
}
};
const urlPathParamsOverlay = {
token: function (stream) {
if (stream.match('/:', true)) {
let ch;
let word = '';
while ((ch = stream.next()) != null) {
if (ch === '/' || ch === '?' || ch === '&' || ch === '=') {
stream.backUp(1);
const found = pathFoundInVariables(word, pathParams);
const status = found ? 'valid' : 'invalid';
const randomClass = `random-${(Math.random() + 1).toString(36).substring(9)}`;
return `variable-${status} ${randomClass}`;
}
word += ch;
}
// If we've consumed all characters and the word is not empty, it might be a path parameter at the end of the URL.
if (word) {
const found = pathFoundInVariables(word, pathParams);
const status = found ? 'valid' : 'invalid';
const randomClass = `random-${(Math.random() + 1).toString(36).substring(9)}`;
return `variable-${status} ${randomClass}`;
}
}
stream.skipTo('/:') || stream.skipToEnd();
return null;
}
};
let baseMode = CodeMirror.getMode(config, parserConfig.backdrop || mode);
if (highlightVariables) {
baseMode = CodeMirror.overlayMode(baseMode, variablesOverlay);
}
if (highlightPathParams) {
baseMode = CodeMirror.overlayMode(baseMode, urlPathParamsOverlay);
}
return baseMode;
});
};
export const getCodeMirrorModeBasedOnContentType = (contentType, body) => {
if (typeof body === 'object') {
return 'application/ld+json';
}
if (!contentType || typeof contentType !== 'string') {
return 'application/text';
}
if (contentType.includes('json')) {
return 'application/ld+json';
} else if (contentType.includes('javascript') || contentType.includes('ecmascript')) {
return 'application/javascript';
} else if (contentType.includes('image')) {
return 'application/image';
} else if (contentType.includes('xml')) {
return 'application/xml';
} else if (contentType.includes('html')) {
return 'application/html';
} else if (contentType.includes('text')) {
return 'application/text';
} else if (contentType.includes('application/edn')) {
return 'application/xml';
} else if (contentType.includes('yaml')) {
return 'application/yaml';
} else {
return 'application/text';
}
};