@@ -21,6 +21,7 @@ import {
2121 switchMap ,
2222 take ,
2323 finalize ,
24+ filter ,
2425} from 'rxjs/operators'
2526
2627import { ConsoleLogger , createWebSocketConnection } from '@sourcegraph/vscode-ws-jsonrpc'
@@ -189,31 +190,22 @@ async function tryToCreateAccessToken(): Promise<string | undefined> {
189190}
190191
191192async function connectAndInitialize (
192- address : string ,
193+ address : URL ,
193194 root : URL ,
194195 token : string | undefined
195196) : Promise < rpc . MessageConnection > {
196- const connectingToGoLangserverHelp = [
197- `Unable to connect to the Go language server at ${ address } .` ,
198- `Make sure ${ 'go.address' as keyof Settings } in your Sourcegraph settings is set to the address of the language server (e.g. wss://sourcegraph.example.com/go).` ,
199- `Read the full documentation for more information: https://github.com/sourcegraph/sourcegraph-go` ,
200- ] . join ( '\n' )
201-
202- const connectingToSourcegraphHelp = [
203- `The Go language server running on ${ address } was unable to fetch repository contents from Sourcegraph running on ${ sourcegraphURL ( ) } .` ,
204- `Make sure ${ 'go.sourcegraphUrl' as keyof Settings } in your settings is set to the address of Sourcegraph from the perspective of the language server (e.g. http://sourcegraph-frontend:30080 when running in Kubernetes).` ,
205- `Read the full documentation for more information: https://github.com/sourcegraph/sourcegraph-go` ,
206- ] . join ( '\n' )
207-
208- const connection = ( await new Promise ( ( resolve , reject ) => {
197+ const connection = ( await new Promise < wsrpc . MessageConnection > ( ( resolve , reject ) => {
209198 try {
210- const webSocket = new WebSocket ( address )
199+ const webSocket = new WebSocket ( address . href )
211200 const conn = createWebSocketConnection ( wsrpc . toSocket ( webSocket ) , new ConsoleLogger ( ) )
212201 webSocket . addEventListener ( 'open' , ( ) => resolve ( conn ) )
213- webSocket . addEventListener ( 'error' , event => reject ( new Error ( connectingToGoLangserverHelp ) ) )
202+ webSocket . addEventListener ( 'error' , event => {
203+ notifyUnableToConnectToLanguageServer ( address )
204+ reject ( event )
205+ } )
214206 } catch ( e ) {
215207 if ( 'message' in e && / F a i l e d t o c o n s t r u c t / . test ( e . message ) ) {
216- console . error ( connectingToGoLangserverHelp )
208+ notifyUnableToConnectToLanguageServer ( address )
217209 }
218210 reject ( e )
219211 }
@@ -246,8 +238,11 @@ async function connectAndInitialize(
246238 }
247239 )
248240 } catch ( e ) {
249- if ( 'message' in e && ( / n o s u c h h o s t / . test ( e . message ) || / i \/ o t i m e o u t / . test ( e . message ) ) ) {
250- console . error ( connectingToSourcegraphHelp )
241+ if (
242+ 'message' in e &&
243+ ( / n o s u c h h o s t / . test ( e . message ) || / i \/ o t i m e o u t / . test ( e . message ) || / c o n n e c t i o n r e f u s e d / . test ( e . message ) )
244+ ) {
245+ notifyLanguageServerUnableToConnectToSourcegraph ( )
251246 }
252247 throw e
253248 }
@@ -286,7 +281,7 @@ function repoNameFromDoc(doc: sourcegraph.TextDocument): string {
286281 * Internally, this maintains a mapping from rootURI to the connection
287282 * associated with that rootURI, so it supports multiple roots (untested).
288283 */
289- function mkSendRequest ( address : string , token : string | undefined ) : Observable < SendRequest > {
284+ function mkSendRequest ( address : URL , token : string | undefined ) : Observable < SendRequest > {
290285 const rootURIToConnection : { [ rootURI : string ] : Promise < rpc . MessageConnection > } = { }
291286 async function connectionFor ( root : URL ) : Promise < rpc . MessageConnection > {
292287 if ( rootURIToConnection [ root . href ] ) {
@@ -563,6 +558,121 @@ function positionParams(doc: sourcegraph.TextDocument, pos: sourcegraph.Position
563558 }
564559}
565560
561+ function notifyUnableToConnectToLanguageServer ( address : URL ) {
562+ sourcegraph . internal . updateContext ( {
563+ showError : true ,
564+ 'codeIntel.error.message' : 'Error in language server setup. Click to open documentation.' ,
565+ 'codeIntel.error.link' : 'https://sourcegraph.com/extensions/sourcegraph/go' ,
566+ } )
567+ const portByProtocol : { [ protocol : string ] : string } = { 'wss:' : '443' , 'ws:' : '80' }
568+ sourcegraph . app . activeWindow ! . showNotification (
569+ [
570+ '**❌ Unable to access to the Go language server**' ,
571+ '' ,
572+ 'Your browser could not access:' ,
573+ '' ,
574+ '```' ,
575+ `"go.serverUrl": "${ address } "` ,
576+ '```' ,
577+ '' ,
578+ ...( [ 'ws:' , 'wss:' ] . includes ( address . protocol )
579+ ? [
580+ 'Troubleshoot using these commands:' ,
581+ '' ,
582+ `- **\`ping ${ address . hostname } \`**` ,
583+ `- **\`telnet ${ address . hostname } ${ address . port || portByProtocol [ address . protocol ] } \`**` ,
584+ '' ,
585+ ]
586+ : [
587+ `The protocol **\`${
588+ address . protocol
589+ } \`** is invalid. Only **\`ws:\`** or **\`wss:\`** are valid.`,
590+ '' ,
591+ ] ) ,
592+ '' ,
593+ `To apply the fix:` ,
594+ '' ,
595+ `- Correct the value in your [global settings](${
596+ sourcegraph . internal . sourcegraphURL
597+ } site-admin/global-settings).`,
598+ '' ,
599+ 'To temporarily disable:' ,
600+ '' ,
601+ `- Comment out **\`go.serverUrl\`** in your [global settings](${
602+ sourcegraph . internal . sourcegraphURL
603+ } site-admin/global-settings).`,
604+ ] . join ( '\n' )
605+ )
606+ }
607+
608+ function notifyLanguageServerUnableToConnectToSourcegraph ( ) {
609+ sourcegraph . internal . updateContext ( {
610+ showError : true ,
611+ 'codeIntel.error.message' : 'Error in language server setup. Click to open documentation.' ,
612+ 'codeIntel.error.link' : 'https://sourcegraph.com/extensions/sourcegraph/go' ,
613+ } )
614+ sourcegraph . app . activeWindow ! . showNotification (
615+ [
616+ '**❌ Error in Go language server setup**' ,
617+ '' ,
618+ `The Go language server was unable to fetch repository contents from Sourcegraph at:` ,
619+ '' ,
620+ '```' ,
621+ `"go.sourcegraphUrl": "${ sourcegraphURL ( ) } "` ,
622+ '```' ,
623+ '' ,
624+ 'To troubleshoot this, **`docker exec`** into the go-langserver container and run:' ,
625+ '' ,
626+ `- **\`ping ${ sourcegraphURL ( ) . hostname } \`**` ,
627+ `- **\`curl ${ sourcegraphURL ( ) } \`**` ,
628+ '' ,
629+ `To apply the fix:` ,
630+ '' ,
631+ `- Correct the value in your [global settings](${
632+ sourcegraph . internal . sourcegraphURL
633+ } site-admin/global-settings).`,
634+ '' ,
635+ 'To temporarily disable:' ,
636+ '' ,
637+ `- Comment out **\`go.serverUrl\`** in your [global settings](${
638+ sourcegraph . internal . sourcegraphURL
639+ } site-admin/global-settings).`,
640+ ] . join ( '\n' )
641+ )
642+ }
643+
644+ async function canary ( address : URL ) : Promise < void > {
645+ const data = await queryGraphQL ( '{currentUser{siteAdmin}}' )
646+ if ( ! data || ! data . currentUser || data . currentUser . siteAdmin === undefined ) {
647+ console . log (
648+ 'Failed to determine whether or not the current user is an admin. Check the Network tab to see what went wrong.'
649+ )
650+ return
651+ }
652+ if ( ! data . currentUser . siteAdmin ) {
653+ // Don't show these notifications to normal users because they don't
654+ // have access to global settings, and therefore can't fix this for all
655+ // users.
656+ return
657+ }
658+
659+ try {
660+ await new Promise < void > ( ( resolve , reject ) => {
661+ let webSocket : WebSocket
662+ try {
663+ webSocket = new WebSocket ( address . href )
664+ } catch ( e ) {
665+ notifyUnableToConnectToLanguageServer ( address )
666+ return
667+ }
668+ webSocket . addEventListener ( 'open' , ( ) => resolve ( ) )
669+ webSocket . addEventListener ( 'error' , error => reject ( error ) )
670+ } )
671+ } catch ( e ) {
672+ notifyUnableToConnectToLanguageServer ( address )
673+ }
674+ }
675+
566676/**
567677 * Uses WebSockets to communicate with a language server.
568678 */
@@ -574,7 +684,23 @@ export async function activateUsingWebSockets(ctx: sourcegraph.ExtensionContext)
574684 settings . next ( sourcegraph . configuration . get < Settings > ( ) . value )
575685 } )
576686 )
577- const langserverAddress = settings . pipe ( map ( settings => settings [ 'go.serverUrl' ] ) )
687+ const langserverAddress = settings . pipe (
688+ map ( settings => settings [ 'go.serverUrl' ] ) ,
689+ concatMap ( address => {
690+ if ( address === undefined ) {
691+ return [ undefined ]
692+ }
693+ try {
694+ return [ new URL ( address ) ]
695+ } catch ( e ) {
696+ return [ ]
697+ }
698+ } )
699+ )
700+
701+ ctx . subscriptions . add (
702+ langserverAddress . pipe ( filter ( ( x : URL | undefined ) : x is URL => Boolean ( x ) ) ) . subscribe ( url => canary ( url ) )
703+ )
578704
579705 const NO_ADDRESS_ERROR = `To get Go code intelligence, add "${ 'go.address' as keyof Settings } ": "wss://example.com" to your settings.`
580706
0 commit comments