Skip to content

Commit 17e0cef

Browse files
authored
Allow sending document without active focus in Chat requests (#421)
1 parent 7c2d440 commit 17e0cef

File tree

4 files changed

+108
-38
lines changed

4 files changed

+108
-38
lines changed

server/aws-lsp-codewhisperer/src/language-server/chat/contexts/documentContext.test.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -99,35 +99,6 @@ describe('DocumentContext', () => {
9999

100100
assert.deepStrictEqual(result, expected)
101101
})
102-
103-
it('returns undefined cursorState if the end position was collapsed', async () => {
104-
const documentContextExtractor = new DocumentContextExtractor({ characterLimits: 0 })
105-
106-
const expected: DocumentContext = {
107-
programmingLanguage: { languageName: 'typescript' },
108-
relativeFilePath: 'file://test.ts',
109-
documentSymbols: [],
110-
text: '',
111-
hasCodeSnippet: false,
112-
totalEditorCharacters: mockTypescriptCodeBlock.length,
113-
cursorState: undefined,
114-
}
115-
116-
const result = await documentContextExtractor.extractDocumentContext(mockTSDocument, {
117-
range: {
118-
start: {
119-
line: 1,
120-
character: 13,
121-
},
122-
end: {
123-
line: 1,
124-
character: 13,
125-
},
126-
},
127-
})
128-
129-
assert.deepStrictEqual(result, expected)
130-
})
131102
})
132103

