Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 3 additions & 2 deletions com.microsoft.copilot.eclipse.ui/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Require-Bundle: com.microsoft.copilot.eclipse.core;bundle-version="0.20.0",
org.eclipse.ui.editors;bundle-version="3.17.100",
org.eclipse.ui;bundle-version="3.205.0",
org.eclipse.ui.navigator;bundle-version="3.12.200",
org.eclipse.jface.text;bundle-version="3.24.200",
org.eclipse.jface.text;bundle-version="3.24.200",
org.eclipse.core.runtime;bundle-version="[3.30.0,4.0.0)",
org.eclipse.core.expressions,
org.eclipse.jdt.annotation;resolution:=optional,
Expand Down Expand Up @@ -68,4 +68,5 @@ Require-Bundle: com.microsoft.copilot.eclipse.core;bundle-version="0.20.0",
org.eclipse.ui.editors,
org.eclipse.debug.core;resolution:=optional,
org.eclipse.jdt.core;resolution:=optional,
org.eclipse.jdt.debug;resolution:=optional
org.eclipse.jdt.debug;resolution:=optional,
org.eclipse.core.resources.semantic;bundle-version="0.9.0"
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.semantic.ISemanticFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.lsp4j.FileChangeType;
import org.eclipse.swt.widgets.Display;

import com.microsoft.copilot.eclipse.core.CopilotCore;
import com.microsoft.copilot.eclipse.core.lsp.protocol.InputSchema;
Expand Down Expand Up @@ -197,6 +203,8 @@ private void applyChangesToFile(String changedContent, IFile file) throws CoreEx
if (!validateEdit(file)) {
throw new IllegalStateException("File validation failed for " + file.getFullPath());
}
verifyTransportRequestForAdtLock(file);

ByteArrayInputStream inputStream = getInputStream(changedContent, file);

// Set the file contents
Expand All @@ -207,6 +215,64 @@ private void applyChangesToFile(String changedContent, IFile file) throws CoreEx

// Close the input stream
inputStream.close();

var buffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(file.getFullPath(), LocationKind.IFILE);
if (buffer != null && buffer.isDirty()) {
// Some editors (e.g. the ABAP source editor) do not listen for changes to the underlying file and
// therefore leave a dirty, out-of-date buffer after we have written the new contents to disk. Force
// the buffer to reload from disk by reverting it, so the open editor reflects the edit we just applied.
Display.getDefault().asyncExec(() -> {
try {
buffer.revert(new NullProgressMonitor());
} catch (CoreException e) {
CopilotCore.LOGGER.error(e);
}
});
}
}

private static final String ADT_LOCK_RESULT_CLASS = "com.sap.adt.tools.core.internal.locking.AdtLockResult";
private static final QualifiedName ADT_LOCK_RESULT_PROPERTY = new QualifiedName("com.sap.adt.tools.filesystem",
"LockResult");

/**
* When an ADT (ABAP Development Tools) file is locked in a transport-relevant way, it must be associated with a
* transport request before it can be edited. The lock result is stored as a session property on the semantic file
* and is accessed reflectively, as the ADT classes are not available at compile time.
*
* @param file the file about to be changed
* @throws CoreException if the file is transport-relevant but has no transport request number assigned
*/
private void verifyTransportRequestForAdtLock(IFile file) throws CoreException {
var semanticFile = file.getAdapter(ISemanticFile.class);
if (semanticFile == null) {
return;
}
Object lockResult = semanticFile.getSessionProperty(ADT_LOCK_RESULT_PROPERTY);
if (lockResult == null || !ADT_LOCK_RESULT_CLASS.equals(lockResult.getClass().getCanonicalName())) {
return;
}
Boolean transportRelevant = readField(lockResult, "transportRelevant", Boolean.class);
if (!Boolean.TRUE.equals(transportRelevant)) {
return;
}
String transportRequestNumber = readField(lockResult, "transportRequestNumber", String.class);
if (transportRequestNumber == null || transportRequestNumber.isEmpty()) {
throw new CoreException(Status.error(String.format(
"Cannot edit %s: the file is transport-relevant but no transport request number is assigned.",
file.getFullPath())));
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done with [40fe27c]

}

private <T> T readField(Object target, String fieldName, Class<T> type) {
try {
var field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return type.cast(field.get(target));
} catch (ReflectiveOperationException | SecurityException e) {
CopilotCore.LOGGER.error("Failed to read field '" + fieldName + "' from " + target.getClass().getName(), e);
return null;
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done with [40fe27c]


private ByteArrayInputStream getInputStream(String changedContent, IFile file) {
Expand Down