|
3 | 3 |
|
4 | 4 | package software.aws.toolkits.jetbrains.services.codewhisperer.service
|
5 | 5 |
|
6 |
| -import com.intellij.openapi.command.WriteCommandAction |
7 | 6 | import com.intellij.openapi.components.service
|
8 |
| -import com.intellij.openapi.editor.RangeMarker |
9 |
| -import com.intellij.openapi.util.TextRange |
10 |
| -import com.intellij.psi.PsiDocumentManager |
11 |
| -import com.intellij.psi.PsiFileFactory |
12 |
| -import com.intellij.psi.codeStyle.CodeStyleManager |
13 |
| -import com.intellij.util.LocalTimeCounter |
14 | 7 | import software.amazon.awssdk.services.codewhispererruntime.model.Completion
|
15 |
| -import software.amazon.awssdk.services.codewhispererruntime.model.Reference |
16 | 8 | import software.amazon.awssdk.services.codewhispererruntime.model.Span
|
17 | 9 | import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext
|
18 | 10 | import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk
|
19 | 11 | import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCompletionType
|
20 | 12 | import kotlin.math.max
|
| 13 | +import kotlin.math.min |
21 | 14 |
|
22 | 15 | class CodeWhispererRecommendationManager {
|
23 |
| - fun reformat(requestContext: RequestContext, recommendation: Completion): Completion { |
24 |
| - val project = requestContext.project |
25 |
| - val editor = requestContext.editor |
26 |
| - val document = editor.document |
27 |
| - |
| 16 | + fun reformatReference(requestContext: RequestContext, recommendation: Completion): Completion { |
28 | 17 | // startOffset is the offset at the start of user input since invocation
|
29 | 18 | val invocationStartOffset = requestContext.caretPosition.offset
|
30 |
| - val startOffsetSinceUserInput = editor.caretModel.offset |
31 |
| - |
32 |
| - // Create a temp file for capturing reformatted text and updated content spans |
33 |
| - val tempPsiFile = PsiDocumentManager.getInstance(project).getPsiFile(document)?.let { psiFile -> |
34 |
| - PsiFileFactory.getInstance(project).createFileFromText( |
35 |
| - "codewhisperer_temp", |
36 |
| - psiFile.fileType, |
37 |
| - document.text, |
38 |
| - LocalTimeCounter.currentTime(), |
39 |
| - true |
40 |
| - ) |
41 |
| - } |
42 |
| - val tempDocument = tempPsiFile?.let { psiFile -> |
43 |
| - PsiDocumentManager.getInstance(project).getDocument(psiFile) |
44 |
| - } ?: return recommendation |
45 | 19 |
|
| 20 | + val startOffsetSinceUserInput = requestContext.editor.caretModel.offset |
46 | 21 | val endOffset = invocationStartOffset + recommendation.content().length
|
| 22 | + |
47 | 23 | if (startOffsetSinceUserInput > endOffset) return recommendation
|
48 |
| - WriteCommandAction.runWriteCommandAction(project) { |
49 |
| - tempDocument.insertString(invocationStartOffset, recommendation.content()) |
50 |
| - PsiDocumentManager.getInstance(project).commitDocument(tempDocument) |
51 |
| - } |
52 |
| - val rangeMarkers = mutableMapOf<RangeMarker, Reference>() |
53 |
| - recommendation.references().forEach { |
54 |
| - val referenceStart = invocationStartOffset + it.recommendationContentSpan().start() |
55 |
| - if (referenceStart >= endOffset) return@forEach |
56 |
| - val tempEnd = invocationStartOffset + it.recommendationContentSpan().end() |
57 |
| - val referenceEnd = if (tempEnd <= endOffset) tempEnd else endOffset |
58 |
| - rangeMarkers[ |
59 |
| - tempDocument.createRangeMarker( |
60 |
| - referenceStart, |
61 |
| - referenceEnd |
62 |
| - ) |
63 |
| - ] = it |
64 |
| - } |
65 |
| - val tempRangeMarker = tempDocument.createRangeMarker(invocationStartOffset, endOffset) |
66 | 24 |
|
67 |
| - // Currently, only reformat(adjust line indent) starting from user's input |
68 |
| - WriteCommandAction.runWriteCommandAction(project) { |
69 |
| - CodeStyleManager.getInstance(project).adjustLineIndent(tempPsiFile, TextRange(startOffsetSinceUserInput, endOffset)) |
| 25 | + val reformattedReferences = recommendation.references().filter { |
| 26 | + val referenceStart = invocationStartOffset + it.recommendationContentSpan().start() |
| 27 | + val referenceEnd = invocationStartOffset + it.recommendationContentSpan().end() |
| 28 | + referenceStart < endOffset && referenceEnd > startOffsetSinceUserInput |
| 29 | + }.map { |
| 30 | + val referenceStart = invocationStartOffset + it.recommendationContentSpan().start() |
| 31 | + val referenceEnd = invocationStartOffset + it.recommendationContentSpan().end() |
| 32 | + val updatedReferenceStart = max(referenceStart, startOffsetSinceUserInput) |
| 33 | + val updatedReferenceEnd = min(referenceEnd, endOffset) |
| 34 | + it.toBuilder().recommendationContentSpan( |
| 35 | + Span.builder() |
| 36 | + .start(updatedReferenceStart - invocationStartOffset) |
| 37 | + .end(updatedReferenceEnd - invocationStartOffset) |
| 38 | + .build() |
| 39 | + ).build() |
70 | 40 | }
|
71 | 41 |
|
72 |
| - val reformattedRecommendation = tempDocument.getText(TextRange(tempRangeMarker.startOffset, tempRangeMarker.endOffset)) |
73 |
| - |
74 |
| - val reformattedReferences = rangeMarkers.map { (rangeMarker, reference) -> |
75 |
| - reformatReference(reference, rangeMarker, invocationStartOffset) |
76 |
| - } |
77 | 42 | return Completion.builder()
|
78 |
| - .content(reformattedRecommendation) |
| 43 | + .content(recommendation.content()) |
79 | 44 | .references(reformattedReferences)
|
80 | 45 | .build()
|
81 | 46 | }
|
82 | 47 |
|
83 |
| - /** |
84 |
| - * Build new reference with updated contentSpan(start and end). Since it's reformatted, take the new start and |
85 |
| - * end from the rangeMarker which automatically tracks the range after reformatting |
86 |
| - */ |
87 |
| - fun reformatReference(originalReference: Reference, rangeMarker: RangeMarker, invocationStartOffset: Int): Reference { |
88 |
| - rangeMarker.apply { |
89 |
| - val documentContent = document.charsSequence |
90 |
| - |
91 |
| - // has to plus 1 because right boundary is exclusive |
92 |
| - val spanEndOffset = documentContent.subSequence(0, endOffset).indexOfLast { char -> char != '\n' } + 1 |
93 |
| - return originalReference |
94 |
| - .toBuilder() |
95 |
| - .recommendationContentSpan( |
96 |
| - Span.builder() |
97 |
| - .start(startOffset - invocationStartOffset) |
98 |
| - .end(spanEndOffset - invocationStartOffset) |
99 |
| - .build() |
100 |
| - ) |
101 |
| - .build() |
102 |
| - } |
103 |
| - } |
104 |
| - |
105 | 48 | fun buildRecommendationChunks(
|
106 | 49 | recommendation: String,
|
107 | 50 | matchingSymbols: List<Pair<Int, Int>>
|
@@ -157,21 +100,33 @@ class CodeWhispererRecommendationManager {
|
157 | 100 | )
|
158 | 101 | }
|
159 | 102 |
|
160 |
| - val reformatted = reformat(requestContext, truncated) |
161 |
| - val isDiscardedByRightContextTruncationDedupe = !seen.add(reformatted.content()) |
| 103 | + val isDiscardedByRightContextTruncationDedupe = !seen.add(truncated.content()) |
| 104 | + val isDiscardedByBlankAfterTruncation = truncated.content().isBlank() |
| 105 | + if (isDiscardedByRightContextTruncationDedupe || isDiscardedByBlankAfterTruncation) { |
| 106 | + return@map DetailContext( |
| 107 | + requestId, |
| 108 | + it, |
| 109 | + truncated, |
| 110 | + isDiscarded = true, |
| 111 | + truncated.content().length != it.content().length, |
| 112 | + overlap, |
| 113 | + getCompletionType(it) |
| 114 | + ) |
| 115 | + } |
| 116 | + val reformatted = reformatReference(requestContext, truncated) |
162 | 117 | DetailContext(
|
163 | 118 | requestId,
|
164 | 119 | it,
|
165 | 120 | reformatted,
|
166 |
| - isDiscardedByRightContextTruncationDedupe, |
| 121 | + isDiscarded = false, |
167 | 122 | truncated.content().length != it.content().length,
|
168 | 123 | overlap,
|
169 | 124 | getCompletionType(it)
|
170 | 125 | )
|
171 | 126 | }
|
172 | 127 | }
|
173 | 128 |
|
174 |
| - private fun findRightContextOverlap( |
| 129 | + fun findRightContextOverlap( |
175 | 130 | requestContext: RequestContext,
|
176 | 131 | recommendation: Completion
|
177 | 132 | ): String {
|
|
0 commit comments