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