mirror of
https://github.com/hexedtech/codemp-intellij.git
synced 2024-11-21 22:54:48 +01:00
feat: memory management + create buffer (ux needs improvement)
This commit is contained in:
parent
13c3601e90
commit
fe46d6bfd3
8 changed files with 195 additions and 109 deletions
|
@ -37,8 +37,6 @@ public class CodeMPSettings implements PersistentStateComponent<CodeMPSettings.S
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public static class State {
|
public static class State {
|
||||||
String serverUrl;
|
|
||||||
|
|
||||||
private static CredentialAttributes createCredentialAttributes() {
|
private static CredentialAttributes createCredentialAttributes() {
|
||||||
return new CredentialAttributes(CredentialAttributesKt.generateServiceName(
|
return new CredentialAttributes(CredentialAttributesKt.generateServiceName(
|
||||||
"CodeMP",
|
"CodeMP",
|
||||||
|
|
|
@ -24,11 +24,6 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
||||||
return "CodeMP";
|
return "CodeMP";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public JComponent getPreferredFocusedComponent() {
|
|
||||||
return this.component.serverUrlField;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public JComponent createComponent() {
|
public JComponent createComponent() {
|
||||||
|
@ -40,8 +35,7 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
||||||
public boolean isModified() {
|
public boolean isModified() {
|
||||||
CodeMPSettings.State state = Objects.requireNonNull(CodeMPSettings.getInstance().getState());
|
CodeMPSettings.State state = Objects.requireNonNull(CodeMPSettings.getInstance().getState());
|
||||||
Credentials creds = state.getCredentials();
|
Credentials creds = state.getCredentials();
|
||||||
return !this.component.serverUrlField.getText().equals(state.serverUrl)
|
return (creds == null && (this.component.userNameField.getText() != null || this.component.passwordField.getPassword() != null))
|
||||||
|| (creds == null && (this.component.userNameField.getText() != null || this.component.passwordField.getPassword() != null))
|
|
||||||
|| creds != null && (
|
|| creds != null && (
|
||||||
!Objects.equals(creds.getUserName(), this.component.userNameField.getText())
|
!Objects.equals(creds.getUserName(), this.component.userNameField.getText())
|
||||||
|| !Objects.equals(creds.getPassword(), new OneTimeString(this.component.passwordField.getPassword()))
|
|| !Objects.equals(creds.getPassword(), new OneTimeString(this.component.passwordField.getPassword()))
|
||||||
|
@ -51,7 +45,6 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
||||||
@Override
|
@Override
|
||||||
public void apply() {
|
public void apply() {
|
||||||
CodeMPSettings.State state = Objects.requireNonNull(CodeMPSettings.getInstance().getState());
|
CodeMPSettings.State state = Objects.requireNonNull(CodeMPSettings.getInstance().getState());
|
||||||
state.serverUrl = this.component.serverUrlField.getText();
|
|
||||||
state.setCredentials(new Credentials(
|
state.setCredentials(new Credentials(
|
||||||
this.component.userNameField.getText(),
|
this.component.userNameField.getText(),
|
||||||
this.component.passwordField.getPassword()
|
this.component.passwordField.getPassword()
|
||||||
|
@ -61,8 +54,6 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
CodeMPSettings.State state = Objects.requireNonNull(CodeMPSettings.getInstance().getState());
|
CodeMPSettings.State state = Objects.requireNonNull(CodeMPSettings.getInstance().getState());
|
||||||
this.component.serverUrlField.setText(state.serverUrl);
|
|
||||||
|
|
||||||
Credentials cred = state.getCredentials();
|
Credentials cred = state.getCredentials();
|
||||||
if(cred != null) {
|
if(cred != null) {
|
||||||
this.component.userNameField.setText(cred.getUserName());
|
this.component.userNameField.setText(cred.getUserName());
|
||||||
|
@ -77,14 +68,12 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
||||||
|
|
||||||
private static class Component {
|
private static class Component {
|
||||||
final JPanel mainPanel;
|
final JPanel mainPanel;
|
||||||
final JBTextField serverUrlField = new JBTextField();
|
|
||||||
final JBTextField userNameField = new JBTextField();
|
final JBTextField userNameField = new JBTextField();
|
||||||
final JBPasswordField passwordField = new JBPasswordField();
|
final JBPasswordField passwordField = new JBPasswordField();
|
||||||
|
|
||||||
Component() {
|
Component() {
|
||||||
this.mainPanel = FormBuilder.createFormBuilder()
|
this.mainPanel = FormBuilder.createFormBuilder()
|
||||||
.addComponent(new JBLabel("Connection").withFont(JBFont.h2().asBold()))
|
.addComponent(new JBLabel("Connection").withFont(JBFont.h2().asBold()))
|
||||||
.addLabeledComponent(new JBLabel("Server address:"), this.serverUrlField, 1, false)
|
|
||||||
.addLabeledComponent(new JBLabel("Username:"), this.userNameField, 1, false)
|
.addLabeledComponent(new JBLabel("Username:"), this.userNameField, 1, false)
|
||||||
.addLabeledComponent(new JBLabel("Password:"), this.passwordField, 1, false)
|
.addLabeledComponent(new JBLabel("Password:"), this.passwordField, 1, false)
|
||||||
.addComponentFillVertically(new JPanel(), 0)
|
.addComponentFillVertically(new JPanel(), 0)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.intellij.openapi.wm.ToolWindowFactory;
|
||||||
import com.intellij.ui.content.Content;
|
import com.intellij.ui.content.Content;
|
||||||
import com.intellij.ui.content.ContentFactory;
|
import com.intellij.ui.content.ContentFactory;
|
||||||
import com.intellij.ui.treeStructure.Tree;
|
import com.intellij.ui.treeStructure.Tree;
|
||||||
|
import com.jgoodies.forms.layout.FormLayout;
|
||||||
import mp.code.intellij.CodeMP;
|
import mp.code.intellij.CodeMP;
|
||||||
import mp.code.intellij.util.cb.BufferCallback;
|
import mp.code.intellij.util.cb.BufferCallback;
|
||||||
import mp.code.intellij.util.FileUtil;
|
import mp.code.intellij.util.FileUtil;
|
||||||
|
@ -23,7 +24,7 @@ import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class CodeMPToolWindowFactory implements ToolWindowFactory, DumbAware {
|
public class CodeMPWindowFactory implements ToolWindowFactory, DumbAware {
|
||||||
@Override
|
@Override
|
||||||
public void createToolWindowContent(
|
public void createToolWindowContent(
|
||||||
@NotNull Project project,
|
@NotNull Project project,
|
||||||
|
@ -111,6 +112,7 @@ public class CodeMPToolWindowFactory implements ToolWindowFactory, DumbAware {
|
||||||
CodeMPToolWindow.this.redraw(project);
|
CodeMPToolWindow.this.redraw(project);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
createButton.setSize(createButton.getPreferredSize());
|
||||||
|
|
||||||
JTree tree = drawTree(CodeMP.getActiveWorkspace().getFileTree(Optional.empty(), false));
|
JTree tree = drawTree(CodeMP.getActiveWorkspace().getFileTree(Optional.empty(), false));
|
||||||
tree.addMouseListener(new SimpleMouseListener() {
|
tree.addMouseListener(new SimpleMouseListener() {
|
||||||
|
@ -138,6 +140,7 @@ public class CodeMPToolWindowFactory implements ToolWindowFactory, DumbAware {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.add(createButton);
|
this.add(createButton);
|
||||||
this.add(tree);
|
this.add(tree);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,9 @@ import com.intellij.credentialStore.Credentials;
|
||||||
import com.intellij.notification.Notification;
|
import com.intellij.notification.Notification;
|
||||||
import com.intellij.notification.NotificationType;
|
import com.intellij.notification.NotificationType;
|
||||||
import com.intellij.notification.Notifications;
|
import com.intellij.notification.Notifications;
|
||||||
import com.intellij.openapi.application.ApplicationManager;
|
import com.intellij.openapi.Disposable;
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.intellij.openapi.editor.EditorFactory;
|
import com.intellij.openapi.editor.EditorFactory;
|
||||||
import com.intellij.openapi.editor.event.EditorEventMulticaster;
|
import com.intellij.openapi.editor.event.EditorEventMulticaster;
|
||||||
import com.intellij.openapi.editor.markup.HighlighterLayer;
|
|
||||||
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
|
|
||||||
import com.intellij.openapi.editor.markup.RangeHighlighter;
|
|
||||||
import com.intellij.openapi.editor.markup.TextAttributes;
|
|
||||||
import com.intellij.openapi.progress.ProgressIndicator;
|
import com.intellij.openapi.progress.ProgressIndicator;
|
||||||
import com.intellij.openapi.progress.ProgressManager;
|
import com.intellij.openapi.progress.ProgressManager;
|
||||||
import com.intellij.openapi.progress.Task;
|
import com.intellij.openapi.progress.Task;
|
||||||
|
@ -19,18 +14,16 @@ import com.intellij.openapi.project.Project;
|
||||||
import mp.code.BufferController;
|
import mp.code.BufferController;
|
||||||
import mp.code.Client;
|
import mp.code.Client;
|
||||||
import mp.code.Workspace;
|
import mp.code.Workspace;
|
||||||
import mp.code.data.Cursor;
|
|
||||||
import mp.code.exceptions.ConnectionException;
|
import mp.code.exceptions.ConnectionException;
|
||||||
import mp.code.exceptions.ConnectionRemoteException;
|
import mp.code.exceptions.ConnectionRemoteException;
|
||||||
import mp.code.exceptions.ControllerException;
|
|
||||||
import mp.code.intellij.CodeMP;
|
import mp.code.intellij.CodeMP;
|
||||||
import mp.code.intellij.listeners.BufferEventListener;
|
import mp.code.intellij.listeners.BufferEventListener;
|
||||||
import mp.code.intellij.listeners.CursorEventListener;
|
import mp.code.intellij.listeners.CursorEventListener;
|
||||||
import mp.code.intellij.settings.CodeMPSettings;
|
import mp.code.intellij.settings.CodeMPSettings;
|
||||||
|
import mp.code.intellij.util.cb.CursorCallback;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -51,14 +44,11 @@ public class InteractionUtil {
|
||||||
Objects.requireNonNull(credentials.getUserName()),
|
Objects.requireNonNull(credentials.getUserName()),
|
||||||
Objects.requireNonNull(credentials.getPasswordAsString())
|
Objects.requireNonNull(credentials.getPasswordAsString())
|
||||||
);
|
);
|
||||||
|
MemoryManager.startClientLifetime();
|
||||||
|
|
||||||
if(after != null) after.run();
|
if(after != null) after.run();
|
||||||
|
|
||||||
notifyInfo(
|
notifyInfo(project, "Success", "Connected to server!");
|
||||||
project,
|
|
||||||
"Success",
|
|
||||||
String.format("Connected to %s!", state.getServerUrl())
|
|
||||||
);
|
|
||||||
} catch(NullPointerException e) {
|
} catch(NullPointerException e) {
|
||||||
Notifications.Bus.notify(new Notification(
|
Notifications.Bus.notify(new Notification(
|
||||||
"CodeMP",
|
"CodeMP",
|
||||||
|
@ -75,6 +65,7 @@ public class InteractionUtil {
|
||||||
|
|
||||||
public static void disconnect(@Nullable Project project) {
|
public static void disconnect(@Nullable Project project) {
|
||||||
CodeMP.disconnect();
|
CodeMP.disconnect();
|
||||||
|
MemoryManager.endClientLifetime();
|
||||||
notifyInfo(project, "Success", "Disconnected from server!");
|
notifyInfo(project, "Success", "Disconnected from server!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +85,7 @@ public class InteractionUtil {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CodeMP.joinWorkspace(workspaceId);
|
CodeMP.joinWorkspace(workspaceId);
|
||||||
|
MemoryManager.startWorkspaceLifetime(workspaceId);
|
||||||
} catch(ConnectionException e) {
|
} catch(ConnectionException e) {
|
||||||
InteractionUtil.notifyError(project, String.format(
|
InteractionUtil.notifyError(project, String.format(
|
||||||
"Failed to join workspace %s!",
|
"Failed to join workspace %s!",
|
||||||
|
@ -102,70 +94,16 @@ public class InteractionUtil {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Disposable lifetime = MemoryManager.getWorkspaceLifetime(workspaceId);
|
||||||
|
assert lifetime != null; // can never fail
|
||||||
|
|
||||||
EditorEventMulticaster eventMulticaster = EditorFactory.getInstance().getEventMulticaster();
|
EditorEventMulticaster eventMulticaster = EditorFactory.getInstance().getEventMulticaster();
|
||||||
|
|
||||||
eventMulticaster.addDocumentListener(new BufferEventListener()); // TODO disposable
|
eventMulticaster.addDocumentListener(new BufferEventListener(), lifetime);
|
||||||
eventMulticaster.addCaretListener(new CursorEventListener()); // TODO disposable
|
eventMulticaster.addCaretListener(new CursorEventListener(), lifetime);
|
||||||
|
|
||||||
CodeMP.getActiveWorkspace().getCursor().callback(controller -> {
|
CodeMP.getActiveWorkspace().getCursor().callback(controller -> {
|
||||||
new Thread(() -> {
|
new CursorCallback(this.myProject).accept(controller);
|
||||||
try {
|
|
||||||
while(true) {
|
|
||||||
Optional<Cursor> c = controller.tryRecv();
|
|
||||||
if(c.isEmpty()) break;
|
|
||||||
Cursor event = c.get();
|
|
||||||
|
|
||||||
CodeMP.LOGGER.debug(
|
|
||||||
"Cursor moved by user {}! Start pos: {}x {}y; end pos: {}x {}y in buffer {}!",
|
|
||||||
event.user,
|
|
||||||
event.startCol,
|
|
||||||
event.startRow,
|
|
||||||
event.endCol,
|
|
||||||
event.endRow,
|
|
||||||
event.buffer
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
ApplicationManager.getApplication().runReadAction(() -> {
|
|
||||||
Editor editor = FileUtil.getActiveEditorByPath(this.myProject, event.buffer);
|
|
||||||
if(editor == null) return;
|
|
||||||
|
|
||||||
int startOffset = editor.getDocument().getLineStartOffset(event.startRow) + event.startCol;
|
|
||||||
int endOffset = editor.getDocument().getLineStartOffset(event.endRow) + event.endCol;
|
|
||||||
|
|
||||||
int documentLength = editor.getDocument().getTextLength();
|
|
||||||
if(startOffset > documentLength || endOffset > documentLength) {
|
|
||||||
CodeMP.LOGGER.debug(
|
|
||||||
"Out of bounds cursor: start was {}, end was {}, document length was {}!",
|
|
||||||
startOffset, endOffset, documentLength);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RangeHighlighter previous = CodeMP.HIGHLIGHTER_MAP.put(
|
|
||||||
event.user,
|
|
||||||
editor.getMarkupModel().addRangeHighlighter(
|
|
||||||
startOffset,
|
|
||||||
endOffset,
|
|
||||||
HighlighterLayer.SELECTION,
|
|
||||||
new TextAttributes(
|
|
||||||
null,
|
|
||||||
ColorUtil.hashColor(event.user),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
Font.PLAIN
|
|
||||||
),
|
|
||||||
HighlighterTargetArea.EXACT_RANGE
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if(previous != null) previous.dispose();
|
|
||||||
});
|
|
||||||
} catch(IndexOutOfBoundsException ignored) {} // don't crash over a bad cursor event
|
|
||||||
}
|
|
||||||
} catch(ControllerException ex) {
|
|
||||||
notifyError(project, "Error receiving change", ex);
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if(after != null) after.run();
|
if(after != null) after.run();
|
||||||
|
@ -181,6 +119,7 @@ public class InteractionUtil {
|
||||||
|
|
||||||
public static void leaveWorkspace(Project project, String workspaceId) {
|
public static void leaveWorkspace(Project project, String workspaceId) {
|
||||||
CodeMP.leaveWorkspace();
|
CodeMP.leaveWorkspace();
|
||||||
|
MemoryManager.endWorkspaceLifetime(workspaceId);
|
||||||
notifyInfo(
|
notifyInfo(
|
||||||
project,
|
project,
|
||||||
"Success",
|
"Success",
|
||||||
|
@ -201,12 +140,12 @@ public class InteractionUtil {
|
||||||
public static Optional<BufferController> bufferAttach(Project project, Workspace workspace, String path) {
|
public static Optional<BufferController> bufferAttach(Project project, Workspace workspace, String path) {
|
||||||
try {
|
try {
|
||||||
BufferController controller = workspace.attachToBuffer(path);
|
BufferController controller = workspace.attachToBuffer(path);
|
||||||
|
MemoryManager.startBufferLifetime(workspace.getWorkspaceId(), path);
|
||||||
notifyInfo(project, "Success!", String.format(
|
notifyInfo(project, "Success!", String.format(
|
||||||
"Successfully attached to buffer %s on workspace %s!",
|
"Successfully attached to buffer %s on workspace %s!",
|
||||||
path,
|
path,
|
||||||
workspace.getWorkspaceId())
|
workspace.getWorkspaceId())
|
||||||
);
|
);
|
||||||
|
|
||||||
return Optional.of(controller);
|
return Optional.of(controller);
|
||||||
} catch(ConnectionException e) {
|
} catch(ConnectionException e) {
|
||||||
notifyError(project, "Failed to attach to buffer!", e);
|
notifyError(project, "Failed to attach to buffer!", e);
|
||||||
|
@ -214,13 +153,22 @@ public class InteractionUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void notifyInfo(Project project, String title, String msg) {
|
public static void bufferCreate(Project project, String path) {
|
||||||
|
try {
|
||||||
|
Workspace workspace = CodeMP.getActiveWorkspace();
|
||||||
|
workspace.createBuffer(path);
|
||||||
|
} catch(ConnectionRemoteException e) {
|
||||||
|
notifyError(project, "Failed to create a buffer!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyInfo(Project project, String title, String msg) {
|
||||||
Notifications.Bus.notify(new Notification(
|
Notifications.Bus.notify(new Notification(
|
||||||
"CodeMP", title, msg, NotificationType.INFORMATION
|
"CodeMP", title, msg, NotificationType.INFORMATION
|
||||||
), project);
|
), project);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void notifyError(Project project, String title, Throwable t) {
|
public static void notifyError(Project project, String title, Throwable t) {
|
||||||
Notifications.Bus.notify(new Notification(
|
Notifications.Bus.notify(new Notification(
|
||||||
"CodeMP", title,
|
"CodeMP", title,
|
||||||
String.format("%s: %s", t.getClass().getCanonicalName(), t.getMessage()),
|
String.format("%s: %s", t.getClass().getCanonicalName(), t.getMessage()),
|
||||||
|
|
|
@ -12,7 +12,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
* Allows association of IntelliJ {@link Disposable Disposables} with CodeMP-related
|
* Allows association of IntelliJ {@link Disposable Disposables} with CodeMP-related
|
||||||
* lifetimes (which are managed by a {@link Cleaner}).
|
* lifetimes (which are managed by a {@link Cleaner}).
|
||||||
*/
|
*/
|
||||||
public class CodeMPMemoryManager {
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
|
public class MemoryManager {
|
||||||
private static ClientDisposable clientDisposable = null;
|
private static ClientDisposable clientDisposable = null;
|
||||||
|
|
||||||
public static boolean startClientLifetime() {
|
public static boolean startClientLifetime() {
|
||||||
|
@ -35,7 +36,7 @@ public class CodeMPMemoryManager {
|
||||||
|
|
||||||
public static boolean startWorkspaceLifetime(String workspace) {
|
public static boolean startWorkspaceLifetime(String workspace) {
|
||||||
if(clientDisposable.workspaces.containsKey(workspace)) return false;
|
if(clientDisposable.workspaces.containsKey(workspace)) return false;
|
||||||
clientDisposable.workspaces.put(workspace, new DisposableWorkspace());
|
clientDisposable.workspaces.put(workspace, new WorkspaceDisposable());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,29 +44,37 @@ public class CodeMPMemoryManager {
|
||||||
return clientDisposable.workspaces.get(workspace);
|
return clientDisposable.workspaces.get(workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean endWorkspaceLifetime(String workspace, String buffer) {
|
public static boolean endWorkspaceLifetime(String workspace) {
|
||||||
if(clientDisposable == null) return false;
|
WorkspaceDisposable ws = clientDisposable.workspaces.remove(workspace);
|
||||||
ClientDisposable tmp = clientDisposable;
|
if(ws == null) return false;
|
||||||
clientDisposable = null;
|
Disposer.dispose(ws);
|
||||||
Disposer.dispose(tmp);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean startBufferLifetime(String workspace, String buffer) {
|
public static boolean startBufferLifetime(String workspace, String buffer) {
|
||||||
|
WorkspaceDisposable ws = (WorkspaceDisposable) getWorkspaceLifetime(workspace);
|
||||||
|
if(ws == null || ws.buffers.containsKey(buffer)) return false;
|
||||||
|
ws.buffers.put(buffer, Disposer.newDisposable());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @Nullable Disposable getBufferLifetime(String workspace, String buffer) {
|
public static @Nullable Disposable getBufferLifetime(String workspace, String buffer) {
|
||||||
|
WorkspaceDisposable ws = (WorkspaceDisposable) getWorkspaceLifetime(workspace);
|
||||||
|
if(ws == null) return null;
|
||||||
|
return ws.buffers.get(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean endBufferLifetime(String workspace, String buffer) {
|
public static boolean endBufferLifetime(String workspace, String buffer) {
|
||||||
|
WorkspaceDisposable ws = (WorkspaceDisposable) getWorkspaceLifetime(workspace);
|
||||||
|
if(ws == null) return false;
|
||||||
|
Disposable buf = ws.buffers.get(buffer);
|
||||||
|
if(buf == null) return false;
|
||||||
|
Disposer.dispose(buf);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ClientDisposable implements Disposable {
|
private static class ClientDisposable implements Disposable {
|
||||||
private final Map<String, DisposableWorkspace> workspaces = new ConcurrentHashMap<>();
|
private final Map<String, WorkspaceDisposable> workspaces = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
this.workspaces.values().forEach(Disposer::dispose);
|
this.workspaces.values().forEach(Disposer::dispose);
|
||||||
|
@ -73,9 +82,8 @@ public class CodeMPMemoryManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DisposableWorkspace implements Disposable {
|
private static class WorkspaceDisposable implements Disposable {
|
||||||
private final Map<String, Disposable> buffers = new ConcurrentHashMap<>();
|
private final Map<String, Disposable> buffers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
this.buffers.values().forEach(Disposer::dispose);
|
this.buffers.values().forEach(Disposer::dispose);
|
||||||
|
|
|
@ -1,12 +1,65 @@
|
||||||
package mp.code.intellij.util;
|
package mp.code.intellij.util.cb;
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
|
import com.intellij.openapi.command.CommandProcessor;
|
||||||
|
import com.intellij.openapi.editor.Editor;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import mp.code.BufferController;
|
import mp.code.BufferController;
|
||||||
|
import mp.code.data.TextChange;
|
||||||
|
import mp.code.exceptions.ControllerException;
|
||||||
|
import mp.code.intellij.CodeMP;
|
||||||
|
import mp.code.intellij.util.FileUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class BufferCallback implements Consumer<BufferController> {
|
public class BufferCallback implements Consumer<BufferController> {
|
||||||
|
private static final Executor BUFFER_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||||
|
private final Project project;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void accept(BufferController bufferController) {
|
public void accept(BufferController bufferController) {
|
||||||
|
BUFFER_EXECUTOR.execute(() -> {
|
||||||
|
ApplicationManager.getApplication().runReadAction(() -> {
|
||||||
|
Editor editor = FileUtil.getActiveEditorByPath(this.project, bufferController.getName());
|
||||||
|
ApplicationManager.getApplication().invokeLaterOnWriteThread(() -> {
|
||||||
|
List<TextChange> changeList = new ArrayList<>();
|
||||||
|
while(true) {
|
||||||
|
Optional<TextChange> changeOptional;
|
||||||
|
try {
|
||||||
|
changeOptional = bufferController.tryRecv();
|
||||||
|
} catch(ControllerException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(changeOptional.isEmpty())
|
||||||
|
break;
|
||||||
|
TextChange change = changeOptional.get();
|
||||||
|
CodeMP.LOGGER.debug("Received text change {} from offset {} to {}!",
|
||||||
|
change.content, change.start, change.end);
|
||||||
|
changeList.add(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationManager.getApplication().runWriteAction(() ->
|
||||||
|
CommandProcessor.getInstance().executeCommand(
|
||||||
|
this.project,
|
||||||
|
() -> changeList.forEach((change) ->
|
||||||
|
editor.getDocument().replaceString(
|
||||||
|
(int) change.start, (int) change.end, change.content)
|
||||||
|
),
|
||||||
|
"CodeMPBufferReceive",
|
||||||
|
"codemp-buffer-receive",
|
||||||
|
editor.getDocument()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,91 @@
|
||||||
package mp.code.intellij.util.cb;
|
package mp.code.intellij.util.cb;
|
||||||
|
|
||||||
public class CursorCallback {
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
|
import com.intellij.openapi.editor.Editor;
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterLayer;
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
|
||||||
|
import com.intellij.openapi.editor.markup.RangeHighlighter;
|
||||||
|
import com.intellij.openapi.editor.markup.TextAttributes;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mp.code.CursorController;
|
||||||
|
import mp.code.data.Cursor;
|
||||||
|
import mp.code.exceptions.ControllerException;
|
||||||
|
import mp.code.intellij.CodeMP;
|
||||||
|
import mp.code.intellij.util.ColorUtil;
|
||||||
|
import mp.code.intellij.util.FileUtil;
|
||||||
|
import mp.code.intellij.util.InteractionUtil;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CursorCallback implements Consumer<CursorController> {
|
||||||
|
private static final Executor CURSOR_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||||
|
private final Project project;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(CursorController controller) {
|
||||||
|
CURSOR_EXECUTOR.execute(() -> { // necessary
|
||||||
|
try {
|
||||||
|
while(true) {
|
||||||
|
Optional<Cursor> c = controller.tryRecv();
|
||||||
|
if(c.isEmpty()) break;
|
||||||
|
Cursor event = c.get();
|
||||||
|
|
||||||
|
CodeMP.LOGGER.debug(
|
||||||
|
"Cursor moved by user {}! Start pos: {}x {}y; end pos: {}x {}y in buffer {}!",
|
||||||
|
event.user,
|
||||||
|
event.startCol,
|
||||||
|
event.startRow,
|
||||||
|
event.endCol,
|
||||||
|
event.endRow,
|
||||||
|
event.buffer
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ApplicationManager.getApplication().runReadAction(() -> {
|
||||||
|
Editor editor = FileUtil.getActiveEditorByPath(this.project, event.buffer);
|
||||||
|
if(editor == null) return;
|
||||||
|
|
||||||
|
int startOffset = editor.getDocument().getLineStartOffset(event.startRow) + event.startCol;
|
||||||
|
int endOffset = editor.getDocument().getLineStartOffset(event.endRow) + event.endCol;
|
||||||
|
|
||||||
|
int documentLength = editor.getDocument().getTextLength();
|
||||||
|
if(startOffset > documentLength || endOffset > documentLength) {
|
||||||
|
CodeMP.LOGGER.debug(
|
||||||
|
"Out of bounds cursor: start was {}, end was {}, document length was {}!",
|
||||||
|
startOffset, endOffset, documentLength);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RangeHighlighter previous = CodeMP.HIGHLIGHTER_MAP.put(
|
||||||
|
event.user,
|
||||||
|
editor.getMarkupModel().addRangeHighlighter(
|
||||||
|
startOffset,
|
||||||
|
endOffset,
|
||||||
|
HighlighterLayer.SELECTION,
|
||||||
|
new TextAttributes(
|
||||||
|
null,
|
||||||
|
ColorUtil.hashColor(event.user),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Font.PLAIN
|
||||||
|
),
|
||||||
|
HighlighterTargetArea.EXACT_RANGE
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if(previous != null) previous.dispose();
|
||||||
|
});
|
||||||
|
} catch(IndexOutOfBoundsException ignored) {} // don't crash over a bad cursor event
|
||||||
|
}
|
||||||
|
} catch(ControllerException ex) {
|
||||||
|
InteractionUtil.notifyError(project, "Error receiving change", ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<workspaceModel.fileIndexContributor implementation="mp.code.intellij.vfs.CodeMPFileIndexContributor" />
|
<workspaceModel.fileIndexContributor implementation="mp.code.intellij.vfs.CodeMPFileIndexContributor" />
|
||||||
<toolWindow
|
<toolWindow
|
||||||
id="CodeMP"
|
id="CodeMP"
|
||||||
factoryClass="mp.code.intellij.ui.CodeMPToolWindowFactory"
|
factoryClass="mp.code.intellij.ui.CodeMPWindowFactory"
|
||||||
anchor="right"
|
anchor="right"
|
||||||
doNotActivateOnStart="false" />
|
doNotActivateOnStart="false" />
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
Loading…
Reference in a new issue