@@ -7,9 +7,6 @@ import _ from 'lodash'
7
7
import { GetConfig } from './types'
8
8
import { getCompletionsAtPosition , PrevCompletionMap } from './completionsAtPosition'
9
9
import { TriggerCharacterCommand } from './ipcTypes'
10
- import { oneOf } from '@zardoy/utils'
11
- import { isGoodPositionMethodCompletion } from './completions/isGoodPositionMethodCompletion'
12
- import { getParameterListParts } from './completions/snippetForFunctionCall'
13
10
import { getNavTreeItems } from './getPatchedNavTree'
14
11
import decorateCodeActions from './codeActions/decorateProxy'
15
12
import decorateSemanticDiagnostics from './semanticDiagnostics'
@@ -18,156 +15,142 @@ import decorateReferences from './references'
18
15
import handleSpecialCommand from './specialCommands/handle'
19
16
import decorateDefinitions from './definitions'
20
17
import decorateDocumentHighlights from './documentHighlights'
18
+ import completionEntryDetails from './completionEntryDetails'
21
19
22
20
const thisPluginMarker = Symbol ( '__essentialPluginsMarker__' )
23
21
24
- // just to see wether issue is resolved
25
22
let _configuration : Configuration
26
23
const 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 } ) => {
29
114
ts = typescript
30
115
return {
31
- create ( info : ts . server . PluginCreateInfo ) {
116
+ create ( info ) {
32
117
// receive fresh config
33
118
_configuration = info . config
34
119
console . log ( 'receive config' , JSON . stringify ( _configuration ) )
35
120
if ( info . languageService [ thisPluginMarker ] ) return info . languageService
121
+ try {
122
+ info . languageService . getCompletionEntryDetails ( 'disposeLanguageService' , 0 , '' , undefined , undefined , undefined , undefined )
123
+ } catch { }
36
124
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 )
68
126
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 )
111
134
}
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 )
161
138
}
162
- }
163
139
164
- info . languageService [ thisPluginMarker ] = true
140
+ prevPluginEnabledSetting = _configuration . enablePlugin
141
+ } )
165
142
166
143
return proxy
167
144
} ,
168
- onConfigurationChanged ( config : any ) {
145
+ onConfigurationChanged ( config ) {
169
146
console . log ( 'update config' , JSON . stringify ( config ) )
170
147
_configuration = config
148
+ for ( const updateConfigListener of updateConfigListeners ) {
149
+ updateConfigListener ( )
150
+ }
171
151
} ,
172
152
}
173
153
}
154
+
155
+ //@ts -ignore
156
+ export = plugin
0 commit comments