@@ -7,9 +7,6 @@ import _ from 'lodash'
77import { GetConfig } from './types'
88import { getCompletionsAtPosition , PrevCompletionMap } from './completionsAtPosition'
99import { TriggerCharacterCommand } from './ipcTypes'
10- import { oneOf } from '@zardoy/utils'
11- import { isGoodPositionMethodCompletion } from './completions/isGoodPositionMethodCompletion'
12- import { getParameterListParts } from './completions/snippetForFunctionCall'
1310import { getNavTreeItems } from './getPatchedNavTree'
1411import decorateCodeActions from './codeActions/decorateProxy'
1512import decorateSemanticDiagnostics from './semanticDiagnostics'
@@ -18,156 +15,142 @@ import decorateReferences from './references'
1815import handleSpecialCommand from './specialCommands/handle'
1916import decorateDefinitions from './definitions'
2017import decorateDocumentHighlights from './documentHighlights'
18+ import completionEntryDetails from './completionEntryDetails'
2119
2220const thisPluginMarker = Symbol ( '__essentialPluginsMarker__' )
2321
24- // just to see wether issue is resolved
2522let _configuration : Configuration
2623const c : GetConfig = key => get ( _configuration , key )
27- //@ts -ignore
28- export = ( { typescript } : { typescript : typeof ts } ) => {
24+
25+ const decorateLanguageService = ( info : ts . server . PluginCreateInfo , existingProxy ?: ts . LanguageService ) => {
26+ // Set up decorator object
27+ const proxy : ts . LanguageService = existingProxy ?? Object . create ( null )
28+
29+ for ( const k of Object . keys ( info . languageService ) ) {
30+ const x = info . languageService [ k ] !
31+ // @ts -expect-error - JS runtime trickery which is tricky to type tersely
32+ proxy [ k ] = ( ...args : Array < Record < string , unknown > > ) => x . apply ( info . languageService , args )
33+ }
34+
35+ const { languageService } = info
36+
37+ let prevCompletionsMap : PrevCompletionMap
38+ // eslint-disable-next-line complexity
39+ proxy . getCompletionsAtPosition = ( fileName , position , options ) => {
40+ const updateConfigCommand = 'updateConfig'
41+ if ( options ?. triggerCharacter ?. startsWith ( updateConfigCommand ) ) {
42+ _configuration = JSON . parse ( options . triggerCharacter . slice ( updateConfigCommand . length ) )
43+ return { entries : [ ] }
44+ }
45+ const specialCommandResult = options ?. triggerCharacter
46+ ? handleSpecialCommand ( info , fileName , position , options . triggerCharacter as TriggerCharacterCommand , _configuration )
47+ : undefined
48+ // handled specialCommand request
49+ if ( specialCommandResult !== undefined ) return specialCommandResult as any
50+ prevCompletionsMap = { }
51+ const scriptSnapshot = info . project . getScriptSnapshot ( fileName )
52+ // have no idea in which cases its possible, but we can't work without it
53+ if ( ! scriptSnapshot ) return
54+ const result = getCompletionsAtPosition ( fileName , position , options , c , info . languageService , scriptSnapshot , ts )
55+ if ( ! result ) return
56+ prevCompletionsMap = result . prevCompletionsMap
57+ return result . completions
58+ }
59+
60+ proxy . getCompletionEntryDetails = ( fileName , position , entryName , formatOptions , source , preferences , data ) => {
61+ if ( fileName === 'disposeLanguageService' ) {
62+ process . exit ( 1 )
63+ return
64+ }
65+ const program = languageService . getProgram ( )
66+ const sourceFile = program ?. getSourceFile ( fileName )
67+ if ( ! program || ! sourceFile ) return
68+ const { documentationOverride } = prevCompletionsMap [ entryName ] ?? { }
69+ if ( documentationOverride ) {
70+ return {
71+ name : entryName ,
72+ kind : ts . ScriptElementKind . alias ,
73+ kindModifiers : '' ,
74+ displayParts : typeof documentationOverride === 'string' ? [ { kind : 'text' , text : documentationOverride } ] : documentationOverride ,
75+ }
76+ }
77+ const prior = languageService . getCompletionEntryDetails (
78+ fileName ,
79+ position ,
80+ prevCompletionsMap [ entryName ] ?. originalName || entryName ,
81+ formatOptions ,
82+ source ,
83+ preferences ,
84+ data ,
85+ )
86+ if ( ! prior ) return
87+ return completionEntryDetails ( languageService , c , fileName , position , sourceFile , prior )
88+ }
89+
90+ decorateCodeActions ( proxy , info . languageService , c )
91+ decorateCodeFixes ( proxy , info . languageService , c )
92+ decorateSemanticDiagnostics ( proxy , info , c )
93+ decorateDefinitions ( proxy , info , c )
94+ decorateReferences ( proxy , info . languageService , c )
95+ decorateDocumentHighlights ( proxy , info . languageService , c )
96+
97+ if ( ! __WEB__ ) {
98+ // dedicated syntax server (which is enabled by default), which fires navtree doesn't seem to receive onConfigurationChanged
99+ // so we forced to communicate via fs
100+ const config = JSON . parse ( ts . sys . readFile ( require ( 'path' ) . join ( __dirname , '../../plugin-config.json' ) , 'utf8' ) ?? '{}' )
101+ proxy . getNavigationTree = fileName => {
102+ if ( c ( 'patchOutline' ) || config . patchOutline ) return getNavTreeItems ( ts , info , fileName )
103+ return info . languageService . getNavigationTree ( fileName )
104+ }
105+ }
106+
107+ info . languageService [ thisPluginMarker ] = true
108+ return proxy
109+ }
110+
111+ const updateConfigListeners : Array < ( ) => void > = [ ]
112+
113+ const plugin : ts . server . PluginModuleFactory = ( { typescript } ) => {
29114 ts = typescript
30115 return {
31- create ( info : ts . server . PluginCreateInfo ) {
116+ create ( info ) {
32117 // receive fresh config
33118 _configuration = info . config
34119 console . log ( 'receive config' , JSON . stringify ( _configuration ) )
35120 if ( info . languageService [ thisPluginMarker ] ) return info . languageService
121+ try {
122+ info . languageService . getCompletionEntryDetails ( 'disposeLanguageService' , 0 , '' , undefined , undefined , undefined , undefined )
123+ } catch { }
36124
37- // Set up decorator object
38- const proxy : ts . LanguageService = Object . create ( null )
39-
40- for ( const k of Object . keys ( info . languageService ) ) {
41- const x = info . languageService [ k ] !
42- // @ts -expect-error - JS runtime trickery which is tricky to type tersely
43- proxy [ k ] = ( ...args : Array < Record < string , unknown > > ) => x . apply ( info . languageService , args )
44- }
45-
46- let prevCompletionsMap : PrevCompletionMap
47- // eslint-disable-next-line complexity
48- proxy . getCompletionsAtPosition = ( fileName , position , options ) => {
49- const updateConfigCommand = 'updateConfig'
50- if ( options ?. triggerCharacter ?. startsWith ( updateConfigCommand ) ) {
51- _configuration = JSON . parse ( options . triggerCharacter . slice ( updateConfigCommand . length ) )
52- return { entries : [ ] }
53- }
54- const specialCommandResult = options ?. triggerCharacter
55- ? handleSpecialCommand ( info , fileName , position , options . triggerCharacter as TriggerCharacterCommand , _configuration )
56- : undefined
57- // handled specialCommand request
58- if ( specialCommandResult !== undefined ) return specialCommandResult as any
59- prevCompletionsMap = { }
60- const scriptSnapshot = info . project . getScriptSnapshot ( fileName )
61- // have no idea in which cases its possible, but we can't work without it
62- if ( ! scriptSnapshot ) return
63- const result = getCompletionsAtPosition ( fileName , position , options , c , info . languageService , scriptSnapshot , ts )
64- if ( ! result ) return
65- prevCompletionsMap = result . prevCompletionsMap
66- return result . completions
67- }
125+ const proxy = decorateLanguageService ( info , undefined )
68126
69- proxy . getCompletionEntryDetails = ( fileName , position , entryName , formatOptions , source , preferences , data ) => {
70- const program = info . languageService . getProgram ( )
71- const sourceFile = program ?. getSourceFile ( fileName )
72- if ( ! program || ! sourceFile ) return
73- const { documentationOverride } = prevCompletionsMap [ entryName ] ?? { }
74- if ( documentationOverride ) {
75- return {
76- name : entryName ,
77- kind : ts . ScriptElementKind . alias ,
78- kindModifiers : '' ,
79- displayParts : typeof documentationOverride === 'string' ? [ { kind : 'text' , text : documentationOverride } ] : documentationOverride ,
80- }
81- }
82- let prior = info . languageService . getCompletionEntryDetails (
83- fileName ,
84- position ,
85- prevCompletionsMap [ entryName ] ?. originalName || entryName ,
86- formatOptions ,
87- source ,
88- preferences ,
89- data ,
90- )
91- if ( ! prior ) return
92- if (
93- c ( 'enableMethodSnippets' ) &&
94- oneOf (
95- prior . kind ,
96- ts . ScriptElementKind . constElement ,
97- ts . ScriptElementKind . letElement ,
98- ts . ScriptElementKind . alias ,
99- ts . ScriptElementKind . variableElement ,
100- ts . ScriptElementKind . memberVariableElement ,
101- )
102- ) {
103- // - 1 to look for possibly previous completing item
104- let goodPosition = isGoodPositionMethodCompletion ( ts , fileName , sourceFile , position - 1 , info . languageService , c )
105- let rawPartsOverride : ts . SymbolDisplayPart [ ] | undefined
106- if ( goodPosition && prior . kind === ts . ScriptElementKind . alias ) {
107- goodPosition =
108- prior . displayParts [ 5 ] ?. text === 'method' || ( prior . displayParts [ 4 ] ?. kind === 'keyword' && prior . displayParts [ 4 ] . text === 'function' )
109- const { parts, gotMethodHit, hasOptionalParameters } = getParameterListParts ( prior . displayParts )
110- if ( gotMethodHit ) rawPartsOverride = hasOptionalParameters ? [ ...parts , { kind : '' , text : ' ' } ] : parts
127+ let prevPluginEnabledSetting = _configuration . enablePlugin
128+ updateConfigListeners . push ( ( ) => {
129+ if ( prevPluginEnabledSetting && ! _configuration . enablePlugin ) {
130+ // plugin got disabled, restore original languageService methods
131+ for ( const key of Object . keys ( proxy ) ) {
132+ //@ts -expect-error
133+ proxy [ key ] = ( ...args : Array < Record < string , unknown > > ) => info . languageService [ key ] . apply ( info . languageService , args )
111134 }
112- const punctuationIndex = prior . displayParts . findIndex ( ( { kind, text } ) => kind === 'punctuation' && text === ':' )
113- if ( goodPosition && punctuationIndex !== 1 ) {
114- const isParsableMethod = prior . displayParts
115- // next is space
116- . slice ( punctuationIndex + 2 )
117- . map ( ( { text } ) => text )
118- . join ( '' )
119- . match ( / ^ \( ( .* ) \) = > / )
120- if ( rawPartsOverride || isParsableMethod ) {
121- let firstArgMeet = false
122- const args = (
123- rawPartsOverride ||
124- prior . displayParts . filter ( ( { kind } , index , array ) => {
125- if ( kind !== 'parameterName' ) return false
126- if ( array [ index - 1 ] ! . text === '(' ) {
127- if ( ! firstArgMeet ) {
128- // bad parsing, as it doesn't take second and more args
129- firstArgMeet = true
130- return true
131- }
132- return false
133- }
134- return true
135- } )
136- ) . map ( ( { text } ) => text )
137- prior = {
138- ...prior ,
139- documentation : [ ...( prior . documentation ?? [ ] ) , { kind : 'text' , text : `<!-- insert-func: ${ args . join ( ',' ) } -->` } ] ,
140- }
141- }
142- }
143- }
144- return prior
145- }
146-
147- decorateCodeActions ( proxy , info . languageService , c )
148- decorateCodeFixes ( proxy , info . languageService , c )
149- decorateSemanticDiagnostics ( proxy , info , c )
150- decorateDefinitions ( proxy , info , c )
151- decorateReferences ( proxy , info . languageService , c )
152- decorateDocumentHighlights ( proxy , info . languageService , c )
153-
154- if ( ! __WEB__ ) {
155- // dedicated syntax server (which is enabled by default), which fires navtree doesn't seem to receive onConfigurationChanged
156- // so we forced to communicate via fs
157- const config = JSON . parse ( ts . sys . readFile ( require ( 'path' ) . join ( __dirname , '../../plugin-config.json' ) , 'utf8' ) ?? '{}' )
158- proxy . getNavigationTree = fileName => {
159- if ( c ( 'patchOutline' ) || config . patchOutline ) return getNavTreeItems ( ts , info , fileName )
160- return info . languageService . getNavigationTree ( fileName )
135+ } else if ( ! prevPluginEnabledSetting && _configuration . enablePlugin ) {
136+ // plugin got enabled
137+ decorateLanguageService ( info , proxy )
161138 }
162- }
163139
164- info . languageService [ thisPluginMarker ] = true
140+ prevPluginEnabledSetting = _configuration . enablePlugin
141+ } )
165142
166143 return proxy
167144 } ,
168- onConfigurationChanged ( config : any ) {
145+ onConfigurationChanged ( config ) {
169146 console . log ( 'update config' , JSON . stringify ( config ) )
170147 _configuration = config
148+ for ( const updateConfigListener of updateConfigListeners ) {
149+ updateConfigListener ( )
150+ }
171151 } ,
172152 }
173153}
154+
155+ //@ts -ignore
156+ export = plugin
0 commit comments