Skip to content
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

Add jumps between pre processed files #7

Merged
merged 2 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>