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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.action.IAction;
Expand Down Expand Up @@ -90,6 +89,7 @@
import software.aws.toolkits.eclipse.amazonq.util.QEclipseEditorUtils;
import software.aws.toolkits.eclipse.amazonq.preferences.AmazonQPreferencePage;
import software.aws.toolkits.eclipse.amazonq.telemetry.service.DefaultTelemetryService;
import software.aws.toolkits.eclipse.amazonq.util.AbapUtil;
import software.aws.toolkits.eclipse.amazonq.util.Constants;
import software.aws.toolkits.eclipse.amazonq.util.ObjectMapperFactory;
import software.aws.toolkits.eclipse.amazonq.util.ThemeDetector;
Expand Down Expand Up @@ -539,11 +539,19 @@ public final void didCopyFile(final Object params) {

@Override
public final void didWriteFile(final Object params) {
var path = extractFilePathFromParams(params);
if (AbapUtil.isAbapFile(path)) {
AbapUtil.updateAdtServer(path);
Comment on lines +543 to +544

Choose a reason for hiding this comment

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

I wonder do we still want to depend on the extension or should we use editor to check if is in adt plugin environment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For this situation (file not open)we only have the path, no editor is available in this case

}
refreshProjects();
}

@Override
public final void didAppendFile(final Object params) {
var path = extractFilePathFromParams(params);
if (AbapUtil.isAbapFile(path)) {
AbapUtil.updateAdtServer(path);
}
refreshProjects();
}

Expand All @@ -559,6 +567,7 @@ public final void didCreateDirectory(final Object params) {

private void refreshProjects() {
WorkspaceUtils.refreshAllProjects();
WorkspaceUtils.refreshAdtViews();
}

private boolean isUriInWorkspace(final String uri) {
Expand All @@ -577,6 +586,15 @@ private boolean isUriInWorkspace(final String uri) {
}
}

private String extractFilePathFromParams(final Object params) {
if (params instanceof Map) {
var map = (Map<?, ?>) params;
Object path = map.get("path");
return path != null ? path.toString() : null;
Copy link

Choose a reason for hiding this comment

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

do we need a instance check before toString?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The path !=null check does that I believe

}
return null;
}

@Override
public final void sendPinnedContext(final Object params) {
Object updatedParams = params;
Expand Down
165 changes: 164 additions & 1 deletion plugin/src/software/aws/toolkits/eclipse/amazonq/util/AbapUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,28 @@

package software.aws.toolkits.eclipse.amazonq.util;

import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.jface.dialogs.MessageDialog;

import software.aws.toolkits.eclipse.amazonq.plugin.Activator;

/**
* Utility class for ABAP/ADT-related constants and helper methods. Centralizes
Expand All @@ -18,6 +37,9 @@ public final class AbapUtil {
public static final String ADT_CLASS_NAME_PATTERN = "com.sap.adt";
public static final String SEMANTIC_BUNDLE_ID = "org.eclipse.core.resources.semantic";
public static final String SEMANTIC_CACHE_FOLDER = ".cache";
private static final Set<String> ABAP_EXTENSIONS = Set.of("asprog", "aclass", "asinc", "aint", "assrvds",
"asbdef", "asddls", "astablds", "astabldt", "amdp", "apack", "asrv", "aobj", "aexit", "abdef",
"acinc", "asfugr", "apfugr", "asfunc", "asfinc", "apfunc", "apfinc");

private AbapUtil() {
// Prevent instantiation
Expand Down Expand Up @@ -55,6 +77,20 @@ public static String convertSemanticUriToPath(final String semanticUri) {
return cachePath.toFile().toURI().toString();
}

/**
* Checks if a file is an ABAP ADT file referenced via the semantic cache.
* @param file the file to check
* @return true if it's an ABAP ADT file
*/
public static boolean isAbapFile(final String filePath) {
if (StringUtils.isBlank(filePath)) {
return false;
}
// checks whether a given file is an abap file present in cache by looking for certain strings in the path
return StringUtils.containsIgnoreCase(filePath, AbapUtil.SEMANTIC_BUNDLE_ID) && StringUtils.containsIgnoreCase(filePath, ".adt");
}


/**
* Gets the semantic cache path for a given workspace-relative path.
* @param workspaceRelativePath the workspace-relative path
Expand All @@ -72,4 +108,131 @@ public static String getSemanticCachePath(final String workspaceRelativePath) {
}
return cachePath.toString();
}

public static String convertCachePathToWorkspaceRelativePath(final String cachePath) {
IPath semanticCacheBase = Platform.getStateLocation(Platform.getBundle(SEMANTIC_BUNDLE_ID))
.append(SEMANTIC_CACHE_FOLDER);

String cacheBasePath = semanticCacheBase.toString().toLowerCase();
String normalizedCachePath = cachePath.replace("\\", "/").toLowerCase();

if (normalizedCachePath.startsWith(cacheBasePath)) {
String relativePath = cachePath.substring(cacheBasePath.length());
if (relativePath.startsWith("/") || relativePath.startsWith("\\")) {
relativePath = relativePath.substring(1);
}
return relativePath;
}
return null;
}

/**
* For an ABAP ADT file, update the remote server with the file update by triggering a save.
* @param filePath
*/
public static void updateAdtServer(final String filePath) {
Display.getDefault().asyncExec(() -> {
try {
if (!AbapUtil.isAbapFile(filePath)) {
return;
}

IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (window == null) {
return;
}

IWorkbenchPage page = window.getActivePage();
if (page == null) {
return;
}

IFile workspaceFile = getWorkspaceFileFromCache(filePath);
if (workspaceFile == null || !workspaceFile.exists()) {
Activator.getLogger().info("File not found in workspace, new file may need to be configured with the workspace: " + filePath);
MessageDialog.openError(window.getShell(), "File Not Found in Workspace",
"File not found in workspace, new file may need to be configured with the workspace. See file updates at: " + filePath);
return;
}

// Check if file is already open in an editor
var existingEditor = findOpenEditor(page, workspaceFile);
if (existingEditor != null) {
saveOpenEditor(existingEditor);
} else {
// File not open, open it temporarily in an invisible editor and save to update remote
var descriptor = PlatformUI.getWorkbench().getEditorRegistry().getDefaultEditor(workspaceFile.getName());
var editor = page.openEditor(new FileEditorInput(workspaceFile),
descriptor != null ? descriptor.getId() : "org.eclipse.ui.DefaultTextEditor", false);
if (editor != null && AbapUtil.isAdtEditor(editor.getClass().getName())) {
saveOpenEditor(editor);
page.closeEditor(editor, false);
}
}
} catch (Exception e) {
Activator.getLogger().error("Failed to update ABAP ADT editor", e);
}
});
}

private static IEditorPart findOpenEditor(final IWorkbenchPage page, final IFile file) {
for (IEditorReference editorRef : page.getEditorReferences()) {
var editor = editorRef.getEditor(false);
if (editor != null && editor.getEditorInput() instanceof FileEditorInput) {
var input = (FileEditorInput) editor.getEditorInput();
if (file.equals(input.getFile())) {
return editor;
}
}
}
return null;
}

private static IFile getWorkspaceFileFromCache(final String cachePath) {
try {
String workspaceRelativePath = AbapUtil.convertCachePathToWorkspaceRelativePath(cachePath);
if (workspaceRelativePath != null) {
for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
if (project.isOpen()) {
String projectRelativePath = workspaceRelativePath;
if (StringUtils.startsWithIgnoreCase(workspaceRelativePath, project.getName())) {
projectRelativePath = projectRelativePath.substring(project.getName().length() + 1);
}
IFile file = project.getFile(projectRelativePath);
if (file.exists()) {
return file;
}
}
}
}
} catch (Exception e) {
Activator.getLogger().error("Error finding workspace file associated with the cache", e);
}
return null;
}

private static void saveOpenEditor(final IEditorPart editor) {
try {
ITextEditor textEditor = editor.getAdapter(ITextEditor.class);
if (textEditor == null) {
return;
}

var provider = textEditor.getDocumentProvider();
if (provider != null) {
IEditorInput editorInput = editor.getEditorInput();
provider.resetDocument(editorInput);
IDocument document = provider.getDocument(editorInput);
if (document != null) {
var content = document.get();
document.set(content); // Mark as dirty
}
}

editor.doSave(new NullProgressMonitor());
} catch (Exception e) {
Activator.getLogger().error("Failed to save editor", e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.IHandlerService;

import software.aws.toolkits.eclipse.amazonq.plugin.Activator;

Expand All @@ -25,4 +30,32 @@ public static void refreshAllProjects() {
}
}

public static void refreshAdtViews() {
try {
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (window == null) {
return;
}

IWorkbenchPage page = window.getActivePage();
if (page == null) {
return;
}

IViewPart adtView = page.findView("com.sap.adt.tools.core.ui.views.objectnavigator");
if (adtView != null) {
// Force refresh of ADT view which triggers server sync
adtView.getSite().getPage().activate(adtView);

// Send refresh command to ADT view
var handlerService = adtView.getSite().getService(IHandlerService.class);
if (handlerService != null) {
handlerService.executeCommand("org.eclipse.ui.file.refresh", null);
}
}
} catch (Exception e) {
Activator.getLogger().error("Failed to refresh ADT views", e);
}
}

}