1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754 |
- /*
- * Copyright (C) 2011 Google Inc. All rights reserved.
- * Copyright (C) 2010 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- /**
- * @constructor
- * @extends {WebInspector.View}
- * @implements {WebInspector.TextEditor}
- * @param {?string} url
- * @param {WebInspector.TextEditorDelegate} delegate
- */
- WebInspector.DefaultTextEditor = function(url, delegate)
- {
- WebInspector.View.call(this);
- this._delegate = delegate;
- this._url = url;
- this.registerRequiredCSS("textEditor.css");
- this.element.className = "text-editor monospace";
- this.markAsLayoutBoundary();
- // Prevent middle-click pasting in the editor unless it is explicitly enabled for certain component.
- this.element.addEventListener("mouseup", preventDefaultOnMouseUp.bind(this), false);
- function preventDefaultOnMouseUp(event)
- {
- if (event.button === 1)
- event.consume(true);
- }
- this._textModel = new WebInspector.TextEditorModel();
- this._textModel.addEventListener(WebInspector.TextEditorModel.Events.TextChanged, this._textChanged, this);
- var syncScrollListener = this._syncScroll.bind(this);
- var syncDecorationsForLineListener = this._syncDecorationsForLine.bind(this);
- var syncLineHeightListener = this._syncLineHeight.bind(this);
- this._mainPanel = new WebInspector.TextEditorMainPanel(this._delegate, this._textModel, url, syncScrollListener, syncDecorationsForLineListener);
- this._gutterPanel = new WebInspector.TextEditorGutterPanel(this._textModel, syncDecorationsForLineListener, syncLineHeightListener);
- this._mainPanel.element.addEventListener("scroll", this._handleScrollChanged.bind(this), false);
- this._gutterPanel.element.addEventListener("mousedown", this._onMouseDown.bind(this), true);
- // Explicitly enable middle-click pasting in the editor main panel.
- this._mainPanel.element.addEventListener("mouseup", consumeMouseUp.bind(this), false);
- function consumeMouseUp(event)
- {
- if (event.button === 1)
- event.consume(false);
- }
- this.element.appendChild(this._mainPanel.element);
- this.element.appendChild(this._gutterPanel.element);
- // Forward mouse wheel events from the unscrollable gutter to the main panel.
- function forwardWheelEvent(event)
- {
- var clone = document.createEvent("WheelEvent");
- clone.initWebKitWheelEvent(event.wheelDeltaX, event.wheelDeltaY,
- event.view,
- event.screenX, event.screenY,
- event.clientX, event.clientY,
- event.ctrlKey, event.altKey, event.shiftKey, event.metaKey);
- this._mainPanel.element.dispatchEvent(clone);
- }
- this._gutterPanel.element.addEventListener("mousewheel", forwardWheelEvent.bind(this), false);
- this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
- this.element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
- this._wordMovementController = new WebInspector.DefaultTextEditor.WordMovementController(this, this._textModel);
- this._registerShortcuts();
- }
- /**
- * @constructor
- * @param {WebInspector.TextRange} range
- * @param {string} text
- */
- WebInspector.DefaultTextEditor.EditInfo = function(range, text)
- {
- this.range = range;
- this.text = text;
- }
- WebInspector.DefaultTextEditor.prototype = {
- /**
- * @return {boolean}
- */
- isClean: function()
- {
- return this._textModel.isClean();
- },
- markClean: function()
- {
- this._textModel.markClean();
- },
- /**
- * @param {number} lineNumber
- * @param {number} column
- * @return {?{startColumn: number, endColumn: number, type: string}}
- */
- tokenAtTextPosition: function(lineNumber, column)
- {
- return this._mainPanel.tokenAtTextPosition(lineNumber, column);
- },
- /*
- * @param {number} lineNumber
- * @param {number} column
- * @return {?{x: number, y: number, height: number}}
- */
- cursorPositionToCoordinates: function(lineNumber, column)
- {
- return this._mainPanel.cursorPositionToCoordinates(lineNumber, column);
- },
- /**
- * @param {number} x
- * @param {number} y
- * @return {?WebInspector.TextRange}
- */
- coordinatesToCursorPosition: function(x, y)
- {
- return this._mainPanel.coordinatesToCursorPosition(x, y);
- },
- /**
- * @param {WebInspector.TextRange} range
- * @return {string}
- */
- copyRange: function(range)
- {
- return this._textModel.copyRange(range);
- },
- /**
- * @param {string} regex
- * @param {string} cssClass
- * @return {Object}
- */
- highlightRegex: function(regex, cssClass)
- {
- return this._mainPanel.highlightRegex(regex, cssClass);
- },
- /**
- * @param {Object} highlightDescriptor
- */
- removeHighlight: function(highlightDescriptor)
- {
- this._mainPanel.removeHighlight(highlightDescriptor);
- },
- /**
- * @param {WebInspector.TextRange} range
- * @param {string} cssClass
- * @return {Object}
- */
- highlightRange: function(range, cssClass)
- {
- return this._mainPanel.highlightRange(range, cssClass);
- },
- /**
- * @param {string} mimeType
- */
- set mimeType(mimeType)
- {
- this._mainPanel.mimeType = mimeType;
- },
- /**
- * @param {boolean} readOnly
- */
- setReadOnly: function(readOnly)
- {
- if (this._mainPanel.readOnly() === readOnly)
- return;
- this._mainPanel.setReadOnly(readOnly, this.isShowing());
- WebInspector.markBeingEdited(this.element, !readOnly);
- },
- /**
- * @return {boolean}
- */
- readOnly: function()
- {
- return this._mainPanel.readOnly();
- },
- /**
- * @return {Element}
- */
- defaultFocusedElement: function()
- {
- return this._mainPanel.defaultFocusedElement();
- },
- /**
- * @param {number} lineNumber
- */
- revealLine: function(lineNumber)
- {
- this._mainPanel.revealLine(lineNumber);
- },
- _onMouseDown: function(event)
- {
- var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number");
- if (!target)
- return;
- this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: target.lineNumber, event: event });
- },
- /**
- * @param {number} lineNumber
- * @param {boolean} disabled
- * @param {boolean} conditional
- */
- addBreakpoint: function(lineNumber, disabled, conditional)
- {
- this.beginUpdates();
- this._gutterPanel.addDecoration(lineNumber, "webkit-breakpoint");
- if (disabled)
- this._gutterPanel.addDecoration(lineNumber, "webkit-breakpoint-disabled");
- else
- this._gutterPanel.removeDecoration(lineNumber, "webkit-breakpoint-disabled");
- if (conditional)
- this._gutterPanel.addDecoration(lineNumber, "webkit-breakpoint-conditional");
- else
- this._gutterPanel.removeDecoration(lineNumber, "webkit-breakpoint-conditional");
- this.endUpdates();
- },
- /**
- * @param {number} lineNumber
- */
- removeBreakpoint: function(lineNumber)
- {
- this.beginUpdates();
- this._gutterPanel.removeDecoration(lineNumber, "webkit-breakpoint");
- this._gutterPanel.removeDecoration(lineNumber, "webkit-breakpoint-disabled");
- this._gutterPanel.removeDecoration(lineNumber, "webkit-breakpoint-conditional");
- this.endUpdates();
- },
- /**
- * @param {number} lineNumber
- */
- setExecutionLine: function(lineNumber)
- {
- this._executionLineNumber = lineNumber;
- this._mainPanel.addDecoration(lineNumber, "webkit-execution-line");
- this._gutterPanel.addDecoration(lineNumber, "webkit-execution-line");
- },
- clearExecutionLine: function()
- {
- if (typeof this._executionLineNumber === "number") {
- this._mainPanel.removeDecoration(this._executionLineNumber, "webkit-execution-line");
- this._gutterPanel.removeDecoration(this._executionLineNumber, "webkit-execution-line");
- }
- delete this._executionLineNumber;
- },
- /**
- * @param {number} lineNumber
- * @param {Element} element
- */
- addDecoration: function(lineNumber, element)
- {
- this._mainPanel.addDecoration(lineNumber, element);
- this._gutterPanel.addDecoration(lineNumber, element);
- this._syncDecorationsForLine(lineNumber);
- },
- /**
- * @param {number} lineNumber
- * @param {Element} element
- */
- removeDecoration: function(lineNumber, element)
- {
- this._mainPanel.removeDecoration(lineNumber, element);
- this._gutterPanel.removeDecoration(lineNumber, element);
- this._syncDecorationsForLine(lineNumber);
- },
- /**
- * @param {WebInspector.TextRange} range
- */
- markAndRevealRange: function(range)
- {
- if (range)
- this.setSelection(range);
- this._mainPanel.markAndRevealRange(range);
- },
- /**
- * @param {number} lineNumber
- */
- highlightLine: function(lineNumber)
- {
- if (typeof lineNumber !== "number" || lineNumber < 0)
- return;
- lineNumber = Math.min(lineNumber, this._textModel.linesCount - 1);
- this._mainPanel.highlightLine(lineNumber);
- },
- clearLineHighlight: function()
- {
- this._mainPanel.clearLineHighlight();
- },
- /**
- * @return {Array.<Element>}
- */
- elementsToRestoreScrollPositionsFor: function()
- {
- return [this._mainPanel.element];
- },
- /**
- * @param {WebInspector.TextEditor} textEditor
- */
- inheritScrollPositions: function(textEditor)
- {
- this._mainPanel.element._scrollTop = textEditor._mainPanel.element.scrollTop;
- this._mainPanel.element._scrollLeft = textEditor._mainPanel.element.scrollLeft;
- },
- beginUpdates: function()
- {
- this._mainPanel.beginUpdates();
- this._gutterPanel.beginUpdates();
- },
- endUpdates: function()
- {
- this._mainPanel.endUpdates();
- this._gutterPanel.endUpdates();
- this._updatePanelOffsets();
- },
- onResize: function()
- {
- this._mainPanel.resize();
- this._gutterPanel.resize();
- this._updatePanelOffsets();
- },
- _textChanged: function(event)
- {
- this._mainPanel.textChanged(event.data.oldRange, event.data.newRange);
- this._gutterPanel.textChanged(event.data.oldRange, event.data.newRange);
- this._updatePanelOffsets();
- if (event.data.editRange)
- this._delegate.onTextChanged(event.data.oldRange, event.data.newRange);
- },
- /**
- * @param {WebInspector.TextRange} range
- * @param {string} text
- * @return {WebInspector.TextRange}
- */
- editRange: function(range, text)
- {
- return this._textModel.editRange(range, text, this.lastSelection());
- },
- _updatePanelOffsets: function()
- {
- var lineNumbersWidth = this._gutterPanel.element.offsetWidth;
- if (lineNumbersWidth)
- this._mainPanel.element.style.setProperty("left", (lineNumbersWidth + 2) + "px");
- else
- this._mainPanel.element.style.removeProperty("left"); // Use default value set in CSS.
- },
- _syncScroll: function()
- {
- var mainElement = this._mainPanel.element;
- var gutterElement = this._gutterPanel.element;
- // Handle horizontal scroll bar at the bottom of the main panel.
- this._gutterPanel.syncClientHeight(mainElement.clientHeight);
- gutterElement.scrollTop = mainElement.scrollTop;
- },
- /**
- * @param {number} lineNumber
- */
- _syncDecorationsForLine: function(lineNumber)
- {
- if (lineNumber >= this._textModel.linesCount)
- return;
- var mainChunk = this._mainPanel.chunkForLine(lineNumber);
- if (mainChunk.linesCount === 1 && mainChunk.isDecorated()) {
- var gutterChunk = this._gutterPanel.makeLineAChunk(lineNumber);
- var height = mainChunk.height;
- if (height)
- gutterChunk.element.style.setProperty("height", height + "px");
- else
- gutterChunk.element.style.removeProperty("height");
- } else {
- var gutterChunk = this._gutterPanel.chunkForLine(lineNumber);
- if (gutterChunk.linesCount === 1)
- gutterChunk.element.style.removeProperty("height");
- }
- },
- /**
- * @param {Element} gutterRow
- */
- _syncLineHeight: function(gutterRow)
- {
- if (this._lineHeightSynced)
- return;
- if (gutterRow && gutterRow.offsetHeight) {
- // Force equal line heights for the child panels.
- this.element.style.setProperty("line-height", gutterRow.offsetHeight + "px");
- this._lineHeightSynced = true;
- }
- },
- _registerShortcuts: function()
- {
- var keys = WebInspector.KeyboardShortcut.Keys;
- var modifiers = WebInspector.KeyboardShortcut.Modifiers;
- this._shortcuts = {};
- this._shortcuts[WebInspector.KeyboardShortcut.SelectAll] = this._handleSelectAll.bind(this);
- this._wordMovementController._registerShortcuts(this._shortcuts);
- },
- _handleSelectAll: function()
- {
- this.setSelection(this._textModel.range());
- return true;
- },
- _handleKeyDown: function(e)
- {
- // If the event was not triggered from the entire editor, then
- // ignore it. https://bugs.webkit.org/show_bug.cgi?id=102906
- if (e.target.enclosingNodeOrSelfWithClass("webkit-line-decorations"))
- return;
- var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e);
- var handler = this._shortcuts[shortcutKey];
- if (handler && handler()) {
- e.consume(true);
- return;
- }
- this._mainPanel.handleKeyDown(shortcutKey, e);
- },
- _contextMenu: function(event)
- {
- var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
- if (anchor)
- return;
- var contextMenu = new WebInspector.ContextMenu(event);
- var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number");
- if (target)
- this._delegate.populateLineGutterContextMenu(contextMenu, target.lineNumber);
- else {
- this._mainPanel.populateContextMenu(event.target, contextMenu);
- }
- contextMenu.show();
- },
- _handleScrollChanged: function(event)
- {
- var visibleFrom = this._mainPanel.scrollTop();
- var firstVisibleLineNumber = this._mainPanel.lineNumberAtOffset(visibleFrom);
- this._delegate.scrollChanged(firstVisibleLineNumber);
- },
- /**
- * @param {number} lineNumber
- */
- scrollToLine: function(lineNumber)
- {
- this._mainPanel.scrollToLine(lineNumber);
- },
- /**
- * @return {WebInspector.TextRange}
- */
- selection: function()
- {
- return this._mainPanel.selection();
- },
- /**
- * @return {WebInspector.TextRange?}
- */
- lastSelection: function()
- {
- return this._mainPanel.lastSelection();
- },
- /**
- * @param {WebInspector.TextRange} textRange
- */
- setSelection: function(textRange)
- {
- this._mainPanel.setSelection(textRange);
- },
- /**
- * @param {string} text
- */
- setText: function(text)
- {
- this._textModel.setText(text);
- },
- /**
- * @return {string}
- */
- text: function()
- {
- return this._textModel.text();
- },
- /**
- * @return {WebInspector.TextRange}
- */
- range: function()
- {
- return this._textModel.range();
- },
- /**
- * @param {number} lineNumber
- * @return {string}
- */
- line: function(lineNumber)
- {
- return this._textModel.line(lineNumber);
- },
- /**
- * @return {number}
- */
- get linesCount()
- {
- return this._textModel.linesCount;
- },
- /**
- * @param {number} line
- * @param {string} name
- * @param {?Object} value
- */
- setAttribute: function(line, name, value)
- {
- this._textModel.setAttribute(line, name, value);
- },
- /**
- * @param {number} line
- * @param {string} name
- * @return {?Object} value
- */
- getAttribute: function(line, name)
- {
- return this._textModel.getAttribute(line, name);
- },
- /**
- * @param {number} line
- * @param {string} name
- */
- removeAttribute: function(line, name)
- {
- this._textModel.removeAttribute(line, name);
- },
- wasShown: function()
- {
- if (!this.readOnly())
- WebInspector.markBeingEdited(this.element, true);
- this._mainPanel.wasShown();
- },
- willHide: function()
- {
- this._mainPanel.willHide();
- this._gutterPanel.willHide();
- if (!this.readOnly())
- WebInspector.markBeingEdited(this.element, false);
- },
- /**
- * @param {Element} element
- * @param {Array.<Object>} resultRanges
- * @param {string} styleClass
- * @param {Array.<Object>=} changes
- */
- highlightRangesWithStyleClass: function(element, resultRanges, styleClass, changes)
- {
- this._mainPanel.beginDomUpdates();
- WebInspector.highlightRangesWithStyleClass(element, resultRanges, styleClass, changes);
- this._mainPanel.endDomUpdates();
- },
- /**
- * @param {number} scrollTop
- * @param {number} clientHeight
- * @param {number} chunkSize
- */
- overrideViewportForTest: function(scrollTop, clientHeight, chunkSize)
- {
- this._mainPanel.overrideViewportForTest(scrollTop, clientHeight, chunkSize);
- },
- __proto__: WebInspector.View.prototype
- }
- /**
- * @constructor
- * @param {WebInspector.TextEditorModel} textModel
- */
- WebInspector.TextEditorChunkedPanel = function(textModel)
- {
- this._textModel = textModel;
- this.element = document.createElement("div");
- this.element.addEventListener("scroll", this._scroll.bind(this), false);
- this._defaultChunkSize = 50;
- this._paintCoalescingLevel = 0;
- this._domUpdateCoalescingLevel = 0;
- }
- WebInspector.TextEditorChunkedPanel.prototype = {
- /**
- * @param {number} lineNumber
- */
- scrollToLine: function(lineNumber)
- {
- if (lineNumber >= this._textModel.linesCount)
- return;
- var chunk = this.makeLineAChunk(lineNumber);
- this.element.scrollTop = chunk.offsetTop;
- },
- /**
- * @param {number} lineNumber
- */
- revealLine: function(lineNumber)
- {
- if (lineNumber >= this._textModel.linesCount)
- return;
- var chunk = this.makeLineAChunk(lineNumber);
- chunk.element.scrollIntoViewIfNeeded();
- },
- /**
- * @param {number} lineNumber
- * @param {string|Element} decoration
- */
- addDecoration: function(lineNumber, decoration)
- {
- if (lineNumber >= this._textModel.linesCount)
- return;
- var chunk = this.makeLineAChunk(lineNumber);
- chunk.addDecoration(decoration);
- },
- /**
- * @param {number} lineNumber
- * @param {string|Element} decoration
- */
- removeDecoration: function(lineNumber, decoration)
- {
- if (lineNumber >= this._textModel.linesCount)
- return;
- var chunk = this.chunkForLine(lineNumber);
- chunk.removeDecoration(decoration);
- },
- buildChunks: function()
- {
- this.beginDomUpdates();
- this._container.removeChildren();
- this._textChunks = [];
- for (var i = 0; i < this._textModel.linesCount; i += this._defaultChunkSize) {
- var chunk = this.createNewChunk(i, i + this._defaultChunkSize);
- this._textChunks.push(chunk);
- this._container.appendChild(chunk.element);
- }
- this.repaintAll();
- this.endDomUpdates();
- },
- /**
- * @param {number} lineNumber
- * @return {Object}
- */
- makeLineAChunk: function(lineNumber)
- {
- var chunkNumber = this.chunkNumberForLine(lineNumber);
- var oldChunk = this._textChunks[chunkNumber];
- if (!oldChunk) {
- console.error("No chunk for line number: " + lineNumber);
- return null;
- }
- if (oldChunk.linesCount === 1)
- return oldChunk;
- return this.splitChunkOnALine(lineNumber, chunkNumber, true);
- },
- /**
- * @param {number} lineNumber
- * @param {number} chunkNumber
- * @param {boolean=} createSuffixChunk
- * @return {Object}
- */
- splitChunkOnALine: function(lineNumber, chunkNumber, createSuffixChunk)
- {
- this.beginDomUpdates();
- var oldChunk = this._textChunks[chunkNumber];
- var wasExpanded = oldChunk.expanded();
- oldChunk.collapse();
- var insertIndex = chunkNumber + 1;
- // Prefix chunk.
- if (lineNumber > oldChunk.startLine) {
- var prefixChunk = this.createNewChunk(oldChunk.startLine, lineNumber);
- this._textChunks.splice(insertIndex++, 0, prefixChunk);
- this._container.insertBefore(prefixChunk.element, oldChunk.element);
- }
- // Line chunk.
- var endLine = createSuffixChunk ? lineNumber + 1 : oldChunk.startLine + oldChunk.linesCount;
- var lineChunk = this.createNewChunk(lineNumber, endLine);
- this._textChunks.splice(insertIndex++, 0, lineChunk);
- this._container.insertBefore(lineChunk.element, oldChunk.element);
- // Suffix chunk.
- if (oldChunk.startLine + oldChunk.linesCount > endLine) {
- var suffixChunk = this.createNewChunk(endLine, oldChunk.startLine + oldChunk.linesCount);
- this._textChunks.splice(insertIndex, 0, suffixChunk);
- this._container.insertBefore(suffixChunk.element, oldChunk.element);
- }
- // Remove enclosing chunk.
- this._textChunks.splice(chunkNumber, 1);
- this._container.removeChild(oldChunk.element);
- if (wasExpanded) {
- if (prefixChunk)
- prefixChunk.expand();
- lineChunk.expand();
- if (suffixChunk)
- suffixChunk.expand();
- }
- this.endDomUpdates();
- return lineChunk;
- },
- createNewChunk: function(startLine, endLine)
- {
- throw new Error("createNewChunk() should be implemented by descendants");
- },
- _scroll: function()
- {
- this._scheduleRepaintAll();
- if (this._syncScrollListener)
- this._syncScrollListener();
- },
- _scheduleRepaintAll: function()
- {
- if (this._repaintAllTimer)
- clearTimeout(this._repaintAllTimer);
- this._repaintAllTimer = setTimeout(this.repaintAll.bind(this), 50);
- },
- beginUpdates: function()
- {
- this._paintCoalescingLevel++;
- },
- endUpdates: function()
- {
- this._paintCoalescingLevel--;
- if (!this._paintCoalescingLevel)
- this.repaintAll();
- },
- beginDomUpdates: function()
- {
- this._domUpdateCoalescingLevel++;
- },
- endDomUpdates: function()
- {
- this._domUpdateCoalescingLevel--;
- },
- /**
- * @param {number} lineNumber
- * @return {number}
- */
- chunkNumberForLine: function(lineNumber)
- {
- function compareLineNumbers(value, chunk)
- {
- return value < chunk.startLine ? -1 : 1;
- }
- var insertBefore = insertionIndexForObjectInListSortedByFunction(lineNumber, this._textChunks, compareLineNumbers);
- return insertBefore - 1;
- },
- /**
- * @param {number} lineNumber
- * @return {Object}
- */
- chunkForLine: function(lineNumber)
- {
- return this._textChunks[this.chunkNumberForLine(lineNumber)];
- },
- /**
- * @param {number} visibleFrom
- * @return {number}
- */
- _findFirstVisibleChunkNumber: function(visibleFrom)
- {
- function compareOffsetTops(value, chunk)
- {
- return value < chunk.offsetTop ? -1 : 1;
- }
- var insertBefore = insertionIndexForObjectInListSortedByFunction(visibleFrom, this._textChunks, compareOffsetTops);
- return insertBefore - 1;
- },
- /**
- * @param {number} visibleFrom
- * @param {number} visibleTo
- * @return {{start: number, end: number}}
- */
- findVisibleChunks: function(visibleFrom, visibleTo)
- {
- var span = (visibleTo - visibleFrom) * 0.5;
- visibleFrom = Math.max(visibleFrom - span, 0);
- visibleTo = visibleTo + span;
- var from = this._findFirstVisibleChunkNumber(visibleFrom);
- for (var to = from + 1; to < this._textChunks.length; ++to) {
- if (this._textChunks[to].offsetTop >= visibleTo)
- break;
- }
- return { start: from, end: to };
- },
- /**
- * @param {number} visibleFrom
- * @return {number}
- */
- lineNumberAtOffset: function(visibleFrom)
- {
- var chunk = this._textChunks[this._findFirstVisibleChunkNumber(visibleFrom)];
- if (!chunk.expanded())
- return chunk.startLine;
- var lineNumbers = [];
- for (var i = 0; i < chunk.linesCount; ++i) {
- lineNumbers.push(chunk.startLine + i);
- }
- function compareLineRowOffsetTops(value, lineNumber)
- {
- var lineRow = chunk.expandedLineRow(lineNumber);
- return value < lineRow.offsetTop ? -1 : 1;
- }
- var insertBefore = insertionIndexForObjectInListSortedByFunction(visibleFrom, lineNumbers, compareLineRowOffsetTops);
- return lineNumbers[insertBefore - 1];
- },
- repaintAll: function()
- {
- delete this._repaintAllTimer;
- if (this._paintCoalescingLevel)
- return;
- var visibleFrom = this.scrollTop();
- var visibleTo = visibleFrom + this.clientHeight();
- if (visibleTo) {
- var result = this.findVisibleChunks(visibleFrom, visibleTo);
- this.expandChunks(result.start, result.end);
- }
- },
- scrollTop: function()
- {
- return typeof this._scrollTopOverrideForTest === "number" ? this._scrollTopOverrideForTest : this.element.scrollTop;
- },
- clientHeight: function()
- {
- return typeof this._clientHeightOverrideForTest === "number" ? this._clientHeightOverrideForTest : this.element.clientHeight;
- },
- /**
- * @param {number} fromIndex
- * @param {number} toIndex
- */
- expandChunks: function(fromIndex, toIndex)
- {
- // First collapse chunks to collect the DOM elements into a cache to reuse them later.
- for (var i = 0; i < fromIndex; ++i)
- this._textChunks[i].collapse();
- for (var i = toIndex; i < this._textChunks.length; ++i)
- this._textChunks[i].collapse();
- for (var i = fromIndex; i < toIndex; ++i)
- this._textChunks[i].expand();
- },
- /**
- * @param {Element} firstElement
- * @param {Element=} lastElement
- * @return {number}
- */
- totalHeight: function(firstElement, lastElement)
- {
- lastElement = (lastElement || firstElement).nextElementSibling;
- if (lastElement)
- return lastElement.offsetTop - firstElement.offsetTop;
- var offsetParent = firstElement.offsetParent;
- if (offsetParent && offsetParent.scrollHeight > offsetParent.clientHeight)
- return offsetParent.scrollHeight - firstElement.offsetTop;
- var total = 0;
- while (firstElement && firstElement !== lastElement) {
- total += firstElement.offsetHeight;
- firstElement = firstElement.nextElementSibling;
- }
- return total;
- },
- resize: function()
- {
- this.repaintAll();
- }
- }
- /**
- * @constructor
- * @extends {WebInspector.TextEditorChunkedPanel}
- * @param {WebInspector.TextEditorModel} textModel
- * @param {function(number)} syncDecorationsForLineListener
- * @param {function(Element)} syncLineHeightListener
- */
- WebInspector.TextEditorGutterPanel = function(textModel, syncDecorationsForLineListener, syncLineHeightListener)
- {
- WebInspector.TextEditorChunkedPanel.call(this, textModel);
- this._syncDecorationsForLineListener = syncDecorationsForLineListener;
- this._syncLineHeightListener = syncLineHeightListener;
- this.element.className = "text-editor-lines";
- this._container = document.createElement("div");
- this._container.className = "inner-container";
- this.element.appendChild(this._container);
- this._freeCachedElements();
- this.buildChunks();
- this._decorations = {};
- }
- WebInspector.TextEditorGutterPanel.prototype = {
- _freeCachedElements: function()
- {
- this._cachedRows = [];
- },
- willHide: function()
- {
- this._freeCachedElements();
- },
- /**
- * @param {number} startLine
- * @param {number} endLine
- * @return {WebInspector.TextEditorGutterChunk}
- */
- createNewChunk: function(startLine, endLine)
- {
- return new WebInspector.TextEditorGutterChunk(this, startLine, endLine);
- },
- /**
- * @param {WebInspector.TextRange} oldRange
- * @param {WebInspector.TextRange} newRange
- */
- textChanged: function(oldRange, newRange)
- {
- this.beginDomUpdates();
- var linesDiff = newRange.linesCount - oldRange.linesCount;
- if (linesDiff) {
- // Remove old chunks (if needed).
- for (var chunkNumber = this._textChunks.length - 1; chunkNumber >= 0; --chunkNumber) {
- var chunk = this._textChunks[chunkNumber];
- if (chunk.startLine + chunk.linesCount <= this._textModel.linesCount)
- break;
- chunk.collapse();
- this._container.removeChild(chunk.element);
- }
- this._textChunks.length = chunkNumber + 1;
- // Add new chunks (if needed).
- var totalLines = 0;
- if (this._textChunks.length) {
- var lastChunk = this._textChunks[this._textChunks.length - 1];
- totalLines = lastChunk.startLine + lastChunk.linesCount;
- }
- for (var i = totalLines; i < this._textModel.linesCount; i += this._defaultChunkSize) {
- var chunk = this.createNewChunk(i, i + this._defaultChunkSize);
- this._textChunks.push(chunk);
- this._container.appendChild(chunk.element);
- }
- // Shift decorations if necessary
- var decorationsToRestore = {};
- for (var lineNumber in this._decorations) {
- lineNumber = parseInt(lineNumber, 10);
- // Do not move decorations before the start position.
- if (lineNumber < oldRange.startLine)
- continue;
- // Decorations follow the first character of line.
- if (lineNumber === oldRange.startLine && oldRange.startColumn)
- continue;
- var lineDecorationsCopy = this._decorations[lineNumber].slice();
- for (var i = 0; i < lineDecorationsCopy.length; ++i)
- this.removeDecoration(lineNumber, lineDecorationsCopy[i]);
- // Do not restore the decorations before the end position.
- if (lineNumber >= oldRange.endLine)
- decorationsToRestore[lineNumber] = lineDecorationsCopy;
- }
- for (var lineNumber in decorationsToRestore) {
- lineNumber = parseInt(lineNumber, 10);
- var lineDecorationsCopy = decorationsToRestore[lineNumber];
- for (var i = 0; i < lineDecorationsCopy.length; ++i)
- this.addDecoration(lineNumber + linesDiff, lineDecorationsCopy[i]);
- }
- this.repaintAll();
- } else {
- // Decorations may have been removed, so we may have to sync those lines.
- var chunkNumber = this.chunkNumberForLine(newRange.startLine);
- var chunk = this._textChunks[chunkNumber];
- while (chunk && chunk.startLine <= newRange.endLine) {
- if (chunk.linesCount === 1)
- this._syncDecorationsForLineListener(chunk.startLine);
- chunk = this._textChunks[++chunkNumber];
- }
- }
- this.endDomUpdates();
- },
- /**
- * @param {number} clientHeight
- */
- syncClientHeight: function(clientHeight)
- {
- if (this.element.offsetHeight > clientHeight)
- this._container.style.setProperty("padding-bottom", (this.element.offsetHeight - clientHeight) + "px");
- else
- this._container.style.removeProperty("padding-bottom");
- },
- /**
- * @param {number} lineNumber
- * @param {string|Element} decoration
- */
- addDecoration: function(lineNumber, decoration)
- {
- WebInspector.TextEditorChunkedPanel.prototype.addDecoration.call(this, lineNumber, decoration);
- var decorations = this._decorations[lineNumber];
- if (!decorations) {
- decorations = [];
- this._decorations[lineNumber] = decorations;
- }
- decorations.push(decoration);
- },
- /**
- * @param {number} lineNumber
- * @param {string|Element} decoration
- */
- removeDecoration: function(lineNumber, decoration)
- {
- WebInspector.TextEditorChunkedPanel.prototype.removeDecoration.call(this, lineNumber, decoration);
- var decorations = this._decorations[lineNumber];
- if (decorations) {
- decorations.remove(decoration);
- if (!decorations.length)
- delete this._decorations[lineNumber];
- }
- },
- __proto__: WebInspector.TextEditorChunkedPanel.prototype
- }
- /**
- * @constructor
- * @param {WebInspector.TextEditorGutterPanel} chunkedPanel
- * @param {number} startLine
- * @param {number} endLine
- */
- WebInspector.TextEditorGutterChunk = function(chunkedPanel, startLine, endLine)
- {
- this._chunkedPanel = chunkedPanel;
- this._textModel = chunkedPanel._textModel;
- this.startLine = startLine;
- endLine = Math.min(this._textModel.linesCount, endLine);
- this.linesCount = endLine - startLine;
- this._expanded = false;
- this.element = document.createElement("div");
- this.element.lineNumber = startLine;
- this.element.className = "webkit-line-number";
- if (this.linesCount === 1) {
- // Single line chunks are typically created for decorations. Host line number in
- // the sub-element in order to allow flexible border / margin management.
- var innerSpan = document.createElement("span");
- innerSpan.className = "webkit-line-number-inner";
- innerSpan.textContent = startLine + 1;
- var outerSpan = document.createElement("div");
- outerSpan.className = "webkit-line-number-outer";
- outerSpan.appendChild(innerSpan);
- this.element.appendChild(outerSpan);
- } else {
- var lineNumbers = [];
- for (var i = startLine; i < endLine; ++i)
- lineNumbers.push(i + 1);
- this.element.textContent = lineNumbers.join("\n");
- }
- }
- WebInspector.TextEditorGutterChunk.prototype = {
- /**
- * @param {string} decoration
- */
- addDecoration: function(decoration)
- {
- this._chunkedPanel.beginDomUpdates();
- if (typeof decoration === "string")
- this.element.addStyleClass(decoration);
- this._chunkedPanel.endDomUpdates();
- },
- /**
- * @param {string} decoration
- */
- removeDecoration: function(decoration)
- {
- this._chunkedPanel.beginDomUpdates();
- if (typeof decoration === "string")
- this.element.removeStyleClass(decoration);
- this._chunkedPanel.endDomUpdates();
- },
- /**
- * @return {boolean}
- */
- expanded: function()
- {
- return this._expanded;
- },
- expand: function()
- {
- if (this.linesCount === 1)
- this._chunkedPanel._syncDecorationsForLineListener(this.startLine);
- if (this._expanded)
- return;
- this._expanded = true;
- if (this.linesCount === 1)
- return;
- this._chunkedPanel.beginDomUpdates();
- this._expandedLineRows = [];
- var parentElement = this.element.parentElement;
- for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
- var lineRow = this._createRow(i);
- parentElement.insertBefore(lineRow, this.element);
- this._expandedLineRows.push(lineRow);
- }
- parentElement.removeChild(this.element);
- this._chunkedPanel._syncLineHeightListener(this._expandedLineRows[0]);
- this._chunkedPanel.endDomUpdates();
- },
- collapse: function()
- {
- if (this.linesCount === 1)
- this._chunkedPanel._syncDecorationsForLineListener(this.startLine);
- if (!this._expanded)
- return;
- this._expanded = false;
- if (this.linesCount === 1)
- return;
- this._chunkedPanel.beginDomUpdates();
- var elementInserted = false;
- for (var i = 0; i < this._expandedLineRows.length; ++i) {
- var lineRow = this._expandedLineRows[i];
- var parentElement = lineRow.parentElement;
- if (parentElement) {
- if (!elementInserted) {
- elementInserted = true;
- parentElement.insertBefore(this.element, lineRow);
- }
- parentElement.removeChild(lineRow);
- }
- this._chunkedPanel._cachedRows.push(lineRow);
- }
- delete this._expandedLineRows;
- this._chunkedPanel.endDomUpdates();
- },
- /**
- * @return {number}
- */
- get height()
- {
- if (!this._expandedLineRows)
- return this._chunkedPanel.totalHeight(this.element);
- return this._chunkedPanel.totalHeight(this._expandedLineRows[0], this._expandedLineRows[this._expandedLineRows.length - 1]);
- },
- /**
- * @return {number}
- */
- get offsetTop()
- {
- return (this._expandedLineRows && this._expandedLineRows.length) ? this._expandedLineRows[0].offsetTop : this.element.offsetTop;
- },
- /**
- * @param {number} lineNumber
- * @return {Element}
- */
- _createRow: function(lineNumber)
- {
- var lineRow = this._chunkedPanel._cachedRows.pop() || document.createElement("div");
- lineRow.lineNumber = lineNumber;
- lineRow.className = "webkit-line-number";
- lineRow.textContent = lineNumber + 1;
- return lineRow;
- }
- }
- /**
- * @constructor
- * @extends {WebInspector.TextEditorChunkedPanel}
- * @param {WebInspector.TextEditorDelegate} delegate
- * @param {WebInspector.TextEditorModel} textModel
- * @param {?string} url
- * @param {function()} syncScrollListener
- * @param {function(number)} syncDecorationsForLineListener
- */
- WebInspector.TextEditorMainPanel = function(delegate, textModel, url, syncScrollListener, syncDecorationsForLineListener)
- {
- WebInspector.TextEditorChunkedPanel.call(this, textModel);
- this._delegate = delegate;
- this._syncScrollListener = syncScrollListener;
- this._syncDecorationsForLineListener = syncDecorationsForLineListener;
- this._url = url;
- this._highlighter = new WebInspector.TextEditorHighlighter(textModel, this._highlightDataReady.bind(this));
- this._readOnly = true;
- this.element.className = "text-editor-contents";
- this.element.tabIndex = 0;
- this._container = document.createElement("div");
- this._container.className = "inner-container";
- this._container.tabIndex = 0;
- this.element.appendChild(this._container);
- this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
- this.element.addEventListener("textInput", this._handleTextInput.bind(this), false);
- this.element.addEventListener("cut", this._handleCut.bind(this), false);
- this.element.addEventListener("keypress", this._handleKeyPress.bind(this), false);
- this._showWhitespace = WebInspector.experimentsSettings.showWhitespaceInEditor.isEnabled();
- this._container.addEventListener("focus", this._handleFocused.bind(this), false);
- this._highlightDescriptors = [];
- this._tokenHighlighter = new WebInspector.TextEditorMainPanel.TokenHighlighter(this, textModel);
- this._braceMatcher = new WebInspector.TextEditorModel.BraceMatcher(textModel);
- this._braceHighlighter = new WebInspector.TextEditorMainPanel.BraceHighlightController(this, textModel, this._braceMatcher);
- this._smartBraceController = new WebInspector.TextEditorMainPanel.SmartBraceController(this, textModel, this._braceMatcher);
- this._freeCachedElements();
- this.buildChunks();
- this._registerShortcuts();
- }
- WebInspector.TextEditorMainPanel._ConsecutiveWhitespaceChars = {
- 1: " ",
- 2: " ",
- 4: " ",
- 8: " ",
- 16: " "
- };
- WebInspector.TextEditorMainPanel.prototype = {
- /**
- * @param {number} lineNumber
- * @param {number} column
- * @return {?{startColumn: number, endColumn: number, type: string}}
- */
- tokenAtTextPosition: function(lineNumber, column)
- {
- if (lineNumber >= this._textModel.linesCount || lineNumber < 0)
- return null;
- var line = this._textModel.line(lineNumber);
- if (column >= line.length || column < 0)
- return null;
- var highlight = this._textModel.getAttribute(lineNumber, "highlight");
- if (!highlight)
- return this._tokenAtUnhighlightedLine(line, column);
- function compare(value, object)
- {
- if (value >= object.startColumn && value <= object.endColumn)
- return 0;
- return value - object.startColumn;
- }
- var index = binarySearch(column, highlight.ranges, compare);
- if (index >= 0) {
- var range = highlight.ranges[index];
- return {
- startColumn: range.startColumn,
- endColumn: range.endColumn,
- type: range.token
- };
- }
- return null;
- },
- /**
- * @param {number} lineNumber
- * @param {number} column
- * @return {?{x: number, y: number, height: number}}
- */
- cursorPositionToCoordinates: function(lineNumber, column)
- {
- if (lineNumber >= this._textModel.linesCount || lineNumber < 0)
- return null;
- var line = this._textModel.line(lineNumber);
- if (column > line.length || column < 0)
- return null;
- var chunk = this.chunkForLine(lineNumber);
- if (!chunk.expanded())
- return null;
- var lineRow = chunk.expandedLineRow(lineNumber);
- var ranges = [{
- startColumn: column,
- endColumn: column,
- token: "measure-cursor-position"
- }];
- var selection = this.selection();
- this.beginDomUpdates();
- this._renderRanges(lineRow, line, ranges);
- var spans = lineRow.getElementsByClassName("webkit-measure-cursor-position");
- if (WebInspector.debugDefaultTextEditor)
- console.assert(spans.length === 0);
- var totalOffset = spans[0].totalOffset();
- var height = spans[0].offsetHeight;
- this._paintLineRows([lineRow]);
- this.endDomUpdates();
- this._restoreSelection(selection);
- return {
- x: totalOffset.left,
- y: totalOffset.top,
- height: height
- };
- },
- /**
- * @param {number} x
- * @param {number} y
- * @return {?WebInspector.TextRange}
- */
- coordinatesToCursorPosition: function(x, y)
- {
- var element = document.elementFromPoint(x, y);
- if (!element)
- return null;
- var lineRow = element.enclosingNodeOrSelfWithClass("webkit-line-content");
- if (!lineRow)
- return null;
- var line = this._textModel.line(lineRow.lineNumber) + " ";
- var ranges = [];
- const prefix = "character-position-";
- for(var i = 0; i < line.length; ++i) {
- ranges.push({
- startColumn: i,
- endColumn: i,
- token: prefix + i
- });
- }
- var selection = this.selection();
- this.beginDomUpdates();
- this._renderRanges(lineRow, line, ranges);
- var charElement = document.elementFromPoint(x, y);
- this._paintLineRows([lineRow]);
- this.endDomUpdates();
- this._restoreSelection(selection);
- var className = charElement.className;
- if (className.indexOf(prefix) < 0)
- return null;
- var column = parseInt(className.substring(className.indexOf(prefix) + prefix.length), 10);
- return WebInspector.TextRange.createFromLocation(lineRow.lineNumber, column);
- },
- /**
- * @param {string} line
- * @param {number} column
- * @return {?{startColumn: number, endColumn: number, type: string}}
- */
- _tokenAtUnhighlightedLine: function(line, column)
- {
- var tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer(this.mimeType);
- tokenizer.condition = tokenizer.createInitialCondition();
- tokenizer.line = line;
- var lastTokenizedColumn = 0;
- while (lastTokenizedColumn < line.length) {
- var newColumn = tokenizer.nextToken(lastTokenizedColumn);
- if (column < newColumn) {
- if (!tokenizer.tokenType)
- return null;
- return {
- startColumn: lastTokenizedColumn,
- endColumn: newColumn - 1,
- type: tokenizer.tokenType
- };
- } else
- lastTokenizedColumn = newColumn;
- }
- return null;
- },
- _registerShortcuts: function()
- {
- var keys = WebInspector.KeyboardShortcut.Keys;
- var modifiers = WebInspector.KeyboardShortcut.Modifiers;
- this._shortcuts = {};
- this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Enter.code, WebInspector.KeyboardShortcut.Modifiers.None)] = this._handleEnterKey.bind(this);
- this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.CtrlOrMeta)] = this._handleUndoRedo.bind(this, false);
- var handleRedo = this._handleUndoRedo.bind(this, true);
- this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.Shift | modifiers.CtrlOrMeta)] = handleRedo;
- if (!WebInspector.isMac())
- this._shortcuts[WebInspector.KeyboardShortcut.makeKey("y", modifiers.CtrlOrMeta)] = handleRedo;
- var handleTabKey = this._handleTabKeyPress.bind(this, false);
- var handleShiftTabKey = this._handleTabKeyPress.bind(this, true);
- this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Tab.code)] = handleTabKey;
- this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Tab.code, modifiers.Shift)] = handleShiftTabKey;
- var homeKey = WebInspector.isMac() ? keys.Right : keys.Home;
- var homeModifier = WebInspector.isMac() ? modifiers.Meta : modifiers.None;
- this._shortcuts[WebInspector.KeyboardShortcut.makeKey(homeKey.code, homeModifier)] = this._handleHomeKey.bind(this, false);
- this._shortcuts[WebInspector.KeyboardShortcut.makeKey(homeKey.code, homeModifier | modifiers.Shift)] = this._handleHomeKey.bind(this, true);
- this._charOverrides = {};
- this._smartBraceController.registerShortcuts(this._shortcuts);
- this._smartBraceController.registerCharOverrides(this._charOverrides);
- },
- _handleKeyPress: function(event)
- {
- var char = String.fromCharCode(event.which);
- var handler = this._charOverrides[char];
- if (handler && handler()) {
- event.consume(true);
- return;
- }
- this._keyDownCode = event.keyCode;
- },
- /**
- * @param {boolean} shift
- */
- _handleHomeKey: function(shift)
- {
- var selection = this.selection();
- var line = this._textModel.line(selection.endLine);
- var firstNonBlankCharacter = 0;
- while (firstNonBlankCharacter < line.length) {
- var char = line.charAt(firstNonBlankCharacter);
- if (char === " " || char === "\t")
- ++firstNonBlankCharacter;
- else
- break;
- }
- if (firstNonBlankCharacter >= line.length || selection.endColumn === firstNonBlankCharacter)
- return false;
- selection.endColumn = firstNonBlankCharacter;
- if (!shift)
- selection = selection.collapseToEnd();
- this._restoreSelection(selection);
- return true;
- },
- /**
- * @param {string} regex
- * @param {string} cssClass
- * @return {Object}
- */
- highlightRegex: function(regex, cssClass)
- {
- var highlightDescriptor = new WebInspector.TextEditorMainPanel.RegexHighlightDescriptor(new RegExp(regex, "g"), cssClass);
- this._highlightDescriptors.push(highlightDescriptor);
- this._repaintLineRowsAffectedByHighlightDescriptors([highlightDescriptor]);
- return highlightDescriptor;
- },
- /**
- * @param {Object} highlightDescriptor
- */
- removeHighlight: function(highlightDescriptor)
- {
- this._highlightDescriptors.remove(highlightDescriptor);
- this._repaintLineRowsAffectedByHighlightDescriptors([highlightDescriptor]);
- },
- /**
- * @param {WebInspector.TextRange} range
- * @param {string} cssClass
- * @return {Object}
- */
- highlightRange: function(range, cssClass)
- {
- var highlightDescriptor = new WebInspector.TextEditorMainPanel.RangeHighlightDescriptor(range, cssClass);
- this._highlightDescriptors.push(highlightDescriptor);
- this._repaintLineRowsAffectedByHighlightDescriptors([highlightDescriptor]);
- return highlightDescriptor;
- },
- /**
- * @param {Array.<WebInspector.TextEditorMainPanel.HighlightDescriptor>} highlightDescriptors
- */
- _repaintLineRowsAffectedByHighlightDescriptors: function(highlightDescriptors)
- {
- var visibleFrom = this.scrollTop();
- var visibleTo = visibleFrom + this.clientHeight();
- var visibleChunks = this.findVisibleChunks(visibleFrom, visibleTo);
- var affectedLineRows = [];
- for (var i = visibleChunks.start; i < visibleChunks.end; ++i) {
- var chunk = this._textChunks[i];
- if (!chunk.expanded())
- continue;
- for (var lineNumber = chunk.startLine; lineNumber < chunk.startLine + chunk.linesCount; ++lineNumber) {
- var lineRow = chunk.expandedLineRow(lineNumber);
- var line = this._textModel.line(lineNumber);
- for(var j = 0; j < highlightDescriptors.length; ++j) {
- if (highlightDescriptors[j].affectsLine(lineNumber, line)) {
- affectedLineRows.push(lineRow);
- break;
- }
- }
- }
- }
- if (affectedLineRows.length === 0)
- return;
- var selection = this.selection();
- this._paintLineRows(affectedLineRows);
- this._restoreSelection(selection);
- },
- resize: function()
- {
- WebInspector.TextEditorChunkedPanel.prototype.resize.call(this);
- this._repaintLineRowsAffectedByHighlightDescriptors(this._highlightDescriptors);
- },
- wasShown: function()
- {
- this._boundSelectionChangeListener = this._handleSelectionChange.bind(this);
- document.addEventListener("selectionchange", this._boundSelectionChangeListener, false);
- this._isShowing = true;
- this._attachMutationObserver();
- },
- willHide: function()
- {
- document.removeEventListener("selectionchange", this._boundSelectionChangeListener, false);
- delete this._boundSelectionChangeListener;
- this._detachMutationObserver();
- this._isShowing = false;
- this._freeCachedElements();
- },
- /**
- * @param {Element} eventTarget
- * @param {WebInspector.ContextMenu} contextMenu
- */
- populateContextMenu: function(eventTarget, contextMenu)
- {
- var target = this._enclosingLineRowOrSelf(eventTarget);
- this._delegate.populateTextAreaContextMenu(contextMenu, target && target.lineNumber);
- },
- /**
- * @param {WebInspector.TextRange} textRange
- */
- setSelection: function(textRange)
- {
- this._lastSelection = textRange;
- if (this.element.isAncestor(document.activeElement))
- this._restoreSelection(textRange);
- },
- _handleFocused: function()
- {
- if (this._lastSelection)
- this.setSelection(this._lastSelection);
- },
- _attachMutationObserver: function()
- {
- if (!this._isShowing)
- return;
- if (this._mutationObserver)
- this._mutationObserver.disconnect();
- this._mutationObserver = new NonLeakingMutationObserver(this._handleMutations.bind(this));
- this._mutationObserver.observe(this._container, { subtree: true, childList: true, characterData: true });
- },
- _detachMutationObserver: function()
- {
- if (!this._isShowing)
- return;
- if (this._mutationObserver) {
- this._mutationObserver.disconnect();
- delete this._mutationObserver;
- }
- },
- /**
- * @param {string} mimeType
- */
- set mimeType(mimeType)
- {
- this._highlighter.mimeType = mimeType;
- },
- get mimeType()
- {
- return this._highlighter.mimeType;
- },
- /**
- * @param {boolean} readOnly
- * @param {boolean} requestFocus
- */
- setReadOnly: function(readOnly, requestFocus)
- {
- if (this._readOnly === readOnly)
- return;
- this.beginDomUpdates();
- this._readOnly = readOnly;
- if (this._readOnly)
- this._container.removeStyleClass("text-editor-editable");
- else {
- this._container.addStyleClass("text-editor-editable");
- if (requestFocus)
- this._updateSelectionOnStartEditing();
- }
- this.endDomUpdates();
- },
- /**
- * @return {boolean}
- */
- readOnly: function()
- {
- return this._readOnly;
- },
- _handleElementFocus: function()
- {
- if (!this._readOnly)
- this._container.focus();
- },
- /**
- * @return {Element}
- */
- defaultFocusedElement: function()
- {
- if (this._readOnly)
- return this.element;
- return this._container;
- },
- _updateSelectionOnStartEditing: function()
- {
- // focus() needs to go first for the case when the last selection was inside the editor and
- // the "Edit" button was clicked. In this case we bail at the check below, but the
- // editor does not receive the focus, thus "Esc" does not cancel editing until at least
- // one change has been made to the editor contents.
- this._container.focus();
- var selection = window.getSelection();
- if (selection.rangeCount) {
- var commonAncestorContainer = selection.getRangeAt(0).commonAncestorContainer;
- if (this._container.isSelfOrAncestor(commonAncestorContainer))
- return;
- }
- selection.removeAllRanges();
- var range = document.createRange();
- range.setStart(this._container, 0);
- range.setEnd(this._container, 0);
- selection.addRange(range);
- },
- /**
- * @param {WebInspector.TextRange} range
- */
- markAndRevealRange: function(range)
- {
- if (this._rangeToMark) {
- var markedLine = this._rangeToMark.startLine;
- delete this._rangeToMark;
- // Remove the marked region immediately.
- this.beginDomUpdates();
- var chunk = this.chunkForLine(markedLine);
- var wasExpanded = chunk.expanded();
- chunk.collapse();
- chunk.updateCollapsedLineRow();
- if (wasExpanded)
- chunk.expand();
- this.endDomUpdates();
- }
- if (range) {
- this._rangeToMark = range;
- this.revealLine(range.startLine);
- var chunk = this.makeLineAChunk(range.startLine);
- this._paintLines(chunk.startLine, chunk.startLine + 1);
- if (this._markedRangeElement)
- this._markedRangeElement.scrollIntoViewIfNeeded();
- }
- delete this._markedRangeElement;
- },
- /**
- * @param {number} lineNumber
- */
- highlightLine: function(lineNumber)
- {
- this.clearLineHighlight();
- this._highlightedLine = lineNumber;
- this.revealLine(lineNumber);
- if (!this._readOnly)
- this._restoreSelection(WebInspector.TextRange.createFromLocation(lineNumber, 0), false);
- this.addDecoration(lineNumber, "webkit-highlighted-line");
- },
- clearLineHighlight: function()
- {
- if (typeof this._highlightedLine === "number") {
- this.removeDecoration(this._highlightedLine, "webkit-highlighted-line");
- delete this._highlightedLine;
- }
- },
- _freeCachedElements: function()
- {
- this._cachedSpans = [];
- this._cachedTextNodes = [];
- this._cachedRows = [];
- },
- /**
- * @param {boolean} redo
- * @return {boolean}
- */
- _handleUndoRedo: function(redo)
- {
- if (this.readOnly())
- return false;
- this.beginUpdates();
- var range = redo ? this._textModel.redo() : this._textModel.undo();
- this.endUpdates();
- // Restore location post-repaint.
- if (range)
- this._restoreSelection(range, true);
- return true;
- },
- /**
- * @param {boolean} shiftKey
- * @return {boolean}
- */
- _handleTabKeyPress: function(shiftKey)
- {
- if (this.readOnly())
- return false;
- var selection = this.selection();
- if (!selection)
- return false;
- var range = selection.normalize();
- this.beginUpdates();
- var newRange;
- var rangeWasEmpty = range.isEmpty();
- if (shiftKey)
- newRange = this._textModel.unindentLines(range);
- else {
- if (rangeWasEmpty)
- newRange = this._textModel.editRange(range, WebInspector.settings.textEditorIndent.get());
- else
- newRange = this._textModel.indentLines(range);
- }
- this.endUpdates();
- if (rangeWasEmpty)
- newRange.startColumn = newRange.endColumn;
- this._restoreSelection(newRange, true);
- return true;
- },
- _handleEnterKey: function()
- {
- if (this.readOnly())
- return false;
- var range = this.selection();
- if (!range)
- return false;
- range = range.normalize();
- if (range.endColumn === 0)
- return false;
- var line = this._textModel.line(range.startLine);
- var linePrefix = line.substring(0, range.startColumn);
- var indentMatch = linePrefix.match(/^\s+/);
- var currentIndent = indentMatch ? indentMatch[0] : "";
- var textEditorIndent = WebInspector.settings.textEditorIndent.get();
- var indent = WebInspector.TextEditorModel.endsWithBracketRegex.test(linePrefix) ? currentIndent + textEditorIndent : currentIndent;
- if (!indent)
- return false;
- this.beginDomUpdates();
- var lineBreak = this._textModel.lineBreak;
- var newRange;
- if (range.isEmpty() && line.substr(range.endColumn - 1, 2) === '{}') {
- // {|}
- // becomes
- // {
- // |
- // }
- newRange = this._textModel.editRange(range, lineBreak + indent + lineBreak + currentIndent);
- newRange.endLine--;
- newRange.endColumn += textEditorIndent.length;
- } else
- newRange = this._textModel.editRange(range, lineBreak + indent);
- this.endDomUpdates();
- this._restoreSelection(newRange.collapseToEnd(), true);
- return true;
- },
- /**
- * @param {number} lineNumber
- * @param {number} chunkNumber
- * @param {boolean=} createSuffixChunk
- * @return {Object}
- */
- splitChunkOnALine: function(lineNumber, chunkNumber, createSuffixChunk)
- {
- var selection = this.selection();
- var chunk = WebInspector.TextEditorChunkedPanel.prototype.splitChunkOnALine.call(this, lineNumber, chunkNumber, createSuffixChunk);
- this._restoreSelection(selection);
- return chunk;
- },
- beginDomUpdates: function()
- {
- if (!this._domUpdateCoalescingLevel)
- this._detachMutationObserver();
- WebInspector.TextEditorChunkedPanel.prototype.beginDomUpdates.call(this);
- },
- endDomUpdates: function()
- {
- WebInspector.TextEditorChunkedPanel.prototype.endDomUpdates.call(this);
- if (!this._domUpdateCoalescingLevel)
- this._attachMutationObserver();
- },
- buildChunks: function()
- {
- for (var i = 0; i < this._textModel.linesCount; ++i)
- this._textModel.removeAttribute(i, "highlight");
- WebInspector.TextEditorChunkedPanel.prototype.buildChunks.call(this);
- },
- /**
- * @param {number} startLine
- * @param {number} endLine
- * @return {WebInspector.TextEditorMainChunk}
- */
- createNewChunk: function(startLine, endLine)
- {
- return new WebInspector.TextEditorMainChunk(this, startLine, endLine);
- },
- /**
- * @param {number} fromIndex
- * @param {number} toIndex
- */
- expandChunks: function(fromIndex, toIndex)
- {
- var lastChunk = this._textChunks[toIndex - 1];
- var lastVisibleLine = lastChunk.startLine + lastChunk.linesCount;
- var selection = this.selection();
- this._muteHighlightListener = true;
- this._highlighter.highlight(lastVisibleLine);
- delete this._muteHighlightListener;
- WebInspector.TextEditorChunkedPanel.prototype.expandChunks.call(this, fromIndex, toIndex);
- this._restoreSelection(selection);
- },
- /**
- * @param {number} fromLine
- * @param {number} toLine
- */
- _highlightDataReady: function(fromLine, toLine)
- {
- if (this._muteHighlightListener)
- return;
- this._paintLines(fromLine, toLine, true /*restoreSelection*/);
- },
- /**
- * @param {number} fromLine
- * @param {number} toLine
- * @param {boolean=} restoreSelection
- */
- _paintLines: function(fromLine, toLine, restoreSelection)
- {
- var lineRows = [];
- var chunk;
- for (var lineNumber = fromLine; lineNumber < toLine; ++lineNumber) {
- if (!chunk || lineNumber < chunk.startLine || lineNumber >= chunk.startLine + chunk.linesCount)
- chunk = this.chunkForLine(lineNumber);
- var lineRow = chunk.expandedLineRow(lineNumber);
- if (!lineRow)
- continue;
- lineRows.push(lineRow);
- }
- if (lineRows.length === 0)
- return;
- var selection;
- if (restoreSelection)
- selection = this.selection();
- this._paintLineRows(lineRows);
- if (restoreSelection)
- this._restoreSelection(selection);
- },
- /**
- * @param {Array.<Element>} lineRows
- */
- _paintLineRows: function(lineRows)
- {
- var highlight = {};
- this.beginDomUpdates();
- for(var i = 0; i < this._highlightDescriptors.length; ++i) {
- var highlightDescriptor = this._highlightDescriptors[i];
- this._measureHighlightDescriptor(highlight, lineRows, highlightDescriptor);
- }
- for(var i = 0; i < lineRows.length; ++i)
- this._paintLine(lineRows[i], highlight[lineRows[i].lineNumber]);
- this.endDomUpdates();
- },
- /**
- * @param {Object.<number, Array.<WebInspector.TextEditorMainPanel.LineOverlayHighlight>>} highlight
- * @param {Array.<Element>} lineRows
- * @param {WebInspector.TextEditorMainPanel.HighlightDescriptor} highlightDescriptor
- */
- _measureHighlightDescriptor: function(highlight, lineRows, highlightDescriptor)
- {
- var rowsToMeasure = [];
- for(var i = 0; i < lineRows.length; ++i) {
- var lineRow = lineRows[i];
- var line = this._textModel.line(lineRow.lineNumber);
- var ranges = highlightDescriptor.rangesForLine(lineRow.lineNumber, line);
- if (ranges.length === 0)
- continue;
- for(var j = 0; j < ranges.length; ++j)
- ranges[j].token = "measure-span";
- this._renderRanges(lineRow, line, ranges);
- rowsToMeasure.push(lineRow);
- }
- for(var i = 0; i < rowsToMeasure.length; ++i) {
- var lineRow = rowsToMeasure[i];
- var lineNumber = lineRow.lineNumber;
- var metrics = this._measureSpans(lineRow);
- if (!highlight[lineNumber])
- highlight[lineNumber] = [];
- highlight[lineNumber].push(new WebInspector.TextEditorMainPanel.LineOverlayHighlight(metrics, highlightDescriptor.cssClass()));
- }
- },
- /**
- * @param {Element} lineRow
- * @return {Array.<WebInspector.TextEditorMainPanel.ElementMetrics>}
- */
- _measureSpans: function(lineRow)
- {
- var spans = lineRow.getElementsByClassName("webkit-measure-span");
- var metrics = [];
- for(var i = 0; i < spans.length; ++i)
- metrics.push(new WebInspector.TextEditorMainPanel.ElementMetrics(spans[i]));
- return metrics;
- },
- /**
- * @param {Element} lineRow
- * @param {WebInspector.TextEditorMainPanel.LineOverlayHighlight} highlight
- */
- _appendOverlayHighlight: function(lineRow, highlight)
- {
- var metrics = highlight.metrics;
- var cssClass = highlight.cssClass;
- for(var i = 0; i < metrics.length; ++i) {
- var highlightSpan = document.createElement("span");
- highlightSpan._isOverlayHighlightElement = true;
- highlightSpan.addStyleClass(cssClass);
- highlightSpan.style.left = metrics[i].left + "px";
- highlightSpan.style.width = metrics[i].width + "px";
- highlightSpan.style.height = metrics[i].height + "px";
- highlightSpan.addStyleClass("text-editor-overlay-highlight");
- lineRow.insertBefore(highlightSpan, lineRow.decorationsElement);
- }
- },
- /**
- * @param {Element} lineRow
- * @param {string} line
- * @param {Array.<{startColumn: number, endColumn: number, token: ?string}>} ranges
- * @param {boolean=} splitWhitespaceSequences
- */
- _renderRanges: function(lineRow, line, ranges, splitWhitespaceSequences)
- {
- var decorationsElement = lineRow.decorationsElement;
- if (!decorationsElement)
- lineRow.removeChildren();
- else {
- while (true) {
- var child = lineRow.firstChild;
- if (!child || child === decorationsElement)
- break;
- lineRow.removeChild(child);
- }
- }
- if (!line)
- lineRow.insertBefore(document.createElement("br"), decorationsElement);
- var plainTextStart = 0;
- for(var i = 0; i < ranges.length; i++) {
- var rangeStart = ranges[i].startColumn;
- var rangeEnd = ranges[i].endColumn;
- if (plainTextStart < rangeStart) {
- this._insertSpanBefore(lineRow, decorationsElement, line.substring(plainTextStart, rangeStart));
- }
- if (splitWhitespaceSequences && ranges[i].token === "whitespace")
- this._renderWhitespaceCharsWithFixedSizeSpans(lineRow, decorationsElement, rangeEnd - rangeStart + 1);
- else
- this._insertSpanBefore(lineRow, decorationsElement, line.substring(rangeStart, rangeEnd + 1), ranges[i].token ? "webkit-" + ranges[i].token : "");
- plainTextStart = rangeEnd + 1;
- }
- if (plainTextStart < line.length) {
- this._insertSpanBefore(lineRow, decorationsElement, line.substring(plainTextStart, line.length));
- }
- },
- /**
- * @param {Element} lineRow
- * @param {Element} decorationsElement
- * @param {number} length
- */
- _renderWhitespaceCharsWithFixedSizeSpans: function(lineRow, decorationsElement, length)
- {
- for (var whitespaceLength = 16; whitespaceLength > 0; whitespaceLength >>= 1) {
- var cssClass = "webkit-whitespace webkit-whitespace-" + whitespaceLength;
- for (; length >= whitespaceLength; length -= whitespaceLength)
- this._insertSpanBefore(lineRow, decorationsElement, WebInspector.TextEditorMainPanel._ConsecutiveWhitespaceChars[whitespaceLength], cssClass);
- }
- },
- /**
- * @param {Element} lineRow
- * @param {Array.<WebInspector.TextEditorMainPanel.LineOverlayHighlight>} overlayHighlight
- */
- _paintLine: function(lineRow, overlayHighlight)
- {
- var lineNumber = lineRow.lineNumber;
- this.beginDomUpdates();
- try {
- var syntaxHighlight = this._textModel.getAttribute(lineNumber, "highlight");
- var line = this._textModel.line(lineNumber);
- var ranges = syntaxHighlight ? syntaxHighlight.ranges : [];
- this._renderRanges(lineRow, line, ranges, this._showWhitespace);
- if (overlayHighlight)
- for(var i = 0; i < overlayHighlight.length; ++i)
- this._appendOverlayHighlight(lineRow, overlayHighlight[i]);
- } finally {
- if (this._rangeToMark && this._rangeToMark.startLine === lineNumber)
- this._markedRangeElement = WebInspector.highlightSearchResult(lineRow, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
- this.endDomUpdates();
- }
- },
- /**
- * @param {Element} lineRow
- */
- _releaseLinesHighlight: function(lineRow)
- {
- if (!lineRow)
- return;
- if ("spans" in lineRow) {
- var spans = lineRow.spans;
- for (var j = 0; j < spans.length; ++j)
- this._cachedSpans.push(spans[j]);
- delete lineRow.spans;
- }
- if ("textNodes" in lineRow) {
- var textNodes = lineRow.textNodes;
- for (var j = 0; j < textNodes.length; ++j)
- this._cachedTextNodes.push(textNodes[j]);
- delete lineRow.textNodes;
- }
- this._cachedRows.push(lineRow);
- },
- /**
- * @param {?Node=} lastUndamagedLineRow
- * @return {WebInspector.TextRange}
- */
- selection: function(lastUndamagedLineRow)
- {
- var selection = window.getSelection();
- if (!selection.rangeCount)
- return null;
- // Selection may be outside of the editor.
- if (!this._container.isAncestor(selection.anchorNode) || !this._container.isAncestor(selection.focusNode))
- return null;
- // Selection may be inside one of decorations.
- if (selection.focusNode.enclosingNodeOrSelfWithClass("webkit-line-decorations", this._container))
- return null;
- var start = this._selectionToPosition(selection.anchorNode, selection.anchorOffset, lastUndamagedLineRow);
- var end = selection.isCollapsed ? start : this._selectionToPosition(selection.focusNode, selection.focusOffset, lastUndamagedLineRow);
- return new WebInspector.TextRange(start.line, start.column, end.line, end.column);
- },
- lastSelection: function()
- {
- return this._lastSelection;
- },
- /**
- * @param {boolean=} scrollIntoView
- */
- _restoreSelection: function(range, scrollIntoView)
- {
- if (!range)
- return;
- var start = this._positionToSelection(range.startLine, range.startColumn);
- var end = range.isEmpty() ? start : this._positionToSelection(range.endLine, range.endColumn);
- window.getSelection().setBaseAndExtent(start.container, start.offset, end.container, end.offset);
- if (scrollIntoView) {
- for (var node = end.container; node; node = node.parentElement) {
- if (node.scrollIntoViewIfNeeded) {
- node.scrollIntoViewIfNeeded();
- break;
- }
- }
- }
- this._lastSelection = range;
- },
- /**
- * @param {Node} container
- * @param {number} offset
- * @param {?Node=} lastUndamagedLineRow
- * @return {{line: number, column: number}}
- */
- _selectionToPosition: function(container, offset, lastUndamagedLineRow)
- {
- if (container === this._container && offset === 0)
- return { line: 0, column: 0 };
- if (container === this._container && offset === 1)
- return { line: this._textModel.linesCount - 1, column: this._textModel.lineLength(this._textModel.linesCount - 1) };
- // This method can be called on the damaged DOM (when DOM does not match model).
- // We need to start counting lines from the first undamaged line if it is given.
- var lineNumber;
- var column = 0;
- var node;
- var scopeNode;
- if (lastUndamagedLineRow === null) {
- // Last undamaged row is given, but is null - force traverse from the beginning
- node = this._container.firstChild;
- scopeNode = this._container;
- lineNumber = 0;
- } else {
- var lineRow = this._enclosingLineRowOrSelf(container);
- if (!lastUndamagedLineRow || (typeof lineRow.lineNumber === "number" && lineRow.lineNumber <= lastUndamagedLineRow.lineNumber)) {
- // DOM is consistent (or we belong to the first damaged row)- lookup the row we belong to and start with it.
- node = lineRow;
- scopeNode = node;
- lineNumber = node.lineNumber;
- } else {
- // Start with the node following undamaged row. It corresponds to lineNumber + 1.
- node = lastUndamagedLineRow.nextSibling;
- scopeNode = this._container;
- lineNumber = lastUndamagedLineRow.lineNumber + 1;
- }
- }
- // Fast return the line start.
- if (container === node && offset === 0)
- return { line: lineNumber, column: 0 };
- // Traverse text and increment lineNumber / column.
- for (; node && node !== container; node = node.traverseNextNode(scopeNode)) {
- if (node.nodeName.toLowerCase() === "br") {
- lineNumber++;
- column = 0;
- } else if (node.nodeType === Node.TEXT_NODE) {
- var text = node.textContent;
- for (var i = 0; i < text.length; ++i) {
- if (text.charAt(i) === "\n") {
- lineNumber++;
- column = 0;
- } else
- column++;
- }
- }
- }
- // We reached our container node, traverse within itself until we reach given offset.
- if (node === container && offset) {
- var text = node.textContent;
- // In case offset == 1 and lineRow is a chunk div, we need to traverse it all.
- var textOffset = (node._chunk && offset === 1) ? text.length : offset;
- for (var i = 0; i < textOffset; ++i) {
- if (text.charAt(i) === "\n") {
- lineNumber++;
- column = 0;
- } else
- column++;
- }
- }
- return { line: lineNumber, column: column };
- },
- /**
- * @param {number} line
- * @param {number} column
- * @return {{container: Element, offset: number}}
- */
- _positionToSelection: function(line, column)
- {
- var chunk = this.chunkForLine(line);
- // One-lined collapsed chunks may still stay highlighted.
- var lineRow = chunk.linesCount === 1 ? chunk.element : chunk.expandedLineRow(line);
- if (lineRow)
- var rangeBoundary = lineRow.rangeBoundaryForOffset(column);
- else {
- var offset = column;
- for (var i = chunk.startLine; i < line && i < this._textModel.linesCount; ++i)
- offset += this._textModel.lineLength(i) + 1; // \n
- lineRow = chunk.element;
- if (lineRow.firstChild)
- var rangeBoundary = { container: lineRow.firstChild, offset: offset };
- else
- var rangeBoundary = { container: lineRow, offset: 0 };
- }
- return rangeBoundary;
- },
- /**
- * @param {Node} element
- * @return {?Node}
- */
- _enclosingLineRowOrSelf: function(element)
- {
- var lineRow = element.enclosingNodeOrSelfWithClass("webkit-line-content");
- if (lineRow)
- return lineRow;
- for (lineRow = element; lineRow; lineRow = lineRow.parentElement) {
- if (lineRow.parentElement === this._container)
- return lineRow;
- }
- return null;
- },
- /**
- * @param {Element} element
- * @param {Element} oldChild
- * @param {string} content
- * @param {string=} className
- */
- _insertSpanBefore: function(element, oldChild, content, className)
- {
- if (className === "html-resource-link" || className === "html-external-link") {
- element.insertBefore(this._createLink(content, className === "html-external-link"), oldChild);
- return;
- }
- var span = this._cachedSpans.pop() || document.createElement("span");
- if (!className)
- span.removeAttribute("class");
- else
- span.className = className;
- if (WebInspector.FALSE) // For paint debugging.
- span.addStyleClass("debug-fadeout");
- span.textContent = content;
- element.insertBefore(span, oldChild);
- if (!("spans" in element))
- element.spans = [];
- element.spans.push(span);
- },
- /**
- * @param {Element} element
- * @param {Element} oldChild
- * @param {string} text
- */
- _insertTextNodeBefore: function(element, oldChild, text)
- {
- var textNode = this._cachedTextNodes.pop();
- if (textNode)
- textNode.nodeValue = text;
- else
- textNode = document.createTextNode(text);
- element.insertBefore(textNode, oldChild);
- if (!("textNodes" in element))
- element.textNodes = [];
- element.textNodes.push(textNode);
- },
- /**
- * @param {string} content
- * @param {boolean} isExternal
- * @return {Element}
- */
- _createLink: function(content, isExternal)
- {
- var quote = content.charAt(0);
- if (content.length > 1 && (quote === "\"" || quote === "'"))
- content = content.substring(1, content.length - 1);
- else
- quote = null;
- var span = document.createElement("span");
- span.className = "webkit-html-attribute-value";
- if (quote)
- span.appendChild(document.createTextNode(quote));
- span.appendChild(this._delegate.createLink(content, isExternal));
- if (quote)
- span.appendChild(document.createTextNode(quote));
- return span;
- },
- /**
- * @param {Array.<WebKitMutation>} mutations
- */
- _handleMutations: function(mutations)
- {
- if (this._readOnly) {
- delete this._keyDownCode;
- return;
- }
- // Annihilate noop BR addition + removal that takes place upon line removal.
- var filteredMutations = mutations.slice();
- var addedBRs = new Map();
- for (var i = 0; i < mutations.length; ++i) {
- var mutation = mutations[i];
- if (mutation.type !== "childList")
- continue;
- if (mutation.addedNodes.length === 1 && mutation.addedNodes[0].nodeName === "BR")
- addedBRs.put(mutation.addedNodes[0], mutation);
- else if (mutation.removedNodes.length === 1 && mutation.removedNodes[0].nodeName === "BR") {
- var noopMutation = addedBRs.get(mutation.removedNodes[0]);
- if (noopMutation) {
- filteredMutations.remove(mutation);
- filteredMutations.remove(noopMutation);
- }
- }
- }
- var dirtyLines;
- for (var i = 0; i < filteredMutations.length; ++i) {
- var mutation = filteredMutations[i];
- var changedNodes = [];
- if (mutation.type === "childList" && mutation.addedNodes.length)
- changedNodes = Array.prototype.slice.call(mutation.addedNodes);
- else if (mutation.type === "childList" && mutation.removedNodes.length)
- changedNodes = Array.prototype.slice.call(mutation.removedNodes);
- changedNodes.push(mutation.target);
- for (var j = 0; j < changedNodes.length; ++j) {
- var lines = this._collectDirtyLines(mutation, changedNodes[j]);
- if (!lines)
- continue;
- if (!dirtyLines) {
- dirtyLines = lines;
- continue;
- }
- dirtyLines.start = Math.min(dirtyLines.start, lines.start);
- dirtyLines.end = Math.max(dirtyLines.end, lines.end);
- }
- }
- if (dirtyLines) {
- delete this._rangeToMark;
- this._applyDomUpdates(dirtyLines);
- }
- this._assertDOMMatchesTextModel();
- delete this._keyDownCode;
- },
- /**
- * @param {WebKitMutation} mutation
- * @param {Node} target
- * @return {?Object}
- */
- _collectDirtyLines: function(mutation, target)
- {
- var lineRow = this._enclosingLineRowOrSelf(target);
- if (!lineRow)
- return null;
- if (lineRow.decorationsElement && lineRow.decorationsElement.isSelfOrAncestor(target)) {
- if (this._syncDecorationsForLineListener)
- this._syncDecorationsForLineListener(lineRow.lineNumber);
- return null;
- }
- if (typeof lineRow.lineNumber !== "number")
- return null;
- var startLine = lineRow.lineNumber;
- var endLine = lineRow._chunk ? lineRow._chunk.endLine - 1 : lineRow.lineNumber;
- return { start: startLine, end: endLine };
- },
- /**
- * @param {Object} dirtyLines
- */
- _applyDomUpdates: function(dirtyLines)
- {
- var lastUndamagedLineNumber = dirtyLines.start - 1; // Can be -1
- var firstUndamagedLineNumber = dirtyLines.end + 1; // Can be this._textModel.linesCount
- var lastUndamagedLineChunk = lastUndamagedLineNumber >= 0 ? this._textChunks[this.chunkNumberForLine(lastUndamagedLineNumber)] : null;
- var firstUndamagedLineChunk = firstUndamagedLineNumber < this._textModel.linesCount ? this._textChunks[this.chunkNumberForLine(firstUndamagedLineNumber)] : null;
- var collectLinesFromNode = lastUndamagedLineChunk ? lastUndamagedLineChunk.lineRowContainingLine(lastUndamagedLineNumber) : null;
- var collectLinesToNode = firstUndamagedLineChunk ? firstUndamagedLineChunk.lineRowContainingLine(firstUndamagedLineNumber) : null;
- var lines = this._collectLinesFromDOM(collectLinesFromNode, collectLinesToNode);
- var startLine = dirtyLines.start;
- var endLine = dirtyLines.end;
- var originalSelection = this._lastSelection;
- var editInfo = this._guessEditRangeBasedOnSelection(startLine, endLine, lines);
- if (!editInfo) {
- if (WebInspector.debugDefaultTextEditor)
- console.warn("Falling back to expensive edit");
- var range = new WebInspector.TextRange(startLine, 0, endLine, this._textModel.lineLength(endLine));
- if (!lines.length) {
- // Entire damaged area has collapsed. Replace everything between start and end lines with nothing.
- editInfo = new WebInspector.DefaultTextEditor.EditInfo(this._textModel.growRangeRight(range), "");
- } else
- editInfo = new WebInspector.DefaultTextEditor.EditInfo(range, lines.join("\n"));
- }
- var selection = this.selection(collectLinesFromNode);
- // Unindent after block
- if (editInfo.text === "}" && editInfo.range.isEmpty() && selection.isEmpty() && !this._textModel.line(editInfo.range.endLine).trim()) {
- var offset = this._closingBlockOffset(editInfo.range);
- if (offset >= 0) {
- editInfo.range.startColumn = offset;
- selection.startColumn = offset + 1;
- selection.endColumn = offset + 1;
- }
- }
- this._textModel.editRange(editInfo.range, editInfo.text, originalSelection);
- this._restoreSelection(selection);
- },
- /**
- * @param {number} startLine
- * @param {number} endLine
- * @param {Array.<string>} lines
- * @return {?WebInspector.DefaultTextEditor.EditInfo}
- */
- _guessEditRangeBasedOnSelection: function(startLine, endLine, lines)
- {
- // Analyze input data
- var textInputData = this._textInputData;
- delete this._textInputData;
- var isBackspace = this._keyDownCode === WebInspector.KeyboardShortcut.Keys.Backspace.code;
- var isDelete = this._keyDownCode === WebInspector.KeyboardShortcut.Keys.Delete.code;
- if (!textInputData && (isDelete || isBackspace))
- textInputData = "";
- // Return if there is no input data or selection
- if (typeof textInputData === "undefined" || !this._lastSelection)
- return null;
- // Adjust selection based on the keyboard actions (grow for backspace, etc.).
- textInputData = textInputData || "";
- var range = this._lastSelection.normalize();
- if (isBackspace && range.isEmpty())
- range = this._textModel.growRangeLeft(range);
- else if (isDelete && range.isEmpty())
- range = this._textModel.growRangeRight(range);
- // Test that selection intersects damaged lines
- if (startLine > range.endLine || endLine < range.startLine)
- return null;
- var replacementLineCount = textInputData.split("\n").length - 1;
- var lineCountDelta = replacementLineCount - range.linesCount;
- if (startLine + lines.length - endLine - 1 !== lineCountDelta)
- return null;
- // Clone text model of the size that fits both: selection before edit and the damaged lines after edit.
- var cloneFromLine = Math.min(range.startLine, startLine);
- var postLastLine = startLine + lines.length + lineCountDelta;
- var cloneToLine = Math.min(Math.max(postLastLine, range.endLine) + 1, this._textModel.linesCount);
- var domModel = this._textModel.slice(cloneFromLine, cloneToLine);
- domModel.editRange(range.shift(-cloneFromLine), textInputData);
- // Then we'll test if this new model matches the DOM lines.
- for (var i = 0; i < lines.length; ++i) {
- if (domModel.line(i + startLine - cloneFromLine) !== lines[i])
- return null;
- }
- return new WebInspector.DefaultTextEditor.EditInfo(range, textInputData);
- },
- _assertDOMMatchesTextModel: function()
- {
- if (!WebInspector.debugDefaultTextEditor)
- return;
- console.assert(this.element.innerText === this._textModel.text() + "\n", "DOM does not match model.");
- for (var lineRow = this._container.firstChild; lineRow; lineRow = lineRow.nextSibling) {
- var lineNumber = lineRow.lineNumber;
- if (typeof lineNumber !== "number") {
- console.warn("No line number on line row");
- continue;
- }
- if (lineRow._chunk) {
- var chunk = lineRow._chunk;
- console.assert(lineNumber === chunk.startLine);
- var chunkText = this._textModel.copyRange(new WebInspector.TextRange(chunk.startLine, 0, chunk.endLine - 1, this._textModel.lineLength(chunk.endLine - 1)));
- if (chunkText !== lineRow.textContent)
- console.warn("Chunk is not matching: %d %O", lineNumber, lineRow);
- } else if (this._textModel.line(lineNumber) !== lineRow.textContent)
- console.warn("Line is not matching: %d %O", lineNumber, lineRow);
- }
- },
- /**
- * @param {WebInspector.TextRange} oldRange
- * @return {number}
- */
- _closingBlockOffset: function(oldRange)
- {
- var leftBrace = this._braceMatcher.findLeftCandidate(oldRange.startLine, oldRange.startColumn);
- if (!leftBrace || leftBrace.token !== "block-start")
- return -1;
- var lineContent = this._textModel.line(leftBrace.lineNumber);
- return lineContent.length - lineContent.trimLeft().length;
- },
- /**
- * @param {WebInspector.TextRange} oldRange
- * @param {WebInspector.TextRange} newRange
- */
- textChanged: function(oldRange, newRange)
- {
- this.beginDomUpdates();
- this._removeDecorationsInRange(oldRange);
- this._updateChunksForRanges(oldRange, newRange);
- this._updateHighlightsForRange(newRange);
- this.endDomUpdates();
- },
- /**
- * @param {WebInspector.TextRange} range
- */
- _removeDecorationsInRange: function(range)
- {
- for (var i = this.chunkNumberForLine(range.startLine); i < this._textChunks.length; ++i) {
- var chunk = this._textChunks[i];
- if (chunk.startLine > range.endLine)
- break;
- chunk.removeAllDecorations();
- }
- },
- /**
- * @param {WebInspector.TextRange} oldRange
- * @param {WebInspector.TextRange} newRange
- */
- _updateChunksForRanges: function(oldRange, newRange)
- {
- var firstDamagedChunkNumber = this.chunkNumberForLine(oldRange.startLine);
- var lastDamagedChunkNumber = firstDamagedChunkNumber;
- while (lastDamagedChunkNumber + 1 < this._textChunks.length) {
- if (this._textChunks[lastDamagedChunkNumber + 1].startLine > oldRange.endLine)
- break;
- ++lastDamagedChunkNumber;
- }
- var firstDamagedChunk = this._textChunks[firstDamagedChunkNumber];
- var lastDamagedChunk = this._textChunks[lastDamagedChunkNumber];
- var linesDiff = newRange.linesCount - oldRange.linesCount;
- // First, detect chunks that have not been modified and simply shift them.
- if (linesDiff) {
- for (var chunkNumber = lastDamagedChunkNumber + 1; chunkNumber < this._textChunks.length; ++chunkNumber)
- this._textChunks[chunkNumber].startLine += linesDiff;
- }
- // Remove damaged chunks from DOM and from textChunks model.
- var lastUndamagedChunk = firstDamagedChunkNumber > 0 ? this._textChunks[firstDamagedChunkNumber - 1] : null;
- var firstUndamagedChunk = lastDamagedChunkNumber + 1 < this._textChunks.length ? this._textChunks[lastDamagedChunkNumber + 1] : null;
- var removeDOMFromNode = lastUndamagedChunk ? lastUndamagedChunk.lastElement().nextSibling : this._container.firstChild;
- var removeDOMToNode = firstUndamagedChunk ? firstUndamagedChunk.firstElement() : null;
- // Fast case - patch single expanded chunk that did not grow / shrink during edit.
- if (!linesDiff && firstDamagedChunk === lastDamagedChunk && firstDamagedChunk._expandedLineRows) {
- var lastUndamagedLineRow = lastDamagedChunk.expandedLineRow(oldRange.startLine - 1);
- var firstUndamagedLineRow = firstDamagedChunk.expandedLineRow(oldRange.endLine + 1);
- var localRemoveDOMFromNode = lastUndamagedLineRow ? lastUndamagedLineRow.nextSibling : removeDOMFromNode;
- var localRemoveDOMToNode = firstUndamagedLineRow || removeDOMToNode;
- removeSubsequentNodes(localRemoveDOMFromNode, localRemoveDOMToNode);
- for (var i = newRange.startLine; i < newRange.endLine + 1; ++i) {
- var row = firstDamagedChunk._createRow(i);
- firstDamagedChunk._expandedLineRows[i - firstDamagedChunk.startLine] = row;
- this._container.insertBefore(row, localRemoveDOMToNode);
- }
- firstDamagedChunk.updateCollapsedLineRow();
- this._assertDOMMatchesTextModel();
- return;
- }
- removeSubsequentNodes(removeDOMFromNode, removeDOMToNode);
- this._textChunks.splice(firstDamagedChunkNumber, lastDamagedChunkNumber - firstDamagedChunkNumber + 1);
- // Compute damaged chunks span
- var startLine = firstDamagedChunk.startLine;
- var endLine = lastDamagedChunk.endLine + linesDiff;
- var lineSpan = endLine - startLine;
- // Re-create chunks for damaged area.
- var insertionIndex = firstDamagedChunkNumber;
- var chunkSize = Math.ceil(lineSpan / Math.ceil(lineSpan / this._defaultChunkSize));
- for (var i = startLine; i < endLine; i += chunkSize) {
- var chunk = this.createNewChunk(i, Math.min(endLine, i + chunkSize));
- this._textChunks.splice(insertionIndex++, 0, chunk);
- this._container.insertBefore(chunk.element, removeDOMToNode);
- }
- this._assertDOMMatchesTextModel();
- },
- /**
- * @param {WebInspector.TextRange} range
- */
- _updateHighlightsForRange: function(range)
- {
- var visibleFrom = this.scrollTop();
- var visibleTo = visibleFrom + this.clientHeight();
- var result = this.findVisibleChunks(visibleFrom, visibleTo);
- var chunk = this._textChunks[result.end - 1];
- var lastVisibleLine = chunk.startLine + chunk.linesCount;
- lastVisibleLine = Math.max(lastVisibleLine, range.endLine + 1);
- lastVisibleLine = Math.min(lastVisibleLine, this._textModel.linesCount);
- var updated = this._highlighter.updateHighlight(range.startLine, lastVisibleLine);
- if (!updated) {
- // Highlights for the chunks below are invalid, so just collapse them.
- for (var i = this.chunkNumberForLine(range.startLine); i < this._textChunks.length; ++i)
- this._textChunks[i].collapse();
- }
- this.repaintAll();
- },
- /**
- * @param {Node} from
- * @param {Node} to
- * @return {Array.<string>}
- */
- _collectLinesFromDOM: function(from, to)
- {
- var textContents = [];
- var hasContent = false;
- for (var node = from ? from.nextSibling : this._container; node && node !== to; node = node.traverseNextNode(this._container)) {
- // Skip all children of the decoration container and overlay highlight spans.
- while (node && node !== to && (node._isDecorationsElement || node._isOverlayHighlightElement))
- node = node.nextSibling;
- if (!node || node === to)
- break;
- hasContent = true;
- if (node.nodeName.toLowerCase() === "br")
- textContents.push("\n");
- else if (node.nodeType === Node.TEXT_NODE)
- textContents.push(node.textContent);
- }
- if (!hasContent)
- return [];
- var textContent = textContents.join("");
- // The last \n (if any) does not "count" in a DIV.
- textContent = textContent.replace(/\n$/, "");
- return textContent.split("\n");
- },
- /**
- * @param {Event} event
- */
- _handleSelectionChange: function(event)
- {
- var textRange = this.selection();
- if (textRange)
- this._lastSelection = textRange;
- this._tokenHighlighter.handleSelectionChange(textRange);
- this._braceHighlighter.handleSelectionChange(textRange);
- this._delegate.selectionChanged(textRange);
- },
- /**
- * @param {Event} event
- */
- _handleTextInput: function(event)
- {
- this._textInputData = event.data;
- },
- /**
- * @param {number} shortcutKey
- * @param {Event} event
- */
- handleKeyDown: function(shortcutKey, event)
- {
- var handler = this._shortcuts[shortcutKey];
- if (handler && handler()) {
- event.consume(true);
- return;
- }
- this._keyDownCode = event.keyCode;
- },
- /**
- * @param {Event} event
- */
- _handleCut: function(event)
- {
- this._keyDownCode = WebInspector.KeyboardShortcut.Keys.Delete.code;
- },
- /**
- * @param {number} scrollTop
- * @param {number} clientHeight
- * @param {number} chunkSize
- */
- overrideViewportForTest: function(scrollTop, clientHeight, chunkSize)
- {
- this._scrollTopOverrideForTest = scrollTop;
- this._clientHeightOverrideForTest = clientHeight;
- this._defaultChunkSize = chunkSize;
- },
- __proto__: WebInspector.TextEditorChunkedPanel.prototype
- }
- /**
- * @interface
- */
- WebInspector.TextEditorMainPanel.HighlightDescriptor = function() { }
- WebInspector.TextEditorMainPanel.HighlightDescriptor.prototype = {
- /**
- * @param {number} lineNumber
- * @param {string} line
- * @return {boolean}
- */
- affectsLine: function(lineNumber, line) { return false; },
- /**
- * @param {number} lineNumber
- * @param {string} line
- * @return {Array.<{startColumn: number, endColumn: number}>}
- */
- rangesForLine: function(lineNumber, line) { return []; },
- /**
- * @return {string}
- */
- cssClass: function() { return ""; },
- }
- /**
- * @constructor
- * @implements {WebInspector.TextEditorMainPanel.HighlightDescriptor}
- */
- WebInspector.TextEditorMainPanel.RegexHighlightDescriptor = function(regex, cssClass)
- {
- this._cssClass = cssClass;
- this._regex = regex;
- }
- WebInspector.TextEditorMainPanel.RegexHighlightDescriptor.prototype = {
- /**
- * @param {number} lineNumber
- * @param {string} line
- * @return {boolean}
- */
- affectsLine: function(lineNumber, line)
- {
- this._regex.lastIndex = 0;
- return this._regex.test(line);
- },
- /**
- * @param {number} lineNumber
- * @param {string} line
- * @return {Array.<{startColumn: number, endColumn: number}>}
- */
- rangesForLine: function(lineNumber, line)
- {
- var ranges = [];
- var regexResult;
- this._regex.lastIndex = 0;
- while (regexResult = this._regex.exec(line)) {
- ranges.push({
- startColumn: regexResult.index,
- endColumn: regexResult.index + regexResult[0].length - 1
- });
- }
- return ranges;
- },
- /**
- * @return {string}
- */
- cssClass: function()
- {
- return this._cssClass;
- }
- }
- /**
- * @constructor
- * @implements {WebInspector.TextEditorMainPanel.HighlightDescriptor}
- * @param {WebInspector.TextRange} range
- * @param {string} cssClass
- */
- WebInspector.TextEditorMainPanel.RangeHighlightDescriptor = function(range, cssClass)
- {
- this._cssClass = cssClass;
- this._range = range;
- }
- WebInspector.TextEditorMainPanel.RangeHighlightDescriptor.prototype = {
- /**
- * @param {number} lineNumber
- * @param {string} line
- * @return {boolean}
- */
- affectsLine: function(lineNumber, line)
- {
- return this._range.startLine <= lineNumber && lineNumber <= this._range.endLine && line.length > 0;
- },
- /**
- * @param {number} lineNumber
- * @param {string} line
- * @return {Array.<{startColumn: number, endColumn: number}>}
- */
- rangesForLine: function(lineNumber, line)
- {
- if (!this.affectsLine(lineNumber, line))
- return [];
- var startColumn = lineNumber === this._range.startLine ? this._range.startColumn : 0;
- var endColumn = lineNumber === this._range.endLine ? Math.min(this._range.endColumn, line.length) : line.length;
- return [{
- startColumn: startColumn,
- endColumn: endColumn
- }];
- },
- /**
- * @return {string}
- */
- cssClass: function()
- {
- return this._cssClass;
- }
- }
- /**
- * @constructor
- * @param {Element} element
- */
- WebInspector.TextEditorMainPanel.ElementMetrics = function(element)
- {
- this.width = element.offsetWidth;
- this.height = element.offsetHeight;
- this.left = element.offsetLeft;
- }
- /**
- * @constructor
- * @param {Array.<WebInspector.TextEditorMainPanel.ElementMetrics>} metrics
- * @param {string} cssClass
- */
- WebInspector.TextEditorMainPanel.LineOverlayHighlight = function(metrics, cssClass)
- {
- this.metrics = metrics;
- this.cssClass = cssClass;
- }
- /**
- * @constructor
- * @param {WebInspector.TextEditorChunkedPanel} chunkedPanel
- * @param {number} startLine
- * @param {number} endLine
- */
- WebInspector.TextEditorMainChunk = function(chunkedPanel, startLine, endLine)
- {
- this._chunkedPanel = chunkedPanel;
- this._textModel = chunkedPanel._textModel;
- this.element = document.createElement("div");
- this.element.lineNumber = startLine;
- this.element.className = "webkit-line-content";
- this.element._chunk = this;
- this._startLine = startLine;
- endLine = Math.min(this._textModel.linesCount, endLine);
- this.linesCount = endLine - startLine;
- this._expanded = false;
- this.updateCollapsedLineRow();
- }
- WebInspector.TextEditorMainChunk.prototype = {
- /**
- * @param {Element|string} decoration
- */
- addDecoration: function(decoration)
- {
- this._chunkedPanel.beginDomUpdates();
- if (typeof decoration === "string")
- this.element.addStyleClass(decoration);
- else {
- if (!this.element.decorationsElement) {
- this.element.decorationsElement = document.createElement("div");
- this.element.decorationsElement.className = "webkit-line-decorations";
- this.element.decorationsElement._isDecorationsElement = true;
- this.element.appendChild(this.element.decorationsElement);
- }
- this.element.decorationsElement.appendChild(decoration);
- }
- this._chunkedPanel.endDomUpdates();
- },
- /**
- * @param {string|Element} decoration
- */
- removeDecoration: function(decoration)
- {
- this._chunkedPanel.beginDomUpdates();
- if (typeof decoration === "string")
- this.element.removeStyleClass(decoration);
- else if (this.element.decorationsElement)
- this.element.decorationsElement.removeChild(decoration);
- this._chunkedPanel.endDomUpdates();
- },
- removeAllDecorations: function()
- {
- this._chunkedPanel.beginDomUpdates();
- this.element.className = "webkit-line-content";
- if (this.element.decorationsElement) {
- if (this.element.decorationsElement.parentElement)
- this.element.removeChild(this.element.decorationsElement);
- delete this.element.decorationsElement;
- }
- this._chunkedPanel.endDomUpdates();
- },
- /**
- * @return {boolean}
- */
- isDecorated: function()
- {
- return this.element.className !== "webkit-line-content" || !!(this.element.decorationsElement && this.element.decorationsElement.firstChild);
- },
- /**
- * @return {number}
- */
- get startLine()
- {
- return this._startLine;
- },
- /**
- * @return {number}
- */
- get endLine()
- {
- return this._startLine + this.linesCount;
- },
- set startLine(startLine)
- {
- this._startLine = startLine;
- this.element.lineNumber = startLine;
- if (this._expandedLineRows) {
- for (var i = 0; i < this._expandedLineRows.length; ++i)
- this._expandedLineRows[i].lineNumber = startLine + i;
- }
- },
- /**
- * @return {boolean}
- */
- expanded: function()
- {
- return this._expanded;
- },
- expand: function()
- {
- if (this._expanded)
- return;
- this._expanded = true;
- if (this.linesCount === 1) {
- this._chunkedPanel._paintLines(this.startLine, this.startLine + 1);
- return;
- }
- this._chunkedPanel.beginDomUpdates();
- this._expandedLineRows = [];
- var parentElement = this.element.parentElement;
- for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
- var lineRow = this._createRow(i);
- parentElement.insertBefore(lineRow, this.element);
- this._expandedLineRows.push(lineRow);
- }
- parentElement.removeChild(this.element);
- this._chunkedPanel._paintLines(this.startLine, this.startLine + this.linesCount);
- this._chunkedPanel.endDomUpdates();
- },
- collapse: function()
- {
- if (!this._expanded)
- return;
- this._expanded = false;
- if (this.linesCount === 1)
- return;
- this._chunkedPanel.beginDomUpdates();
- var elementInserted = false;
- for (var i = 0; i < this._expandedLineRows.length; ++i) {
- var lineRow = this._expandedLineRows[i];
- var parentElement = lineRow.parentElement;
- if (parentElement) {
- if (!elementInserted) {
- elementInserted = true;
- parentElement.insertBefore(this.element, lineRow);
- }
- parentElement.removeChild(lineRow);
- }
- this._chunkedPanel._releaseLinesHighlight(lineRow);
- }
- delete this._expandedLineRows;
- this._chunkedPanel.endDomUpdates();
- },
- /**
- * @return {number}
- */
- get height()
- {
- if (!this._expandedLineRows)
- return this._chunkedPanel.totalHeight(this.element);
- return this._chunkedPanel.totalHeight(this._expandedLineRows[0], this._expandedLineRows[this._expandedLineRows.length - 1]);
- },
- /**
- * @return {number}
- */
- get offsetTop()
- {
- return (this._expandedLineRows && this._expandedLineRows.length) ? this._expandedLineRows[0].offsetTop : this.element.offsetTop;
- },
- /**
- * @param {number} lineNumber
- * @return {Element}
- */
- _createRow: function(lineNumber)
- {
- var lineRow = this._chunkedPanel._cachedRows.pop() || document.createElement("div");
- lineRow.lineNumber = lineNumber;
- lineRow.className = "webkit-line-content";
- lineRow.textContent = this._textModel.line(lineNumber);
- if (!lineRow.textContent)
- lineRow.appendChild(document.createElement("br"));
- return lineRow;
- },
- /**
- * Called on potentially damaged / inconsistent chunk
- * @param {number} lineNumber
- * @return {?Node}
- */
- lineRowContainingLine: function(lineNumber)
- {
- if (!this._expanded)
- return this.element;
- return this.expandedLineRow(lineNumber);
- },
- /**
- * @param {number} lineNumber
- * @return {Element}
- */
- expandedLineRow: function(lineNumber)
- {
- if (!this._expanded || lineNumber < this.startLine || lineNumber >= this.startLine + this.linesCount)
- return null;
- if (!this._expandedLineRows)
- return this.element;
- return this._expandedLineRows[lineNumber - this.startLine];
- },
- updateCollapsedLineRow: function()
- {
- if (this.linesCount === 1 && this._expanded)
- return;
- var lines = [];
- for (var i = this.startLine; i < this.startLine + this.linesCount; ++i)
- lines.push(this._textModel.line(i));
- if (WebInspector.FALSE)
- console.log("Rebuilding chunk with " + lines.length + " lines");
- this.element.removeChildren();
- this.element.textContent = lines.join("\n");
- // The last empty line will get swallowed otherwise.
- if (!lines[lines.length - 1])
- this.element.appendChild(document.createElement("br"));
- },
- firstElement: function()
- {
- return this._expandedLineRows ? this._expandedLineRows[0] : this.element;
- },
- /**
- * @return {Element}
- */
- lastElement: function()
- {
- return this._expandedLineRows ? this._expandedLineRows[this._expandedLineRows.length - 1] : this.element;
- }
- }
- /**
- * @constructor
- * @param {WebInspector.TextEditorMainPanel} mainPanel
- * @param {WebInspector.TextEditorModel} textModel
- */
- WebInspector.TextEditorMainPanel.TokenHighlighter = function(mainPanel, textModel)
- {
- this._mainPanel = mainPanel;
- this._textModel = textModel;
- }
- WebInspector.TextEditorMainPanel.TokenHighlighter.prototype = {
- /**
- * @param {WebInspector.TextRange} range
- */
- handleSelectionChange: function(range)
- {
- if (!range) {
- this._removeHighlight();
- return;
- }
- if (range.startLine !== range.endLine) {
- this._removeHighlight();
- return;
- }
- range = range.normalize();
- var selectedText = this._textModel.copyRange(range);
- if (selectedText === this._selectedWord)
- return;
- if (selectedText === "") {
- this._removeHighlight();
- return;
- }
- if (this._isWord(range, selectedText))
- this._highlight(selectedText);
- else
- this._removeHighlight();
- },
- /**
- * @param {string} word
- */
- _regexString: function(word)
- {
- return "\\b" + word + "\\b";
- },
- /**
- * @param {string} selectedWord
- */
- _highlight: function(selectedWord)
- {
- this._removeHighlight();
- this._selectedWord = selectedWord;
- this._highlightDescriptor = this._mainPanel.highlightRegex(this._regexString(selectedWord), "text-editor-token-highlight")
- },
- _removeHighlight: function()
- {
- if (this._selectedWord) {
- this._mainPanel.removeHighlight(this._highlightDescriptor);
- delete this._selectedWord;
- delete this._highlightDescriptor;
- }
- },
- /**
- * @param {WebInspector.TextRange} range
- * @param {string} selectedText
- * @return {boolean}
- */
- _isWord: function(range, selectedText)
- {
- var line = this._textModel.line(range.startLine);
- var leftBound = range.startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(range.startColumn - 1));
- var rightBound = range.endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(range.endColumn));
- return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText);
- }
- }
- /**
- * @constructor
- * @param {WebInspector.TextEditorModel} textModel
- * @param {WebInspector.TextEditor} textEditor
- */
- WebInspector.DefaultTextEditor.WordMovementController = function(textEditor, textModel)
- {
- this._textModel = textModel;
- this._textEditor = textEditor;
- }
- WebInspector.DefaultTextEditor.WordMovementController.prototype = {
- /**
- * @param {Object.<number, function()>} shortcuts
- */
- _registerShortcuts: function(shortcuts)
- {
- var keys = WebInspector.KeyboardShortcut.Keys;
- var modifiers = WebInspector.KeyboardShortcut.Modifiers;
- const wordJumpModifier = WebInspector.isMac() ? modifiers.Alt : modifiers.Ctrl;
- shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Backspace.code, wordJumpModifier)] = this._handleCtrlBackspace.bind(this);
- shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Left.code, wordJumpModifier)] = this._handleCtrlArrow.bind(this, "left");
- shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Right.code, wordJumpModifier)] = this._handleCtrlArrow.bind(this, "right");
- shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Left.code, modifiers.Shift | wordJumpModifier)] = this._handleCtrlShiftArrow.bind(this, "left");
- shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Right.code, modifiers.Shift | wordJumpModifier)] = this._handleCtrlShiftArrow.bind(this, "right");
- },
- /**
- * @param {WebInspector.TextRange} selection
- * @param {string} direction
- * @return {WebInspector.TextRange}
- */
- _rangeForCtrlArrowMove: function(selection, direction)
- {
- const isStopChar = WebInspector.TextUtils.isStopChar;
- const isSpaceChar = WebInspector.TextUtils.isSpaceChar;
- var lineNumber = selection.endLine;
- var column = selection.endColumn;
- if (direction === "left")
- --column;
- if (column === -1 && direction === "left") {
- if (lineNumber > 0)
- return new WebInspector.TextRange(selection.startLine, selection.startColumn, lineNumber - 1, this._textModel.line(lineNumber - 1).length);
- else
- return selection.clone();
- }
- var line = this._textModel.line(lineNumber);
- if (column === line.length && direction === "right") {
- if (lineNumber + 1 < this._textModel.linesCount)
- return new WebInspector.TextRange(selection.startLine, selection.startColumn, selection.endLine + 1, 0);
- else
- return selection.clone();
- }
- var delta = direction === "left" ? -1 : +1;
- var directionDependentEndColumnOffset = (delta + 1) / 2;
- if (isSpaceChar(line.charAt(column))) {
- while(column + delta >= 0 && column + delta < line.length && isSpaceChar(line.charAt(column + delta)))
- column += delta;
- if (column + delta < 0 || column + delta === line.length)
- return new WebInspector.TextRange(selection.startLine, selection.startColumn, lineNumber, column + directionDependentEndColumnOffset);
- else
- column += delta;
- }
- var group = isStopChar(line.charAt(column));
- while(column + delta >= 0 && column + delta < line.length && isStopChar(line.charAt(column + delta)) === group && !isSpaceChar(line.charAt(column + delta)))
- column += delta;
- return new WebInspector.TextRange(selection.startLine, selection.startColumn, lineNumber, column + directionDependentEndColumnOffset);
- },
- /**
- * @param {string} direction
- * @return {boolean}
- */
- _handleCtrlArrow: function(direction)
- {
- var newSelection = this._rangeForCtrlArrowMove(this._textEditor.selection(), direction);
- this._textEditor.setSelection(newSelection.collapseToEnd());
- return true;
- },
- /**
- * @param {string} direction
- * @return {boolean}
- */
- _handleCtrlShiftArrow: function(direction)
- {
- this._textEditor.setSelection(this._rangeForCtrlArrowMove(this._textEditor.selection(), direction));
- return true;
- },
- /**
- * @return {boolean}
- */
- _handleCtrlBackspace: function()
- {
- var selection = this._textEditor.selection();
- if (!selection.isEmpty())
- return false;
- var newSelection = this._rangeForCtrlArrowMove(selection, "left");
- this._textModel.editRange(newSelection.normalize(), "", selection);
- this._textEditor.setSelection(newSelection.collapseToEnd());
- return true;
- }
- }
- /**
- * @constructor
- * @param {WebInspector.TextEditorMainPanel} textEditor
- * @param {WebInspector.TextEditorModel} textModel
- * @param {WebInspector.TextEditorModel.BraceMatcher} braceMatcher
- */
- WebInspector.TextEditorMainPanel.BraceHighlightController = function(textEditor, textModel, braceMatcher)
- {
- this._textEditor = textEditor;
- this._textModel = textModel;
- this._braceMatcher = braceMatcher;
- this._highlightDescriptors = [];
- }
- WebInspector.TextEditorMainPanel.BraceHighlightController.prototype = {
- /**
- * @param {string} line
- * @param {number} column
- * @return {number}
- */
- activeBraceColumnForCursorPosition: function(line, column)
- {
- var char = line.charAt(column);
- if (WebInspector.TextUtils.isOpeningBraceChar(char))
- return column;
- var previousChar = line.charAt(column - 1);
- if (WebInspector.TextUtils.isBraceChar(previousChar))
- return column - 1;
- if (WebInspector.TextUtils.isBraceChar(char))
- return column;
- else
- return -1;
- },
- /**
- * @param {WebInspector.TextRange} selectionRange
- */
- handleSelectionChange: function(selectionRange)
- {
- if (!selectionRange || !selectionRange.isEmpty()) {
- this._removeHighlight();
- return;
- }
- if (this._highlightedRange && this._highlightedRange.compareTo(selectionRange) === 0)
- return;
- this._removeHighlight();
- var lineNumber = selectionRange.startLine;
- var column = selectionRange.startColumn;
- var line = this._textModel.line(lineNumber);
- column = this.activeBraceColumnForCursorPosition(line, column);
- if (column < 0)
- return;
- var enclosingBraces = this._braceMatcher.enclosingBraces(lineNumber, column);
- if (!enclosingBraces)
- return;
- this._highlightedRange = selectionRange;
- this._highlightDescriptors.push(this._textEditor.highlightRange(WebInspector.TextRange.createFromLocation(enclosingBraces.leftBrace.lineNumber, enclosingBraces.leftBrace.column), "text-editor-brace-match"));
- this._highlightDescriptors.push(this._textEditor.highlightRange(WebInspector.TextRange.createFromLocation(enclosingBraces.rightBrace.lineNumber, enclosingBraces.rightBrace.column), "text-editor-brace-match"));
- },
- _removeHighlight: function()
- {
- if (!this._highlightDescriptors.length)
- return;
- for(var i = 0; i < this._highlightDescriptors.length; ++i)
- this._textEditor.removeHighlight(this._highlightDescriptors[i]);
- this._highlightDescriptors = [];
- delete this._highlightedRange;
- }
- }
- /**
- * @constructor
- * @param {WebInspector.TextEditorMainPanel} mainPanel
- * @param {WebInspector.TextEditorModel} textModel
- * @param {WebInspector.TextEditorModel.BraceMatcher} braceMatcher
- */
- WebInspector.TextEditorMainPanel.SmartBraceController = function(mainPanel, textModel, braceMatcher)
- {
- this._mainPanel = mainPanel;
- this._textModel = textModel;
- this._braceMatcher = braceMatcher
- }
- WebInspector.TextEditorMainPanel.SmartBraceController.prototype = {
- /**
- * @param {Object.<number, function()>} shortcuts
- */
- registerShortcuts: function(shortcuts)
- {
- if (!WebInspector.experimentsSettings.textEditorSmartBraces.isEnabled())
- return;
- var keys = WebInspector.KeyboardShortcut.Keys;
- var modifiers = WebInspector.KeyboardShortcut.Modifiers;
- shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Backspace.code, modifiers.None)] = this._handleBackspace.bind(this);
- },
- /**
- * @param {Object.<string, function()>} charOverrides
- */
- registerCharOverrides: function(charOverrides)
- {
- if (!WebInspector.experimentsSettings.textEditorSmartBraces.isEnabled())
- return;
- charOverrides["("] = this._handleBracePairInsertion.bind(this, "()");
- charOverrides[")"] = this._handleClosingBraceOverride.bind(this, ")");
- charOverrides["{"] = this._handleBracePairInsertion.bind(this, "{}");
- charOverrides["}"] = this._handleClosingBraceOverride.bind(this, "}");
- },
- _handleBackspace: function()
- {
- var selection = this._mainPanel.lastSelection();
- if (!selection || !selection.isEmpty())
- return false;
- var column = selection.startColumn;
- if (column == 0)
- return false;
- var lineNumber = selection.startLine;
- var line = this._textModel.line(lineNumber);
- if (column === line.length)
- return false;
- var pair = line.substr(column - 1, 2);
- if (pair === "()" || pair === "{}") {
- this._textModel.editRange(new WebInspector.TextRange(lineNumber, column - 1, lineNumber, column + 1), "");
- this._mainPanel.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, column - 1));
- return true;
- } else
- return false;
- },
- /**
- * @param {string} bracePair
- * @return {boolean}
- */
- _handleBracePairInsertion: function(bracePair)
- {
- var selection = this._mainPanel.lastSelection().normalize();
- if (selection.isEmpty()) {
- var lineNumber = selection.startLine;
- var column = selection.startColumn;
- var line = this._textModel.line(lineNumber);
- if (column < line.length) {
- var char = line.charAt(column);
- if (WebInspector.TextUtils.isWordChar(char) || (!WebInspector.TextUtils.isBraceChar(char) && WebInspector.TextUtils.isStopChar(char)))
- return false;
- }
- }
- this._textModel.editRange(selection, bracePair);
- this._mainPanel.setSelection(WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn + 1));
- return true;
- },
- /**
- * @param {string} brace
- * @return {boolean}
- */
- _handleClosingBraceOverride: function(brace)
- {
- var selection = this._mainPanel.lastSelection().normalize();
- if (!selection || !selection.isEmpty())
- return false;
- var lineNumber = selection.startLine;
- var column = selection.startColumn;
- var line = this._textModel.line(lineNumber);
- if (line.charAt(column) !== brace)
- return false;
- var braces = this._braceMatcher.enclosingBraces(lineNumber, column);
- if (braces && braces.rightBrace.lineNumber === lineNumber && braces.rightBrace.column === column) {
- this._mainPanel.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, column + 1));
- return true;
- } else
- return false;
- },
- }
- WebInspector.debugDefaultTextEditor = false;
|