-
-
Notifications
You must be signed in to change notification settings - Fork 4
Add pair programming plugin #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Daniel-ADFA
wants to merge
2
commits into
main
Choose a base branch
from
ADFA-4395
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| .gradle/ | ||
| build/ | ||
| .idea/ | ||
| local.properties | ||
| *.iml | ||
| .DS_Store | ||
| captures/ | ||
| .externalNativeBuild/ | ||
| .cxx/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| # Pair | ||
|
|
||
| A plugin for [CodeOnTheGo](https://github.com/appdevforall/CodeOnTheGo) that turns the IDE into a real-time collaborative editor. Two phones on the same WiFi share one editing session: one device **hosts**, others **join** by typing the host's `ip:port` or scanning its QR code, and from then on edits, cursors, and file opens flow between devices as they happen — no server, no cloud, no signup. | ||
|
|
||
| It surfaces as a **Pair** tab in the editor. Host a session and an invite card shows the address and a QR code; join one and the peer list fills in. When the host types in `MainActivity.kt`, the guest sees the same file open and the text arrive keystroke by keystroke, with each peer's live position shown in the peer list. | ||
|
|
||
| ## Building | ||
|
|
||
| ```sh | ||
| cd pair | ||
| ./gradlew clean assemblePlugin | ||
| ``` | ||
|
|
||
| The `.cgp` lands in `build/plugin/`. Install it from inside CodeOnTheGo via the Plugin Manager. Always `clean` first — the plugin builder copies the built APK into the `.cgp` and then deletes the source APK, so an incremental build can package an empty artifact. | ||
|
|
||
| ## How it works | ||
|
|
||
| - **Pair tab** — contributes an editor tab and sidebar entry via `EditorTabExtension` + `UIExtension`; the whole UI is Jetpack Compose (Home, Host, Guest, and QR-scan screens). | ||
| - **Edit sync** — subscribes to the host IDE's `DocumentChangeEvent` on the EventBus for local edits and replays remote edits through `IdeEditorService.replaceRange(...)`. A single loopback guard stops an applied remote edit from rebroadcasting as a local one. | ||
| - **File-relative identity** — a file's wire identity is its path **relative to the project root** (`IdeProjectService.getCurrentProject().rootDir`). The sender strips the root; the receiver re-anchors to its own, so a session works across two devices whose projects live at different absolute paths. | ||
| - **Presence** — cursor positions ride alongside edits; each peer gets a color, shown in the peer list and (with the extended API below) as an inline caret inside the editor. | ||
| - **Transport** — a star topology over `ws://`: the host runs a `WebSocketServer`, each guest opens one `WebSocketClient`, and the host echoes messages to the other guests. Messages are compact JSON (`hi` / `edit` / `cur` / `fo` / `fc` / `ff` / `sync` / `bye`). | ||
| - **Conflict handling** — per-file sequence numbers with the host as authority; a divergence flags the session *out of sync* rather than dropping the edit, and the host can push an authoritative resync of the full file. | ||
| - **Session history** — past sessions persist as JSON under `IdeEnvironmentService.getPluginDataDirectory()`; the Home screen lists them to rename, delete, or reconnect. | ||
|
|
||
| ## Source layout | ||
|
|
||
| ``` | ||
| pair/ | ||
| ├── build.gradle.kts, settings.gradle.kts, proguard-rules.pro | ||
| └── src/main/ | ||
| ├── AndroidManifest.xml plugin id, main class, icons, permissions | ||
| ├── assets/ icon_day.png, icon_night.png (190×190 interlocking-rings mark) | ||
| └── kotlin/com/appdevforall/pair/plugin/ | ||
| ├── PairPlugin.kt IPlugin + EditorTabExtension + UIExtension entry | ||
| ├── data/ wire protocol, WebSocket server/client, session store | ||
| ├── domain/ EditBroker orchestrator, observer/applier, path mapping, peer registry | ||
| ├── ui/ Compose theme, components, and screens | ||
| └── util/ LAN discovery, QR decode | ||
| ``` | ||
|
|
||
| ## Dependencies from `libs/` | ||
|
|
||
| - `../libs/plugin-api.jar` — the plugin API surface (`IPlugin`, extensions, `Ide*Service`); `compileOnly`, provided by the IDE at runtime. | ||
| - `../libs/gradle-plugin.jar` — the `com.itsaky.androidide.plugins.build` Gradle plugin that packages the `.cgp`. | ||
| - `../libs/eventbus-events.jar` — the editor and file `*Event` types Pair subscribes to on the EventBus. | ||
| - `../libs/shared.jar` — `com.itsaky.androidide.models.Range`, the type of `DocumentChangeEvent.changeRange` that Pair reads. | ||
|
|
||
| Jetpack Compose is linked `compileOnly` (host-provided), not bundled. | ||
|
|
||
| > **Note:** Pair requires an extended `plugin-api` beyond the current `stage` baseline — `IdeProjectService.openProject(File)` (open a project after a pull-model sync) and `IdeEditorService.showPeerCursor` / `hidePeerCursor` / `clearPeerCursors` (inline remote-cursor decoration). These land on the `feat/ADFA-4419-remote-peer-editor-decoration` branch. If `assemblePlugin` fails with unresolved references to those symbols, the shared `libs/` jars are older than the API Pair needs; refresh them from a CodeOnTheGo build that includes the extensions (`../scripts/update-libs.sh --local <path-to-CodeOnTheGo> --ref feat/ADFA-4419-remote-peer-editor-decoration`). | ||
|
|
||
| ## License | ||
|
|
||
| Pair is an open-source example plugin for Code on the Go. Its source is licensed per the surrounding `plugin-examples` repository (see `LICENSE` at the repo root). It makes no cloud calls — all traffic stays on the local network between the paired devices. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| plugins { | ||
| id("com.android.application") | ||
| id("org.jetbrains.kotlin.android") | ||
| id("org.jetbrains.kotlin.plugin.compose") | ||
| id("com.itsaky.androidide.plugins.build") | ||
| } | ||
|
|
||
| pluginBuilder { | ||
| pluginName = "pair" | ||
| } | ||
|
|
||
| android { | ||
| namespace = "com.appdevforall.pair.plugin" | ||
| compileSdk = 34 | ||
|
|
||
| defaultConfig { | ||
| applicationId = "com.appdevforall.pair.plugin" | ||
| minSdk = 26 | ||
| targetSdk = 34 | ||
| versionCode = 1 | ||
| versionName = "1.0.0" | ||
|
|
||
| ndk { | ||
| abiFilters += listOf("arm64-v8a", "armeabi-v7a") | ||
| } | ||
| } | ||
|
|
||
| buildTypes { | ||
| release { | ||
| isMinifyEnabled = false | ||
| isShrinkResources = false | ||
| signingConfig = signingConfigs.getByName("debug") | ||
| proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") | ||
| } | ||
| } | ||
|
|
||
| compileOptions { | ||
| sourceCompatibility = JavaVersion.VERSION_17 | ||
| targetCompatibility = JavaVersion.VERSION_17 | ||
| } | ||
|
|
||
| buildFeatures { | ||
| compose = true | ||
| } | ||
|
|
||
| packaging { | ||
| resources { | ||
| excludes += setOf( | ||
| "META-INF/versions/9/OSGI-INF/MANIFEST.MF", | ||
| "META-INF/DEPENDENCIES", | ||
| "META-INF/LICENSE", | ||
| "META-INF/LICENSE.txt", | ||
| "META-INF/NOTICE", | ||
| "META-INF/NOTICE.txt", | ||
| "META-INF/INDEX.LIST", | ||
| "META-INF/io.netty.versions.properties" | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| dependencies { | ||
| compileOnly(files("../libs/plugin-api.jar")) | ||
| compileOnly(files("../libs/eventbus-events.jar")) | ||
| compileOnly(files("../libs/shared.jar")) | ||
|
|
||
| implementation("com.google.android.material:material:1.10.0") | ||
| implementation("androidx.fragment:fragment-ktx:1.8.8") | ||
| implementation("androidx.core:core-ktx:1.12.0") | ||
| implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7") | ||
| implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") | ||
| implementation("org.jetbrains.kotlin:kotlin-stdlib:2.3.0") | ||
| implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1") | ||
|
|
||
| implementation("org.java-websocket:Java-WebSocket:1.5.6") | ||
| implementation("org.slf4j:slf4j-nop:1.7.36") | ||
|
|
||
| compileOnly("org.greenrobot:eventbus:3.3.1") | ||
|
|
||
| val composeBom = platform("androidx.compose:compose-bom:2024.02.00") | ||
| compileOnly(composeBom) | ||
| compileOnly("androidx.compose.ui:ui") | ||
| compileOnly("androidx.compose.ui:ui-graphics") | ||
| compileOnly("androidx.compose.ui:ui-tooling-preview") | ||
| compileOnly("androidx.compose.foundation:foundation") | ||
| compileOnly("androidx.compose.material3:material3") | ||
| compileOnly("androidx.compose.runtime:runtime") | ||
| implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7") | ||
| implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7") | ||
|
|
||
| debugImplementation(platform("androidx.compose:compose-bom:2024.02.00")) | ||
| debugImplementation("androidx.compose.ui:ui-tooling") | ||
| debugImplementation("androidx.compose.ui:ui-tooling-preview") | ||
| debugImplementation("androidx.compose.ui:ui") | ||
| debugImplementation("androidx.compose.ui:ui-graphics") | ||
| debugImplementation("androidx.compose.foundation:foundation") | ||
| debugImplementation("androidx.compose.material3:material3") | ||
| debugImplementation("androidx.compose.runtime:runtime") | ||
| } | ||
|
|
||
| kotlin { | ||
| compilerOptions { | ||
| jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 | ||
| org.gradle.parallel=true | ||
| org.gradle.caching=true | ||
| android.useAndroidX=true | ||
| android.nonTransitiveRClass=true | ||
| kotlin.code.style=official |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| distributionBase=GRADLE_USER_HOME | ||
| distributionPath=wrapper/dists | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip | ||
| networkTimeout=10000 | ||
| validateDistributionUrl=true | ||
| zipStoreBase=GRADLE_USER_HOME | ||
| zipStorePath=wrapper/dists |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocker (build fails).
../libs/shared.jardoesn't exist in the repo'slibs/(onlycommon.jar,eventbus-events.jar,gradle-plugin.jar,idetooltips.jar,plugin-api.jar).assemblePluginfails here:The jar providing
com.itsaky.androidide.models.Rangeneeds to be committed tolibs/, or this dependency reworked to use what's already there.