- Add Show History to Pijul menu - Always ignore .idea and .pijul in tracking. - Make findPijul a generic function to allow to find editor-server. - Only show one revision for directories. - Add `Hunk::resolvePath(Path)` to resolve the affected file to a Java NIO Path. - Fix StringOutOfBounds in Change Parsering Algorithm - Use editor-server instead of copie for interfacing with `pijul record` file. - Fix FileStatus provider not returning correctly for untracked files. - Add CommittedChangesProvider for Pijul.
[?]
Apr 1, 2021, 5:04 PM
B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQCDependencies
- [2]
5AUENX2YAdd support to view files affected by a revision - [3]
ZCRW57C5Improved support for revisions - [4]
6CR2EFUNFirst ChangeProvider implementation!!! Wheehooo - [5]
QXUEMZ3BInitial CahngeProvider - [6]
FRFFQV7VBasic show history support. - [7]
FNNW5IEAAdded more plugin files to Pijul - [8]
7L5LODGZParse changes from `pijul change` - [9]
Q7FXTHVUFirst record support, YEAAAH, RECOOORD - [10]
OPFG6CZ2File status tracking supported. - [11]
EAGIDXOLBuild 2 to only listen to changes in project under pijul - [*]
GGYFPXNDInitial plugin - [*]
MTPTFTHGInitial plugin 2
Change contents
- edit in src/main/resources/META-INF/plugin.xml at line 62[3.1052][14.2072]
<reference ref="Vcs.ShowHistoryForBlock"/><reference ref="Vcs.ShowTabbedFileHistory"/> - replacement in src/main/kotlin/com/github/jonathanxd/dracon/vfs/PijulVirtualFileStatusProvider.kt at line 27
if (virtualFile.name == ".idea" || virtualFile.name == "dracon_diffs")if (virtualFile.name == ".idea" || virtualFile.name == ".pijul") - file addition: Application.kt[3.1784]
package com.github.jonathanxd.dracon.utilimport java.io.Fileimport java.nio.file.Filesimport java.nio.file.Pathimport java.nio.file.Pathsfun findBinary(app: String): String {// Works in Windows, Linux and MacOS when app is installed with cargo.val localPijul = findCargo(app)// For Homebrew/Linuxbrew installationsval brewPijul = findBrew(app)// For *nix only.val usrBinPijul = Paths.get(File.pathSeparator, "usr", "bin", app).asExecutableStringOrNull()val usrLocalBinPijul = Paths.get(File.pathSeparator, "usr", "local", "bin", app).asExecutableStringOrNull()val binPijul = Paths.get(File.pathSeparator, "bin", app).asExecutableStringOrNull()// For Windows only.// Windows could download Pijul binaries from https://github.com/boringcactus/pijul-windows-builds/releases/latest// However, Dracon plugin could not detect Pijul outside from these directories, for the cases where// Pijul is not in these locations, a search through $PATH variable will be made, so if the $PATH is correctly// configured to point to Pijul executable, then it will be found, this applies to all OSes.val programFiles = Paths.get("C:"+ File.pathSeparator, "Program Files", app, "$app.exe").asExecutableStringOrNull()val programFiles86 = Paths.get("C:"+ File.pathSeparator, "Program Files (x86)", app, "$app.exe").asExecutableStringOrNull()return localPijul?: brewPijul?: usrBinPijul?: usrLocalBinPijul?: binPijul?: programFiles?: programFiles86?: findExecutableOnPath(app)?: app}private fun Path.asExecutableStringOrNull(): String? =this.existsOrNull()?.isRegularFileOrNull()?.isExecutableOrNull()?.toAbsolutePath()?.toString()private fun findCargo(app: String): String? {return System.getProperty("user.dir")?.let {Paths.get(it, ".cargo", "bin", app)}?.asExecutableStringOrNull()}private fun findBrew(app: String): String? {return System.getenv("HOMEBREW_PREFIX")?.ifBlank { null }?.ifEmpty { null }?.let {Paths.get(it, "bin", app)}?.asExecutableStringOrNull()}fun findExecutableOnPath(name: String): String? {for (dirname in System.getenv("PATH").split(File.pathSeparator)) {val path = Paths.get(dirname, name)if (Files.isRegularFile(path) && Files.isExecutable(path)) {return path.toAbsolutePath().toString()}}return null} - edit in src/main/kotlin/com/github/jonathanxd/dracon/provider/PijulHistoryProvider.kt at line 102
val ctx = this.project.service<PijulVcsContext>()val pathToView = ctx.resolveUnderVcs(path) - replacement in src/main/kotlin/com/github/jonathanxd/dracon/provider/PijulHistoryProvider.kt at line 116
// TODO: Should we limit to show only one revision when introspecting directories instead of all revisions?val deleted = it.hunks.filterIsInstance<HunkWithPath>().filter {it.resolvePath(ctx.root) == path}.filterIsInstance<FileDelHunk>().isNotEmpty()partner.acceptRevision(PijulVcsFileRevision(this.project,root.toNioPath(),pathToView,PijulRevisionNumber(it.changeHash, it.date),it.authors.map { VcsUserImpl(it.name ?: "", it.email ?: "") }.filter { it.name.isNotEmpty() },it.message,currentChannel,deleted))/*// TODO: Should we limit to show only one revision when introspecting directories instead of all revisions? - replacement in src/main/kotlin/com/github/jonathanxd/dracon/provider/PijulHistoryProvider.kt at line 150
}}*/ - replacement in src/main/kotlin/com/github/jonathanxd/dracon/provider/PijulChangeProvider.kt at line 59
val path = Paths.get(dir.path, hunk.resolvedPath)val path = hunk.resolvePath(rootPath) - replacement in src/main/kotlin/com/github/jonathanxd/dracon/provider/PijulChangeProvider.kt at line 69
val path = Paths.get(dir.path, hunk.resolvedPath)val path = hunk.resolvePath(rootPath) - replacement in src/main/kotlin/com/github/jonathanxd/dracon/provider/PijulChangeProvider.kt at line 84
val path = Paths.get(dir.path, hunk.resolvedPath)val path = hunk.resolvePath(rootPath) - replacement in src/main/kotlin/com/github/jonathanxd/dracon/provider/PijulChangeProvider.kt at line 99
val path = Paths.get(dir.path, hunk.resolvedPath)val path = hunk.resolvePath(rootPath) - edit in src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt at line 14
import com.github.jonathanxd.dracon.editor.EditorServer - edit in src/main/kotlin/com/github/jonathanxd/dracon/pijul/Pijul.kt at line 104
@RequiresBackgroundThreadfun record(project: Project, root: Path, editorServerConsumer: (EditorServer) -> Unit): PijulOperationResult<String> - edit in src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt at line 372
if (lastFound == this.length)break; - replacement in src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt at line 672
val end = this.indexOf('\n', start + 1)val end = this.indexOf('\n', start + 1).let {if (it == -1) this.lengthelse it} - edit in src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt at line 744
if (lastLine == this.length) {break;} - file addition: editor[3.107]
- file addition: EditorServer.kt[0.4403]
package com.github.jonathanxd.dracon.editorimport com.github.jonathanxd.dracon.util.findBinaryimport java.net.ServerSocketimport java.net.URIimport java.net.http.HttpClientimport java.net.http.HttpRequestimport java.net.http.HttpResponseimport java.time.Durationimport java.time.Instantclass EditorServer(val port: Int, val isClosed: () -> Boolean) {private var connected: Boolean = falseprivate val client = HttpClient.newHttpClient()private lateinit var content: Stringfun connectAndRetrieveContent() {val start = Instant.now()while (!this.isClosed()) {if (Duration.between(start, Instant.now()).seconds > 10) {throw IllegalStateException("Could not connect to editor server! Timeout after 10 seconds.")}try {val response = this.client.send(HttpRequest.newBuilder(URI.create("http://0.0.0.0:$port/read")).GET().version(HttpClient.Version.HTTP_2).build(),HttpResponse.BodyHandlers.ofString())if (response.statusCode() == 200) {this.connected = truethis.content = response.body();break;}} catch (t: Throwable) {}Thread.sleep(200)}}fun content() = this.contentfun connected() = this.connectedfun write(text: String) {if (this.isClosed()) {throw IllegalStateException("Could not write: editor-server is already closed.")}if (!this.connected) {throw IllegalStateException("Could not write: Not connected to editor-server!")}val response = this.client.send(HttpRequest.newBuilder(URI.create("http://0.0.0.0:$port/write")).POST(HttpRequest.BodyPublishers.ofString(text)).version(HttpClient.Version.HTTP_2).build(),HttpResponse.BodyHandlers.ofString())handleResponse(response)}private fun handleResponse(response: HttpResponse<*>) {if (response.statusCode() != 200) {throw IllegalStateException("editor-server responded with $response.")}}fun close() {if (this.isClosed()) {throw IllegalStateException("Could not close: editor-server is already closed.")}if (!this.connected) {throw IllegalStateException("Could not close: Not connected to editor-server!")}try {// This will make the connection close immediately// thus throwing an exception.this.client.send(HttpRequest.newBuilder(URI.create("http://0.0.0.0:$port/close")).GET().version(HttpClient.Version.HTTP_2).build(),HttpResponse.BodyHandlers.ofString())} catch (t: Throwable) {}}}fun freePort(): Int = ServerSocket(0).use {it.localPort}fun editorServerPath(): String = findBinary("editor-server") - file move: dialog → dialog
- edit in src/main/kotlin/com/github/jonathanxd/dracon/dialog/PijulExpertRecordDialog.kt at line 4
import com.github.jonathanxd.dracon.editor.EditorServer - edit in src/main/kotlin/com/github/jonathanxd/dracon/dialog/PijulExpertRecordDialog.kt at line 48
val editorServer: EditorServer, - replacement in src/main/kotlin/com/github/jonathanxd/dracon/dialog/PijulExpertRecordDialog.kt at line 96
val record = pijul(this.project).recordFromString(this.project, this.vcsRoot, this.editor.text)this.editorServer.write(this.editor.text)this.editorServer.close()/*val record = pijul(this.project).recordFromString(this.project, this.vcsRoot, this.editor.text) - replacement in src/main/kotlin/com/github/jonathanxd/dracon/dialog/PijulExpertRecordDialog.kt at line 123
}}*/ - file move: context → context
- file move: completion → completion
- edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 16
import com.github.jonathanxd.dracon.editor.EditorServerimport com.github.jonathanxd.dracon.editor.editorServerPathimport com.github.jonathanxd.dracon.editor.freePort - replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 25[3.2779]→[3.11119:11347](∅→∅),[3.4222]→[3.11119:11347](∅→∅),[2.17007]→[3.11119:11347](∅→∅),[3.11119]→[3.11119:11347](∅→∅)
import com.github.jonathanxd.dracon.util.existsOrNullimport com.github.jonathanxd.dracon.util.isExecutableOrNullimport com.github.jonathanxd.dracon.util.isRegularFileOrNullimport com.github.jonathanxd.dracon.util.linesToFlowimport com.github.jonathanxd.dracon.util.* - edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 46
import java.net.ServerSocket - replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 129
nullFileStatus.UNKNOWN - edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 229
}}override fun record(project: Project,root: Path,editorServerConsumer: (EditorServer) -> Unit): PijulOperationResult<String> {val arguments = mutableListOf("record")val operation = this.createPainlessExecPijulWithEditorServer(this.project,root,arguments,editorServerConsumer)return this.doExecutionWithMapper("record", operation) {it - edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 741
private fun findPijul(): String {// Works in Windows, Linux and MacOS when Pijul is installed with cargo.val localPijul = this.findCargoPijul() - replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 742
// For Homebrew/Linuxbrew installationsval brewPijul = this.findBrewPijul()/*** Creates a [PijulExecution] operation that could be executed at any time. This operation uses Kotlin Coroutines* and can be executed immediately through [doExecution] or through [doExecutionWithMapper].** This implementation does not requires a delay value to be provided, like [createExecPijulOperation] does, instead* it uses the kotlin conversion from `CompletionStage` to `Coroutines` and awaits the process through [Process.onExit].** [doExecution] and [doExecutionWithMapper] does execution by scheduling task to [Dispatchers.IO], instead of Main Thread,* offloading the Process execution handling to a different scheduler. However, mapping operation of [doExecutionWithMapper]* is not offloaded from the caller context.**/@RequiresBackgroundThreadprivate fun createPainlessExecPijulWithEditorServer(project: Project,dir: Path,args: List<String>,editorServerConsumer: (EditorServer) -> Unit): PijulExecution {val freePort = freePort()val process = ProcessBuilder().apply {environment()["EDITOR_SERVER_PORT"] = freePort.toString()environment()["VISUAL"] = editorServerPath()environment()["EDITOR"] = editorServerPath()}.command(listOf(this.findPijul()) + args).directory(dir.toFile()).start() - replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 770
// For *nix only.val usrBinPijul = Paths.get(File.pathSeparator, "usr", "bin", "pijul").asPijulExecutableStringOrNull()val usrLocalBinPijul = Paths.get(File.pathSeparator, "usr", "local", "bin", "pijul").asPijulExecutableStringOrNull()val binPijul = Paths.get(File.pathSeparator, "bin", "pijul").asPijulExecutableStringOrNull()editorServerConsumer(EditorServer(freePort) {!process.isAlive}) - replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 774
// For Windows only.// Windows could download Pijul binaries from https://github.com/boringcactus/pijul-windows-builds/releases/latest// However, Dracon plugin could not detect Pijul outside from these directories, for the cases where// Pijul is not in these locations, a search through $PATH variable will be made, so if the $PATH is correctly// configured to point to Pijul executable, then it will be found, this applies to all OSes.val programFiles = Paths.get("C:"+ File.pathSeparator, "Program Files", "pijul", "pijul.exe").asPijulExecutableStringOrNull()val programFiles86 = Paths.get("C:"+ File.pathSeparator, "Program Files (x86)", "pijul", "pijul.exe").asPijulExecutableStringOrNull()val input = process.inputStreamval error = process.errorStream - edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 777
return PijulExecution(input.linesToFlow().onEach {draconConsoleWriter(project).logCommand("pijul", args, it)},error.linesToFlow().onEach {draconConsoleWriter(project).logCommandError("pijul", args, it)},flow {process.onExit().await() - replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 787
return localPijul?: brewPijul?: usrBinPijul?: usrLocalBinPijul?: binPijul?: programFiles?: programFiles86?: this.findExecutableOnPath("pijul")?: "pijul"}val exit = process.exitValue() - replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 789
private fun Path.asPijulExecutableStringOrNull(): String? =this.existsOrNull()?.isRegularFileOrNull()?.isExecutableOrNull()?.toAbsolutePath()?.toString()if (exit == 0) {draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")} else {draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")} - replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 795
private fun findCargoPijul(): String? {return System.getProperty("user.dir")?.let {Paths.get(it, ".cargo", "bin", "pijul")}?.asPijulExecutableStringOrNull()emit(exit)}.flowOn(Dispatchers.IO)) - edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 800
private fun findBrewPijul(): String? {return System.getenv("HOMEBREW_PREFIX")?.ifBlank { null }?.ifEmpty { null }?.let {Paths.get(it, "bin", "pijul")}?.asPijulExecutableStringOrNull()} - replacement in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 801
fun findExecutableOnPath(name: String): String? {for (dirname in System.getenv("PATH").split(File.pathSeparator)) {val path = Paths.get(dirname, name)if (Files.isRegularFile(path) && Files.isExecutable(path)) {return path.toAbsolutePath().toString()}}private fun findPijul(): String = findBinary("pijul") - edit in src/main/kotlin/com/github/jonathanxd/dracon/cmd/PijulCmd.kt at line 803
return null} - file move: changes → changes
- edit in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 14
import com.intellij.openapi.util.io.FileUtil - edit in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 16
import com.intellij.openapi.vcs.changes.ChangeListimport com.intellij.openapi.vcs.changes.ChangesUtilimport com.intellij.openapi.vcs.history.VcsFileRevision - edit in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 23
import com.intellij.util.text.trimMiddle - edit in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 25
import com.intellij.vcs.log.VcsFullCommitDetailsimport com.intellij.vcs.log.util.VcsLogUtil - edit in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 26
import org.jetbrains.annotations.NotNull - replacement in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 50
this.getChanges(settings, location, maxCount) {this.getChanges(settings, maxCount) { - replacement in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 63
getChanges(settings, location, maxCount) {getChanges(settings, maxCount) { - replacement in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 71
private fun getChanges(settings: ChangeBrowserSettings,location: RepositoryLocation,maxCount: Int,consumer: (PijulCommittedChangeList) -> Unit) {private fun getChanges(settings: ChangeBrowserSettings,maxCount: Int,revision: VcsRevisionNumber? = null,consumer: (PijulCommittedChangeList) -> Unit) { - edit in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 121
}?.filter {revision == null || it.revision.hash == revision.asString() - edit in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 125
entry.message.trimMiddle(20), - edit in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 127
"", - replacement in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 169
return if (i == 0) {return if (i + 1 >= allRevisions.size) { - replacement in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 172
allRevisions[i - 1]allRevisions[i + 1] - replacement in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 196
val allRevisions = pijul(this.project).allRevisions(this.project, ctx.root).result!!val changes = mutableListOf<CommittedChangeList>() - replacement in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 198
val rev = pijul(this.project).log(this.project, ctx.root).result?.entries?.filter {it.revision.hash == number.asString()}?.map { entry ->PijulCommittedChangeList(entry.message,"",entry.authors.firstOrNull()?.name ?: "No author",Date.from(entry.date.toInstant()),entry.hunks.filterIsInstance<HunkWithPath>().map { hunk ->val beforeRevision =if (hunk is FileAddHunk) nullelse findARevisionBefore(entry.changeHash, allRevisions)?.let {PijulContentRevision(ctx.root,hunk.resolvePath(ctx.root),it,this.project)}this.getChanges(ChangeBrowserSettings(), 1, number) {changes.add(it)} - replacement in src/main/kotlin/com/github/jonathanxd/dracon/changes/PijulCommittedChangesProvider.kt at line 202
val afterRevision =if (hunk is FileDelHunk) nullelse PijulContentRevision(ctx.root,hunk.resolvePath(ctx.root),entry.revision,this.project)Change(beforeRevision,afterRevision,hunk.status)}.toMutableList(),entry.revision,false,pijulVcs(this.project))}?.firstOrNull() ?: return nullval rev = changes.firstOrNull() ?: return null - edit in src/main/kotlin/com/github/jonathanxd/dracon/actions/PijulExpertRecord.kt at line 5
import com.github.jonathanxd.dracon.i18n.DraconBundleimport com.github.jonathanxd.dracon.pijul.NonZeroExitStatusCodeimport com.github.jonathanxd.dracon.pijul.SuccessStatusCode - edit in src/main/kotlin/com/github/jonathanxd/dracon/actions/PijulExpertRecord.kt at line 9
import com.intellij.notification.Notificationimport com.intellij.notification.NotificationGroupimport com.intellij.notification.NotificationTypeimport com.intellij.notification.Notifications - edit in src/main/kotlin/com/github/jonathanxd/dracon/actions/PijulExpertRecord.kt at line 24
private val EXPERT_MODE_GROUP =NotificationGroup.createIdWithTitle("Expert Mode", DraconBundle.message("expert.mode.notification.group.id")) - edit in src/main/kotlin/com/github/jonathanxd/dracon/actions/PijulExpertRecord.kt at line 32
val record = pijul(project).record(project, root) {it.connectAndRetrieveContent()if (it.connected()) {PijulExpertRecordDialog(project, root, it, it.content()).showAndGet()}} - replacement in src/main/kotlin/com/github/jonathanxd/dracon/actions/PijulExpertRecord.kt at line 41
if (recordString.result != null) {if (record.statusCode is SuccessStatusCode) {val result = record.result!!val hash = result.substring(result.indexOf("Hash:") + "Hash:".length).trim()Notifications.Bus.notify(Notification(EXPERT_MODE_GROUP,DraconBundle.message("expert.mode.notification.title"),DraconBundle.message("expert.mode.notification.success", hash),NotificationType.ERROR,),project)} else {record.statusCode as NonZeroExitStatusCodeNotifications.Bus.notify(Notification(EXPERT_MODE_GROUP,DraconBundle.message("expert.mode.notification.title"),DraconBundle.message("expert.mode.notification.failure", record.statusCode.exitCode, record.statusCode.message),NotificationType.INFORMATION,),project)}/*if (recordString.result != null) { - replacement in src/main/kotlin/com/github/jonathanxd/dracon/actions/PijulExpertRecord.kt at line 69
/**//* - replacement in src/main/kotlin/com/github/jonathanxd/dracon/actions/PijulExpertRecord.kt at line 79
*/*//* - replacement in src/main/kotlin/com/github/jonathanxd/dracon/actions/PijulExpertRecord.kt at line 84
/*FileEditorManager.getInstance(project).openTextEditor(*//*FileEditorManager.getInstance(project).openTextEditor( - replacement in src/main/kotlin/com/github/jonathanxd/dracon/actions/PijulExpertRecord.kt at line 87
)*/})*//*}*/ - edit in README.md at line 33
Recently versions of Dracon requires [editor-server](https://crates.io/crates/editor-server) to be installed in order to work, the finalrelease version will come bundled with the [editor-server](https://crates.io/crates/editor-server).