133104
it('handles other languages correctly', async () => {

server/aws-lsp-codewhisperer/src/language-server/chat/contexts/triggerContext.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TriggerType } from '@aws/chat-client-ui-types'
22
import { ChatTriggerType, GenerateAssistantResponseCommandInput, UserIntent } from '@amzn/codewhisperer-streaming'
3-
import { ChatParams } from '@aws/language-server-runtimes/server-interface'
3+
import { ChatParams, CursorState } from '@aws/language-server-runtimes/server-interface'
44
import { Features } from '../../types'
55
import { DocumentContext, DocumentContextExtractor } from './documentContext'
66

@@ -10,6 +10,8 @@ export interface TriggerContext extends Partial<DocumentContext> {
1010
}
1111

1212
export class QChatTriggerContext {
13+
private static readonly DEFAULT_CURSOR_STATE: CursorState = { position: { line: 0, character: 0 } }
14+
1315
#workspace: Features['workspace']
1416
#documentContextExtractor: DocumentContextExtractor
1517

@@ -19,7 +21,7 @@ export class QChatTriggerContext {
1921
}
2022

2123
async getNewTriggerContext(params: ChatParams): Promise<TriggerContext> {
22-
const documentContext: DocumentContext | undefined = await this.#extractDocumentContext(params)
24+
const documentContext: DocumentContext | undefined = await this.extractDocumentContext(params)
2325

2426
return {
2527
...documentContext,
@@ -66,16 +68,22 @@ export class QChatTriggerContext {
6668
this.#documentContextExtractor.dispose()
6769
}
6870

69-
async #extractDocumentContext(
71+
// public for testing
72+
async extractDocumentContext(
7073
input: Pick<ChatParams, 'cursorState' | 'textDocument'>
7174
): Promise<DocumentContext | undefined> {
72-
const { textDocument: textDocumentIdentifier, cursorState = [] } = input
75+
const { textDocument: textDocumentIdentifier, cursorState } = input
7376

7477
const textDocument =
7578
textDocumentIdentifier?.uri && (await this.#workspace.getTextDocument(textDocumentIdentifier.uri))
7679

7780
return textDocument
78-
? this.#documentContextExtractor.extractDocumentContext(textDocument, cursorState[0])
81+
? this.#documentContextExtractor.extractDocumentContext(
82+
textDocument,
83+
// we want to include a default position if a text document is found so users can still ask questions about the opened file
84+
// the range will be expanded up to the max characters downstream
85+
cursorState?.[0] ?? QChatTriggerContext.DEFAULT_CURSOR_STATE
86+
)
7987
: undefined
8088
}
8189

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { TestFeatures } from '@aws/language-server-runtimes/testing'
2+
import { QChatTriggerContext } from './triggerContext'
3+
import assert = require('assert')
4+
import { TextDocument } from 'vscode-languageserver-textdocument'
5+
import { DocumentContext, DocumentContextExtractor } from './documentContext'
6+
import sinon = require('sinon')
7+
8+
describe('QChatTriggerContext', () => {
9+
let testFeatures: TestFeatures
10+
11+
const filePath = 'file://test.ts'
12+
const mockTSDocument = TextDocument.create(filePath, 'typescript', 1, '')
13+
const mockDocumentContext: DocumentContext = {
14+
text: '',
15+
programmingLanguage: { languageName: 'typescript' },
16+
relativeFilePath: 'file://test.ts',
17+
documentSymbols: [],
18+
hasCodeSnippet: false,
19+
totalEditorCharacters: 0,
20+
}
21+
22+
beforeEach(() => {
23+
testFeatures = new TestFeatures()
24+
sinon.stub(DocumentContextExtractor.prototype, 'extractDocumentContext').resolves(mockDocumentContext)
25+
})
26+
27+
afterEach(() => {
28+
sinon.restore()
29+
})
30+
31+
it('returns null if text document is not defined in params', async () => {
32+
const triggerContext = new QChatTriggerContext(testFeatures.workspace, testFeatures.logging)
33+
34+
const documentContext = await triggerContext.extractDocumentContext({
35+
cursorState: [
36+
{
37+
position: {
38+
line: 5,
39+
character: 0,
40+
},
41+
},
42+
],
43+
textDocument: undefined,
44+
})
45+
46+
assert.deepStrictEqual(documentContext, undefined)
47+
})
48+
49+
it('returns null if text document is not found', async () => {
50+
const triggerContext = new QChatTriggerContext(testFeatures.workspace, testFeatures.logging)
51+
52+
const documentContext = await triggerContext.extractDocumentContext({
53+
cursorState: [
54+
{
55+
position: {
56+
line: 5,
57+
character: 0,
58+
},
59+
},
60+
],
61+
textDocument: {
62+
uri: filePath,
63+
},
64+
})
65+
66+
assert.deepStrictEqual(documentContext, undefined)
67+
})
68+
69+
it('passes default cursor state if no cursor is found', async () => {
70+
const triggerContext = new QChatTriggerContext(testFeatures.workspace, testFeatures.logging)
71+
72+
const documentContext = await triggerContext.extractDocumentContext({
73+
cursorState: [],
74+
textDocument: {
75+
uri: filePath,
76+
},
77+
})
78+
79+
assert.deepStrictEqual(documentContext, undefined)
80+
})
81+
82+
it('includes cursor state from the parameters and text document if found', async () => {
83+
const triggerContext = new QChatTriggerContext(testFeatures.workspace, testFeatures.logging)
84+
85+
testFeatures.openDocument(mockTSDocument)
86+
const documentContext = await triggerContext.extractDocumentContext({
87+
cursorState: [],
88+
textDocument: {
89+
uri: filePath,
90+
},
91+
})
92+
93+
assert.deepStrictEqual(documentContext, mockDocumentContext)
94+
})
95+
})

server/aws-lsp-codewhisperer/src/language-server/chat/contexts/utils.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,6 @@ export function getExtendedCodeBlockRange(
6161
* reflects the position in the entire document needs to be adjusted.
6262
*/
6363
export function getSelectionWithinExtendedRange(selection: Range, extendedRange: Range): Range | undefined {
64-
if (selection.start.line === selection.end.line && selection.start.character === selection.end.character) {
65-
return undefined
66-
}
67-
6864
return {
6965
start: {
7066
line: selection.start.line - extendedRange.start.line,

0 commit comments

Comments
 (0)