Skip to content

Commit

Permalink
Merge pull request #7 from nea89o/feat/jump-to-file
Browse files Browse the repository at this point in the history
Add jumps between pre processed files
  • Loading branch information
pauliesnug authored May 25, 2024
2 parents 30a4564 + b57ac4e commit 152aeca
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

## [Unreleased]

### Added

- Add jump to/from preprocessed file action. You will need to manually assign a keybinding for this feature.

## [2.0.0] - 2024-05-03

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package org.polyfrost.sorbet.intelliprocessor

import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.isDirectory
import kotlin.io.path.isRegularFile
import kotlin.io.path.relativeToOrNull
import kotlin.streams.asSequence

class PreprocessorFileJumpAction : DumbAwareAction() {

fun getMainProjectVersion(rootDirectory: Path): String? {
val mainProject = rootDirectory.resolve("versions/mainProject")
if (!mainProject.isRegularFile())
return null
return Files.readString(mainProject).trim()
}

fun showWarning(text: String, project: Project?) {
NotificationGroupManager.getInstance()
.getNotificationGroup("Jump Failure")
.createNotification(text, NotificationType.ERROR)
.notify(project)
}

override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return showWarning("Missing project", null)

val rootDirectory =
// TODO: derive this from the source root
project.guessProjectDir()?.toNioPath()
?: return showWarning("Could not find project root directory", project)

val mainVersion = getMainProjectVersion(rootDirectory)
?: return showWarning("Could not find mainProject. Is this a preprocessor project?", project)

val editor = e.getData(PlatformDataKeys.EDITOR)
?: return showWarning("Could not find an open editor", project)

val currentPsiFile =
getActiveFile(editor, project) ?: return showWarning("Could not find an opened file", project)
val currentlyEditingFile = currentPsiFile.virtualFile?.toNioPath()
?: return showWarning("Could not find file on disk", project)

if (rootDirectory.fileSystem != currentlyEditingFile.fileSystem)
return showWarning("Current file not in project root", project)

val projectPath = currentlyEditingFile.relativeToOrNull(rootDirectory)?.toList()
?: return showWarning("Current file not in project root", project)

val currentSourceSetFile = getSourceSetFrom(projectPath)
?: return showWarning("File does not seem to be a preprocessor source or generated file", project)

val allVersions = getAllVersions(rootDirectory)
if (allVersions.size < 2)
return showWarning("Could not find any preprocessed source sets. Make sure to build your project", project)

val targets = allVersions.map { currentSourceSetFile.copy(version = it) }
.filter { it.version != mainVersion } // The preprocessed sources are not generated for the main project

val ideView = LangDataKeys.IDE_VIEW.getData(e.dataContext)
?: return showWarning("Could not find IDE view", project)

val caret = editor.caretModel.currentCaret.visualPosition

JBPopupFactory.getInstance()
.createListPopup(object : BaseListPopupStep<SourceSetFile>("Choose an Alternative Source File", targets) {
override fun getTextFor(value: SourceSetFile): String {
return (value.version ?: mainVersion) + "/" + value.classPath.toString()
}

override fun isSpeedSearchEnabled(): Boolean {
return true
}

override fun isSelectable(value: SourceSetFile): Boolean {
return value.version != currentSourceSetFile.version
}

override fun getIndexedString(value: SourceSetFile): String {
return value.version ?: mainVersion
}

override fun onChosen(selectedValue: SourceSetFile?, finalChoice: Boolean): PopupStep<*>? {
selectedValue ?: return null
val virtualFile = VfsUtil.findFile(rootDirectory.resolve(selectedValue.toRelativePath()), true)
if (virtualFile == null) {
showWarning("Could not find file for version ${selectedValue.version ?: mainVersion} on disk. Try building your project",
project)
return null
}
val psiFile = PsiManager.getInstance(project).findFile(virtualFile)
if (psiFile == null) {
showWarning("Could not open file. Is this project properly loaded?", project)
return null
}
return doFinalStep {
ideView.selectElement(psiFile)
val newEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile)
if (newEditor is TextEditor) {
newEditor.editor.caretModel.moveToVisualPosition(caret)
} else {
showWarning("Could not set cursor for non-text file", project)
}
}
}
})
.showCenteredInCurrentWindow(project)
}

fun getActiveFile(editor: Editor, project: Project): PsiFile? {
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return null
return file
}

// TODO: derive this from the source set roots
fun getAllVersions(rootDirectory: Path): MutableList<String?> {
val versions: MutableList<String?> = mutableListOf(null)
Files.list(rootDirectory.resolve("versions"))
.asSequence()
.filter { it.isDirectory() }
.forEach {
versions.add(it.fileName.toString())
}
return versions
}

data class SourceSetFile(
val sourceSetName: String,
val language: String,
val classPath: Path,
val version: String?,
) {
fun toRelativePath(): Path {
return if (version == null) Path.of("src", sourceSetName, language).resolve(classPath)
else Path.of("versions", version, "build", "preprocessed", sourceSetName, language).resolve(classPath)
}
}

fun Iterable<Path>.joinToPath(): Path {
return reduce { acc, path -> acc.resolve(path) }
}

fun getSourceSetFrom(path: List<Path>): SourceSetFile? {
if (path.size < 4) return null

// A path in the format of src/<sourceset>/<language>/<package>/<class>
if (path[0].toString() == "src") return SourceSetFile(
path[1].toString(),
path[2].toString(),
path.subList(3, path.size).joinToPath(),
null
)

if (path.size < 7) return null

// A path in the format of versions/<version>/build/preprocessed/<sourceset>/<language>/<package>/<class>
if (path[0].toString() == "versions"
&& path[2].toString() == "build"
&& path[3].toString() == "preprocessed"
) return SourceSetFile(
path[4].toString(),
path[5].toString(),
path.subList(6, path.size).joinToPath(),
path[1].toString()
)

return null
}


}
11 changes: 11 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,16 @@
implementationClass="org.polyfrost.sorbet.intelliprocessor.PreprocessorFolding"
order="first"
/>
<notificationGroup displayType="BALLOON" id="Jump Failure" />
</extensions>

<actions>

<action id="org.polyfrost.sorbet.intelliprocessor.PreprocessorFileJumpAction"
class="org.polyfrost.sorbet.intelliprocessor.PreprocessorFileJumpAction"
text="Jump To Pre-Processed File"
description="Jump from or to this file in the preprocessed source. Will not update those source files, so you might need to build your project to update those files.">
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
</actions>
</idea-plugin>

0 comments on commit 152aeca

Please sign in to comment.