Skip to content

Commit

Permalink
More documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
InSyncWithFoo committed Feb 11, 2025
1 parent a51095f commit da7ba85
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 2 deletions.
29 changes: 29 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/Paths.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package insyncwithfoo.ryecharm

import com.intellij.execution.configurations.PathEnvironmentVariableUtil
import com.intellij.openapi.util.SystemInfo
import java.io.File
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.nio.file.Files
import kotlin.io.path.div
import kotlin.io.path.exists
import kotlin.io.path.getLastModifiedTime
import kotlin.io.path.nameWithoutExtension

Expand Down Expand Up @@ -33,30 +36,56 @@ internal fun String.toPathOrNull() =
}


/**
* Attempt to convert the string to a normalized [Path],
* then call [toNullIfNotExists] on it.
*/
internal fun String.toPathIfItExists() =
this.toPathOrNull()?.normalize()?.toNullIfNotExists()


/**
* Append `.exe` to the string if the current system is Windows.
*/
internal fun String.toOSDependentFileName() = when {
SystemInfo.isWindows -> "$this.exe"
else -> this
}


/**
* Return the path unchanged if it is occupied.
* Otherwise, return `null`.
*
* This necessarily uses [File.exists] rather than [Path.exists],
* which itself calls [Files.exists].
*/
internal fun Path.toNullIfNotExists() =
this.takeIf { it.toFile().exists() }


/**
* Return a new path with the extension removed.
*
* Example: `foo/bar.qux` → `foo/bar`.
*/
internal fun Path.removeExtension() =
when {
parent == null -> nameWithoutExtension.toPathOrNull()
else -> parent / nameWithoutExtension
}


/**
* Return the path constructed by joining the current one with [name],
* if it is occupied.
*/
internal fun Path.findExecutableChild(name: String) =
resolve(name.toOSDependentFileName()).toNullIfNotExists()


/**
* Look for [name] in PATH.
*/
internal fun findExecutableInPath(name: String) =
PathEnvironmentVariableUtil.findExecutableInPathOnAnyOS(name)?.toPath()
71 changes: 71 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/ProcessNotifications.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package insyncwithfoo.ryecharm
import com.intellij.execution.process.ProcessOutput
import com.intellij.notification.Notification
import com.intellij.notification.NotificationGroup
import com.intellij.openapi.editor.Document
import com.intellij.openapi.project.Project
import insyncwithfoo.ryecharm.configurations.ruff.ruffConfigurations
import insyncwithfoo.ryecharm.configurations.ruffExecutable
Expand All @@ -13,6 +14,14 @@ import insyncwithfoo.ryecharm.configurations.uvExecutable
import java.nio.file.Path


