diff --git a/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js b/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js
index 4989ac80d..88fe4ee01 100644
--- a/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js
+++ b/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js
@@ -69,6 +69,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
onChange={(newValue) => onUrlChange(newValue)}
onRun={handleRun}
collection={collection}
+ item={item}
/>
{
- if (!cm.state.completionActive /*Enables keyboard navigation in autocomplete list*/ && event.keyCode != 13) {
+ if (!cm.state.completionActive /*Enables keyboard navigation in autocomplete list*/ && event.key !== 'Enter') {
/*Enter - do not open autocomplete list just after item has been selected in it*/
CodeMirror.commands.autocomplete(cm, CodeMirror.hint.anyword, { autocomplete: this.props.autocomplete });
}
@@ -90,7 +92,7 @@ class SingleLineEditor extends Component {
}
this.editor.setValue(String(this.props.value) || '');
this.editor.on('change', this._onEdit);
- this.addOverlay();
+ this.addOverlay(variables);
}
_onEdit = () => {
@@ -108,10 +110,10 @@ class SingleLineEditor extends Component {
// event loop.
this.ignoreChangeEvent = true;
- let variables = getAllVariables(this.props.collection);
+ let variables = getAllVariables(this.props.collection, this.props.item);
if (!isEqual(variables, this.variables)) {
this.editor.options.brunoVarInfo.variables = variables;
- this.addOverlay();
+ this.addOverlay(variables);
}
if (this.props.theme !== prevProps.theme && this.editor) {
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
@@ -127,12 +129,10 @@ class SingleLineEditor extends Component {
this.editor.getWrapperElement().remove();
}
- addOverlay = () => {
- let variables = getAllVariables(this.props.collection);
+ addOverlay = (variables) => {
this.variables = variables;
-
defineCodeMirrorBrunoVariablesMode(variables, 'text/plain');
- this.editor.setOption('mode', 'brunovariables');
+ this.editor.setOption('mode', 'combinedmode');
};
render() {
diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js
index 045c6acf7..1632aa43a 100644
--- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js
+++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js
@@ -23,10 +23,23 @@ if (!SERVER_RENDERED) {
if (!str || !str.length || typeof str !== 'string') {
return;
}
- // str is of format {{variableName}}, extract variableName
- // we are seeing that from the gql query editor, the token string is of format variableName
- const variableName = str.replace('{{', '').replace('}}', '').trim();
- const variableValue = interpolate(get(options.variables, variableName), options.variables);
+
+ // str is of format {{variableName}} or :variableName, extract variableName
+ let variableName;
+ let variableValue;
+
+ if (str.startsWith('{{')) {
+ variableName = str.replace('{{', '').replace('}}', '').trim();
+ variableValue = interpolate(get(options.variables, variableName), options.variables);
+ } else if (str.startsWith(':')) {
+ variableName = str.replace(':', '').trim();
+ variableValue =
+ options.variables && options.variables.pathParams ? options.variables.pathParams[variableName] : undefined;
+ }
+
+ if (variableValue === undefined) {
+ return;
+ }
const into = document.createElement('div');
const descriptionDiv = document.createElement('div');
diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js
index 8b08174e2..6145ee200 100644
--- a/packages/bruno-app/src/utils/collections/index.js
+++ b/packages/bruno-app/src/utils/collections/index.js
@@ -657,6 +657,18 @@ export const getEnvironmentVariables = (collection) => {
return variables;
};
+const getPathParams = (item) => {
+ let pathParams = {};
+ if (item && item.request && item.request.params) {
+ item.request.params.forEach((param) => {
+ if (param.type === 'path' && param.name && param.value) {
+ pathParams[param.name] = param.value;
+ }
+ });
+ }
+ return pathParams;
+};
+
export const getTotalRequestCountInCollection = (collection) => {
let count = 0;
each(collection.items, (item) => {
@@ -670,12 +682,16 @@ export const getTotalRequestCountInCollection = (collection) => {
return count;
};
-export const getAllVariables = (collection) => {
+export const getAllVariables = (collection, item) => {
const environmentVariables = getEnvironmentVariables(collection);
+ const pathParams = getPathParams(item);
return {
...environmentVariables,
...collection.collectionVariables,
+ pathParams: {
+ ...pathParams
+ },
process: {
env: {
...collection.processEnvVariables
diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js
index a6bf52aed..ecd197428 100644
--- a/packages/bruno-app/src/utils/common/codemirror.js
+++ b/packages/bruno-app/src/utils/common/codemirror.js
@@ -13,32 +13,61 @@ const pathFoundInVariables = (path, obj) => {
};
export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => {
- CodeMirror.defineMode('brunovariables', function (config, parserConfig) {
- let variablesOverlay = {
- token: function (stream, state) {
+ CodeMirror.defineMode('combinedmode', function (config, parserConfig) {
+ const variablesOverlay = {
+ token: function (stream) {
if (stream.match('{{', true)) {
let ch;
let word = '';
while ((ch = stream.next()) != null) {
- if (ch == '}' && stream.next() == '}') {
+ if (ch === '}' && stream.peek() === '}') {
stream.eat('}');
- let found = pathFoundInVariables(word, variables);
- if (found) {
- return 'variable-valid random-' + (Math.random() + 1).toString(36).substring(9);
- } else {
- return 'variable-invalid random-' + (Math.random() + 1).toString(36).substring(9);
- }
- // Random classname added so adjacent variables are not rendered in the same SPAN by CodeMirror.
+ const found = pathFoundInVariables(word, variables);
+ const status = found ? 'valid' : 'invalid';
+ const randomClass = `random-${(Math.random() + 1).toString(36).substring(9)}`;
+ return `variable-${status} ${randomClass}`;
}
word += ch;
}
}
- while (stream.next() != null && !stream.match('{{', false)) {}
+ stream.skipTo('{{') || stream.skipToEnd();
return null;
}
};
- return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || mode), variablesOverlay);
+ 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, variables?.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, variables?.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;
+ }
+ };
+
+ return CodeMirror.overlayMode(
+ CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || mode), variablesOverlay),
+ urlPathParamsOverlay
+ );
});
};