@@ -42,7 +42,7 @@ import { toolExecutionEnvironment } from '../goEnv';
4242import { GoDocumentFormattingEditProvider , usingCustomFormatTool } from './legacy/goFormat' ;
4343import { installTools , latestModuleVersion , promptForMissingTool , promptForUpdatingTool } from '../goInstallTools' ;
4444import { getTool , Tool } from '../goTools' ;
45- import { getFromGlobalState , updateGlobalState , updateWorkspaceState } from '../stateUtils' ;
45+ import { updateGlobalState , updateWorkspaceState } from '../stateUtils' ;
4646import {
4747 getBinPath ,
4848 getCheckForToolsUpdatesConfig ,
@@ -99,14 +99,6 @@ export function updateRestartHistory(goCtx: GoExtensionContext, reason: RestartR
9999 goCtx . restartHistory . push ( new Restart ( reason , new Date ( ) , enabled ) ) ;
100100}
101101
102- function formatRestartHistory ( goCtx : GoExtensionContext ) : string {
103- const result : string [ ] = [ ] ;
104- for ( const restart of goCtx . restartHistory ?? [ ] ) {
105- result . push ( `${ restart . timestamp . toUTCString ( ) } : ${ restart . reason } (enabled: ${ restart . enabled } )` ) ;
106- }
107- return result . join ( '\n' ) ;
108- }
109-
110102export enum RestartReason {
111103 ACTIVATION = 'activation' ,
112104 MANUAL = 'manual' ,
@@ -453,13 +445,7 @@ export async function buildLanguageClient(
453445 } ,
454446 closed : ( ) => {
455447 if ( initializationError !== undefined ) {
456- suggestGoplsIssueReport (
457- goCtx ,
458- cfg ,
459- 'The gopls server failed to initialize.' ,
460- errorKind . initializationFailure ,
461- initializationError
462- ) ;
448+ suggestActionAfterGoplsStartError ( goCtx , cfg ) ;
463449 initializationError = undefined ;
464450 // In case of initialization failure, do not try to restart.
465451 return {
@@ -478,12 +464,7 @@ export async function buildLanguageClient(
478464 action : CloseAction . Restart
479465 } ;
480466 }
481- suggestGoplsIssueReport (
482- goCtx ,
483- cfg ,
484- 'The connection to gopls has been closed. The gopls server may have crashed.' ,
485- errorKind . crash
486- ) ;
467+ suggestActionAfterGoplsStartError ( goCtx , cfg ) ;
487468 updateLanguageServerIconGoStatusBar ( c , true ) ;
488469 return {
489470 message : '' , // suppresses error popups - there will be other popups.
@@ -1371,31 +1352,21 @@ export enum errorKind {
13711352 manualRestart
13721353}
13731354
1374- // suggestGoplsIssueReport prompts users to file an issue with gopls.
1375- export async function suggestGoplsIssueReport (
1355+ // suggestActionAfterStartError potentially suggests actions to the user when gopls fails to start, either
1356+ // updating the language server or double checking their go.languageServerFlags setting.
1357+ export async function suggestActionAfterGoplsStartError (
13761358 goCtx : GoExtensionContext ,
1377- cfg : LanguageServerConfig , // config used when starting this gopls.
1378- msg : string ,
1379- reason : errorKind ,
1380- initializationError ?: ResponseError < InitializeError >
1359+ cfg : LanguageServerConfig // config used when starting this gopls.
13811360) {
1382- const issueTime = new Date ( ) ;
1383-
1384- // Don't prompt users who manually restart to file issues until gopls/v1.0.
1385- if ( reason === errorKind . manualRestart ) {
1386- return ;
1387- }
1388-
13891361 // cfg is the config used when starting this crashed gopls instance, while
13901362 // goCtx.latestConfig is the config used by the latest gopls instance.
13911363 // They may be different if gopls upgrade occurred in between.
1392- // Let's not report issue yet if they don't match.
13931364 if ( JSON . stringify ( goCtx . latestConfig ?. version ) !== JSON . stringify ( cfg . version ) ) {
13941365 return ;
13951366 }
13961367
13971368 // The user may have an outdated version of gopls, in which case we should
1398- // just prompt them to update, not file an issue .
1369+ // just prompt them to update.
13991370 const tool = getTool ( 'gopls' ) ;
14001371 if ( tool ) {
14011372 const versionToUpdate = await shouldUpdateLanguageServer ( tool , goCtx . latestConfig , true ) ;
@@ -1411,30 +1382,13 @@ export async function suggestGoplsIssueReport(
14111382 if ( goCtx . latestConfig ?. serverName !== 'gopls' ) {
14121383 return ;
14131384 }
1414- const promptForIssueOnGoplsRestartKey = 'promptForIssueOnGoplsRestart' ;
1415- let saved : any ;
1416- try {
1417- saved = JSON . parse ( getFromGlobalState ( promptForIssueOnGoplsRestartKey , false ) ) ;
1418- } catch ( err ) {
1419- console . log ( `Failed to parse as JSON ${ getFromGlobalState ( promptForIssueOnGoplsRestartKey , true ) } : ${ err } ` ) ;
1420- return ;
1421- }
1422- // If the user has already seen this prompt, they may have opted-out for
1423- // the future. Only prompt again if it's been more than a year since.
1424- if ( saved ) {
1425- const dateSaved = new Date ( saved [ 'date' ] ) ;
1426- const prompt = < boolean > saved [ 'prompt' ] ;
1427- if ( ! prompt && daysBetween ( new Date ( ) , dateSaved ) <= 365 ) {
1428- return ;
1429- }
1430- }
14311385
1432- const { sanitizedLog , failureReason } = await collectGoplsLog ( goCtx ) ;
1386+ const isIncorrectUsage = await isIncorrectCommandUsage ( goCtx ) ;
14331387
14341388 // If the user has invalid values for "go.languageServerFlags", we may get
14351389 // this error. Prompt them to double check their flags.
14361390 let selected : string | undefined ;
1437- if ( failureReason === GoplsFailureModes . INCORRECT_COMMAND_USAGE ) {
1391+ if ( isIncorrectUsage ) {
14381392 const languageServerFlags = getGoConfig ( ) [ 'languageServerFlags' ] as string [ ] ;
14391393 if ( languageServerFlags && languageServerFlags . length > 0 ) {
14401394 selected = await vscode . window . showErrorMessage (
@@ -1455,87 +1409,6 @@ Please correct the setting.`,
14551409 }
14561410 }
14571411 }
1458- const showMessage = sanitizedLog ? vscode . window . showWarningMessage : vscode . window . showInformationMessage ;
1459- selected = await showMessage (
1460- `${ msg } Would you like to report a gopls issue on GitHub?
1461- You will be asked to provide additional information and logs, so PLEASE READ THE CONTENT IN YOUR BROWSER.` ,
1462- 'Yes' ,
1463- 'Next time' ,
1464- 'Never'
1465- ) ;
1466- switch ( selected ) {
1467- case 'Yes' :
1468- {
1469- // Prefill an issue title and report.
1470- let errKind : string ;
1471- switch ( reason ) {
1472- case errorKind . crash :
1473- errKind = 'crash' ;
1474- break ;
1475- case errorKind . initializationFailure :
1476- errKind = 'initialization' ;
1477- break ;
1478- }
1479- const settings = goCtx . latestConfig . flags . join ( ' ' ) ;
1480- const title = `gopls: automated issue report (${ errKind } )` ;
1481- const goplsStats = await getGoplsStats ( goCtx . latestConfig ?. path ) ;
1482- const goplsLog = sanitizedLog
1483- ? `<pre>${ sanitizedLog } </pre>`
1484- : `Please attach the stack trace from the crash.
1485- A window with the error message should have popped up in the lower half of your screen.
1486- Please copy the stack trace and error messages from that window and paste it in this issue.
1487-
1488- <PASTE STACK TRACE HERE>
1489-
1490- Failed to auto-collect gopls trace: ${ failureReason } .
1491- ` ;
1492-
1493- const body = `
1494- gopls version: ${ cfg . version ?. version } /${ cfg . version ?. goVersion }
1495- gopls flags: ${ settings }
1496- update flags: ${ cfg . checkForUpdates }
1497- extension version: ${ extensionInfo . version }
1498- environment: ${ extensionInfo . appName } ${ process . platform }
1499- initialization error: ${ initializationError }
1500- issue timestamp: ${ issueTime . toUTCString ( ) }
1501- restart history:
1502- ${ formatRestartHistory ( goCtx ) }
1503-
1504- ATTENTION: PLEASE PROVIDE THE DETAILS REQUESTED BELOW.
1505-
1506- Describe what you observed.
1507-
1508- <ANSWER HERE>
1509-
1510- ${ goplsLog }
1511-
1512- <details><summary>gopls stats -anon</summary>
1513- ${ goplsStats }
1514- </details>
1515-
1516- OPTIONAL: If you would like to share more information, you can attach your complete gopls logs.
1517-
1518- NOTE: THESE MAY CONTAIN SENSITIVE INFORMATION ABOUT YOUR CODEBASE.
1519- DO NOT SHARE LOGS IF YOU ARE WORKING IN A PRIVATE REPOSITORY.
1520-
1521- <OPTIONAL: ATTACH LOGS HERE>
1522- ` ;
1523- const url = `https://github.com/golang/vscode-go/issues/new?title=${ title } &labels=automatedReport&body=${ body } ` ;
1524- await vscode . env . openExternal ( vscode . Uri . parse ( url ) ) ;
1525- }
1526- break ;
1527- case 'Next time' :
1528- break ;
1529- case 'Never' :
1530- updateGlobalState (
1531- promptForIssueOnGoplsRestartKey ,
1532- JSON . stringify ( {
1533- prompt : false ,
1534- date : new Date ( )
1535- } )
1536- ) ;
1537- break ;
1538- }
15391412}
15401413
15411414export const showServerOutputChannel : CommandFactory = ( ctx , goCtx ) => ( ) => {
@@ -1570,7 +1443,7 @@ function sleep(ms: number) {
15701443 return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
15711444}
15721445
1573- async function collectGoplsLog ( goCtx : GoExtensionContext ) : Promise < { sanitizedLog ?: string ; failureReason ?: string } > {
1446+ async function isIncorrectCommandUsage ( goCtx : GoExtensionContext ) : Promise < boolean > {
15741447 goCtx . serverOutputChannel ?. show ( ) ;
15751448 // Find the logs in the output channel. There is no way to read
15761449 // an output channel directly, but we can find the open text
@@ -1597,78 +1470,7 @@ async function collectGoplsLog(goCtx: GoExtensionContext): Promise<{ sanitizedLo
15971470 // sleep a bit before the next try. The choice of the sleep time is arbitrary.
15981471 await sleep ( ( i + 1 ) * 100 ) ;
15991472 }
1600- return sanitizeGoplsTrace ( logs ) ;
1601- }
1602-
1603- enum GoplsFailureModes {
1604- NO_GOPLS_LOG = 'no gopls log' ,
1605- EMPTY_PANIC_TRACE = 'empty panic trace' ,
1606- INCORRECT_COMMAND_USAGE = 'incorrect gopls command usage' ,
1607- UNRECOGNIZED_CRASH_PATTERN = 'unrecognized crash pattern'
1608- }
1609-
1610- // capture only panic stack trace and the initialization error message.
1611- // exported for testing.
1612- export function sanitizeGoplsTrace ( logs ?: string ) : { sanitizedLog ?: string ; failureReason ?: string } {
1613- if ( ! logs ) {
1614- return { failureReason : GoplsFailureModes . NO_GOPLS_LOG } ;
1615- }
1616- const panicMsgBegin = logs . lastIndexOf ( 'panic: ' ) ;
1617- if ( panicMsgBegin > - 1 ) {
1618- // panic message was found.
1619- let panicTrace = logs . substr ( panicMsgBegin ) ;
1620- const panicMsgEnd = panicTrace . search ( / \[ ( I n f o | W a r n i n g | E r r o r ) \s + - \s + / ) ;
1621- if ( panicMsgEnd > - 1 ) {
1622- panicTrace = panicTrace . substr ( 0 , panicMsgEnd ) ;
1623- }
1624- const filePattern = / ( \S + \. g o ) : \d + / ;
1625- const sanitized = panicTrace
1626- . split ( '\n' )
1627- . map ( ( line : string ) => {
1628- // Even though this is a crash from gopls, the file path
1629- // can contain user names and user's filesystem directory structure.
1630- // We can still locate the corresponding file if the file base is
1631- // available because the full package path is part of the function
1632- // name. So, leave only the file base.
1633- const m = line . match ( filePattern ) ;
1634- if ( ! m ) {
1635- return line ;
1636- }
1637- const filePath = m [ 1 ] ;
1638- const fileBase = path . basename ( filePath ) ;
1639- return line . replace ( filePath , ' ' + fileBase ) ;
1640- } )
1641- . join ( '\n' ) ;
1642-
1643- if ( sanitized ) {
1644- return { sanitizedLog : sanitized } ;
1645- }
1646- return { failureReason : GoplsFailureModes . EMPTY_PANIC_TRACE } ;
1647- }
1648- // Capture Fatal
1649- // foo.go:1: the last message (caveat - we capture only the first log line)
1650- const m = logs . match ( / ( ^ \S + \. g o : \d + : .* $ ) / gm) ;
1651- if ( m && m . length > 0 ) {
1652- return { sanitizedLog : m [ 0 ] . toString ( ) } ;
1653- }
1654- const initFailMsgBegin = logs . lastIndexOf ( 'gopls client:' ) ;
1655- if ( initFailMsgBegin > - 1 ) {
1656- // client start failed. Capture up to the 'Code:' line.
1657- const initFailMsgEnd = logs . indexOf ( 'Code: ' , initFailMsgBegin ) ;
1658- if ( initFailMsgEnd > - 1 ) {
1659- const lineEnd = logs . indexOf ( '\n' , initFailMsgEnd ) ;
1660- return {
1661- sanitizedLog :
1662- lineEnd > - 1
1663- ? logs . substr ( initFailMsgBegin , lineEnd - initFailMsgBegin )
1664- : logs . substr ( initFailMsgBegin )
1665- } ;
1666- }
1667- }
1668- if ( logs . lastIndexOf ( 'Usage:' ) > - 1 ) {
1669- return { failureReason : GoplsFailureModes . INCORRECT_COMMAND_USAGE } ;
1670- }
1671- return { failureReason : GoplsFailureModes . UNRECOGNIZED_CRASH_PATTERN } ;
1473+ return logs ? logs . lastIndexOf ( 'Usage:' ) > - 1 : false ;
16721474}
16731475
16741476const GOPLS_FETCH_VULNCHECK_RESULT = 'gopls.fetch_vulncheck_result' ;
@@ -1719,22 +1521,3 @@ export function maybePromptForTelemetry(goCtx: GoExtensionContext) {
17191521 } ;
17201522 callback ( ) ;
17211523}
1722-
1723- async function getGoplsStats ( binpath ?: string ) {
1724- if ( ! binpath ) {
1725- return 'gopls path unknown' ;
1726- }
1727- const env = toolExecutionEnvironment ( ) ;
1728- const cwd = getWorkspaceFolderPath ( ) ;
1729- const start = new Date ( ) ;
1730- const execFile = util . promisify ( cp . execFile ) ;
1731- try {
1732- const timeout = 60 * 1000 ; // 60sec;
1733- const { stdout } = await execFile ( binpath , [ 'stats' , '-anon' ] , { env, cwd, timeout } ) ;
1734- return stdout ;
1735- } catch ( e ) {
1736- const duration = new Date ( ) . getTime ( ) - start . getTime ( ) ;
1737- console . log ( `gopls stats -anon failed: ${ JSON . stringify ( e ) } ` ) ;
1738- return `gopls stats -anon failed after ${ duration } ms. Please check if gopls is killed by OS.` ;
1739- }
1740- }
0 commit comments