import { Clipboard , Dialog , InputDialog , showDialog , } from '@jupyterlab/apputils' ; import { PathExt } from '@jupyterlab/coreutils' ; import { DocumentWidget } from '@jupyterlab/docregistry' ; import { FileEditor } from '@jupyterlab/fileeditor' ; import { Notebook , NotebookPanel } from '@jupyterlab/notebook' ; import { LabIcon , addIcon , pythonIcon , fileIcon , rKernelIcon , markdownIcon , } from '@jupyterlab/ui-components' ; import { CodeEditor , IEditorServices } from '@jupyterlab/codeeditor' ; import * as nbformat from '@jupyterlab/nbformat' ; import { JupyterFrontEnd } from '@jupyterlab/application' ; import { CodeCellModel , MarkdownCell , CodeCell } from '@jupyterlab/cells' ; import { Widget } from '@lumino/widgets' ; import { find , StringExt } from '@lumino/algorithm' ; import { Drag } from '@lumino/dragdrop' ; import { MimeData } from '@lumino/coreutils' ; import React from 'react' ; import { CodeSnippetService , ICodeSnippet } from './CodeSnippetService' ; import { FilterTools } from './CodeSnippetFilterTools' ; import { showPreview } from './CodeSnippetPreview' ; import { showMoreOptions } from './CodeSnippetMenu' ; import { CodeSnippetContentsService } from './CodeSnippetContentsService' ; import moreSVGstr from '../style/icon/jupyter_moreicon.svg' ; import { babelIcon , javaIcon , juliaIcon , matlabIcon , schemeIcon , processingIcon , scalaIcon , groovyIcon , forthIcon , haskellIcon , rubyIcon , typescriptIcon , javascriptIcon , coffeescriptIcon , livescriptIcon , csharpIcon , fsharpIcon , goIcon , erlangIcon , ocamlIcon , fortranIcon , perlIcon , phpIcon , clojureIcon , luaIcon , purescriptIcon , cppIcon , prologIcon , lispIcon , cIcon , kotlinIcon , nodejsIcon , coconutIcon , sbtIcon , rustIcon , qsharpIcon , sasIcon , } from './CodeSnippetLanguages' ; import { ICodeSnippetEditorMetadata } from './CodeSnippetEditor' ; import { showMessage } from './CodeSnippetMessage' ; /**
 * The CSS class added to code snippet widget.
 */ const CODE_SNIPPETS_HEADER_CLASS = 'jp-codeSnippetsHeader' ; const CODE_SNIPPET_TITLE = 'jp-codeSnippet-title' ; const CODE_SNIPPETS_CONTAINER = 'jp-codeSnippetsContainer' ; const DISPLAY_NAME_CLASS = 'jp-codeSnippetsContainer-name' ; const BUTTON_CLASS = 'jp-codeSnippetsContainer-button' ; const TITLE_CLASS = 'jp-codeSnippetsContainer-title' ; const ACTION_BUTTONS_WRAPPER_CLASS = 'jp-codeSnippetsContainer-action-buttons' ; const ACTION_BUTTON_CLASS = 'jp-codeSnippetsContainer-actionButton' ; const SEARCH_BOLD = 'jp-codeSnippet-search-bolding' ; const SNIPPET_DRAG_IMAGE = 'jp-codeSnippet-drag-image' ; const CODE_SNIPPET_DRAG_HOVER = 'jp-codeSnippet-drag-hover' ; const CODE_SNIPPET_DRAG_HOVER_SELECTED = 'jp-codeSnippet-drag-hover-selected' ; const CODE_SNIPPET_METADATA = 'jp-codeSnippet-metadata' ; const CODE_SNIPPET_DESC = 'jp-codeSnippet-description' ; const CODE_SNIPPET_EDITOR = 'jp-codeSnippet-editor' ; const CODE_SNIPPET_MORE_OPTIONS = 'jp-codeSnippet-options' ; const CODE_SNIPPET_MORE_OTPIONS_CONTENT = 'jp-codeSnippet-more-options-content' ; const CODE_SNIPPET_MORE_OTPIONS_COPY = 'jp-codeSnippet-more-options-copy' ; const CODE_SNIPPET_MORE_OTPIONS_INSERT = 'jp-codeSnippet-more-options-insert' ; const CODE_SNIPPET_MORE_OTPIONS_EDIT = 'jp-codeSnippet-more-options-edit' ; const CODE_SNIPPET_MORE_OTPIONS_DELETE = 'jp-codeSnippet-more-options-delete' ; const CODE_SNIPPET_MORE_OTPIONS_EXPORT = 'jp-codeSnippet-more-options-export' ; const CODE_SNIPPET_CREATE_NEW_BTN = 'jp-createSnippetBtn' ; const CODE_SNIPPET_NAME = 'jp-codeSnippet-name' ; const OPTIONS_BODY = 'jp-codeSnippet-options-body' ; /**
 * The threshold in pixels to start a drag event.
 */ const DRAG_THRESHOLD = 3 ; /**
 * A class used to indicate a snippet item.
 */ const CODE_SNIPPET_ITEM = 'jp-codeSnippet-item' ; /**
 * The mimetype used for Jupyter cell data.
 */ const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells' ; /**
 * Icon for more options
 */ const moreOptionsIcon = new LabIcon ( { name : 'custom-ui-components:moreOptions' , svgstr : moreSVGstr , } ) ; /**
 * CodeSnippetDisplay props.
 */ interface ICodeSnippetDisplayProps { codeSnippets : ICodeSnippet [ ] ; codeSnippetManager : CodeSnippetService ; app : JupyterFrontEnd ; getCurrentWidget : ( ) => Widget ; openCodeSnippetEditor : ( args : ICodeSnippetEditorMetadata ) => void ; editorServices : IEditorServices ; updateCodeSnippetWidget : ( ) => void ; } /**
 * CodeSnippetDisplay state.
 */ interface ICodeSnippetDisplayState { searchValue : string ; filterTags : string [ ] ; selectedLangTags : string [ ] ; searchOptions : string [ ] ; } /**
 * A React Component for code-snippets display list.
 */ export class CodeSnippetDisplay extends React . Component < ICodeSnippetDisplayProps , ICodeSnippetDisplayState > { _drag : Drag ; _dragData : { pressX : number ; pressY : number ; dragImage : HTMLElement ; } ; constructor ( props ) { super ( props ) ; this . state = { searchValue : '' , filterTags : [ ] , selectedLangTags : [ ] , searchOptions : [ ] , } ; this . _drag = null ; this . _dragData = null ; this . handleDragMove = this . handleDragMove . bind ( this ) ; this . _evtMouseUp = this . _evtMouseUp . bind ( this ) ; this . handleRenameSnippet = this . handleRenameSnippet . bind ( this ) ; this . setSearchOptions = this . setSearchOptions . bind ( this ) ; } filterSnippets = ( codeSnippets , searchValue , filterTags , selectedLangTags ) : { filteredCodeSnippets : ICodeSnippet [ ] ; matchedIndices : { [ id ] : number [ ] ; } ; } => { let filteredSnippets = codeSnippets . slice ( ) ; const matchedIndices = { } ; if ( searchValue !== '' ) { const matchResults = [ ] ; const filteredSnippetsScore = [ ] ; filteredSnippets . forEach ( ( snippet ) => { const matchResult = StringExt . matchSumOfSquares ( ( snippet . language + snippet . name + snippet . code . join ( '\n' ) ) . toLowerCase ( ) , searchValue . replace ( ' ' , '' ) . toLowerCase ( ) ) ; if ( matchResult ) { matchResults . push ( matchResult ) ; filteredSnippetsScore . push ( { score : matchResult . score , snippet : snippet , } ) ; } } ) ; filteredSnippetsScore . sort ( ( a , b ) => a . score - b . score ) ; const newFilteredSnippets = [ ] ; filteredSnippetsScore . forEach ( ( snippetScore ) => newFilteredSnippets . push ( snippetScore . snippet ) ) ; filteredSnippets = newFilteredSnippets ; matchResults . sort ( ( a , b ) => a . score - b . score ) ; matchResults . forEach ( ( res , id ) => { matchedIndices [ filteredSnippets [ id ] . id ] = res . indices ; } ) ; } if ( filterTags . length !== 0 ) { filteredSnippets = filteredSnippets . filter ( ( codeSnippet ) => { return filterTags . some ( ( tag ) => { if ( codeSnippet . tags ) { if ( selectedLangTags . length !== 0 ) { if ( codeSnippet . tags . includes ( tag ) && selectedLangTags . includes ( codeSnippet . language ) ) { return true ; } else if ( filterTags . length === selectedLangTags . length && filterTags . every ( ( value ) => selectedLangTags . includes ( value ) ) ) { if ( selectedLangTags . includes ( codeSnippet . language ) ) { return true ; } } } else { if ( codeSnippet . tags . includes ( tag ) ) { return true ; } } } return false ; } ) ; } ) ; } const willBeRemovedIds = [ ] ; for ( const key in matchedIndices ) { let hasKey = false ; for ( const codeSnippet of filteredSnippets ) { if ( codeSnippet . id === parseInt ( key ) ) { hasKey = true ; } } if ( hasKey === false ) { willBeRemovedIds . push ( parseInt ( key ) ) ; } } willBeRemovedIds . forEach ( ( id ) => delete matchedIndices [ id ] ) ; return { filteredCodeSnippets : filteredSnippets , matchedIndices : matchedIndices , } ; } ; private insertCodeSnippet = async ( snippet ) : Promise < void > => { const widget = this . props . getCurrentWidget ( ) ; const snippetStr = snippet . code . join ( '\n' ) ; if ( widget instanceof DocumentWidget && ( widget as DocumentWidget ) . content instanceof FileEditor ) { const documentWidget = widget as DocumentWidget ; const fileEditor = ( documentWidget . content as FileEditor ) . editor ; const markdownRegex = /^\.(md|mkdn?|mdown|markdown)$/ ; if ( PathExt . extname ( widget . context . path ) . match ( markdownRegex ) !== null ) { fileEditor . replaceSelection ( '```' + snippet . language + '\n' + snippetStr + '\n```' ) ; } else if ( widget . constructor . name === 'PythonFileEditor' ) { this . verifyLanguageAndInsert ( snippet , 'python' , fileEditor ) ; } else { fileEditor . replaceSelection ( snippetStr ) ; } } else if ( widget instanceof NotebookPanel ) { const notebookWidget = widget as NotebookPanel ; const notebookCell = ( notebookWidget . content as Notebook ) . activeCell ; const notebookCellEditor = notebookCell . editor ; if ( notebookCell instanceof CodeCell ) { const kernelInfo = await notebookWidget . sessionContext . session ?. kernel ?. info ; const kernelLanguage = kernelInfo ?. language_info . name || '' ; this . verifyLanguageAndInsert ( snippet , kernelLanguage , notebookCellEditor ) ; } else if ( notebookCell instanceof MarkdownCell ) { notebookCellEditor . replaceSelection ( '```' + snippet . language + '\n' + snippetStr + '\n```' ) ; } else { notebookCellEditor . replaceSelection ( snippetStr ) ; } } else { this . showErrDialog ( 'Code snippet insert failed: Unsupported widget' ) ; } } ; private verifyLanguageAndInsert = async ( snippet , editorLanguage , editor ) : Promise < void > => { const snippetStr = snippet . code . join ( '\n' ) ; if ( editorLanguage && snippet . language . toLowerCase ( ) !== editorLanguage . toLowerCase ( ) ) { const result = await this . showWarnDialog ( editorLanguage , snippet . name ) ; if ( result . button . accept ) { editor . replaceSelection ( snippetStr ) ; } } else { editor . replaceSelection ( snippetStr ) ; } } ; private showWarnDialog = async ( editorLanguage , snippetName ) : Promise < Dialog . IResult < string > > => { return showDialog ( { title : 'Warning' , body : 'Code snippet "' + snippetName + '" is incompatible with ' + editorLanguage + '. Continue?' , buttons : [ Dialog . cancelButton ( ) , Dialog . okButton ( ) ] , } ) ; } ; private showErrDialog = ( errMsg ) : Promise < Dialog . IResult < string > > => { return showDialog ( { title : 'Error' , body : errMsg , buttons : [ Dialog . okButton ( ) ] , } ) ; } ; private dragHoverStyle = ( id ) : void => { document . querySelector ( `#${ CODE_SNIPPET_DRAG_HOVER }${ id }` ) . classList . add ( CODE_SNIPPET_DRAG_HOVER_SELECTED ) ; } ; private dragHoverStyleRemove = ( id ) : void => { if ( document . getElementsByClassName ( CODE_SNIPPET_DRAG_HOVER_SELECTED ) ) { document . querySelector ( `#${ CODE_SNIPPET_DRAG_HOVER }${ id }` ) . classList . remove ( CODE_SNIPPET_DRAG_HOVER_SELECTED ) ; } } ; private boldNameOnSearch = ( id , language , name , matchedIndices ) : JSX . Element => { const displayName = language + name ; if ( this . state . searchValue !== '' ) { const elements = [ ] ; if ( matchedIndices ) { let i = 0 ; while ( i < matchedIndices . length ) { if ( matchedIndices [ i ] >= language . length ) { elements . push ( displayName . substring ( language . length , matchedIndices [ i ] ) ) ; break ; } i ++ ; } if ( i >= matchedIndices . length ) { return < span > { name } < /span>; } else { let currIndex = matchedIndices [ i ] ; let nextIndex ; if ( i < matchedIndices . length - 1 ) { i ++ ; nextIndex = matchedIndices [ i ] ; } else { nextIndex = null ; } while ( nextIndex !== null ) { elements . push ( < mark key = { id + '_' + currIndex } className = { SEARCH_BOLD } > { displayName . substring ( currIndex , currIndex + 1 ) } < /mark>); elements . push ( displayName . substring ( currIndex + 1 , nextIndex ) ) ; currIndex = nextIndex ; if ( i < matchedIndices . length - 1 ) { i ++ ; nextIndex = matchedIndices [ i ] ; } else { nextIndex = null ; } } if ( nextIndex === null ) { elements . push ( < mark key = { id + '_' + currIndex } className = { SEARCH_BOLD } > { displayName . substring ( currIndex , currIndex + 1 ) } < /mark>); elements . push ( displayName . substring ( currIndex + 1 , displayName . length ) ) ; } return < span > { elements } < /span>; } } } return ( < span title = { 'Double click to rename' } className = { CODE_SNIPPET_NAME } onDoubleClick = { this . handleRenameSnippet } > { name } < /span>); } ; private async handleRenameSnippet ( event ) { const target = event . target as HTMLElement ; const oldName = target . innerHTML ; const new_element = document . createElement ( 'input' ) ; new_element . setAttribute ( 'type' , 'text' ) ; new_element . id = 'jp-codeSnippet-rename' ; new_element . innerHTML = target . innerHTML ; target . replaceWith ( new_element ) ; new_element . value = target . innerHTML ; new_element . focus ( ) ; new_element . setSelectionRange ( 0 , new_element . value . length ) ; new_element . onblur = async ( ) : Promise < void > => { if ( target . innerHTML !== new_element . value ) { const newName = new_element . value ; const isDuplicateName = this . props . codeSnippetManager . duplicateNameExists ( newName ) ; if ( isDuplicateName ) { await showDialog ( { title : 'Duplicate Name of Code Snippet' , body : < p > { `"${ newName }" already exists.` } < /p>, buttons : [ Dialog . okButton ( { label : 'Dismiss' } ) ] , } ) ; } else { this . props . codeSnippetManager . renameSnippet ( oldName , newName ) . then ( async ( res ) => { if ( res ) { target . innerHTML = new_element . value ; } else { console . log ( 'Error in renaming snippet!' ) ; } } ) ; } } new_element . replaceWith ( target ) ; } ; new_element . onkeydown = ( event ) : void => { switch ( event . code ) { case 'Enter' : event . stopPropagation ( ) ; event . preventDefault ( ) ; new_element . blur ( ) ; break ; case 'NumpadEnter' : event . stopPropagation ( ) ; event . preventDefault ( ) ; new_element . blur ( ) ; break ; case 'Escape' : event . stopPropagation ( ) ; event . preventDefault ( ) ; new_element . blur ( ) ; break ; case 'ArrowUp' : event . stopPropagation ( ) ; event . preventDefault ( ) ; new_element . selectionStart = new_element . selectionEnd = 0 ; break ; case 'ArrowDown' : event . stopPropagation ( ) ; event . preventDefault ( ) ; new_element . selectionStart = new_element . selectionEnd = new_element . value . length ; break ; default : break ; } } ; } private handleDragSnippet ( event ) { const { button } = event ; if ( ! ( button === 0 ) ) { return ; } const target = event . target as HTMLElement ; this . _dragData = { pressX : event . clientX , pressY : event . clientY , dragImage : target . nextSibling . firstChild . cloneNode ( true ) as HTMLElement , } ; const dragImageTextColor = getComputedStyle ( document . body ) . getPropertyValue ( '--jp-content-font-color3' ) ; ( this . _dragData . dragImage . children [ 0 ] as HTMLElement ) . style . color = dragImageTextColor ; this . _dragData . dragImage . classList . add ( SNIPPET_DRAG_IMAGE ) ; target . addEventListener ( 'mouseup' , this . _evtMouseUp , true ) ; target . addEventListener ( 'mousemove' , this . handleDragMove , true ) ; target . ondragstart = ( ) : boolean => false ; event . preventDefault ( ) ; } private _evtMouseUp ( event ) { event . preventDefault ( ) ; event . stopPropagation ( ) ; const target = event . target as HTMLElement ; target . removeEventListener ( 'mousemove' , this . handleDragMove , true ) ; target . removeEventListener ( 'mouseup' , this . _evtMouseUp , true ) ; } private handleDragMove ( event ) { event . preventDefault ( ) ; event . stopPropagation ( ) ; const data = this . _dragData ; if ( data && this . shouldStartDrag ( data . pressX , data . pressY , event . clientX , event . clientY ) ) { const idx = ( event . target as HTMLElement ) . id . slice ( CODE_SNIPPET_DRAG_HOVER . length ) ; const codeSnippet = this . props . codeSnippets . filter ( ( codeSnippet ) => codeSnippet . id === parseInt ( idx ) ) [ 0 ] ; void this . startDrag ( data . dragImage , codeSnippet , event . clientX , event . clientY ) ; } } private shouldStartDrag ( prevX , prevY , nextX , nextY ) { const dx = Math . abs ( nextX - prevX ) ; const dy = Math . abs ( nextY - prevY ) ; return dx >= 0 || dy >= DRAG_THRESHOLD ; } private async startDrag ( dragImage , codeSnippet , clientX , clientY ) { const target = event . target as HTMLElement ; const model = new CodeCellModel ( { } ) ; model . value . text = codeSnippet . code . join ( '\n' ) ; model . metadata ; const selected = [ model . toJSON ( ) ] ; this . _drag = new Drag ( { mimeData : new MimeData ( ) , dragImage : dragImage , supportedActions : 'copy-move' , proposedAction : 'copy' , source : this , } ) ; this . _drag . mimeData . setData ( JUPYTER_CELL_MIME , selected ) ; const textContent = codeSnippet . code . join ( '\n' ) ; this . _drag . mimeData . setData ( 'text/plain' , textContent ) ; target . removeEventListener ( 'mousemove' , this . handleDragMove , true ) ; target . removeEventListener ( 'mouseup' , this . _evtMouseUp , true ) ; return this . _drag . start ( clientX , clientY ) . then ( ( ) => { this . dragHoverStyleRemove ( codeSnippet . id ) ; this . _drag = null ; this . _dragData = null ; } ) ; } private _evtMouseLeave ( ) { const preview = document . querySelector ( '.jp-codeSnippet-preview' ) ; if ( preview ) { if ( ! preview . classList . contains ( 'inactive' ) ) { preview . classList . add ( 'inactive' ) ; } } } private _setPreviewPosition ( id ) { const realTarget = document . querySelector ( `#${ TITLE_CLASS }${ id }` ) ; const newTarget = document . querySelector ( `#${ CODE_SNIPPET_ITEM }${ id }` ) ; const distDown = realTarget . getBoundingClientRect ( ) . top - 43 ; const elementSnippet = newTarget as HTMLElement ; const heightSnippet = elementSnippet . clientHeight ; const heightPreview = heightSnippet . toString ( 10 ) + 'px' ; document . documentElement . style . setProperty ( '--preview-max-height' , heightPreview ) ; const final = distDown . toString ( 10 ) + 'px' ; document . documentElement . style . setProperty ( '--preview-distance' , final ) ; } private _setOptionsPosition ( event ) { const target = event . target as HTMLElement ; let top ; if ( target . tagName === 'path' ) { top = target . getBoundingClientRect ( ) . top + 10 ; } else { top = target . getBoundingClientRect ( ) . top + 18 ; } if ( top > 0.7 * window . screen . height ) { top -= 120 ; } const leftAsString = ( target . parentElement . style . left + event . pageX ) . toString ( ) + 'px' ; const topAsString = top . toString ( 10 ) + 'px' ; document . documentElement . style . setProperty ( '--more-options-top' , topAsString ) ; document . documentElement . style . setProperty ( '--more-options-left' , leftAsString ) ; } private renderLanguageIcon ( language ) { switch ( language ) { case 'Python' : { return ( < pythonIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Gfm' : { return ( < markdownIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Java' : { return ( < javaIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'R' : { return ( < rKernelIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Julia' : { return ( < juliaIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Matlab' : { return ( < matlabIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Scheme' : { return ( < schemeIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Processing' : { return ( < processingIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Scala' : { return ( < scalaIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Groovy' : { return ( < groovyIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Fortran' : { return ( < fortranIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Haskell' : { return ( < haskellIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Ruby' : { return ( < rubyIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'TypeScript' : { return ( < typescriptIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'JavaScript' : { return ( < javascriptIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'CoffeeScript' : { return ( < coffeescriptIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'LiveScript' : { return ( < livescriptIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'C#' : { return ( < csharpIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'F#' : { return ( < fsharpIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Go' : { return ( < goIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Erlang' : { return ( < erlangIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'OCaml' : { return ( < ocamlIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Forth' : { return ( < forthIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Perl' : { return ( < perlIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'PHP' : { return ( < phpIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Clojure' : { return ( < clojureIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Lua' : { return ( < luaIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'PureScript' : { return ( < purescriptIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'C++' : { return ( < cppIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Prolog' : { return ( < prologIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Common Lisp' : { return ( < lispIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'C' : { return ( < cIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Kotlin' : { return ( < kotlinIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'NodeJS' : { return ( < nodejsIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Coconut' : { return ( < coconutIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Babel' : { return ( < babelIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'SAS' : { return ( < sasIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'sbt' : { return ( < sbtIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Rust' : { return ( < rustIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } case 'Q#' : { return ( < qsharpIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } default : { return ( < fileIcon . react tag = "span" height = "16px" width = "16px" right = "7px" top = "5px" margin - right = "3px" / > ) ; } } } private renderCodeSnippet = ( codeSnippet , matchedIndices ) : JSX . Element => { const id = codeSnippet . id ; const buttonClasses = BUTTON_CLASS ; const displayName = '[' + codeSnippet . language + '] ' + codeSnippet . name ; const name = codeSnippet . name ; const language = codeSnippet . language ; const actionButtons = [ { title : 'Insert, copy, edit, and delete' , icon : moreOptionsIcon , onClick : ( event ) : void => { showMoreOptions ( { body : new OptionsHandler ( this , codeSnippet ) , } ) ; this . _setOptionsPosition ( event ) ; } , } , ] ; return ( < div key = { codeSnippet . name } className = { CODE_SNIPPET_ITEM } id = { `${ CODE_SNIPPET_ITEM }${ id }` } title = { 'Right click for more options' } onMouseOver = { ( ) : void => { this . dragHoverStyle ( id ) ; } } onMouseOut = { ( ) : void => { this . dragHoverStyleRemove ( id ) ; } } onContextMenu = { ( event ) : void => { event . preventDefault ( ) ; showMoreOptions ( { body : new OptionsHandler ( this , codeSnippet ) , } ) ; this . _setOptionsPosition ( event ) ; } } > < div className = { CODE_SNIPPET_DRAG_HOVER } title = "Drag to move" id = { `${ CODE_SNIPPET_DRAG_HOVER }${ id }` } onMouseDown = { ( event ) : void => { this . handleDragSnippet ( event ) ; } } > < /div> < div className = { CODE_SNIPPET_METADATA } onMouseEnter = { ( ) : void => { showPreview ( { id : id , title : displayName , body : new PreviewHandler ( ) , codeSnippet : codeSnippet , } , this . props . editorServices ) ; this . _setPreviewPosition ( id ) ; } } onMouseLeave = { ( ) : void => { this . _evtMouseLeave ( ) ; } } > < div key = { displayName } className = { TITLE_CLASS } id = { `${ TITLE_CLASS }${ id }` } > < div id = { id . toString ( ) } className = { DISPLAY_NAME_CLASS } > { this . renderLanguageIcon ( language ) } { this . boldNameOnSearch ( id , language , name , matchedIndices ) } < /div> < div className = { ACTION_BUTTONS_WRAPPER_CLASS } id = { id . toString ( ) } > { actionButtons . map ( ( btn ) => { return ( < button key = { btn . title } title = { btn . title } className = { buttonClasses + ' ' + ACTION_BUTTON_CLASS } onClick = { ( event ) : void => { btn . onClick ( event ) ; } } > < btn . icon . react tag = "span" elementPosition = "center" width = "16px" height = "16px" / > < /button>); } ) } < /div> < /div> { this . renderDescription ( codeSnippet , id ) } < /div> < /div>); } ; renderDescription ( codeSnippet , id ) { if ( codeSnippet . description . length !== 0 ) { return ( < div className = { CODE_SNIPPET_DESC } id = { id . toString ( ) } > < p id = { id . toString ( ) } > { `${ codeSnippet . description }` } < /p> < /div>); } else { return null ; } } handleFilter = ( searchValue , filterTags , selectedLangTags ) : void => { this . setState ( { searchValue : searchValue , filterTags : filterTags , selectedLangTags : selectedLangTags , } ) ; } ; getActiveTags ( ) { const tags = [ ] ; const languages = [ ] ; for ( const codeSnippet of this . props . codeSnippets ) { if ( codeSnippet . tags ) { for ( const tag of codeSnippet . tags ) { if ( ! tags . includes ( tag ) ) { tags . push ( tag ) ; } } } if ( ! languages . includes ( codeSnippet . language ) ) { languages . push ( codeSnippet . language ) ; } } return [ tags , languages ] ; } getActiveTagsDictionary = ( ) : Map < string , string [ ] > => { const tagsAndLangs = new Map < string , string [ ] > ( ) ; for ( const codeSnippet of this . props . codeSnippets ) { if ( codeSnippet . tags ) { for ( const tag of codeSnippet . tags ) { if ( tag !== codeSnippet . language ) { if ( tagsAndLangs . has ( tag ) ) { const langs = tagsAndLangs . get ( tag ) ; if ( ! langs . includes ( codeSnippet . language ) ) { langs . push ( codeSnippet . language ) ; } tagsAndLangs . set ( tag , langs ) ; } else { tagsAndLangs . set ( tag , [ codeSnippet . language ] ) ; } } } } } return tagsAndLangs ; } ; private deleteCommand ( codeSnippet ) { showDialog ( { title : 'Delete snippet?' , body : 'Are you sure you want to delete "' + codeSnippet . name + '"? ' , buttons : [ Dialog . cancelButton ( ) , Dialog . warnButton ( { label : 'Delete' , } ) , ] , } ) . then ( ( response ) : void => { if ( response . button . accept ) { const widgetId = `${ CODE_SNIPPET_EDITOR }-${ codeSnippet . id }` ; const editor = find ( this . props . app . shell . widgets ( 'main' ) , ( widget , _ ) => { return widget . id === widgetId ; } ) ; if ( editor ) { editor . dispose ( ) ; } this . props . codeSnippetManager . deleteSnippet ( codeSnippet . id ) . then ( ( result ) => { if ( result ) { this . props . updateCodeSnippetWidget ( ) ; } else { console . log ( 'Error in deleting the snippet' ) ; return ; } } ) ; } } ) ; } private exportCommand ( codeSnippet ) { InputDialog . getText ( { title : 'Export Snippet?' , label : 'Directory to Export: ' , placeholder : 'share/snippet' , okLabel : 'Export' , } ) . then ( ( value ) => { if ( value . button . accept ) { const dirs = value . value . split ( '/' ) ; const codeSnippetContentsManager = CodeSnippetContentsService . getInstance ( ) ; let path = '' ; for ( let i = 0 ; i < dirs . length ; i ++ ) { path += dirs [ i ] + '/' ; codeSnippetContentsManager . save ( path , { type : 'directory' } ) . catch ( ( _ ) => { alert ( 'Path should be a relative path' ) ; } ) ; } path += codeSnippet . name + '.json' ; codeSnippetContentsManager . save ( path , { type : 'file' , format : 'text' , content : JSON . stringify ( codeSnippet ) , } ) ; showMessage ( 'export' ) ; } } ) ; } private removeOptionsNode ( ) { const temp = document . getElementsByClassName ( CODE_SNIPPET_MORE_OPTIONS ) [ 0 ] ; if ( ! temp . classList . contains ( 'inactive' ) ) { temp . classList . add ( 'inactive' ) ; } } public createOptionsNode ( codeSnippet ) { const body = document . createElement ( 'div' ) ; body . className = OPTIONS_BODY ; const optionsContainer = document . createElement ( 'div' ) ; optionsContainer . className = CODE_SNIPPET_MORE_OTPIONS_CONTENT ; const insertSnip = document . createElement ( 'div' ) ; insertSnip . className = CODE_SNIPPET_MORE_OTPIONS_INSERT ; insertSnip . textContent = 'Insert snippet' ; insertSnip . onclick = ( ) : void => { this . insertCodeSnippet ( codeSnippet ) ; this . removeOptionsNode ( ) ; } ; const copySnip = document . createElement ( 'div' ) ; copySnip . className = CODE_SNIPPET_MORE_OTPIONS_COPY ; copySnip . textContent = 'Copy snippet to clipboard' ; copySnip . onclick = ( ) : void => { Clipboard . copyToSystem ( codeSnippet . code . join ( '\n' ) ) ; showMessage ( 'copy' ) ; this . removeOptionsNode ( ) ; } ; const editSnip = document . createElement ( 'div' ) ; editSnip . className = CODE_SNIPPET_MORE_OTPIONS_EDIT ; editSnip . textContent = 'Edit snippet' ; editSnip . onclick = ( ) : void => { const allSnippetTags = this . getActiveTags ( ) [ 0 ] ; const allLangTags = this . getActiveTags ( ) [ 1 ] ; this . props . openCodeSnippetEditor ( { name : codeSnippet . name , description : codeSnippet . description , language : codeSnippet . language , code : codeSnippet . code , id : codeSnippet . id , selectedTags : codeSnippet . tags , allSnippetTags : allSnippetTags , allLangTags : allLangTags , fromScratch : false , } ) ; this . removeOptionsNode ( ) ; } ; const deleteSnip = document . createElement ( 'div' ) ; deleteSnip . className = CODE_SNIPPET_MORE_OTPIONS_DELETE ; deleteSnip . textContent = 'Delete snippet' ; deleteSnip . onclick = ( ) : void => { this . deleteCommand ( codeSnippet ) ; this . removeOptionsNode ( ) ; } ; const exportSnip = document . createElement ( 'div' ) ; exportSnip . className = CODE_SNIPPET_MORE_OTPIONS_EXPORT ; exportSnip . textContent = 'Export snippet' ; exportSnip . onclick = ( ) : void => { this . exportCommand ( codeSnippet ) ; this . removeOptionsNode ( ) ; } ; optionsContainer . appendChild ( insertSnip ) ; optionsContainer . appendChild ( copySnip ) ; optionsContainer . appendChild ( editSnip ) ; optionsContainer . appendChild ( exportSnip ) ; optionsContainer . appendChild ( deleteSnip ) ; body . append ( optionsContainer ) ; return body ; } setSearchOptions ( selectedOptions ) { this . setState ( { searchOptions : selectedOptions , } ) ; } render ( ) { const { filteredCodeSnippets , matchedIndices } = this . filterSnippets ( this . props . codeSnippets , this . state . searchValue , this . state . filterTags , this . state . selectedLangTags ) ; return ( < div > < header className = { CODE_SNIPPETS_HEADER_CLASS } > < span className = { CODE_SNIPPET_TITLE } > { 'Snippets' } < /span> < button className = { CODE_SNIPPET_CREATE_NEW_BTN } onClick = { ( ) : void => { this . props . openCodeSnippetEditor ( { name : '' , description : '' , language : 'Python' , code : [ ] , id : this . props . codeSnippets . length , selectedTags : [ ] , allSnippetTags : this . getActiveTags ( ) [ 0 ] , allLangTags : this . getActiveTags ( ) [ 1 ] , fromScratch : true , } ) ; } } > < addIcon . react tag = "span" right = "7px" top = "5px" / > < /button> < /header> < FilterTools tagDictionary = { this . getActiveTagsDictionary ( ) } languageTags = { this . getActiveTags ( ) [ 1 ] } snippetTags = { this . getActiveTags ( ) [ 0 ] } onFilter = { this . handleFilter } / > < div className = { CODE_SNIPPETS_CONTAINER } > < div > { filteredCodeSnippets . map ( ( codeSnippet ) => this . renderCodeSnippet ( codeSnippet , matchedIndices [ codeSnippet . id ] ) ) } < /div> < /div> < /div>); } } class OptionsHandler extends Widget { constructor ( display , codeSnippet ) { super ( { node : display . createOptionsNode ( codeSnippet ) } ) ; } } class PreviewHandler extends Widget { constructor ( ) { super ( { node : Private . createPreviewNode ( ) } ) ; } } class Private { static createPreviewContent ( ) { const body = document . createElement ( 'div' ) ; return body ; } /**
     * Create structure for preview of snippet data.
     */ static createPreviewNode ( ) { return this . createPreviewContent ( ) ; } }