mirror of
https://github.com/hexedtech/codemp-intellij.git
synced 2024-12-22 20:44:53 +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
|
||||
@Setter
|
||||
public static class State {
|
||||
String serverUrl;
|
||||
|
||||
private static CredentialAttributes createCredentialAttributes() {
|
||||
return new CredentialAttributes(CredentialAttributesKt.generateServiceName(
|
||||
"CodeMP",
|
||||
|
|
|
@ -24,11 +24,6 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
|||
return "CodeMP";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return this.component.serverUrlField;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JComponent createComponent() {
|
||||
|
@ -40,8 +35,7 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
|||
public boolean isModified() {
|
||||
CodeMPSettings.State state = Objects.requireNonNull(CodeMPSettings.getInstance().getState());
|
||||
Credentials creds = state.getCredentials();
|
||||
return !this.component.serverUrlField.getText().equals(state.serverUrl)
|
||||
|| (creds == null && (this.component.userNameField.getText() != null || this.component.passwordField.getPassword() != null))
|
||||
return (creds == null && (this.component.userNameField.getText() != null || this.component.passwordField.getPassword() != null))
|
||||
|| creds != null && (
|
||||
!Objects.equals(creds.getUserName(), this.component.userNameField.getText())
|
||||
|| !Objects.equals(creds.getPassword(), new OneTimeString(this.component.passwordField.getPassword()))
|
||||
|
@ -51,7 +45,6 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
|||
@Override
|
||||
public void apply() {
|
||||
CodeMPSettings.State state = Objects.requireNonNull(CodeMPSettings.getInstance().getState());
|
||||
state.serverUrl = this.component.serverUrlField.getText();
|
||||
state.setCredentials(new Credentials(
|
||||
this.component.userNameField.getText(),
|
||||
this.component.passwordField.getPassword()
|
||||
|
@ -61,8 +54,6 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
|||
@Override
|
||||
public void reset() {
|
||||
CodeMPSettings.State state = Objects.requireNonNull(CodeMPSettings.getInstance().getState());
|
||||
this.component.serverUrlField.setText(state.serverUrl);
|
||||
|
||||
Credentials cred = state.getCredentials();
|
||||
if(cred != null) {
|
||||
this.component.userNameField.setText(cred.getUserName());
|
||||
|
@ -77,14 +68,12 @@ final class CodeMPSettingsConfigurable implements Configurable {
|
|||
|
||||
private static class Component {
|
||||
final JPanel mainPanel;
|
||||
final JBTextField serverUrlField = new JBTextField();
|
||||
final JBTextField userNameField = new JBTextField();
|
||||
final JBPasswordField passwordField = new JBPasswordField();
|
||||
|
||||
Component() {
|
||||
this.mainPanel = FormBuilder.createFormBuilder()
|
||||
.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("Password:"), this.passwordField, 1, false)
|
||||
.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.ContentFactory;
|
||||
import com.intellij.ui.treeStructure.Tree;
|
||||
import com.jgoodies.forms.layout.FormLayout;
|
||||
import mp.code.intellij.CodeMP;
|
||||
import mp.code.intellij.util.cb.BufferCallback;
|
||||
import mp.code.intellij.util.FileUtil;
|
||||
|
@ -23,7 +24,7 @@ import java.awt.event.ActionEvent;
|
|||
import java.awt.event.MouseEvent;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CodeMPToolWindowFactory implements ToolWindowFactory, DumbAware {
|
||||
public class CodeMPWindowFactory implements ToolWindowFactory, DumbAware {
|
||||
@Override
|
||||
public void createToolWindowContent(
|
||||
@NotNull Project project,
|
||||
|
@ -111,6 +112,7 @@ public class CodeMPToolWindowFactory implements ToolWindowFactory, DumbAware {
|
|||
CodeMPToolWindow.this.redraw(project);
|
||||
}
|
||||
});
|
||||
createButton.setSize(createButton.getPreferredSize());
|
||||
|
||||
JTree tree = drawTree(CodeMP.getActiveWorkspace().getFileTree(Optional.empty(), false));
|
||||
tree.addMouseListener(new SimpleMouseListener() {
|
||||
|
@ -138,6 +140,7 @@ public class CodeMPToolWindowFactory implements ToolWindowFactory, DumbAware {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.add(createButton);
|
||||
this.add(tree);
|
||||
}
|
||||
|
|
|
@ -4,14 +4,9 @@ import com.intellij.credentialStore.Credentials;
|
|||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationType;
|
||||
import com.intellij.notification.Notifications;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.editor.EditorFactory;
|
||||
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.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
|
@ -19,18 +14,16 @@ import com.intellij.openapi.project.Project;
|
|||
import mp.code.BufferController;
|
||||
import mp.code.Client;
|
||||
import mp.code.Workspace;
|
||||
import mp.code.data.Cursor;
|
||||
import mp.code.exceptions.ConnectionException;
|
||||
import mp.code.exceptions.ConnectionRemoteException;
|
||||
import mp.code.exceptions.ControllerException;
|
||||
import mp.code.intellij.CodeMP;
|
||||
import mp.code.intellij.listeners.BufferEventListener;
|
||||
import mp.code.intellij.listeners.CursorEventListener;
|
||||
import mp.code.intellij.settings.CodeMPSettings;
|
||||
import mp.code.intellij.util.cb.CursorCallback;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -51,14 +44,11 @@ public class InteractionUtil {
|
|||
Objects.requireNonNull(credentials.getUserName()),
|
||||
Objects.requireNonNull(credentials.getPasswordAsString())
|
||||
);
|
||||
MemoryManager.startClientLifetime();
|
||||
|
||||
if(after != null) after.run();
|
||||
|
||||
notifyInfo(
|
||||
project,
|
||||
"Success",
|
||||
String.format("Connected to %s!", state.getServerUrl())
|
||||
);
|
||||
notifyInfo(project, "Success", "Connected to server!");
|
||||
} catch(NullPointerException e) {
|
||||
Notifications.Bus.notify(new Notification(
|
||||
"CodeMP",
|
||||
|
@ -75,6 +65,7 @@ public class InteractionUtil {
|
|||
|
||||
public static void disconnect(@Nullable Project project) {
|
||||
CodeMP.disconnect();
|
||||
MemoryManager.endClientLifetime();
|
||||
notifyInfo(project, "Success", "Disconnected from server!");
|
||||
}
|
||||
|
||||
|
@ -94,6 +85,7 @@ public class InteractionUtil {
|
|||
|
||||
try {
|
||||
CodeMP.joinWorkspace(workspaceId);
|
||||
MemoryManager.startWorkspaceLifetime(workspaceId);
|
||||
} catch(ConnectionException e) {
|
||||
InteractionUtil.notifyError(project, String.format(
|
||||
"Failed to join workspace %s!",
|
||||
|
@ -102,70 +94,16 @@ public class InteractionUtil {
|
|||
return;
|
||||
}
|
||||
|
||||
Disposable lifetime = MemoryManager.getWorkspaceLifetime(workspaceId);
|
||||
assert lifetime != null; // can never fail
|
||||
|
||||
EditorEventMulticaster eventMulticaster = EditorFactory.getInstance().getEventMulticaster();
|
||||
|
||||
eventMulticaster.addDocumentListener(new BufferEventListener()); // TODO disposable
|
||||
eventMulticaster.addCaretListener(new CursorEventListener()); // TODO disposable
|
||||
eventMulticaster.addDocumentListener(new BufferEventListener(), lifetime);
|
||||
eventMulticaster.addCaretListener(new CursorEventListener(), lifetime);
|
||||
|
||||
CodeMP.getActiveWorkspace().getCursor().callback(controller -> {
|
||||
new Thread(() -> {
|
||||
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();
|
||||
new CursorCallback(this.myProject).accept(controller);
|
||||
});
|
||||
|
||||
if(after != null) after.run();
|
||||
|
@ -181,6 +119,7 @@ public class InteractionUtil {
|
|||
|
||||
public static void leaveWorkspace(Project project, String workspaceId) {
|
||||
CodeMP.leaveWorkspace();
|
||||
MemoryManager.endWorkspaceLifetime(workspaceId);
|
||||
notifyInfo(
|
||||
project,
|
||||
"Success",
|
||||
|
@ -201,12 +140,12 @@ public class InteractionUtil {
|
|||
public static Optional<BufferController> bufferAttach(Project project, Workspace workspace, String path) {
|
||||
try {
|
||||
BufferController controller = workspace.attachToBuffer(path);
|
||||
MemoryManager.startBufferLifetime(workspace.getWorkspaceId(), path);
|
||||
notifyInfo(project, "Success!", String.format(
|
||||
"Successfully attached to buffer %s on workspace %s!",
|
||||
path,
|
||||
workspace.getWorkspaceId())
|
||||
);
|
||||
|
||||
return Optional.of(controller);
|
||||
} catch(ConnectionException 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(
|
||||
"CodeMP", title, msg, NotificationType.INFORMATION
|
||||
), 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(
|
||||
"CodeMP", title,
|
||||
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
|
||||
* lifetimes (which are managed by a {@link Cleaner}).
|
||||
*/
|
||||
public class CodeMPMemoryManager {
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public class MemoryManager {
|
||||
private static ClientDisposable clientDisposable = null;
|
||||
|
||||
public static boolean startClientLifetime() {
|
||||
|
@ -35,7 +36,7 @@ public class CodeMPMemoryManager {
|
|||
|
||||
public static boolean startWorkspaceLifetime(String workspace) {
|
||||
if(clientDisposable.workspaces.containsKey(workspace)) return false;
|
||||
clientDisposable.workspaces.put(workspace, new DisposableWorkspace());
|
||||
clientDisposable.workspaces.put(workspace, new WorkspaceDisposable());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -43,29 +44,37 @@ public class CodeMPMemoryManager {
|
|||
return clientDisposable.workspaces.get(workspace);
|
||||
}
|
||||
|
||||
public static boolean endWorkspaceLifetime(String workspace, String buffer) {
|
||||
if(clientDisposable == null) return false;
|
||||
ClientDisposable tmp = clientDisposable;
|
||||
clientDisposable = null;
|
||||
Disposer.dispose(tmp);
|
||||
public static boolean endWorkspaceLifetime(String workspace) {
|
||||
WorkspaceDisposable ws = clientDisposable.workspaces.remove(workspace);
|
||||
if(ws == null) return false;
|
||||
Disposer.dispose(ws);
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
WorkspaceDisposable ws = (WorkspaceDisposable) getWorkspaceLifetime(workspace);
|
||||
if(ws == null) return null;
|
||||
return ws.buffers.get(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 final Map<String, DisposableWorkspace> workspaces = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, WorkspaceDisposable> workspaces = new ConcurrentHashMap<>();
|
||||
@Override
|
||||
public void 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<>();
|
||||
|
||||
@Override
|
||||
public void 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.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;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class BufferCallback implements Consumer<BufferController> {
|
||||
private static final Executor BUFFER_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||
private final Project project;
|
||||
|
||||
@Override
|
||||
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;
|
||||
|
||||
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" />
|
||||
<toolWindow
|
||||
id="CodeMP"
|
||||
factoryClass="mp.code.intellij.ui.CodeMPToolWindowFactory"
|
||||
factoryClass="mp.code.intellij.ui.CodeMPWindowFactory"
|
||||
anchor="right"
|
||||
doNotActivateOnStart="false" />
|
||||
</extensions>
|
||||
|
|
Loading…
Reference in a new issue