editor.js (12723B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 import { showMenu } from "../../context-menu/menu"; 6 7 import { copyToTheClipboard } from "../../utils/clipboard"; 8 import { 9 getRawSourceURL, 10 shouldBlackbox, 11 findBlackBoxRange, 12 } from "../../utils/source"; 13 import { toSourceLine } from "../../utils/editor/index"; 14 import { downloadFile } from "../../utils/utils"; 15 import { features } from "../../utils/prefs"; 16 import { isFulfilled } from "../../utils/async-value"; 17 18 import { createBreakpointItems } from "./editor-breakpoint"; 19 20 import { 21 getPrettySource, 22 getIsCurrentThreadPaused, 23 isSourceWithMap, 24 getBlackBoxRanges, 25 isSourceOnSourceMapIgnoreList, 26 isSourceMapIgnoreListEnabled, 27 getEditorWrapping, 28 } from "../../selectors/index"; 29 30 import { continueToHere } from "../../actions/pause/continueToHere"; 31 import { jumpToMappedLocation } from "../../actions/sources/select"; 32 import { 33 showSource, 34 toggleInlinePreview, 35 toggleEditorWrapping, 36 } from "../../actions/ui"; 37 import { toggleBlackBox } from "../../actions/sources/blackbox"; 38 import { addExpression } from "../../actions/expressions"; 39 import { evaluateInConsole } from "../../actions/toolbox"; 40 41 export function showEditorContextMenu(event, editor, lineObject, location) { 42 return async ({ dispatch, getState }) => { 43 const { source } = location; 44 const state = getState(); 45 const blackboxedRanges = getBlackBoxRanges(state); 46 const isPaused = getIsCurrentThreadPaused(state); 47 const hasMappedLocation = 48 (source.isOriginal || 49 isSourceWithMap(state, source.id) || 50 source.isPrettyPrinted) && 51 !getPrettySource(state, source.id); 52 const isSourceOnIgnoreList = 53 isSourceMapIgnoreListEnabled(state) && 54 isSourceOnSourceMapIgnoreList(state, source); 55 const editorWrappingEnabled = getEditorWrapping(state); 56 57 showMenu( 58 event, 59 editorMenuItems({ 60 blackboxedRanges, 61 hasMappedLocation, 62 location, 63 isPaused, 64 editorWrappingEnabled, 65 selectionText: editor.getSelectedText(), 66 isTextSelected: editor.isTextSelected(), 67 lineObject, 68 isSourceOnIgnoreList, 69 dispatch, 70 }) 71 ); 72 }; 73 } 74 75 export function showEditorGutterContextMenu(event, line, location, lineText) { 76 return async ({ dispatch, getState }) => { 77 const { source } = location; 78 const state = getState(); 79 const blackboxedRanges = getBlackBoxRanges(state); 80 const isPaused = getIsCurrentThreadPaused(state); 81 const isSourceOnIgnoreList = 82 isSourceMapIgnoreListEnabled(state) && 83 isSourceOnSourceMapIgnoreList(state, source); 84 85 showMenu(event, [ 86 ...createBreakpointItems(location, lineText, dispatch), 87 { type: "separator" }, 88 continueToHereItem(location, isPaused, dispatch), 89 { type: "separator" }, 90 blackBoxLineMenuItem( 91 source, 92 line, 93 blackboxedRanges, 94 isSourceOnIgnoreList, 95 location.line, 96 dispatch 97 ), 98 ]); 99 }; 100 } 101 102 // Menu Items 103 const continueToHereItem = (location, isPaused, dispatch) => ({ 104 accesskey: L10N.getStr("editor.continueToHere.accesskey"), 105 disabled: !isPaused, 106 click: () => dispatch(continueToHere(location)), 107 id: "node-menu-continue-to-here", 108 label: L10N.getStr("editor.continueToHere.label"), 109 }); 110 111 const copyToClipboardItem = selectionText => ({ 112 id: "node-menu-copy-to-clipboard", 113 label: L10N.getStr("copyToClipboard.label"), 114 accesskey: L10N.getStr("copyToClipboard.accesskey"), 115 disabled: selectionText.length === 0, 116 click: () => copyToTheClipboard(selectionText), 117 }); 118 119 const copySourceItem = selectedContent => ({ 120 id: "node-menu-copy-source", 121 label: L10N.getStr("copySource.label"), 122 accesskey: L10N.getStr("copySource.accesskey"), 123 disabled: false, 124 click: () => 125 selectedContent.type === "text" && 126 copyToTheClipboard(selectedContent.value), 127 }); 128 129 const copySourceUri2Item = selectedSource => ({ 130 id: "node-menu-copy-source-url", 131 label: L10N.getStr("copySourceUri2"), 132 accesskey: L10N.getStr("copySourceUri2.accesskey"), 133 disabled: !selectedSource.url, 134 click: () => copyToTheClipboard(getRawSourceURL(selectedSource.url)), 135 }); 136 137 const jumpToMappedLocationItem = (location, hasMappedLocation, dispatch) => ({ 138 id: "node-menu-jump", 139 label: L10N.getFormatStr( 140 "editor.jumpToMappedLocation1", 141 location.source.isOriginal 142 ? L10N.getStr("generated") 143 : L10N.getStr("original") 144 ), 145 accesskey: L10N.getStr("editor.jumpToMappedLocation1.accesskey"), 146 disabled: !hasMappedLocation, 147 click: () => dispatch(jumpToMappedLocation(location)), 148 }); 149 150 const showSourceMenuItem = (selectedSource, dispatch) => ({ 151 id: "node-menu-show-source", 152 label: L10N.getStr("sourceTabs.revealInTree"), 153 accesskey: L10N.getStr("sourceTabs.revealInTree.accesskey"), 154 disabled: !selectedSource.url, 155 click: () => dispatch(showSource(selectedSource.id)), 156 }); 157 158 const blackBoxMenuItem = ( 159 selectedSource, 160 blackboxedRanges, 161 isSourceOnIgnoreList, 162 dispatch 163 ) => { 164 const isBlackBoxed = !!blackboxedRanges[selectedSource.url]; 165 return { 166 id: "node-menu-blackbox", 167 label: isBlackBoxed 168 ? L10N.getStr("ignoreContextItem.unignore") 169 : L10N.getStr("ignoreContextItem.ignore"), 170 accesskey: isBlackBoxed 171 ? L10N.getStr("ignoreContextItem.unignore.accesskey") 172 : L10N.getStr("ignoreContextItem.ignore.accesskey"), 173 disabled: isSourceOnIgnoreList || !shouldBlackbox(selectedSource), 174 click: () => dispatch(toggleBlackBox(selectedSource)), 175 }; 176 }; 177 178 const blackBoxLineMenuItem = ( 179 selectedSource, 180 { from, to }, 181 blackboxedRanges, 182 isSourceOnIgnoreList, 183 // the clickedLine is passed when the context menu 184 // is opened from the gutter, it is not available when the 185 // the context menu is opened from the editor. 186 clickedLine = null, 187 dispatch 188 ) => { 189 const startLine = clickedLine ?? toSourceLine(selectedSource, from.line); 190 const endLine = clickedLine ?? toSourceLine(selectedSource, to.line); 191 192 const blackboxRange = findBlackBoxRange(selectedSource, blackboxedRanges, { 193 start: startLine, 194 end: endLine, 195 }); 196 197 const selectedLineIsBlackBoxed = !!blackboxRange; 198 199 const isSingleLine = selectedLineIsBlackBoxed 200 ? blackboxRange.start.line == blackboxRange.end.line 201 : startLine == endLine; 202 203 const isSourceFullyBlackboxed = 204 blackboxedRanges[selectedSource.url] && 205 !blackboxedRanges[selectedSource.url].length; 206 207 // The ignore/unignore line context menu item should be disabled when 208 // 1) The source is on the sourcemap ignore list 209 // 2) The whole source is blackboxed or 210 // 3) Multiple lines are blackboxed or 211 // 4) Multiple lines are selected in the editor 212 const shouldDisable = 213 isSourceOnIgnoreList || isSourceFullyBlackboxed || !isSingleLine; 214 215 return { 216 id: "node-menu-blackbox-line", 217 label: !selectedLineIsBlackBoxed 218 ? L10N.getStr("ignoreContextItem.ignoreLine") 219 : L10N.getStr("ignoreContextItem.unignoreLine"), 220 accesskey: !selectedLineIsBlackBoxed 221 ? L10N.getStr("ignoreContextItem.ignoreLine.accesskey") 222 : L10N.getStr("ignoreContextItem.unignoreLine.accesskey"), 223 disabled: shouldDisable, 224 click: () => { 225 const selectionRange = { 226 start: { 227 line: startLine, 228 column: clickedLine == null ? from.ch : 0, 229 }, 230 end: { 231 line: endLine, 232 column: clickedLine == null ? to.ch : 0, 233 }, 234 }; 235 236 dispatch( 237 toggleBlackBox( 238 selectedSource, 239 !selectedLineIsBlackBoxed, 240 selectedLineIsBlackBoxed ? [blackboxRange] : [selectionRange] 241 ) 242 ); 243 }, 244 }; 245 }; 246 247 const blackBoxLinesMenuItem = ( 248 selectedSource, 249 { from, to }, 250 blackboxedRanges, 251 isSourceOnIgnoreList, 252 clickedLine, 253 dispatch 254 ) => { 255 const startLine = toSourceLine(selectedSource, from.line); 256 const endLine = toSourceLine(selectedSource, to.line); 257 258 const blackboxRange = findBlackBoxRange(selectedSource, blackboxedRanges, { 259 start: startLine, 260 end: endLine, 261 }); 262 263 const selectedLinesAreBlackBoxed = !!blackboxRange; 264 265 return { 266 id: "node-menu-blackbox-lines", 267 label: !selectedLinesAreBlackBoxed 268 ? L10N.getStr("ignoreContextItem.ignoreLines") 269 : L10N.getStr("ignoreContextItem.unignoreLines"), 270 accesskey: !selectedLinesAreBlackBoxed 271 ? L10N.getStr("ignoreContextItem.ignoreLines.accesskey") 272 : L10N.getStr("ignoreContextItem.unignoreLines.accesskey"), 273 disabled: isSourceOnIgnoreList, 274 click: () => { 275 const selectionRange = { 276 start: { 277 line: startLine, 278 column: from.ch, 279 }, 280 end: { 281 line: endLine, 282 column: to.ch, 283 }, 284 }; 285 286 dispatch( 287 toggleBlackBox( 288 selectedSource, 289 !selectedLinesAreBlackBoxed, 290 selectedLinesAreBlackBoxed ? [blackboxRange] : [selectionRange] 291 ) 292 ); 293 }, 294 }; 295 }; 296 297 const watchExpressionItem = (selectedSource, selectionText, dispatch) => ({ 298 id: "node-menu-add-watch-expression", 299 label: L10N.getStr("expressions.label"), 300 accesskey: L10N.getStr("expressions.accesskey"), 301 click: () => dispatch(addExpression(selectionText)), 302 }); 303 304 const evaluateInConsoleItem = (selectedSource, selectionText, dispatch) => ({ 305 id: "node-menu-evaluate-in-console", 306 label: L10N.getStr("evaluateInConsole.label"), 307 click: () => dispatch(evaluateInConsole(selectionText)), 308 }); 309 310 const downloadFileItem = (selectedSource, selectedContent) => ({ 311 id: "node-menu-download-file", 312 label: L10N.getStr("downloadFile.label"), 313 accesskey: L10N.getStr("downloadFile.accesskey"), 314 click: () => downloadFile(selectedContent, selectedSource.shortName), 315 }); 316 317 const inlinePreviewItem = dispatch => ({ 318 id: "node-menu-inline-preview", 319 label: features.inlinePreview 320 ? L10N.getStr("inlinePreview.hide.label") 321 : L10N.getStr("inlinePreview.show.label"), 322 click: () => dispatch(toggleInlinePreview(!features.inlinePreview)), 323 }); 324 325 const editorWrappingItem = (editorWrappingEnabled, dispatch) => ({ 326 id: "node-menu-editor-wrapping", 327 label: editorWrappingEnabled 328 ? L10N.getStr("editorWrapping.hide.label") 329 : L10N.getStr("editorWrapping.show.label"), 330 click: () => dispatch(toggleEditorWrapping(!editorWrappingEnabled)), 331 }); 332 333 function editorMenuItems({ 334 blackboxedRanges, 335 location, 336 selectionText, 337 hasMappedLocation, 338 isTextSelected, 339 isPaused, 340 editorWrappingEnabled, 341 lineObject, 342 isSourceOnIgnoreList, 343 dispatch, 344 }) { 345 const items = []; 346 347 const { source } = location; 348 349 const content = 350 source.content && isFulfilled(source.content) ? source.content.value : null; 351 352 items.push( 353 jumpToMappedLocationItem(location, hasMappedLocation, dispatch), 354 continueToHereItem(location, isPaused, dispatch), 355 { type: "separator" }, 356 copyToClipboardItem(selectionText), 357 ...(!source.isWasm 358 ? [ 359 ...(content ? [copySourceItem(content)] : []), 360 copySourceUri2Item(source), 361 ] 362 : []), 363 ...(content ? [downloadFileItem(source, content)] : []), 364 { type: "separator" }, 365 showSourceMenuItem(source, dispatch), 366 { type: "separator" }, 367 blackBoxMenuItem(source, blackboxedRanges, isSourceOnIgnoreList, dispatch) 368 ); 369 370 const startLine = toSourceLine(source, lineObject.from.line); 371 const endLine = toSourceLine(source, lineObject.to.line); 372 373 // Find any blackbox ranges that exist for the selected lines 374 const blackboxRange = findBlackBoxRange(source, blackboxedRanges, { 375 start: startLine, 376 end: endLine, 377 }); 378 379 const isMultiLineSelection = blackboxRange 380 ? blackboxRange.start.line !== blackboxRange.end.line 381 : startLine !== endLine; 382 383 // When the range is defined and is an empty array, 384 // the whole source is blackboxed 385 const theWholeSourceIsBlackBoxed = 386 blackboxedRanges[source.url] && !blackboxedRanges[source.url].length; 387 388 if (!theWholeSourceIsBlackBoxed) { 389 const blackBoxSourceLinesMenuItem = isMultiLineSelection 390 ? blackBoxLinesMenuItem 391 : blackBoxLineMenuItem; 392 393 items.push( 394 blackBoxSourceLinesMenuItem( 395 source, 396 lineObject, 397 blackboxedRanges, 398 isSourceOnIgnoreList, 399 null, 400 dispatch 401 ) 402 ); 403 } 404 405 if (isTextSelected) { 406 items.push( 407 { type: "separator" }, 408 watchExpressionItem(source, selectionText, dispatch), 409 evaluateInConsoleItem(source, selectionText, dispatch) 410 ); 411 } 412 413 items.push( 414 { type: "separator" }, 415 inlinePreviewItem(dispatch), 416 editorWrappingItem(editorWrappingEnabled, dispatch) 417 ); 418 419 return items; 420 }