import * as fs from 'fs' ; import { bind , each , find , has , includes , isArray , isFunction , kebabCase } from 'lodash' ; import * as path from 'path' ; import { AsyncSubject , Observable } from 'rxjs' ; import { CompositeDisposable , Disposable , IDisposable } from 'ts-disposables' ; import { Omni } from './server/omni' ; const win32 = process . platform === 'win32' ; class OmniSharpAtom { private disposable : CompositeDisposable ; private _started : AsyncSubject < boolean > ; private _activated : AsyncSubject < boolean > ; public activate ( state ) { this . disposable = new CompositeDisposable ( ) ; this . _started = new AsyncSubject < boolean > ( ) ; this . _activated = new AsyncSubject < boolean > ( ) ; this . configureKeybindings ( ) ; this . disposable . add ( atom . commands . add ( 'atom-workspace' , 'omnisharp-atom:toggle' , ( ) => this . toggle ( ) ) ) ; this . disposable . add ( atom . commands . add ( 'atom-workspace' , 'omnisharp-atom:fix-usings' , ( ) => Omni . request ( solution => solution . fixusings ( { } ) ) ) ) ; this . disposable . add ( atom . commands . add ( 'atom-workspace' , 'omnisharp-atom:settings' , ( ) => atom . workspace . open ( 'atom://config/packages' ) . then ( tab => { if ( tab && tab . getURI && tab . getURI ( ) !== 'atom://config/packages/omnisharp-atom' ) { atom . workspace . open ( 'atom://config/packages/omnisharp-atom' ) ; } } ) ) ) ; const grammars = ( < any > atom . grammars ) ; const grammarCb = ( grammar ) => { if ( find ( Omni . grammars , ( gmr ) => gmr . scopeName === grammar . scopeName ) ) { atom . grammars . startIdForScope ( grammar . scopeName ) ; const omnisharpScopeName = `${ grammar . scopeName }.omnisharp` ; const scopeId = grammars . idsByScope [ grammar . scopeName ] ; grammars . idsByScope [ omnisharpScopeName ] = scopeId ; grammars . scopesById [ scopeId ] = omnisharpScopeName ; grammar . scopeName = omnisharpScopeName ; } } ; each ( grammars . grammars , grammarCb ) ; this . disposable . add ( atom . grammars . onDidAddGrammar ( grammarCb ) ) ; require ( 'atom-package-deps' ) . install ( 'omnisharp-atom' ) . then ( ( ) => { console . info ( 'Activating omnisharp-atom solution tracking...' ) ; Omni . activate ( ) ; this . disposable . add ( Omni ) ; this . _started . next ( true ) ; this . _started . complete ( ) ; } ) . then ( ( ) => this . loadFeatures ( this . getFeatures ( 'atom' ) . delay ( Omni [ '_kick_in_the_pants_' ] ? 0 : 2000 ) ) . toPromise ( ) ) . then ( ( ) => { let startingObservable = Omni . activeSolution . filter ( z => ! ! z ) . take ( 1 ) ; if ( Omni [ '_kick_in_the_pants_' ] ) { startingObservable = Observable . of ( null ) ; } this . disposable . add ( startingObservable . flatMap ( ( ) => this . loadFeatures ( this . getFeatures ( 'features' ) ) ) . subscribe ( { complete : ( ) => { this . disposable . add ( atom . workspace . observeTextEditors ( ( editor ) => { this . detectAutoToggleGrammar ( editor ) ; } ) ) ; this . _activated . next ( true ) ; this . _activated . complete ( ) ; } } ) ) ; } ) ; } public getFeatures ( folder ) { const whiteList = atom . config . get < boolean > ( 'omnisharp-atom:feature-white-list' ) ; const featureList = atom . config . get < string [ ] > ( 'omnisharp-atom:feature-list' ) ; const whiteListUndefined = whiteList === undefined ; console . info ( `Getting features for "${ folder }"...` ) ; const featureDir = path . resolve ( __dirname , folder ) ; function loadFeature ( file ) { const result = require ( `./${ folder }/${ file }` ) ; console . info ( `Loading feature "${ folder }/${ file }"...` ) ; return result ; } return Observable . bindNodeCallback ( fs . readdir ) ( featureDir ) . flatMap ( files => files ) . filter ( file => /\.js$/ . test ( file ) ) . flatMap ( file => Observable . bindNodeCallback ( fs . stat ) ( `${ featureDir }/${ file }` ) , ( file , stat ) => ( { file , stat } ) ) . filter ( z => ! z . stat . isDirectory ( ) ) . map ( z => ( { file : `${ folder }/${ path . basename ( z . file ) }` . replace ( /\.js$/ , '' ) , load : ( ) => { const feature = loadFeature ( z . file ) ; const features = [ ] ; each ( feature , ( value , key ) => { if ( ! isFunction ( value ) && ! isArray ( value ) ) { if ( ! value . required ) { this . config [ key ] = { title : `${ value . title }` , description : value . description , type : 'boolean' , default : ( has ( value , 'default' ) ? value . default : true ) } ; } features . push ( { key , activate : ( ) => { return this . activateFeature ( whiteListUndefined , key , value ) ; } } ) ; } } ) ; return Observable . from < { key : string ; activate : ( ) => ( ) => void ; } > ( features ) ; } } ) ) . filter ( l => { if ( whiteList === undefined ) { return true ; } if ( whiteList ) { return includes ( featureList , l . file ) ; } else { return ! includes ( featureList , l . file ) ; } } ) ; } public loadFeatures ( features ) { return features . concatMap ( z => z . load ( ) ) . toArray ( ) . concatMap ( x => x ) . map ( f => f . activate ( ) ) . filter ( x => ! ! x ) . toArray ( ) . do ( { complete : ( ) => { ( < any > atom . config ) . setSchema ( 'omnisharp-atom' , { type : 'object' , properties : this . config } ) ; } } ) . concatMap ( x => x ) . do ( x => x ( ) ) ; } public activateFeature ( whiteListUndefined , key , value ) { let result = null ; let firstRun = true ; if ( whiteListUndefined && has ( this . config , key ) ) { const configKey = `omnisharp-atom.${ key }` ; let enableDisposable ; let disableDisposable ; this . disposable . add ( atom . config . observe ( configKey , enabled => { if ( ! enabled ) { if ( disableDisposable ) { disableDisposable . dispose ( ) ; this . disposable . remove ( disableDisposable ) ; disableDisposable = null ; } try { value . dispose ( ) ; } catch ( ex ) { } enableDisposable = atom . commands . add ( 'atom-workspace' , `omnisharp-feature:enable-${ kebabCase ( key ) }` , ( ) => atom . config . set ( configKey , true ) ) ; this . disposable . add ( enableDisposable ) ; } else { if ( enableDisposable ) { enableDisposable . dispose ( ) ; this . disposable . remove ( disableDisposable ) ; enableDisposable = null ; } console . info ( `Activating feature "${ key }"...` ) ; value . activate ( ) ; if ( isFunction ( value [ 'attach' ] ) ) { if ( firstRun ) { result = ( ) => { console . info ( `Attaching feature "${ key }"...` ) ; value [ 'attach' ] ( ) ; } ; } else { console . info ( `Attaching feature "${ key }"...` ) ; value [ 'attach' ] ( ) ; } } disableDisposable = atom . commands . add ( 'atom-workspace' , `omnisharp-feature:disable-${ kebabCase ( key ) }` , ( ) => atom . config . set ( configKey , false ) ) ; this . disposable . add ( disableDisposable ) ; } firstRun = false ; } ) ) ; this . disposable . add ( atom . commands . add ( 'atom-workspace' , `omnisharp-feature:toggle-${ kebabCase ( key ) }` , ( ) => atom . config . set ( configKey , ! atom . config . get ( configKey ) ) ) ) ; } else { value . activate ( ) ; if ( isFunction ( value [ 'attach' ] ) ) { result = ( ) => { console . info ( `Attaching feature "${ key }"...` ) ; value [ 'attach' ] ( ) ; } ; } } this . disposable . add ( Disposable . create ( ( ) => { try { value . dispose ( ) ; } catch ( ex ) { } } ) ) ; return result ; } private detectAutoToggleGrammar ( editor ) { const grammar = editor . getGrammar ( ) ; this . detectGrammar ( editor , grammar ) ; this . disposable . add ( editor . onDidChangeGrammar ( ( gmr ) => this . detectGrammar ( editor , gmr ) ) ) ; } private detectGrammar ( editor , grammar ) { if ( ! atom . config . get ( 'omnisharp-atom.autoStartOnCompatibleFile' ) ) { return ; } if ( Omni . isValidGrammar ( grammar ) ) { if ( Omni . isOff ) { this . toggle ( ) ; } } else if ( grammar . name === 'JSON' ) { if ( path . basename ( editor . getPath ( ) ) === 'project.json' ) { if ( Omni . isOff ) { this . toggle ( ) ; } } } } public toggle ( ) { if ( Omni . isOff ) { Omni . connect ( ) ; } else if ( Omni . isOn ) { Omni . disconnect ( ) ; } } public deactivate ( ) { this . disposable . dispose ( ) ; } public consumeStatusBar ( statusBar ) { let f = require ( './atom/status-bar' ) ; f . statusBar . setup ( statusBar ) ; f = require ( './atom/framework-selector' ) ; f . frameworkSelector . setup ( statusBar ) ; f = require ( './atom/feature-buttons' ) ; f . featureEditorButtons . setup ( statusBar ) ; } public consumeYeomanEnvironment ( generatorService ) { const { generatorAspnet } = require ( './atom/generator-aspnet' ) ; generatorAspnet . setup ( generatorService ) ; } public provideAutocomplete ( ) { return require ( './services/completion-provider' ) ; } public provideLinter ( ) { return [ ] ; } public provideProjectJson ( ) { return require ( './services/project-provider' ) . concat ( require ( './services/framework-provider' ) ) ; } public consumeLinter ( linter ) { const LinterProvider = require ( './services/linter-provider' ) ; const linters = LinterProvider . provider ; this . disposable . add ( Disposable . create ( ( ) => { each ( linters , l => { linter . deleteLinter ( l ) ; } ) ; } ) ) ; this . disposable . add ( LinterProvider . init ( linter ) ) ; } public consumeIndieLinter ( linter ) { require ( './services/linter-provider' ) . registerIndie ( linter , this . disposable ) ; } private configureKeybindings ( ) { let disposable ; const omnisharpAdvancedFileNew = Omni . packageDir + '/omnisharp-atom/keymaps/omnisharp-file-new.cson' ; this . disposable . add ( atom . config . observe ( 'omnisharp-atom.enableAdvancedFileNew' , ( enabled ) => { if ( enabled ) { disposable = atom . keymaps . loadKeymap ( omnisharpAdvancedFileNew ) ; } else { if ( disposable ) disposable . dispose ( ) ; atom . keymaps . removeBindingsFromSource ( omnisharpAdvancedFileNew ) ; } } ) ) ; } public config = { autoStartOnCompatibleFile : { title : 'Autostart Omnisharp Roslyn' , description : 'Automatically starts Omnisharp Roslyn when a compatible file is opened.' , type : 'boolean' , default : true } , developerMode : { title : 'Developer Mode' , description : 'Outputs detailed server calls in console.log' , type : 'boolean' , default : false } , showDiagnosticsForAllSolutions : { title : 'Show Diagnostics for all Solutions' , description : 'Advanced: This will show diagnostics for all open solutions.  NOTE: May take a restart or change to each server to take effect when turned on.' , type : 'boolean' , default : false } , enableAdvancedFileNew : { title : 'Enable `Advanced File New`' , description : 'Enable `Advanced File New` when doing ctrl-n/cmd-n within a C# editor.' , type : 'boolean' , default : false } , useLeftLabelColumnForSuggestions : { title : 'Use Left-Label column in Suggestions' , description : 'Shows return types in a right-aligned column to the left of the completion suggestion text.' , type : 'boolean' , default : false } , useIcons : { title : 'Use unique icons for kind indicators in Suggestions' , description : 'Shows kinds with unique icons rather than autocomplete default styles.' , type : 'boolean' , default : true } , autoAdjustTreeView : { title : 'Adjust the tree view to match the solution root.' , descrption : 'This will automatically adjust the treeview to be the root of the solution.' , type : 'boolean' , default : false } , nagAdjustTreeView : { title : 'Show the notifications to Adjust the tree view' , type : 'boolean' , default : true } , autoAddExternalProjects : { title : 'Add external projects to the tree view.' , descrption : 'This will automatically add external sources to the tree view.\n External sources are any projects that are loaded outside of the solution root.' , type : 'boolean' , default : false } , nagAddExternalProjects : { title : 'Show the notifications to add or remove external projects' , type : 'boolean' , default : true } , hideLinterInterface : { title : 'Hide the linter interface when using omnisharp-atom editors' , type : 'boolean' , default : true } , wantMetadata : { title : 'Request metadata definition with Goto Definition' , descrption : 'Request symbol metadata from the server, when using go-to-definition.  This is disabled by default on Linux, due to issues with Roslyn on Mono.' , type : 'boolean' , default : win32 } , altGotoDefinition : { title : 'Alt Go To Definition' , descrption : 'Use the alt key instead of the ctrl/cmd key for goto defintion mouse over.' , type : 'boolean' , default : false } , showHiddenDiagnostics : { title : 'Show \'Hidden\' diagnostics in the linter' , descrption : 'Show or hide hidden diagnostics in the linter, this does not affect greying out of namespaces that are unused.' , type : 'boolean' , default : true } } ; } module . exports = new OmniSharpAtom ;