Add basic editor sync

This commit is contained in:
MrLetsplay 2023-12-18 22:35:47 +01:00
parent 9812d9bd2f
commit e092ba0548
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
5 changed files with 282 additions and 124 deletions

View File

@ -30,6 +30,7 @@ import me.mrletsplay.shareclient.util.ChecksumUtil;
import me.mrletsplay.shareclient.util.Peer;
import me.mrletsplay.shareclient.util.ProjectRelativePath;
import me.mrletsplay.shareclient.util.ShareSession;
import me.mrletsplay.shareclient.util.listeners.ShareClientDocumentListener;
import me.mrletsplay.shareclient.util.listeners.ShareClientPageListener;
import me.mrletsplay.shareclient.util.listeners.ShareClientPartListener;
import me.mrletsplay.shareclient.util.listeners.ShareClientWindowListener;
@ -60,25 +61,33 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
// The shared instance
private static ShareClient plugin;
private ShareClientPartListener partListener = new ShareClientPartListener();
private ShareClientPartListener partListener;
private ShareView view;
private ShareSession activeSession;
private Map<ProjectRelativePath, SharedDocument> sharedDocuments;
public ShareClient() {
this.sharedDocuments = new HashMap<>();
}
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
partListener = new ShareClientPartListener();
getPreferenceStore().setDefault(ShareClientPreferences.SERVER_URI, "ws://localhost:5473");
getPreferenceStore().setDefault(ShareClientPreferences.SHOW_CURSORS, true);
// new ShareWSClient(URI.create("ws://localhost:5473")).connect();
PlatformUI.getWorkbench().addWindowListener(ShareClientWindowListener.INSTANCE);
Arrays.stream(PlatformUI.getWorkbench().getWorkbenchWindows()).forEach(w -> {
w.addPageListener(ShareClientPageListener.INSTANCE);
Arrays.stream(w.getPages()).forEach(p -> {
p.addPartListener(partListener);
Arrays.stream(p.getEditorReferences()).forEach(e -> partListener.addDocumentListener(e));
});
});
}
@Override
@ -158,6 +167,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
@Override
public void onMessage(Message message) {
Display.getDefault().asyncExec(() -> {
System.out.println("Got: " + message);
if (message instanceof PeerJoinMessage join) {
activeSession.getPeers().add(new Peer(join.peerName(), join.peerSiteID()));
@ -179,6 +189,8 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
return;
}
ShareClientDocumentListener listener = partListener.getListeners().get(path);
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot workspaceRoot = workspace.getRoot();
IProject project = workspaceRoot.getProject(path.projectName());
@ -206,8 +218,10 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
// TODO: update sharedDocuments
listener.setIgnoreChanges(true);
Files.write(filePath, sync.content());
project.refreshLocal(IResource.DEPTH_INFINITE, null);
listener.setIgnoreChanges(false);
} catch (IOException | CoreException e) {
e.printStackTrace();
MessageDialog.openError(null, "Share Client", "Failed to update file: " + e.toString());
@ -221,6 +235,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
// Sync entire (shared) workspace
for (IProject project : activeSession.getSharedProjects()) {
var files = getProjectFiles(project);
System.out.println(files);
if (files == null) return;
paths.putAll(files);
}
@ -257,14 +272,28 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
if(message instanceof ChangeMessage change) {
Change c = change.change();
ProjectRelativePath path;
try {
ProjectRelativePath path = ProjectRelativePath.of(c.documentPath());
path = ProjectRelativePath.of(c.documentPath());
}catch(IllegalArgumentException e) {
return;
}
// TODO: insert change into document in sharedDocuments
if(activeSession.getSharedDocument(path) != null) return;
// FIXME: doesn't work
SharedDocument doc = activeSession.getOrCreateSharedDocument(path, () -> {
try {
return Files.readString(resolvePath(path));
}catch(IOException e) {
MessageDialog.openError(null, "Share Client", "Failed to read file: " + e.toString());
throw new RuntimeException(e);
}
});
doc.onMessage(message);
}
});
}
public void addSharedProject(IProject project) {
@ -278,7 +307,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
}
private boolean sendFullSyncOrChecksum(RemoteConnection connection, int siteID, ProjectRelativePath relativePath, Path filePath, boolean checksum) {
if (!Files.isRegularFile(filePath)) return false;
if (!Files.isRegularFile(filePath)) return true;
try {
byte[] bytes = Files.readAllBytes(filePath);
@ -297,6 +326,16 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
return true;
}
private Path resolvePath(ProjectRelativePath path) {
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.projectName());
if (project == null || !project.exists()) return null;
if (!activeSession.getSharedProjects().contains(project)) return null;
Path projectLocation = project.getLocation().toPath();
Path filePath = projectLocation.resolve(path.relativePath()).normalize();
if (!filePath.startsWith(projectLocation)) return null;
return filePath;
}
private Map<ProjectRelativePath, Path> getProjectFiles(IProject project) {
try {
Path projectLocation = project.getLocation().toPath();
@ -317,14 +356,6 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
@Override
public void earlyStartup() {
PlatformUI.getWorkbench().addWindowListener(ShareClientWindowListener.INSTANCE);
Arrays.stream(PlatformUI.getWorkbench().getWorkbenchWindows()).forEach(w -> {
w.addPageListener(ShareClientPageListener.INSTANCE);
Arrays.stream(w.getPages()).forEach(p -> {
p.addPartListener(partListener);
Arrays.stream(p.getEditorReferences()).forEach(e -> partListener.addDocumentListener(e));
});
});
}
}

View File

@ -1,7 +1,14 @@
package me.mrletsplay.shareclient.util;
import java.util.Objects;
public record ProjectRelativePath(String projectName, String relativePath) {
public ProjectRelativePath {
Objects.requireNonNull(projectName, "projectName must not be null");
Objects.requireNonNull(relativePath, "relativePath must not be null");
}
@Override
public String toString() {
return projectName + ":" + relativePath;

View File

@ -2,24 +2,35 @@ package me.mrletsplay.shareclient.util;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.widgets.Display;
import me.mrletsplay.shareclient.ShareClient;
import me.mrletsplay.shareclient.util.listeners.ShareClientDocumentListener;
import me.mrletsplay.shareclientcore.connection.RemoteConnection;
import me.mrletsplay.shareclientcore.document.DocumentListener;
import me.mrletsplay.shareclientcore.document.SharedDocument;
public class ShareSession {
private RemoteConnection connection;
private String sessionID;
private List<IProject> sharedProjects;
private Map<ProjectRelativePath, SharedDocument> sharedDocuments;
private List<Peer> peers;
public ShareSession(RemoteConnection connection, String sessionID) {
this.connection = connection;
this.sessionID = sessionID;
this.sharedProjects = new ArrayList<>();
this.sharedDocuments = new HashMap<>();
this.peers = new ArrayList<>();
}
@ -35,6 +46,66 @@ public class ShareSession {
return sharedProjects;
}
public Map<ProjectRelativePath, SharedDocument> getSharedDocuments() {
return sharedDocuments;
}
public SharedDocument getSharedDocument(ProjectRelativePath path) {
return sharedDocuments.get(path);
}
public SharedDocument getOrCreateSharedDocument(ProjectRelativePath path, Supplier<String> initialContents) {
return sharedDocuments.computeIfAbsent(path, p -> {
SharedDocument doc = new SharedDocument(connection, path.toString(), initialContents.get());
doc.addListener(new DocumentListener() {
@Override
public void onInsert(int index, char character) {
Display.getDefault().asyncExec(() -> {
ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListeners().get(path);
if(documentListener != null) {
IDocument document = documentListener.getDocument();
documentListener.setIgnoreChanges(true);
try {
document.replace(index, 0, String.valueOf(character));
} catch (BadLocationException e) {
e.printStackTrace();
// TODO: treat as inconsistency
// MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString());
}
documentListener.setIgnoreChanges(false);
}
});
}
@Override
public void onDelete(int index) {
Display.getDefault().asyncExec(() -> {
ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListeners().get(path);
if(documentListener != null) {
IDocument document = documentListener.getDocument();
documentListener.setIgnoreChanges(true);
try {
document.replace(index, 1, "");
} catch (BadLocationException e) {
e.printStackTrace();
// TODO: treat as inconsistency
// MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString());
}
documentListener.setIgnoreChanges(false);
}
});
}
});
return doc;
});
}
public List<Peer> getPeers() {
return peers;
}

View File

@ -0,0 +1,61 @@
package me.mrletsplay.shareclient.util.listeners;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.swt.widgets.Display;
import me.mrletsplay.shareclient.ShareClient;
import me.mrletsplay.shareclient.util.ProjectRelativePath;
import me.mrletsplay.shareclient.util.ShareSession;
import me.mrletsplay.shareclientcore.document.SharedDocument;
public class ShareClientDocumentListener implements IDocumentListener {
private ProjectRelativePath path;
private IDocument document;
private boolean ignoreChanges = false;
public ShareClientDocumentListener(ProjectRelativePath path, IDocument document) {
this.path = path;
this.document = document;
}
public void setIgnoreChanges(boolean ignoreChanges) {
this.ignoreChanges = ignoreChanges;
}
public boolean isIgnoreChanges() {
return ignoreChanges;
}
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
if(ignoreChanges) return;
System.out.println("UPDATE ON THREAD " + Thread.currentThread());
Display.getDefault().asyncExec(() -> {
ShareSession session = ShareClient.getDefault().getActiveSession();
if(session == null) return;
SharedDocument doc = session.getOrCreateSharedDocument(path, () -> event.fDocument.get());
if(event.getLength() > 0) {
doc.localDelete(event.getOffset(), event.getLength());
}
doc.localInsert(event.getOffset(), event.getText());
});
}
@Override
public void documentChanged(DocumentEvent event) {
}
public IDocument getDocument() {
return document;
}
}

View File

@ -6,9 +6,7 @@ import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener2;
@ -21,25 +19,11 @@ import me.mrletsplay.shareclient.util.ProjectRelativePath;
public class ShareClientPartListener implements IPartListener2 {
private Map<ProjectRelativePath, IDocumentListener> listeners = new HashMap<>();
private Map<ProjectRelativePath, ShareClientDocumentListener> listeners = new HashMap<>();
private IDocumentListener createListener(ProjectRelativePath path) {
private ShareClientDocumentListener createListener(ProjectRelativePath path, IDocument document) {
if(listeners.containsKey(path)) return listeners.get(path);
IDocumentListener listener = new IDocumentListener() {
private boolean ignoreChanges = false;
@Override
public void documentChanged(DocumentEvent event) {
System.out.println("Change in document at " + path);
}
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
}
};
ShareClientDocumentListener listener = new ShareClientDocumentListener(path, document);
listeners.put(path, listener);
return listener;
}
@ -60,8 +44,8 @@ public class ShareClientPartListener implements IPartListener2 {
Path filePath = project.getLocation().toPath().relativize(file.getLocation().toPath());
ProjectRelativePath relPath = new ProjectRelativePath(project.getName(), filePath.toString());
System.out.println(relPath);
document.addDocumentListener(createListener(relPath));
System.out.println("Opened editor: " + relPath);
document.addDocumentListener(createListener(relPath, document));
}
@Override
@ -74,4 +58,8 @@ public class ShareClientPartListener implements IPartListener2 {
addDocumentListener(partRef);
}
public Map<ProjectRelativePath, ShareClientDocumentListener> getListeners() {
return listeners;
}
}