import { WidgetBase } from "./base" ; import { RuntimeManager , IBreakpoint , Breakpoint } from "../runtime" ; import { SubcodeLine , RuntileGlyphs , BreakpointState } from "./subline" ; import { getCodeFolding } from '../languages/turbo-gherkin/folding' ; import { language as gherkin } from '../languages/turbo-gherkin/configuration' ; import * as dom from 'monaco-editor/esm/vs/base/browser/dom' ; import { VanessaGherkinProvider } from "../languages/turbo-gherkin/provider" ; import { VAIndent , VAToken } from "../languages/turbo-gherkin/common" ; import { getModelTokens } from "../languages/turbo-gherkin/model" ; const $ = dom . $ ; export class SubcodeWidget extends WidgetBase { public id : string ; public content : string [ ] ; public decoration : string ; public textNode : HTMLElement ; public leftNode : HTMLElement ; public overlayDom : HTMLElement ; public current : number = 0 ; public selected : number = 0 ; public lines : Array < SubcodeLine > = [ ] ; public runtime : RuntimeManager ; public togleBreakpoint ( lineNumber ) { this . lines . forEach ( ( line ) => { if ( line . lineNumber == lineNumber ) line . togleBreakpoint ( ) ; } ) ; } public onDomNodeTop ( top ) { this . overlayDom . style . top = top + "px" ; } public onComputedHeight ( height ) { this . overlayDom . style . height = height + "px" ; } private overlayWidget : monaco . editor . IOverlayWidget ; private showBreakpoints : boolean = false ; private tokens : Array < VAIndent > ; constructor ( runtime , content ) { super ( runtime . owner ) ; this . runtime = runtime ; this . content = content . split ( /\r\n|\r|\n/ ) ; this . heightInLines = this . content . length ; this . domNode = $ ( ".vanessa-code-widget" , { } , this . textNode = $ ( ".vanessa-code-lines" ) , this . leftNode = $ ( '.vanessa-code-border' ) ) ; this . overlayDom = this . div ( 'vanessa-code-overlays' ) ; this . overlayDom . classList . add ( 'margin-view-overlays' ) ; this . overlayWidget = { getId : ( ) => 'overlay.zone.widget' , getDomNode : ( ) => this . overlayDom , getPosition : ( ) => null } ; this . runtime . editor . addOverlayWidget ( this . overlayWidget ) ; this . tokens = getModelTokens ( VanessaGherkinProvider . instance . matcher , this , this . tabSize ) ; monaco . editor . colorize ( content , gherkin . id , { } ) . then ( ( html ) => { this . textNode . innerHTML = html ; let lineNode = this . textNode . firstElementChild ; while ( lineNode ) { if ( lineNode . nodeName . toUpperCase ( ) == "SPAN" ) { new SubcodeLine ( this , lineNode as HTMLElement ) ; } lineNode = lineNode . nextElementSibling ; } getCodeFolding ( this ) . forEach ( e => this . lines [ e . start - 1 ] . initFolding ( e . end ) ) ; } ) ; this . useDebugger = runtime . useDebugger ; } public dispose ( ) { this . runtime . editor . removeOverlayWidget ( this . overlayWidget ) ; this . overlayDom . remove ( ) ; } public set useDebugger ( value ) { if ( value ) { this . leftNode . classList . add ( "vanessa-use-breakpoints" ) ; } else { this . leftNode . classList . remove ( "vanessa-use-breakpoints" ) ; this . breakpoints = [ ] ; } this . showBreakpoints = value ; } public get useDebugger ( ) { return this . showBreakpoints ; } public show ( editor , lineNumber ) { this . afterLineNumber = lineNumber ; editor . changeViewZones ( changeAccessor => { this . id = changeAccessor . addZone ( this ) ; } ) ; this . domNode . dataset . id = String ( this . id ) ; this . leftNode . dataset . id = String ( this . id ) ; this . decoration = editor . deltaDecorations ( [ ] , [ { range : new monaco . Range ( lineNumber , 1 , lineNumber , 1 ) , options : { stickiness : monaco . editor . TrackedRangeStickiness . NeverGrowsWhenTypingAtEdges } , } ] ) [ 0 ] ; setTimeout ( ( ) => this . layoutViewZone ( ) , 200 ) ; return this . id ; } public updateOverlayVisible ( ) { if ( this . domNode . offsetHeight ) this . overlayDom . classList . remove ( "vanessa-hidden" ) ; else this . overlayDom . classList . add ( "vanessa-hidden" ) ; } public lineNumber ( editor ) { return editor . getModel ( ) . getDecorationRange ( this . decoration ) . endLineNumber ; } public getContent ( ) { return this . content . join ( "\r\n" ) ; } public getLineCount ( ) { return this . content . length ; } public getLineContent ( lineNumber ) { return this . content [ lineNumber - 1 ] ; } public getLineToken ( lineNumber ) { return this . tokens [ lineNumber - 1 ] ; } public get tabSize ( ) { return this . model . getOptions ( ) . tabSize ; } public getCurrent ( ) { return this . current ; } public next ( ) { return this . setCurrent ( this . getCurrent ( ) + 1 ) ; } get model ( ) { return this . runtime . editor ?. getModel ( ) ; } get breakpoints ( ) : Array < IBreakpoint > { let breakpoints = [ ] ; this . lines . forEach ( ( line ) => { switch ( line . getBreakpoint ( ) ) { case BreakpointState . Breakpoint : breakpoints . push ( new Breakpoint ( line . lineNumber , this . id , true ) ) ; break ; case BreakpointState . Unverified : breakpoints . push ( new Breakpoint ( line . lineNumber , this . id , false ) ) ; break ; } } ) ; return breakpoints ; } set breakpoints ( breakpoints ) { let State = BreakpointState ; this . lines . forEach ( ( line ) => { let breakpoint = undefined ; breakpoints . forEach ( b => { if ( b . lineNumber == line . lineNumber && b . codeWidget == this . id ) breakpoint = b ; } ) ; line . setBreakpoint ( breakpoint ? ( breakpoint . enable ? State . Breakpoint : State . Unverified ) : State . Unmarked ) ; } ) ; } public setCurrent ( lineNumber ) { this . current = 0 ; this . lines . forEach ( ( line ) => { line . setCurrent ( line . lineNumber == lineNumber ) ; if ( line . lineNumber == lineNumber ) { this . revealLine ( lineNumber ) ; this . current = line . lineNumber ; } } ) ; return this . current ; } public setStatus ( status , lines , inline = undefined ) { this . lines . forEach ( ( line ) => { if ( lines . some ( n => n == line . lineNumber ) ) line . setStatus ( status , inline ) ; } ) ; } public setStyle ( lines , bold , italic , underline ) { this . lines . forEach ( ( line ) => { if ( lines . some ( n => n == line . lineNumber ) ) line . setStyle ( bold , italic , underline ) ; } ) ; } public clearStatus ( ) { this . current = 0 ; this . lines . forEach ( ( line ) => line . clearStatus ( ) ) ; } public clearStyle ( ) { this . lines . forEach ( ( line ) => line . clearStyle ( ) ) ; } public showError ( lineNumber , data , text ) { this . lines . forEach ( ( line ) => { if ( line . lineNumber == lineNumber ) { line . showError ( data , text ) ; } } ) ; this . layoutViewZone ( ) ; } public clearErrors ( ) { this . lines . forEach ( ( line ) => line . clearErrors ( ) ) ; this . layoutViewZone ( ) ; } public layoutViewZone ( ) { this . heightInLines = 0 ; this . lines . forEach ( ( line ) => this . heightInLines += line . heightInLines ) ; this . afterLineNumber = this . model . getDecorationRange ( this . decoration ) . endLineNumber ; this . runtime . editor . changeViewZones ( changeAccessor => changeAccessor . layoutZone ( this . id ) ) ; } get position ( ) : any { return { lineNumber : this . selected , codeWidget : this . id , column : 1 , } ; } set position ( position ) { this . lines . forEach ( ( line ) => { if ( line . lineNumber == position . lineNumber ) line . select ( ) ; } ) ; } get selection ( ) : Object { let startLineNumber = 0 ; let endLineNumber = 0 ; this . lines . forEach ( ( line ) => { if ( line . selected ) { if ( startLineNumber == 0 ) startLineNumber = line . lineNumber ; endLineNumber = line . lineNumber ; } } ) ; if ( startLineNumber ) return { codeWidget : this . id , startLineNumber : startLineNumber , endLineNumber : endLineNumber , startColumn : 1 , endColumn : 1 , } ; return undefined ; } public setFolding ( lineNumber , collapsed ) { this . lines . forEach ( ( line ) => { if ( line . lineNumber == lineNumber ) line . setFolding ( collapsed ) ; } ) ; } public revealLine ( lineNumber ) { let size = { top : 0 , bottom : 2 } ; let lineHeight = this . runtime . editor . getOption ( monaco . editor . EditorOption . lineHeight ) ; this . lines . forEach ( ( line ) => { if ( line . lineNumber <= lineNumber ) { size . top = size . bottom ; size . bottom += line . heightInLines ; } } ) ; let editor = this . runtime . editor ; let afterLine = editor . getModel ( ) . getDecorationRange ( this . decoration ) . endLineNumber ; let top = editor . getTopForLineNumber ( afterLine ) ; size . top = top + size . top * lineHeight ; size . bottom = top + size . bottom * lineHeight ; let scrollTop = editor . getScrollTop ( ) ; let clientHeight = editor . getDomNode ( ) . clientHeight ; let scrollBottom = scrollTop + clientHeight ; if ( size . bottom > scrollBottom ) editor . setScrollTop ( size . bottom - clientHeight ) ; else if ( size . top < scrollTop ) editor . setScrollTop ( size . top ) ; } public revealLineInCenter ( lineNumber ) { let size = { top : 0 , bottom : 2 , center : 0 } ; let lineHeight = this . runtime . editor . getOption ( monaco . editor . EditorOption . lineHeight ) ; this . lines . forEach ( ( line ) => { if ( line . lineNumber <= lineNumber ) { size . top = size . bottom ; size . bottom += line . heightInLines ; } } ) ; let editor = this . runtime . editor ; let afterLine = editor . getModel ( ) . getDecorationRange ( this . decoration ) . endLineNumber ; let top = editor . getTopForLineNumber ( afterLine ) ; size . top = top + size . top * lineHeight ; size . bottom = top + size . bottom * lineHeight ; size . center = ( size . top + size . bottom ) / 2 ; let clientHeight = editor . getDomNode ( ) . clientHeight ; editor . setScrollTop ( size . center - clientHeight / 2 ) ; } }