@@ -37,6 +37,7 @@ import {
37
37
SUPPORTED_LANGUAGE_IDS ,
38
38
FEATURE_FLAGS ,
39
39
featureEnabled ,
40
+ PathConverterInterface ,
40
41
} from "./common" ;
41
42
import { Ruby } from "./ruby" ;
42
43
import { WorkspaceChannel } from "./workspaceChannel" ;
@@ -60,7 +61,7 @@ function enabledFeatureFlags(): Record<string, boolean> {
60
61
// Get the executables to start the server based on the user's configuration
61
62
function getLspExecutables (
62
63
workspaceFolder : vscode . WorkspaceFolder ,
63
- env : NodeJS . ProcessEnv ,
64
+ ruby : Ruby ,
64
65
) : ServerOptions {
65
66
let run : Executable ;
66
67
let debug : Executable ;
@@ -73,8 +74,8 @@ function getLspExecutables(
73
74
const executableOptions : ExecutableOptions = {
74
75
cwd : workspaceFolder . uri . fsPath ,
75
76
env : bypassTypechecker
76
- ? { ...env , RUBY_LSP_BYPASS_TYPECHECKER : "true" }
77
- : env ,
77
+ ? { ...ruby . env , RUBY_LSP_BYPASS_TYPECHECKER : "true" }
78
+ : ruby . env ,
78
79
shell : true ,
79
80
} ;
80
81
@@ -128,6 +129,9 @@ function getLspExecutables(
128
129
} ;
129
130
}
130
131
132
+ run = ruby . activateExecutable ( run ) ;
133
+ debug = ruby . activateExecutable ( debug ) ;
134
+
131
135
return { run, debug } ;
132
136
}
133
137
@@ -165,6 +169,32 @@ function collectClientOptions(
165
169
} ,
166
170
) ;
167
171
172
+ const pathConverter = ruby . pathConverter ;
173
+
174
+ const pushAlternativePaths = (
175
+ path : string ,
176
+ schemes : string [ ] = supportedSchemes ,
177
+ ) => {
178
+ schemes . forEach ( ( scheme ) => {
179
+ [
180
+ pathConverter . toLocalPath ( path ) ,
181
+ pathConverter . toRemotePath ( path ) ,
182
+ ] . forEach ( ( convertedPath ) => {
183
+ if ( convertedPath !== path ) {
184
+ SUPPORTED_LANGUAGE_IDS . forEach ( ( language ) => {
185
+ documentSelector . push ( {
186
+ scheme,
187
+ language,
188
+ pattern : `${ convertedPath } /**/*` ,
189
+ } ) ;
190
+ } ) ;
191
+ }
192
+ } ) ;
193
+ } ) ;
194
+ } ;
195
+
196
+ pushAlternativePaths ( fsPath ) ;
197
+
168
198
// Only the first language server we spawn should handle unsaved files, otherwise requests will be duplicated across
169
199
// all workspaces
170
200
if ( isMainWorkspace ) {
@@ -184,6 +214,8 @@ function collectClientOptions(
184
214
pattern : `${ gemPath } /**/*` ,
185
215
} ) ;
186
216
217
+ pushAlternativePaths ( gemPath , [ scheme ] ) ;
218
+
187
219
// Because of how default gems are installed, the gemPath location is actually not exactly where the files are
188
220
// located. With the regex, we are correcting the default gem path from this (where the files are not located)
189
221
// /opt/rubies/3.3.1/lib/ruby/gems/3.3.0
@@ -194,15 +226,50 @@ function collectClientOptions(
194
226
// Notice that we still need to add the regular path to the selector because some version managers will install
195
227
// gems under the non-corrected path
196
228
if ( / l i b \/ r u b y \/ g e m s \/ (? = \d ) / . test ( gemPath ) ) {
229
+ const correctedPath = gemPath . replace (
230
+ / l i b \/ r u b y \/ g e m s \/ (? = \d ) / ,
231
+ "lib/ruby/" ,
232
+ ) ;
233
+
197
234
documentSelector . push ( {
198
235
scheme,
199
236
language : "ruby" ,
200
- pattern : `${ gemPath . replace ( / l i b \/ r u b y \/ g e m s \/ (? = \d ) / , "lib/ruby/" ) } /**/*` ,
237
+ pattern : `${ correctedPath } /**/*` ,
201
238
} ) ;
239
+
240
+ pushAlternativePaths ( correctedPath , [ scheme ] ) ;
202
241
}
203
242
} ) ;
204
243
} ) ;
205
244
245
+ // Add other mapped paths to the document selector
246
+ pathConverter . pathMapping . forEach ( ( [ local , remote ] ) => {
247
+ if (
248
+ ( documentSelector as { pattern : string } [ ] ) . some (
249
+ ( selector ) =>
250
+ selector . pattern ?. startsWith ( local ) ||
251
+ selector . pattern ?. startsWith ( remote ) ,
252
+ )
253
+ ) {
254
+ return ;
255
+ }
256
+
257
+ supportedSchemes . forEach ( ( scheme ) => {
258
+ SUPPORTED_LANGUAGE_IDS . forEach ( ( language ) => {
259
+ documentSelector . push ( {
260
+ language,
261
+ pattern : `${ local } /**/*` ,
262
+ } ) ;
263
+
264
+ documentSelector . push ( {
265
+ scheme,
266
+ language,
267
+ pattern : `${ remote } /**/*` ,
268
+ } ) ;
269
+ } ) ;
270
+ } ) ;
271
+ } ) ;
272
+
206
273
// This is a temporary solution as an escape hatch for users who cannot upgrade the `ruby-lsp` gem to a version that
207
274
// supports ERB
208
275
if ( ! configuration . get < boolean > ( "erbSupport" ) ) {
@@ -211,9 +278,29 @@ function collectClientOptions(
211
278
} ) ;
212
279
}
213
280
281
+ outputChannel . info (
282
+ `Document Selector Paths: ${ JSON . stringify ( documentSelector ) } ` ,
283
+ ) ;
284
+
285
+ // Map using pathMapping
286
+ const code2Protocol = ( uri : vscode . Uri ) => {
287
+ const remotePath = pathConverter . toRemotePath ( uri . fsPath ) ;
288
+ return vscode . Uri . file ( remotePath ) . toString ( ) ;
289
+ } ;
290
+
291
+ const protocol2Code = ( uri : string ) => {
292
+ const remoteUri = vscode . Uri . parse ( uri ) ;
293
+ const localPath = pathConverter . toLocalPath ( remoteUri . fsPath ) ;
294
+ return vscode . Uri . file ( localPath ) ;
295
+ } ;
296
+
214
297
return {
215
298
documentSelector,
216
299
workspaceFolder,
300
+ uriConverters : {
301
+ code2Protocol,
302
+ protocol2Code,
303
+ } ,
217
304
diagnosticCollectionName : LSP_NAME ,
218
305
outputChannel,
219
306
revealOutputChannelOn : RevealOutputChannelOn . Never ,
@@ -316,6 +403,7 @@ export default class Client extends LanguageClient implements ClientInterface {
316
403
private readonly baseFolder ;
317
404
private readonly workspaceOutputChannel : WorkspaceChannel ;
318
405
private readonly virtualDocuments = new Map < string , string > ( ) ;
406
+ private readonly pathConverter : PathConverterInterface ;
319
407
320
408
#context: vscode . ExtensionContext ;
321
409
#formatter: string ;
@@ -333,7 +421,7 @@ export default class Client extends LanguageClient implements ClientInterface {
333
421
) {
334
422
super (
335
423
LSP_NAME ,
336
- getLspExecutables ( workspaceFolder , ruby . env ) ,
424
+ getLspExecutables ( workspaceFolder , ruby ) ,
337
425
collectClientOptions (
338
426
vscode . workspace . getConfiguration ( "rubyLsp" ) ,
339
427
workspaceFolder ,
@@ -348,6 +436,7 @@ export default class Client extends LanguageClient implements ClientInterface {
348
436
this . registerFeature ( new ExperimentalCapabilities ( ) ) ;
349
437
this . workspaceOutputChannel = outputChannel ;
350
438
this . virtualDocuments = virtualDocuments ;
439
+ this . pathConverter = ruby . pathConverter ;
351
440
352
441
// Middleware are part of client options, but because they must reference `this`, we cannot make it a part of the
353
442
// `super` call (TypeScript does not allow accessing `this` before invoking `super`)
@@ -428,7 +517,9 @@ export default class Client extends LanguageClient implements ClientInterface {
428
517
range ?: Range ,
429
518
) : Promise < { ast : string } | null > {
430
519
return this . sendRequest ( "rubyLsp/textDocument/showSyntaxTree" , {
431
- textDocument : { uri : uri . toString ( ) } ,
520
+ textDocument : {
521
+ uri : this . pathConverter . toRemoteUri ( uri ) . toString ( ) ,
522
+ } ,
432
523
range,
433
524
} ) ;
434
525
}
@@ -624,10 +715,12 @@ export default class Client extends LanguageClient implements ClientInterface {
624
715
token ,
625
716
_next ,
626
717
) => {
718
+ const remoteUri = this . pathConverter . toRemoteUri ( document . uri ) ;
719
+
627
720
const response : vscode . TextEdit [ ] | null = await this . sendRequest (
628
721
"textDocument/onTypeFormatting" ,
629
722
{
630
- textDocument : { uri : document . uri . toString ( ) } ,
723
+ textDocument : { uri : remoteUri . toString ( ) } ,
631
724
position,
632
725
ch,
633
726
options,
@@ -695,9 +788,65 @@ export default class Client extends LanguageClient implements ClientInterface {
695
788
token ?: vscode . CancellationToken ,
696
789
) => Promise < T > ,
697
790
) => {
698
- return this . benchmarkMiddleware ( type , param , ( ) =>
791
+ this . workspaceOutputChannel . trace (
792
+ `Sending request: ${ JSON . stringify ( type ) } with params: ${ JSON . stringify ( param ) } ` ,
793
+ ) ;
794
+
795
+ const result = ( await this . benchmarkMiddleware ( type , param , ( ) =>
699
796
next ( type , param , token ) ,
797
+ ) ) as any ;
798
+
799
+ this . workspaceOutputChannel . trace (
800
+ `Received response for ${ JSON . stringify ( type ) } : ${ JSON . stringify ( result ) } ` ,
700
801
) ;
802
+
803
+ const request = typeof type === "string" ? type : type . method ;
804
+
805
+ try {
806
+ switch ( request ) {
807
+ case "rubyLsp/workspace/dependencies" :
808
+ return result . map ( ( dep : { path : string } ) => {
809
+ return {
810
+ ...dep ,
811
+ path : this . pathConverter . toLocalPath ( dep . path ) ,
812
+ } ;
813
+ } ) ;
814
+
815
+ case "textDocument/codeAction" :
816
+ return result . map ( ( action : { uri : string } ) => {
817
+ const remotePath = vscode . Uri . parse ( action . uri ) . fsPath ;
818
+ const localPath = this . pathConverter . toLocalPath ( remotePath ) ;
819
+
820
+ return {
821
+ ...action ,
822
+ uri : vscode . Uri . file ( localPath ) . toString ( ) ,
823
+ } ;
824
+ } ) ;
825
+
826
+ case "textDocument/hover" :
827
+ if (
828
+ result ?. contents ?. kind === "markdown" &&
829
+ result . contents . value
830
+ ) {
831
+ result . contents . value = result . contents . value . replace (
832
+ / \( ( f i l e : \/ \/ .+ ?) # / gim,
833
+ ( _match : string , path : string ) => {
834
+ const remotePath = vscode . Uri . parse ( path ) . fsPath ;
835
+ const localPath =
836
+ this . pathConverter . toLocalPath ( remotePath ) ;
837
+ return `(${ vscode . Uri . file ( localPath ) . toString ( ) } #` ;
838
+ } ,
839
+ ) ;
840
+ }
841
+ break ;
842
+ }
843
+ } catch ( error ) {
844
+ this . workspaceOutputChannel . error (
845
+ `Error while processing response for ${ request } : ${ error } ` ,
846
+ ) ;
847
+ }
848
+
849
+ return result ;
701
850
} ,
702
851
sendNotification : async < TR > (
703
852
type : string | MessageSignature ,
0 commit comments