forked from microsoft/copilot-for-eclipse
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCompletionManager.java
More file actions
196 lines (174 loc) · 7.81 KB
/
Copy pathCompletionManager.java
File metadata and controls
196 lines (174 loc) · 7.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package com.microsoft.copilot.eclipse.ui.completion;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.codemining.AbstractCodeMining;
import org.eclipse.jface.text.codemining.ICodeMining;
import org.eclipse.jface.text.source.ISourceViewerExtension5;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.ui.texteditor.ITextEditor;
import com.microsoft.copilot.eclipse.core.CopilotCore;
import com.microsoft.copilot.eclipse.core.completion.CompletionProvider;
import com.microsoft.copilot.eclipse.core.lsp.CopilotLanguageServerConnection;
import com.microsoft.copilot.eclipse.ui.completion.codemining.BlockGhostText;
import com.microsoft.copilot.eclipse.ui.completion.codemining.LineContentGhostText;
import com.microsoft.copilot.eclipse.ui.completion.codemining.LineEndGhostText;
import com.microsoft.copilot.eclipse.ui.preferences.LanguageServerSettingManager;
/**
* A class to listen events which are completion related and notify the completion manager to render the ghost text or
* apply the suggestion to document.
*/
public class CompletionManager extends BaseCompletionManager {
/**
* Creates a new completion manager. The manager is responsible for trigger the completion, apply suggestions to the
* document. And schedule the rendering of ghost text.
*/
public CompletionManager(CopilotLanguageServerConnection lsConnection, CompletionProvider provider,
ITextEditor editor, LanguageServerSettingManager settingsManager) {
super(lsConnection, provider, editor, settingsManager);
}
/**
* Update the ghost texts for rendering.
*/
@Override
protected void updateGhostTexts(Position position) {
resolveCodeMiningGhostTexts(position);
this.updateCodeMinings();
}
/**
* Clear the completion ghost text.
*/
@Override
public void clearGhostTexts() {
disableContext();
if (this.suggestionUpdateManager != null) {
this.suggestionUpdateManager.reset();
}
this.codeMinings.clear();
this.updateCodeMinings();
// Clear legacy vertical indentation for the block ghost text line when the line is out of the visible range.
// Fix issue: https://github.com/microsoft/copilot-eclipse/issues/105
redrawBlockLineAtModelOffset(this.cachedModelOffset);
}
private void updateCodeMinings() {
// Use asyncExec to avoid deadlock: updateCodeMinings() triggers LSP4E code that acquires
// locks which may be held by background threads waiting for the document lock.
// When called from document change events, the document lock is still held, causing deadlock.
// See: https://github.com/microsoft/copilot-eclipse-feedback/issues/96
StyledText textWidget = textViewer.getTextWidget();
if (textWidget == null || textWidget.isDisposed()) {
return;
}
textWidget.getDisplay().asyncExec(() -> {
if (!textWidget.isDisposed() && textViewer instanceof ISourceViewerExtension5 sve) {
sve.updateCodeMinings();
}
});
}
private void resolveCodeMiningGhostTexts(Position position) {
if (this.suggestionUpdateManager.getSize() == 0) {
this.codeMinings.clear();
return;
}
List<ICodeMining> cm = new ArrayList<>();
String firstLine = this.suggestionUpdateManager.getFirstLine();
if (StringUtils.isNotEmpty(firstLine)) {
try {
cm.addAll(getCodeMiningGhostTexts(position, this.document, getCurrentLine(position), firstLine));
} catch (BadLocationException e) {
CopilotCore.LOGGER.error(e);
}
}
String remainingLines = this.suggestionUpdateManager.getRemainingLines();
if (StringUtils.isNotEmpty(remainingLines)) {
try {
int lineOffset = document.getLineOfOffset(position.offset) + 1;
if (lineOffset >= document.getNumberOfLines()) {
return;
}
cm.add(new BlockGhostText(lineOffset, document, null, remainingLines));
} catch (BadLocationException e) {
CopilotCore.LOGGER.error(e);
}
}
if (cm.size() != this.codeMinings.size()) {
this.updateCodeMinings();
} else {
for (int i = 0; i < cm.size(); i++) {
AbstractCodeMining newMining = (AbstractCodeMining) cm.get(i);
AbstractCodeMining oldMining = (AbstractCodeMining) this.codeMinings.get(i);
if (newMining.getPosition().getOffset() != oldMining.getPosition().getOffset()
|| newMining.getPosition().getLength() != oldMining.getPosition().getLength()
|| !newMining.getLabel().equals(oldMining.getLabel())) {
this.updateCodeMinings();
break;
}
}
}
this.codeMinings = cm;
}
private List<AbstractCodeMining> getCodeMiningGhostTexts(Position position, IDocument document, String documentLine,
String completionLine) throws BadLocationException {
// LineContentCodeMining for eclipse 2024-12 requires position length > 0, this is a work around.
Position lineContentPostion = new Position(position.getOffset(), position.getLength());
if (lineContentPostion.getLength() == 0 && position.getOffset() + 1 <= document.getLength()) {
lineContentPostion.setLength(1);
}
List<AbstractCodeMining> ghostTexts = new ArrayList<>();
if (documentLine.isEmpty()) {
ghostTexts
.add(new LineEndGhostText(document, document.getLineOfOffset(position.getOffset()), null, completionLine));
return ghostTexts;
}
if (documentLine.isBlank()) {
ghostTexts.add(new LineContentGhostText(position, true, null, completionLine));
return ghostTexts;
}
// strip trailing whitespaces, tabs, etc., across all platforms. These characters are not visually considered the
// end of document line, so we should ignore them when calculating the starting point offset for ghost text
// rendering.
int i = Math.max(0, StringUtils.stripEnd(documentLine, null).length() - 1);
int j = completionLine.length() - 1;
StringBuilder sb = new StringBuilder();
while (i >= 0 && j >= 0) {
if (documentLine.charAt(i) == completionLine.charAt(j)) {
if (sb.length() > 0) {
// passing i + 1 here because the current char indexed with i are the same, the ghost
// text should display the content which is different from the document.
// while calculating 'i' here, we use the original document line without stripping trailing whitespaces since
// if there are trailing whitespaces, the ghost text should always be the inline ghost text instead of the eol
// ghost text.
ghostTexts.add(0, createCodeMiningGhostText(document, position.getOffset(), i + 1, sb.toString(),
i == documentLine.length() - 1));
sb.setLength(0);
continue;
}
i--;
j--;
} else {
sb.insert(0, completionLine.charAt(j--));
}
}
String remaining = sb.toString();
if (j >= 0) {
remaining = completionLine.substring(0, j + 1) + remaining;
}
if (StringUtils.isNotEmpty(remaining)) {
ghostTexts.add(0,
createCodeMiningGhostText(document, position.getOffset(), i, remaining, i == documentLine.length() - 1));
}
return ghostTexts;
}
private AbstractCodeMining createCodeMiningGhostText(IDocument document, int base, int offset, String text,
boolean isEol) throws BadLocationException {
// use Math.max to avoid negative offset (i may == -1 after the while loop))
int position = isEol ? base + offset : Math.max(base + offset, base);
return isEol ? new LineEndGhostText(document, document.getLineOfOffset(position), null, text)
: new LineContentGhostText(new Position(position, 1), true, null, text);
}
}