/**
* If the process was cancelled, do nothing.
* If the process timed out, call [processTimeout] and return.
* If none of those cases happened, call [notifyWarningsFromOutput].
*
* Then, if the process was successful (see [ProcessOutput.isSuccessful]),
* call [handleSuccessfulOutput]. Otherwise, call [unknownError].
*/
internal fun Project.notifyIfProcessIsUnsuccessfulOr(
command: Command,
output: ProcessOutput,
Expand Down Expand Up @@ -40,6 +49,9 @@ internal fun Project.notifyIfProcessIsUnsuccessful(command: Command, output: Pro
notifyIfProcessIsUnsuccessfulOr(command, output) {}


/**
* Call [notifyIfProcessIsUnsuccessfulOr] with [processCompletedSuccessfully] as the callback.
*/
internal fun Project.notifyProcessResult(command: Command, output: ProcessOutput) =
notifyIfProcessIsUnsuccessfulOr(command, output) {
processCompletedSuccessfully()
Expand All @@ -50,6 +62,10 @@ internal fun NotificationGroup.genericWarning(content: String) =
warning(message("notifications.warning.title"), content)


/**
* Search for lines starting with `warning:` in stderr
* and re-emit them as notifications.
*/
internal fun Project.notifyWarningsFromOutput(output: ProcessOutput) {
val warning = """^warning: (.+)""".toRegex()
val warnings = warning.findAll(output.stderr)
Expand All @@ -75,6 +91,13 @@ private fun NotificationGroup.processCompletedSuccessfully(content: String? = nu
}


/**
* Emit a notification saying that the process has completed successfully
* with [content] as the body.
*
* Typically used when a process is neither cancelled nor timed out,
* and the exit code is 0.
*/
internal fun Project.processCompletedSuccessfully(content: String? = null) =
unimportantNotificationGroup.processCompletedSuccessfully(content).notify(this)

Expand All @@ -94,6 +117,18 @@ private fun NotificationGroup.unknownError(
}


/**
* Emit a diagnostic saying that an unknown error has happened.
*
* Typically used when the process is neither cancelled nor timed out,
* but the exit code is not 0.
*
* To assist with debugging, the following actions are provided:
*
* * See stdout/stderr (see [addSeeOutputActions])
* * Copy command (see [addCopyCommandAction])
* * Open plugin issue tracker (see [addOpenPluginIssueTrackerAction])
*/
internal fun Project.unknownError(command: Command, processOutput: ProcessOutput? = null) =
importantNotificationGroup.unknownError(command, processOutput).notify(this)

Expand All @@ -119,6 +154,14 @@ private fun NotificationGroup.noProjectFound(): Notification {
}


/**
* Emit a notification saying no project is found.
*
* Typically used when a project-based action cannot be completed
* due to the lack of a project context
* (e.g., an event has no corresponding [Project],
* the path of a [Project] cannot be determined).
*/
internal fun noProjectFound() =
importantNotificationGroup.noProjectFound().notify(defaultProject)

Expand All @@ -131,6 +174,13 @@ private fun NotificationGroup.noDocumentFound(): Notification {
}


/**
* Emit a notification saying no [Document] is found.
*
* Typically used when an editor-based action cannot be completed
* due to the lack of an editor context
* (e.g., user is not interacting with any editors).
*/
internal fun Project.noDocumentFound() =
unimportantNotificationGroup.noDocumentFound().notify(this)

Expand All @@ -143,6 +193,16 @@ private fun NotificationGroup.unableToRunCommand(): Notification {
}


/**
* Emit a notification saying that the command in question
* could not be run for some reason, as documented by [debugNote].
*
* Typically used via [couldNotConstructCommandFactory].
*
* The [debugNote] can be viewed using an action.
*
* @see addOpenTemporaryFileAction
*/
internal fun Project.unableToRunCommand(debugNote: String) {
val debugInfo = """
|${debugNote}
Expand All @@ -166,6 +226,12 @@ internal fun Project.unableToRunCommand(debugNote: String) {
}


/**
* Thin wrapper around [unableToRunCommand],
* attaching this factory's class name as part of the debug note.
*
* @see CommandFactory
*/
internal inline fun <reified F : CommandFactory> Project.couldNotConstructCommandFactory(extraNote: String) {
unableToRunCommand(
"""
Expand Down Expand Up @@ -198,5 +264,10 @@ private fun NotificationGroup.cannotOpenFile(path: Path): Notification {
}


/**
* Emit a notification saying the file at [path] cannot be opened.
*
* Typically used when [openFile] has failed.
*/
internal fun Project.cannotOpenFile(path: Path) =
importantNotificationGroup.cannotOpenFile(path).notify(this)
9 changes: 9 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/ProcessOutputs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@ internal val ProcessOutput.isSuccessful: Boolean
get() = exitCode == 0


/**
* Whether the process is cancelled, timed out or unsuccessful.
*
* @see [isSuccessful]
*/
internal val ProcessOutput.completedAbnormally: Boolean
get() = isTimeout || isCancelled || !isSuccessful


/**
* [ProcessOutput] in serializable form,
* to be used in logging functions.
*/
@Suppress("unused")
@Serializable
internal class ProcessOutputSurrogate(
Expand Down
32 changes: 31 additions & 1 deletion src/main/kotlin/insyncwithfoo/ryecharm/Project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ internal val openProjects: Sequence<Project>
get() = projectManager.openProjects.asSequence()


/**
* Return the so-called "default" project singleton.
*
* The default project is used to
* store default settings for new projects.
*
* In extreme cases, it is used in lieu of an actual project.
*/
internal val defaultProject: Project
get() = projectManager.defaultProject

Expand All @@ -51,7 +59,9 @@ internal val Project.modules: Array<Module>


/**
* @see [pythonSdk]
* The Python SDK of this project.
*
* @see pythonSdk
*/
private val Project.sdk: Sdk?
get() = rootManager.projectSdk?.takeIf { PythonSdkUtil.isPythonSdk(it) }
Expand All @@ -62,6 +72,12 @@ internal val Project.path: Path?
?: basePath?.toPathOrNull()?.toNullIfNotExists()


/**
* Attempt to convert the result of [Sdk.getHomePath] to a [Path].
*
* @see sdk
* @see toPathIfItExists
*/
internal val Project.interpreterPath: Path?
get() = sdk?.homePath?.toPathIfItExists()

Expand All @@ -88,18 +104,32 @@ internal val Project.inspectionProfileManager: ProjectInspectionProfileManager
get() = ProjectInspectionProfileManager.getInstance(this)


/**
* Return the first file in the project's environment
* whose name without extension matches the given name.
*
* @see interpreterDirectory
*/
internal fun Project.findExecutableInVenv(nameWithoutExtension: String) =
interpreterDirectory?.listDirectoryEntries()
?.find { it.nameWithoutExtension == nameWithoutExtension }


/**
* Attempt to open the given [virtualFile]
* and switch focus to the new editor.
*/
internal fun Project.openFile(virtualFile: VirtualFile?) {
val focusEditor = true

fileEditorManager.openFile(virtualFile!!, focusEditor)
}


/**
* Create a new [LightVirtualFile] with the given [filename] and [content]
* and open an editor for it.
*/
internal fun Project.openLightFile(filename: String, content: String) {
val fileType = FileTypeManager.getInstance().getFileTypeByFileName(filename)
val file = LightVirtualFile(filename, fileType, content)
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/insyncwithfoo/ryecharm/Registry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal class Logging(override val parentPrefix: String) : Prefixed {


/**
* Thin wrapper around [Registry] to allow for .
* Thin wrapper around [Registry] to allow for ergonomic syntax.
*/
internal object RyeCharmRegistry : Prefixed {

Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/Sdk.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@ import com.intellij.openapi.projectRoots.Sdk
import java.nio.file.Path


/**
* Attempt to convert the result of [Sdk.getHomePath] to a [Path].
*
* @see toPathOrNull
*/
internal val Sdk.path: Path?
get() = homePath?.toPathOrNull()

0 comments on commit da7ba85

Please sign in to comment.