summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/actions/context-menus/editor.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/actions/context-menus/editor.js')
-rw-r--r--devtools/client/debugger/src/actions/context-menus/editor.js436
1 files changed, 436 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/context-menus/editor.js b/devtools/client/debugger/src/actions/context-menus/editor.js
new file mode 100644
index 0000000000..1125790a9b
--- /dev/null
+++ b/devtools/client/debugger/src/actions/context-menus/editor.js
@@ -0,0 +1,436 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+import { showMenu } from "../../context-menu/menu";
+
+import { copyToTheClipboard } from "../../utils/clipboard";
+import {
+ isPretty,
+ getRawSourceURL,
+ getFilename,
+ shouldBlackbox,
+ findBlackBoxRange,
+} from "../../utils/source";
+import { toSourceLine } from "../../utils/editor/index";
+import { downloadFile } from "../../utils/utils";
+import { features } from "../../utils/prefs";
+import { isFulfilled } from "../../utils/async-value";
+
+import { createBreakpointItems } from "./editor-breakpoint";
+
+import {
+ getPrettySource,
+ getIsCurrentThreadPaused,
+ isSourceWithMap,
+ getBlackBoxRanges,
+ isSourceOnSourceMapIgnoreList,
+ isSourceMapIgnoreListEnabled,
+ getEditorWrapping,
+} from "../../selectors/index";
+
+import { continueToHere } from "../../actions/pause/continueToHere";
+import { jumpToMappedLocation } from "../../actions/sources/select";
+import {
+ showSource,
+ toggleInlinePreview,
+ toggleEditorWrapping,
+} from "../../actions/ui";
+import { toggleBlackBox } from "../../actions/sources/blackbox";
+import { addExpression } from "../../actions/expressions";
+import { evaluateInConsole } from "../../actions/toolbox";
+
+export function showEditorContextMenu(event, editor, location) {
+ return async ({ dispatch, getState }) => {
+ const { source } = location;
+ const state = getState();
+ const blackboxedRanges = getBlackBoxRanges(state);
+ const isPaused = getIsCurrentThreadPaused(state);
+ const hasMappedLocation =
+ (source.isOriginal ||
+ isSourceWithMap(state, source.id) ||
+ isPretty(source)) &&
+ !getPrettySource(state, source.id);
+ const isSourceOnIgnoreList =
+ isSourceMapIgnoreListEnabled(state) &&
+ isSourceOnSourceMapIgnoreList(state, source);
+ const editorWrappingEnabled = getEditorWrapping(state);
+
+ showMenu(
+ event,
+ editorMenuItems({
+ blackboxedRanges,
+ hasMappedLocation,
+ location,
+ isPaused,
+ editorWrappingEnabled,
+ selectionText: editor.codeMirror.getSelection().trim(),
+ isTextSelected: editor.codeMirror.somethingSelected(),
+ editor,
+ isSourceOnIgnoreList,
+ dispatch,
+ })
+ );
+ };
+}
+
+export function showEditorGutterContextMenu(event, editor, location, lineText) {
+ return async ({ dispatch, getState }) => {
+ const { source } = location;
+ const state = getState();
+ const blackboxedRanges = getBlackBoxRanges(state);
+ const isPaused = getIsCurrentThreadPaused(state);
+ const isSourceOnIgnoreList =
+ isSourceMapIgnoreListEnabled(state) &&
+ isSourceOnSourceMapIgnoreList(state, source);
+
+ showMenu(event, [
+ ...createBreakpointItems(location, lineText, dispatch),
+ { type: "separator" },
+ continueToHereItem(location, isPaused, dispatch),
+ { type: "separator" },
+ blackBoxLineMenuItem(
+ source,
+ editor,
+ blackboxedRanges,
+ isSourceOnIgnoreList,
+ location.line,
+ dispatch
+ ),
+ ]);
+ };
+}
+
+// Menu Items
+const continueToHereItem = (location, isPaused, dispatch) => ({
+ accesskey: L10N.getStr("editor.continueToHere.accesskey"),
+ disabled: !isPaused,
+ click: () => dispatch(continueToHere(location)),
+ id: "node-menu-continue-to-here",
+ label: L10N.getStr("editor.continueToHere.label"),
+});
+
+const copyToClipboardItem = selectionText => ({
+ id: "node-menu-copy-to-clipboard",
+ label: L10N.getStr("copyToClipboard.label"),
+ accesskey: L10N.getStr("copyToClipboard.accesskey"),
+ disabled: selectionText.length === 0,
+ click: () => copyToTheClipboard(selectionText),
+});
+
+const copySourceItem = selectedContent => ({
+ id: "node-menu-copy-source",
+ label: L10N.getStr("copySource.label"),
+ accesskey: L10N.getStr("copySource.accesskey"),
+ disabled: false,
+ click: () =>
+ selectedContent.type === "text" &&
+ copyToTheClipboard(selectedContent.value),
+});
+
+const copySourceUri2Item = selectedSource => ({
+ id: "node-menu-copy-source-url",
+ label: L10N.getStr("copySourceUri2"),
+ accesskey: L10N.getStr("copySourceUri2.accesskey"),
+ disabled: !selectedSource.url,
+ click: () => copyToTheClipboard(getRawSourceURL(selectedSource.url)),
+});
+
+const jumpToMappedLocationItem = (location, hasMappedLocation, dispatch) => ({
+ id: "node-menu-jump",
+ label: L10N.getFormatStr(
+ "editor.jumpToMappedLocation1",
+ location.source.isOriginal
+ ? L10N.getStr("generated")
+ : L10N.getStr("original")
+ ),
+ accesskey: L10N.getStr("editor.jumpToMappedLocation1.accesskey"),
+ disabled: !hasMappedLocation,
+ click: () => dispatch(jumpToMappedLocation(location)),
+});
+
+const showSourceMenuItem = (selectedSource, dispatch) => ({
+ id: "node-menu-show-source",
+ label: L10N.getStr("sourceTabs.revealInTree"),
+ accesskey: L10N.getStr("sourceTabs.revealInTree.accesskey"),
+ disabled: !selectedSource.url,
+ click: () => dispatch(showSource(selectedSource.id)),
+});
+
+const blackBoxMenuItem = (
+ selectedSource,
+ blackboxedRanges,
+ isSourceOnIgnoreList,
+ dispatch
+) => {
+ const isBlackBoxed = !!blackboxedRanges[selectedSource.url];
+ return {
+ id: "node-menu-blackbox",
+ label: isBlackBoxed
+ ? L10N.getStr("ignoreContextItem.unignore")
+ : L10N.getStr("ignoreContextItem.ignore"),
+ accesskey: isBlackBoxed
+ ? L10N.getStr("ignoreContextItem.unignore.accesskey")
+ : L10N.getStr("ignoreContextItem.ignore.accesskey"),
+ disabled: isSourceOnIgnoreList || !shouldBlackbox(selectedSource),
+ click: () => dispatch(toggleBlackBox(selectedSource)),
+ };
+};
+
+const blackBoxLineMenuItem = (
+ selectedSource,
+ editor,
+ blackboxedRanges,
+ isSourceOnIgnoreList,
+ // the clickedLine is passed when the context menu
+ // is opened from the gutter, it is not available when the
+ // the context menu is opened from the editor.
+ clickedLine = null,
+ dispatch
+) => {
+ const { codeMirror } = editor;
+ const from = codeMirror.getCursor("from");
+ const to = codeMirror.getCursor("to");
+
+ const startLine = clickedLine ?? toSourceLine(selectedSource.id, from.line);
+ const endLine = clickedLine ?? toSourceLine(selectedSource.id, to.line);
+
+ const blackboxRange = findBlackBoxRange(selectedSource, blackboxedRanges, {
+ start: startLine,
+ end: endLine,
+ });
+
+ const selectedLineIsBlackBoxed = !!blackboxRange;
+
+ const isSingleLine = selectedLineIsBlackBoxed
+ ? blackboxRange.start.line == blackboxRange.end.line
+ : startLine == endLine;
+
+ const isSourceFullyBlackboxed =
+ blackboxedRanges[selectedSource.url] &&
+ !blackboxedRanges[selectedSource.url].length;
+
+ // The ignore/unignore line context menu item should be disabled when
+ // 1) The source is on the sourcemap ignore list
+ // 2) The whole source is blackboxed or
+ // 3) Multiple lines are blackboxed or
+ // 4) Multiple lines are selected in the editor
+ const shouldDisable =
+ isSourceOnIgnoreList || isSourceFullyBlackboxed || !isSingleLine;
+
+ return {
+ id: "node-menu-blackbox-line",
+ label: !selectedLineIsBlackBoxed
+ ? L10N.getStr("ignoreContextItem.ignoreLine")
+ : L10N.getStr("ignoreContextItem.unignoreLine"),
+ accesskey: !selectedLineIsBlackBoxed
+ ? L10N.getStr("ignoreContextItem.ignoreLine.accesskey")
+ : L10N.getStr("ignoreContextItem.unignoreLine.accesskey"),
+ disabled: shouldDisable,
+ click: () => {
+ const selectionRange = {
+ start: {
+ line: startLine,
+ column: clickedLine == null ? from.ch : 0,
+ },
+ end: {
+ line: endLine,
+ column: clickedLine == null ? to.ch : 0,
+ },
+ };
+
+ dispatch(
+ toggleBlackBox(
+ selectedSource,
+ !selectedLineIsBlackBoxed,
+ selectedLineIsBlackBoxed ? [blackboxRange] : [selectionRange]
+ )
+ );
+ },
+ };
+};
+
+const blackBoxLinesMenuItem = (
+ selectedSource,
+ editor,
+ blackboxedRanges,
+ isSourceOnIgnoreList,
+ clickedLine = null,
+ dispatch
+) => {
+ const { codeMirror } = editor;
+ const from = codeMirror.getCursor("from");
+ const to = codeMirror.getCursor("to");
+
+ const startLine = toSourceLine(selectedSource.id, from.line);
+ const endLine = toSourceLine(selectedSource.id, to.line);
+
+ const blackboxRange = findBlackBoxRange(selectedSource, blackboxedRanges, {
+ start: startLine,
+ end: endLine,
+ });
+
+ const selectedLinesAreBlackBoxed = !!blackboxRange;
+
+ return {
+ id: "node-menu-blackbox-lines",
+ label: !selectedLinesAreBlackBoxed
+ ? L10N.getStr("ignoreContextItem.ignoreLines")
+ : L10N.getStr("ignoreContextItem.unignoreLines"),
+ accesskey: !selectedLinesAreBlackBoxed
+ ? L10N.getStr("ignoreContextItem.ignoreLines.accesskey")
+ : L10N.getStr("ignoreContextItem.unignoreLines.accesskey"),
+ disabled: isSourceOnIgnoreList,
+ click: () => {
+ const selectionRange = {
+ start: {
+ line: startLine,
+ column: from.ch,
+ },
+ end: {
+ line: endLine,
+ column: to.ch,
+ },
+ };
+
+ dispatch(
+ toggleBlackBox(
+ selectedSource,
+ !selectedLinesAreBlackBoxed,
+ selectedLinesAreBlackBoxed ? [blackboxRange] : [selectionRange]
+ )
+ );
+ },
+ };
+};
+
+const watchExpressionItem = (selectedSource, selectionText, dispatch) => ({
+ id: "node-menu-add-watch-expression",
+ label: L10N.getStr("expressions.label"),
+ accesskey: L10N.getStr("expressions.accesskey"),
+ click: () => dispatch(addExpression(selectionText)),
+});
+
+const evaluateInConsoleItem = (selectedSource, selectionText, dispatch) => ({
+ id: "node-menu-evaluate-in-console",
+ label: L10N.getStr("evaluateInConsole.label"),
+ click: () => dispatch(evaluateInConsole(selectionText)),
+});
+
+const downloadFileItem = (selectedSource, selectedContent) => ({
+ id: "node-menu-download-file",
+ label: L10N.getStr("downloadFile.label"),
+ accesskey: L10N.getStr("downloadFile.accesskey"),
+ click: () => downloadFile(selectedContent, getFilename(selectedSource)),
+});
+
+const inlinePreviewItem = dispatch => ({
+ id: "node-menu-inline-preview",
+ label: features.inlinePreview
+ ? L10N.getStr("inlinePreview.hide.label")
+ : L10N.getStr("inlinePreview.show.label"),
+ click: () => dispatch(toggleInlinePreview(!features.inlinePreview)),
+});
+
+const editorWrappingItem = (editorWrappingEnabled, dispatch) => ({
+ id: "node-menu-editor-wrapping",
+ label: editorWrappingEnabled
+ ? L10N.getStr("editorWrapping.hide.label")
+ : L10N.getStr("editorWrapping.show.label"),
+ click: () => dispatch(toggleEditorWrapping(!editorWrappingEnabled)),
+});
+
+function editorMenuItems({
+ blackboxedRanges,
+ location,
+ selectionText,
+ hasMappedLocation,
+ isTextSelected,
+ isPaused,
+ editorWrappingEnabled,
+ editor,
+ isSourceOnIgnoreList,
+ dispatch,
+}) {
+ const items = [];
+
+ const { source } = location;
+
+ const content =
+ source.content && isFulfilled(source.content) ? source.content.value : null;
+
+ items.push(
+ jumpToMappedLocationItem(location, hasMappedLocation, dispatch),
+ continueToHereItem(location, isPaused, dispatch),
+ { type: "separator" },
+ copyToClipboardItem(selectionText),
+ ...(!source.isWasm
+ ? [
+ ...(content ? [copySourceItem(content)] : []),
+ copySourceUri2Item(source),
+ ]
+ : []),
+ ...(content ? [downloadFileItem(source, content)] : []),
+ { type: "separator" },
+ showSourceMenuItem(source, dispatch),
+ { type: "separator" },
+ blackBoxMenuItem(source, blackboxedRanges, isSourceOnIgnoreList, dispatch)
+ );
+
+ const startLine = toSourceLine(
+ source.id,
+ editor.codeMirror.getCursor("from").line
+ );
+ const endLine = toSourceLine(
+ source.id,
+ editor.codeMirror.getCursor("to").line
+ );
+
+ // Find any blackbox ranges that exist for the selected lines
+ const blackboxRange = findBlackBoxRange(source, blackboxedRanges, {
+ start: startLine,
+ end: endLine,
+ });
+
+ const isMultiLineSelection = blackboxRange
+ ? blackboxRange.start.line !== blackboxRange.end.line
+ : startLine !== endLine;
+
+ // When the range is defined and is an empty array,
+ // the whole source is blackboxed
+ const theWholeSourceIsBlackBoxed =
+ blackboxedRanges[source.url] && !blackboxedRanges[source.url].length;
+
+ if (!theWholeSourceIsBlackBoxed) {
+ const blackBoxSourceLinesMenuItem = isMultiLineSelection
+ ? blackBoxLinesMenuItem
+ : blackBoxLineMenuItem;
+
+ items.push(
+ blackBoxSourceLinesMenuItem(
+ source,
+ editor,
+ blackboxedRanges,
+ isSourceOnIgnoreList,
+ null,
+ dispatch
+ )
+ );
+ }
+
+ if (isTextSelected) {
+ items.push(
+ { type: "separator" },
+ watchExpressionItem(source, selectionText, dispatch),
+ evaluateInConsoleItem(source, selectionText, dispatch)
+ );
+ }
+
+ items.push(
+ { type: "separator" },
+ inlinePreviewItem(dispatch),
+ editorWrappingItem(editorWrappingEnabled, dispatch)
+ );
+
+ return items;
+}