Add basic editor sync
This commit is contained in:
parent
9812d9bd2f
commit
e092ba0548
@ -30,6 +30,7 @@ import me.mrletsplay.shareclient.util.ChecksumUtil;
|
|||||||
import me.mrletsplay.shareclient.util.Peer;
|
import me.mrletsplay.shareclient.util.Peer;
|
||||||
import me.mrletsplay.shareclient.util.ProjectRelativePath;
|
import me.mrletsplay.shareclient.util.ProjectRelativePath;
|
||||||
import me.mrletsplay.shareclient.util.ShareSession;
|
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.ShareClientPageListener;
|
||||||
import me.mrletsplay.shareclient.util.listeners.ShareClientPartListener;
|
import me.mrletsplay.shareclient.util.listeners.ShareClientPartListener;
|
||||||
import me.mrletsplay.shareclient.util.listeners.ShareClientWindowListener;
|
import me.mrletsplay.shareclient.util.listeners.ShareClientWindowListener;
|
||||||
@ -60,25 +61,33 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
|||||||
// The shared instance
|
// The shared instance
|
||||||
private static ShareClient plugin;
|
private static ShareClient plugin;
|
||||||
|
|
||||||
private ShareClientPartListener partListener = new ShareClientPartListener();
|
private ShareClientPartListener partListener;
|
||||||
|
|
||||||
private ShareView view;
|
private ShareView view;
|
||||||
private ShareSession activeSession;
|
private ShareSession activeSession;
|
||||||
|
|
||||||
private Map<ProjectRelativePath, SharedDocument> sharedDocuments;
|
|
||||||
|
|
||||||
public ShareClient() {
|
public ShareClient() {
|
||||||
this.sharedDocuments = new HashMap<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(BundleContext context) throws Exception {
|
public void start(BundleContext context) throws Exception {
|
||||||
super.start(context);
|
super.start(context);
|
||||||
plugin = this;
|
plugin = this;
|
||||||
|
partListener = new ShareClientPartListener();
|
||||||
|
|
||||||
getPreferenceStore().setDefault(ShareClientPreferences.SERVER_URI, "ws://localhost:5473");
|
getPreferenceStore().setDefault(ShareClientPreferences.SERVER_URI, "ws://localhost:5473");
|
||||||
getPreferenceStore().setDefault(ShareClientPreferences.SHOW_CURSORS, true);
|
getPreferenceStore().setDefault(ShareClientPreferences.SHOW_CURSORS, true);
|
||||||
// new ShareWSClient(URI.create("ws://localhost:5473")).connect();
|
// 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
|
@Override
|
||||||
@ -158,6 +167,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(Message message) {
|
public void onMessage(Message message) {
|
||||||
|
Display.getDefault().asyncExec(() -> {
|
||||||
System.out.println("Got: " + message);
|
System.out.println("Got: " + message);
|
||||||
if (message instanceof PeerJoinMessage join) {
|
if (message instanceof PeerJoinMessage join) {
|
||||||
activeSession.getPeers().add(new Peer(join.peerName(), join.peerSiteID()));
|
activeSession.getPeers().add(new Peer(join.peerName(), join.peerSiteID()));
|
||||||
@ -179,6 +189,8 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShareClientDocumentListener listener = partListener.getListeners().get(path);
|
||||||
|
|
||||||
IWorkspace workspace = ResourcesPlugin.getWorkspace();
|
IWorkspace workspace = ResourcesPlugin.getWorkspace();
|
||||||
IWorkspaceRoot workspaceRoot = workspace.getRoot();
|
IWorkspaceRoot workspaceRoot = workspace.getRoot();
|
||||||
IProject project = workspaceRoot.getProject(path.projectName());
|
IProject project = workspaceRoot.getProject(path.projectName());
|
||||||
@ -206,8 +218,10 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
|||||||
|
|
||||||
// TODO: update sharedDocuments
|
// TODO: update sharedDocuments
|
||||||
|
|
||||||
|
listener.setIgnoreChanges(true);
|
||||||
Files.write(filePath, sync.content());
|
Files.write(filePath, sync.content());
|
||||||
project.refreshLocal(IResource.DEPTH_INFINITE, null);
|
project.refreshLocal(IResource.DEPTH_INFINITE, null);
|
||||||
|
listener.setIgnoreChanges(false);
|
||||||
} catch (IOException | CoreException e) {
|
} catch (IOException | CoreException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
MessageDialog.openError(null, "Share Client", "Failed to update file: " + e.toString());
|
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
|
// Sync entire (shared) workspace
|
||||||
for (IProject project : activeSession.getSharedProjects()) {
|
for (IProject project : activeSession.getSharedProjects()) {
|
||||||
var files = getProjectFiles(project);
|
var files = getProjectFiles(project);
|
||||||
|
System.out.println(files);
|
||||||
if (files == null) return;
|
if (files == null) return;
|
||||||
paths.putAll(files);
|
paths.putAll(files);
|
||||||
}
|
}
|
||||||
@ -257,14 +272,28 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
|||||||
|
|
||||||
if(message instanceof ChangeMessage change) {
|
if(message instanceof ChangeMessage change) {
|
||||||
Change c = change.change();
|
Change c = change.change();
|
||||||
|
ProjectRelativePath path;
|
||||||
try {
|
try {
|
||||||
ProjectRelativePath path = ProjectRelativePath.of(c.documentPath());
|
path = ProjectRelativePath.of(c.documentPath());
|
||||||
}catch(IllegalArgumentException e) {
|
}catch(IllegalArgumentException e) {
|
||||||
return;
|
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) {
|
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) {
|
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 {
|
try {
|
||||||
byte[] bytes = Files.readAllBytes(filePath);
|
byte[] bytes = Files.readAllBytes(filePath);
|
||||||
@ -297,6 +326,16 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
|||||||
return true;
|
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) {
|
private Map<ProjectRelativePath, Path> getProjectFiles(IProject project) {
|
||||||
try {
|
try {
|
||||||
Path projectLocation = project.getLocation().toPath();
|
Path projectLocation = project.getLocation().toPath();
|
||||||
@ -317,14 +356,6 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void earlyStartup() {
|
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));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
package me.mrletsplay.shareclient.util;
|
package me.mrletsplay.shareclient.util;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public record ProjectRelativePath(String projectName, String relativePath) {
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return projectName + ":" + relativePath;
|
return projectName + ":" + relativePath;
|
||||||
|
@ -2,24 +2,35 @@ package me.mrletsplay.shareclient.util;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.eclipse.core.resources.IProject;
|
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.ShareClient;
|
||||||
|
import me.mrletsplay.shareclient.util.listeners.ShareClientDocumentListener;
|
||||||
import me.mrletsplay.shareclientcore.connection.RemoteConnection;
|
import me.mrletsplay.shareclientcore.connection.RemoteConnection;
|
||||||
|
import me.mrletsplay.shareclientcore.document.DocumentListener;
|
||||||
|
import me.mrletsplay.shareclientcore.document.SharedDocument;
|
||||||
|
|
||||||
public class ShareSession {
|
public class ShareSession {
|
||||||
|
|
||||||
private RemoteConnection connection;
|
private RemoteConnection connection;
|
||||||
private String sessionID;
|
private String sessionID;
|
||||||
private List<IProject> sharedProjects;
|
private List<IProject> sharedProjects;
|
||||||
|
private Map<ProjectRelativePath, SharedDocument> sharedDocuments;
|
||||||
private List<Peer> peers;
|
private List<Peer> peers;
|
||||||
|
|
||||||
public ShareSession(RemoteConnection connection, String sessionID) {
|
public ShareSession(RemoteConnection connection, String sessionID) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.sessionID = sessionID;
|
this.sessionID = sessionID;
|
||||||
this.sharedProjects = new ArrayList<>();
|
this.sharedProjects = new ArrayList<>();
|
||||||
|
this.sharedDocuments = new HashMap<>();
|
||||||
this.peers = new ArrayList<>();
|
this.peers = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +46,66 @@ public class ShareSession {
|
|||||||
return sharedProjects;
|
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() {
|
public List<Peer> getPeers() {
|
||||||
return peers;
|
return peers;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,9 +6,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import org.eclipse.core.resources.IFile;
|
import org.eclipse.core.resources.IFile;
|
||||||
import org.eclipse.core.resources.IProject;
|
import org.eclipse.core.resources.IProject;
|
||||||
import org.eclipse.jface.text.DocumentEvent;
|
|
||||||
import org.eclipse.jface.text.IDocument;
|
import org.eclipse.jface.text.IDocument;
|
||||||
import org.eclipse.jface.text.IDocumentListener;
|
|
||||||
import org.eclipse.ui.IEditorInput;
|
import org.eclipse.ui.IEditorInput;
|
||||||
import org.eclipse.ui.IEditorPart;
|
import org.eclipse.ui.IEditorPart;
|
||||||
import org.eclipse.ui.IPartListener2;
|
import org.eclipse.ui.IPartListener2;
|
||||||
@ -21,25 +19,11 @@ import me.mrletsplay.shareclient.util.ProjectRelativePath;
|
|||||||
|
|
||||||
public class ShareClientPartListener implements IPartListener2 {
|
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);
|
if(listeners.containsKey(path)) return listeners.get(path);
|
||||||
|
ShareClientDocumentListener listener = new ShareClientDocumentListener(path, document);
|
||||||
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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
listeners.put(path, listener);
|
listeners.put(path, listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
@ -60,8 +44,8 @@ public class ShareClientPartListener implements IPartListener2 {
|
|||||||
|
|
||||||
Path filePath = project.getLocation().toPath().relativize(file.getLocation().toPath());
|
Path filePath = project.getLocation().toPath().relativize(file.getLocation().toPath());
|
||||||
ProjectRelativePath relPath = new ProjectRelativePath(project.getName(), filePath.toString());
|
ProjectRelativePath relPath = new ProjectRelativePath(project.getName(), filePath.toString());
|
||||||
System.out.println(relPath);
|
System.out.println("Opened editor: " + relPath);
|
||||||
document.addDocumentListener(createListener(relPath));
|
document.addDocumentListener(createListener(relPath, document));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -74,4 +58,8 @@ public class ShareClientPartListener implements IPartListener2 {
|
|||||||
addDocumentListener(partRef);
|
addDocumentListener(partRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<ProjectRelativePath, ShareClientDocumentListener> getListeners() {
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user