ConsolePrompt.js 9.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. export default class ConsolePrompt extends UI.Widget{constructor(){super();this.registerRequiredCSS('console/consolePrompt.css');this._addCompletionsFromHistory=true;this._history=new Console.ConsoleHistoryManager();this._initialText='';this._editor=null;this._eagerPreviewElement=createElementWithClass('div','console-eager-preview');this._textChangeThrottler=new Common.Throttler(150);this._formatter=new ObjectUI.RemoteObjectPreviewFormatter();this._requestPreviewBound=this._requestPreview.bind(this);this._innerPreviewElement=this._eagerPreviewElement.createChild('div','console-eager-inner-preview');this._eagerPreviewElement.appendChild(UI.Icon.create('smallicon-command-result','preview-result-icon'));const editorContainerElement=this.element.createChild('div','console-prompt-editor-container');this.element.appendChild(this._eagerPreviewElement);this._promptIcon=UI.Icon.create('smallicon-text-prompt','console-prompt-icon');this.element.appendChild(this._promptIcon);this._iconThrottler=new Common.Throttler(0);this._eagerEvalSetting=Common.settings.moduleSetting('consoleEagerEval');this._eagerEvalSetting.addChangeListener(this._eagerSettingChanged.bind(this));this._eagerPreviewElement.classList.toggle('hidden',!this._eagerEvalSetting.get());this.element.tabIndex=0;this._previewRequestForTest=null;this._defaultAutocompleteConfig=null;this._highlightingNode=false;self.runtime.extension(UI.TextEditorFactory).instance().then(gotFactory.bind(this));function gotFactory(factory){this._editor=factory.createEditor({devtoolsAccessibleName:ls`Console prompt`,lineNumbers:false,lineWrapping:true,mimeType:'javascript',autoHeight:true});this._defaultAutocompleteConfig=ObjectUI.JavaScriptAutocompleteConfig.createConfigForEditor(this._editor);this._editor.configureAutocomplete(Object.assign({},this._defaultAutocompleteConfig,{suggestionsCallback:this._wordsWithQuery.bind(this),anchorBehavior:UI.GlassPane.AnchorBehavior.PreferTop}));this._editor.widget().element.addEventListener('keydown',this._editorKeyDown.bind(this),true);this._editor.widget().show(editorContainerElement);this._editor.addEventListener(UI.TextEditor.Events.CursorChanged,this._updatePromptIcon,this);this._editor.addEventListener(UI.TextEditor.Events.TextChanged,this._onTextChanged,this);this._editor.addEventListener(UI.TextEditor.Events.SuggestionChanged,this._onTextChanged,this);this.setText(this._initialText);delete this._initialText;if(this.hasFocus()){this.focus();}
  2. this.element.removeAttribute('tabindex');this._editor.widget().element.tabIndex=-1;this._editorSetForTest();Host.userMetrics.panelLoaded('console','DevTools.Launch.Console');}}
  3. _eagerSettingChanged(){const enabled=this._eagerEvalSetting.get();this._eagerPreviewElement.classList.toggle('hidden',!enabled);if(enabled){this._requestPreview();}}
  4. belowEditorElement(){return this._eagerPreviewElement;}
  5. _onTextChanged(){if(this._eagerEvalSetting.get()){const asSoonAsPossible=!this._editor.textWithCurrentSuggestion();this._previewRequestForTest=this._textChangeThrottler.schedule(this._requestPreviewBound,asSoonAsPossible);}
  6. this._updatePromptIcon();this.dispatchEventToListeners(Events.TextChanged);}
  7. async _requestPreview(){const text=this._editor.textWithCurrentSuggestion().trim();const executionContext=UI.context.flavor(SDK.ExecutionContext);const{preview,result}=await ObjectUI.JavaScriptREPL.evaluateAndBuildPreview(text,true,500);this._innerPreviewElement.removeChildren();if(preview.deepTextContent()!==this._editor.textWithCurrentSuggestion().trim()){this._innerPreviewElement.appendChild(preview);}
  8. if(result&&result.object&&result.object.subtype==='node'){this._highlightingNode=true;SDK.OverlayModel.highlightObjectAsDOMNode(result.object);}else if(this._highlightingNode){this._highlightingNode=false;SDK.OverlayModel.hideDOMNodeHighlight();}
  9. if(result){executionContext.runtimeModel.releaseEvaluationResult(result);}}
  10. willHide(){if(this._highlightingNode){this._highlightingNode=false;SDK.OverlayModel.hideDOMNodeHighlight();}}
  11. history(){return this._history;}
  12. clearAutocomplete(){if(this._editor){this._editor.clearAutocomplete();}}
  13. _isCaretAtEndOfPrompt(){return!!this._editor&&this._editor.selection().collapseToEnd().equal(this._editor.fullRange().collapseToEnd());}
  14. moveCaretToEndOfPrompt(){if(this._editor){this._editor.setSelection(TextUtils.TextRange.createFromLocation(Infinity,Infinity));}}
  15. setText(text){if(this._editor){this._editor.setText(text);}else{this._initialText=text;}
  16. this.dispatchEventToListeners(Events.TextChanged);}
  17. text(){return this._editor?this._editor.text():this._initialText;}
  18. setAddCompletionsFromHistory(value){this._addCompletionsFromHistory=value;}
  19. _editorKeyDown(event){const keyboardEvent=(event);let newText;let isPrevious;const selection=this._editor.selection();const cursorY=this._editor.visualCoordinates(selection.endLine,selection.endColumn).y;switch(keyboardEvent.keyCode){case UI.KeyboardShortcut.Keys.Up.code:const startY=this._editor.visualCoordinates(0,0).y;if(keyboardEvent.shiftKey||!selection.isEmpty()||cursorY!==startY){break;}
  20. newText=this._history.previous(this.text());isPrevious=true;break;case UI.KeyboardShortcut.Keys.Down.code:const fullRange=this._editor.fullRange();const endY=this._editor.visualCoordinates(fullRange.endLine,fullRange.endColumn).y;if(keyboardEvent.shiftKey||!selection.isEmpty()||cursorY!==endY){break;}
  21. newText=this._history.next();break;case UI.KeyboardShortcut.Keys.P.code:if(Host.isMac()&&keyboardEvent.ctrlKey&&!keyboardEvent.metaKey&&!keyboardEvent.altKey&&!keyboardEvent.shiftKey){newText=this._history.previous(this.text());isPrevious=true;}
  22. break;case UI.KeyboardShortcut.Keys.N.code:if(Host.isMac()&&keyboardEvent.ctrlKey&&!keyboardEvent.metaKey&&!keyboardEvent.altKey&&!keyboardEvent.shiftKey){newText=this._history.next();}
  23. break;case UI.KeyboardShortcut.Keys.Enter.code:this._enterKeyPressed(keyboardEvent);break;case UI.KeyboardShortcut.Keys.Tab.code:if(!this.text()){keyboardEvent.consume();}
  24. break;}
  25. if(newText===undefined){return;}
  26. keyboardEvent.consume(true);this.setText(newText);if(isPrevious){this._editor.setSelection(TextUtils.TextRange.createFromLocation(0,Infinity));}else{this.moveCaretToEndOfPrompt();}}
  27. async _enterWillEvaluate(){if(!this._isCaretAtEndOfPrompt()){return true;}
  28. return await ObjectUI.JavaScriptAutocomplete.isExpressionComplete(this.text());}
  29. _updatePromptIcon(){this._iconThrottler.schedule(async()=>{const canComplete=await this._enterWillEvaluate();this._promptIcon.classList.toggle('console-prompt-incomplete',!canComplete);});}
  30. async _enterKeyPressed(event){if(event.altKey||event.ctrlKey||event.shiftKey){return;}
  31. event.consume(true);this.element.scrollIntoView();this.clearAutocomplete();const str=this.text();if(!str.length){return;}
  32. if(await this._enterWillEvaluate()){await this._appendCommand(str,true);}else{this._editor.newlineAndIndent();}
  33. this._enterProcessedForTest();}
  34. async _appendCommand(text,useCommandLineAPI){this.setText('');const currentExecutionContext=UI.context.flavor(SDK.ExecutionContext);if(currentExecutionContext){const executionContext=currentExecutionContext;const message=SDK.consoleModel.addCommandMessage(executionContext,text);const wrappedResult=await ObjectUI.JavaScriptREPL.preprocessExpression(text);SDK.consoleModel.evaluateCommandInConsole(executionContext,message,wrappedResult.text,useCommandLineAPI,wrappedResult.preprocessed);if(Console.ConsolePanel.instance().isShowing()){Host.userMetrics.actionTaken(Host.UserMetrics.Action.CommandEvaluatedInConsolePanel);}}}
  35. _enterProcessedForTest(){}
  36. _historyCompletions(prefix,force){const text=this.text();if(!this._addCompletionsFromHistory||!this._isCaretAtEndOfPrompt()||(!text&&!force)){return[];}
  37. const result=[];const set=new Set();const data=this._history.historyData();for(let i=data.length-1;i>=0&&result.length<50;--i){const item=data[i];if(!item.startsWith(text)){continue;}
  38. if(set.has(item)){continue;}
  39. set.add(item);result.push({text:item.substring(text.length-prefix.length),iconType:'smallicon-text-prompt',isSecondary:true});}
  40. return result;}
  41. focus(){if(this._editor){this._editor.widget().focus();}else{this.element.focus();}}
  42. async _wordsWithQuery(queryRange,substituteRange,force){const query=this._editor.text(queryRange);const words=await this._defaultAutocompleteConfig.suggestionsCallback(queryRange,substituteRange,force);const historyWords=this._historyCompletions(query,force);return words.concat(historyWords);}
  43. _editorSetForTest(){}}
  44. export class ConsoleHistoryManager{constructor(){this._data=[];this._historyOffset=1;}
  45. historyData(){return this._data;}
  46. setHistoryData(data){this._data=data.slice();this._historyOffset=1;}
  47. pushHistoryItem(text){if(this._uncommittedIsTop){this._data.pop();delete this._uncommittedIsTop;}
  48. this._historyOffset=1;if(text===this._currentHistoryItem()){return;}
  49. this._data.push(text);}
  50. _pushCurrentText(currentText){if(this._uncommittedIsTop){this._data.pop();}
  51. this._uncommittedIsTop=true;this._data.push(currentText);}
  52. previous(currentText){if(this._historyOffset>this._data.length){return undefined;}
  53. if(this._historyOffset===1){this._pushCurrentText(currentText);}
  54. ++this._historyOffset;return this._currentHistoryItem();}
  55. next(){if(this._historyOffset===1){return undefined;}
  56. --this._historyOffset;return this._currentHistoryItem();}
  57. _currentHistoryItem(){return this._data[this._data.length-this._historyOffset];}}
  58. export const Events={TextChanged:Symbol('TextChanged')};self.Console=self.Console||{};Console=Console||{};Console.ConsolePrompt=ConsolePrompt;Console.ConsolePrompt.Events=Events;Console.ConsoleHistoryManager=ConsoleHistoryManager;