diff --git a/gradle.properties b/gradle.properties index 39ce9bc5..54fc3763 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ intellijPluginVersion=1.1.2 javassistVersion=3.27.0-GA kotlinVersion=1.5.20 mockitoKotlinVersion=3.2.0 -projectorClientVersion=7bff9b80 +projectorClientVersion=b137a164 projectorClientGroup=com.github.JetBrains.projector-client targetJvm=11 # Give JitPack some time to build projector-client: diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/ProjectorServer.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/ProjectorServer.kt index 757bd66c..945fba15 100644 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/ProjectorServer.kt +++ b/projector-server/src/main/kotlin/org/jetbrains/projector/server/ProjectorServer.kt @@ -25,6 +25,10 @@ package org.jetbrains.projector.server +import com.intellij.openapi.application.invokeAndWaitIfNeeded +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.command.executeCommand +import com.intellij.openapi.editor.EditorFactory import org.java_websocket.WebSocket import org.java_websocket.exceptions.WebsocketNotConnectedException import org.jetbrains.projector.awt.PClipboard @@ -111,6 +115,8 @@ class ProjectorServer private constructor( private val markdownQueue = ConcurrentLinkedQueue() + private val speculativeQueue = ConcurrentLinkedQueue>() + private var windowColorsEvent: ServerWindowColorsEvent? = null private val ideaColors = IdeColors { colors -> @@ -273,7 +279,7 @@ class ProjectorServer private constructor( } @OptIn(ExperimentalStdlibApi::class) - private fun createDataToSend(): List { + private fun createDataToSend(): List> { val clipboardEvent = when (val clipboardContents = PClipboard.extractLastContents()) { null -> emptyList() @@ -326,17 +332,26 @@ class ProjectorServer private constructor( val markdownEvents = extractData(markdownQueue) - val commandsCount = caretInfoEvents.size + - newImagesCopy.size + clipboardEvent.size + drawCommands.size + windowSetChangedEvent.size + markdownEvents.size + 1 + val speculativeEvents = extractData(speculativeQueue).map { + FilterableEvent(it.first) { _, settings -> it.second == settings.address } + } + + val commandsCount = caretInfoEvents.size + newImagesCopy.size + clipboardEvent.size + drawCommands.size + + windowSetChangedEvent.size + markdownEvents.size + speculativeEvents.size + 1 + + fun toFilterableEvent(event: ServerEvent): FilterableEvent<*> { + return FilterableEvent(event) { _, _ -> true } + } val allEvents = buildList(commandsCount) { - addAll(caretInfoEvents) - addAll(newImagesCopy) - addAll(clipboardEvent) - addAll(drawCommands) - addAll(windowSetChangedEvent) - addAll(markdownEvents) - windowColorsEvent?.let { add(it); windowColorsEvent = null } + addAll(caretInfoEvents.map(::toFilterableEvent)) + addAll(newImagesCopy.map(::toFilterableEvent)) + addAll(clipboardEvent.map(::toFilterableEvent)) + addAll(drawCommands.map(::toFilterableEvent)) + addAll(windowSetChangedEvent.map(::toFilterableEvent)) + addAll(markdownEvents.map(::toFilterableEvent)) + addAll(speculativeEvents) + windowColorsEvent?.let { add(toFilterableEvent(it)); windowColorsEvent = null } } ProjectorImageCacher.collectGarbage() @@ -493,6 +508,45 @@ class ProjectorServer private constructor( } is ClientWindowCloseEvent -> SwingUtilities.invokeLater { PWindow.getWindow(message.windowId)?.close() } + is ClientSpeculativeKeyPressEvent -> { + + val editor = EditorFactory.getInstance().allEditors.find { + System.identityHashCode(it) == message.editorId + } + + if (editor == null) { + processMessage(clientSettings, message.originalEvent) // fallback + } else { + + invokeAndWaitIfNeeded { + runWriteAction { + executeCommand { + val insertedString = message.originalEvent.char.toString() + + val selectionInfo = message.selectionInfo + + editor.document.apply { + if (selectionInfo != null) { + replaceString(selectionInfo.startOffset, selectionInfo.endOffset, insertedString) + } + else { + insertString(message.offset, insertedString) + } + } + + val newOffset = (selectionInfo?.startOffset ?: message.offset) + insertedString.length + + editor.caretModel.primaryCaret.apply { + removeSelection() + moveToOffset(newOffset) + } + } + } + } + } + + speculativeQueue.add(SpeculativeEvent.SpeculativeStringDrawnEvent(message.requestId) to clientSettings.address!!) + } } } @@ -642,13 +696,18 @@ class ProjectorServer private constructor( updateClientsCount() } - private fun sendPictures(dataToSend: List) { + private fun sendPictures(dataToSend: List>) { httpWsTransport.forEachOpenedConnection { client -> val readyClientSettings = client.getAttachment() as? ReadyClientSettings ?: return@forEachOpenedConnection val compressed = with(readyClientSettings.setUpClientData) { val requestedData = extractData(readyClientSettings.requestedData) - val message = requestedData + dataToSend + val message = requestedData + dataToSend.mapNotNull { + when (it.isValidForClient(readyClientSettings)) { + true -> it.originalEvent + false -> null + } + } if (message.isEmpty()) { return@forEachOpenedConnection @@ -919,4 +978,8 @@ class ProjectorServer private constructor( fun getEnvPort() = (getProperty(PORT_PROPERTY_NAME) ?: getProperty(PORT_PROPERTY_NAME_OLD))?.toIntOrNull() ?: DEFAULT_PORT } + + class FilterableEvent(val originalEvent: T, private val filter: (T, ReadyClientSettings) -> Boolean) { + fun isValidForClient(clientSettings: ReadyClientSettings) = filter(originalEvent, clientSettings) + } } diff --git a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/CaretInfoUpdater.kt b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/CaretInfoUpdater.kt index 89ba69d7..cf755ec3 100644 --- a/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/CaretInfoUpdater.kt +++ b/projector-server/src/main/kotlin/org/jetbrains/projector/server/idea/CaretInfoUpdater.kt @@ -39,6 +39,7 @@ import org.jetbrains.projector.common.protocol.data.CommonRectangle import org.jetbrains.projector.common.protocol.data.Point import org.jetbrains.projector.common.protocol.toClient.ServerCaretInfoChangedEvent import org.jetbrains.projector.common.protocol.toClient.data.idea.CaretInfo +import org.jetbrains.projector.common.protocol.toClient.data.idea.SelectionInfo import org.jetbrains.projector.server.core.ij.invokeWhenIdeaIsInitialized import org.jetbrains.projector.server.platform.getTextAttributesCompat import org.jetbrains.projector.server.platform.readAction @@ -123,12 +124,41 @@ class CaretInfoUpdater(private val onCaretInfoChanged: (ServerCaretInfoChangedEv val points = focusedEditor.caretModel.allCarets.map { val caretLocationInEditor = invokeAndWaitIfNeeded { it.editor.visualPositionToXY(it.visualPosition) } + val caretOffset = readAction { it.offset } + val selectionStart = readAction { it.selectionStart } + val selectionEnd = readAction { it.selectionEnd } + + val selectionInfo = if (selectionStart == selectionEnd) { + null + } else { + val selectionStartPointVisual = invokeAndWaitIfNeeded { it.editor.visualPositionToXY(it.selectionStartPosition) } + + val selectionStartPoint = Point( + x = (editorLocationInWindowX + selectionStartPointVisual.x).toDouble(), + y = (editorLocationInWindowY + selectionStartPointVisual.y).toDouble(), + ) + + val selectionEndPointVisual = invokeAndWaitIfNeeded { it.editor.visualPositionToXY(it.selectionEndPosition) } + + val selectionEndPoint = Point( + x = (editorLocationInWindowX + selectionEndPointVisual.x).toDouble(), + y = (editorLocationInWindowY + selectionEndPointVisual.y).toDouble(), + ) + + SelectionInfo( + selectionStartPoint, + selectionStart, + selectionEndPoint, + selectionEnd, + ) + } + val point = Point( x = (editorLocationInWindowX + caretLocationInEditor.x).toDouble(), y = (editorLocationInWindowY + caretLocationInEditor.y).toDouble(), ) - CaretInfo(point) + CaretInfo(point, caretOffset, selectionInfo) } val isVerticalScrollBarVisible = visibleEditorRect.height < focusedEditorComponent.height @@ -154,6 +184,8 @@ class CaretInfoUpdater(private val onCaretInfoChanged: (ServerCaretInfoChangedEv verticalScrollBarWidth = verticalScrollBarWidth, textColor = textColor, backgroundColor = backgroundColor, + editorScrolled = Point(visibleEditorRect.x.toDouble(), visibleEditorRect.y.toDouble()), + editorId = System.identityHashCode(focusedEditor), ) } }