Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions packages/amazonq/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,21 @@ import { InlineCompletionManager } from '../app/inline/completion'
import { AmazonQLspAuth, encryptionKey, notificationTypes } from './auth'
import { AuthUtil } from 'aws-core-vscode/codewhisperer'
import { ConnectionMetadata } from '@aws/language-server-runtimes/protocol'
import { Settings, oidcClientName, createServerOptions, globals, Experiments, Commands } from 'aws-core-vscode/shared'
import {
Settings,
oidcClientName,
createServerOptions,
globals,
Experiments,
Commands,
validateNodeExe,
getLogger,
} from 'aws-core-vscode/shared'
import { activate } from './chat/activation'
import { AmazonQResourcePaths } from './lspInstaller'

const localize = nls.loadMessageBundle()
const logger = getLogger('amazonqLsp.lspClient')

export async function startLanguageServer(
extensionContext: vscode.ExtensionContext,
Expand All @@ -25,24 +35,27 @@ export async function startLanguageServer(

const serverModule = resourcePaths.lsp

const argv = [
'--nolazy',
'--preserve-symlinks',
'--stdio',
'--pre-init-encryption',
'--set-credentials-encryption-key',
]
const serverOptions = createServerOptions({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot that we have 2 of these modules. Will this module (packages/amazonq/src/lsp/client.ts) replace packages/core/src/amazonq/lsp/lspClient.ts @jpinkney-aws ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lsp/client.ts is the amazon q language server, lsp/lspClient.ts is the workspace context language server

Copy link
Contributor

@jpinkney-aws jpinkney-aws Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may end up deprecating the lsp/lspClient.ts one eventually now that workspace context is technically in flare

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may end up deprecating the lsp/lspClient.ts one eventually now that workspace context is technically in flare

yes. also, this logic is pretty similar yet lives in 2 different places

encryptionKey,
executable: resourcePaths.node,
serverModule,
execArgv: [
'--nolazy',
'--preserve-symlinks',
'--stdio',
'--pre-init-encryption',
'--set-credentials-encryption-key',
],
execArgv: argv,
})

const documentSelector = [{ scheme: 'file', language: '*' }]

const clientId = 'amazonq'
const traceServerEnabled = Settings.instance.isSet(`${clientId}.trace.server`)

await validateNodeExe(resourcePaths.node, resourcePaths.lsp, argv, logger)

// Options to control the language client
const clientOptions: LanguageClientOptions = {
// Register the server for json documents
Expand Down
55 changes: 3 additions & 52 deletions packages/core/src/amazonq/lsp/lspClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,13 @@ import { fs } from '../../shared/fs/fs'
import { getLogger } from '../../shared/logger/logger'
import globals from '../../shared/extensionGlobals'
import { ResourcePaths } from '../../shared/lsp/types'
import { createServerOptions } from '../../shared/lsp/utils/platform'
import { createServerOptions, validateNodeExe } from '../../shared/lsp/utils/platform'
import { waitUntil } from '../../shared/utilities/timeoutUtils'
import { ToolkitError } from '../../shared/errors'
import { ChildProcess } from '../../shared/utilities/processUtils'

const localize = nls.loadMessageBundle()

const key = crypto.randomBytes(32)
const logger = getLogger('amazonqLsp.lspClient')
const logger = getLogger('amazonqWorkspaceLsp')

/**
* LspClient manages the API call between VS Code extension and LSP server
Expand Down Expand Up @@ -226,53 +224,6 @@ export class LspClient {
}
}

/**
* Checks that we can actually run the `node` executable and execute code with it.
*/
async function validateNodeExe(nodePath: string, lsp: string, args: string[]) {
// Check that we can start `node` by itself.
const proc = new ChildProcess(nodePath, ['-e', 'console.log("ok " + process.version)'], { logging: 'no' })
const r = await proc.run()
const ok = r.exitCode === 0 && r.stdout.includes('ok')
if (!ok) {
const msg = `failed to run basic "node -e" test (exitcode=${r.exitCode}): ${proc.toString(false, true)}`
logger.error(msg)
throw new ToolkitError(`amazonqLsp: ${msg}`)
}

// Check that we can start `node …/lsp.js --stdio …`.
const lspProc = new ChildProcess(nodePath, [lsp, ...args], { logging: 'no' })
try {
// Start asynchronously (it never stops; we need to stop it below).
lspProc.run().catch((e) => logger.error('failed to run: %s', lspProc.toString(false, true)))

const ok2 =
!lspProc.stopped &&
(await waitUntil(
async () => {
return lspProc.pid() !== undefined
},
{
timeout: 5000,
interval: 100,
truthy: true,
}
))
const selfExit = await waitUntil(async () => lspProc.stopped, {
timeout: 500,
interval: 100,
truthy: true,
})
if (!ok2 || selfExit) {
throw new ToolkitError(
`amazonqLsp: failed to run (exitcode=${lspProc.exitCode()}): ${lspProc.toString(false, true)}`
)
}
} finally {
lspProc.stop(true)
}
}

/**
* Activates the language server (assumes the LSP server has already been downloaded):
* 1. start LSP server running over IPC protocol.
Expand Down Expand Up @@ -312,7 +263,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths

const documentSelector = [{ scheme: 'file', language: '*' }]

await validateNodeExe(resourcePaths.node, resourcePaths.lsp, debugOptions.execArgv)
await validateNodeExe(resourcePaths.node, resourcePaths.lsp, debugOptions.execArgv, logger)

// Options to control the language client
const clientOptions: LanguageClientOptions = {
Expand Down
49 changes: 49 additions & 0 deletions packages/core/src/shared/lsp/utils/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
*/

import { ToolkitError } from '../../errors'
import { Logger } from '../../logger/logger'
import { ChildProcess } from '../../utilities/processUtils'
import { waitUntil } from '../../utilities/timeoutUtils'
import { isDebugInstance } from '../../vscode/env'

export function getNodeExecutableName(): string {
Expand All @@ -24,6 +26,53 @@ function getEncryptionInit(key: Buffer): string {
return JSON.stringify(request) + '\n'
}

/**
* Checks that we can actually run the `node` executable and execute code with it.
*/
export async function validateNodeExe(nodePath: string, lsp: string, args: string[], logger: Logger) {
// Check that we can start `node` by itself.
const proc = new ChildProcess(nodePath, ['-e', 'console.log("ok " + process.version)'], { logging: 'no' })
const r = await proc.run()
const ok = r.exitCode === 0 && r.stdout.includes('ok')
if (!ok) {
const msg = `failed to run basic "node -e" test (exitcode=${r.exitCode}): ${proc.toString(false, true)}`
logger.error(msg)
throw new ToolkitError(`amazonqLsp: ${msg}`)
}

// Check that we can start `node …/lsp.js --stdio …`.
const lspProc = new ChildProcess(nodePath, [lsp, ...args], { logging: 'no' })
try {
// Start asynchronously (it never stops; we need to stop it below).
lspProc.run().catch((e) => logger.error('failed to run: %s', lspProc.toString(false, true)))

const ok2 =
!lspProc.stopped &&
(await waitUntil(
async () => {
return lspProc.pid() !== undefined
},
{
timeout: 5000,
interval: 100,
truthy: true,
}
))
const selfExit = await waitUntil(async () => lspProc.stopped, {
timeout: 500,
interval: 100,
truthy: true,
})
if (!ok2 || selfExit) {
throw new ToolkitError(
`amazonqLsp: failed to run (exitcode=${lspProc.exitCode()}): ${lspProc.toString(false, true)}`
)
}
} finally {
lspProc.stop(true)
}
}

export function createServerOptions({
encryptionKey,
executable,
Expand Down
Loading