Initial support for reformatting by Ormolu by replacing custom Hindent and Stylish-Haskell paths by Ormolu path.

This commit is contained in:
Rik van der Kleij 2020-02-09 20:00:56 +01:00
parent efe93aaf39
commit 537906a8c4
9 changed files with 115 additions and 32 deletions

View File

@ -21,4 +21,10 @@ object HTool {
case object StylishHaskell extends HTool {
def name: String = "stylish-haskell"
}
case object Ormolu extends HTool {
def name: String = "ormolu"
}
}

View File

@ -21,6 +21,7 @@ import com.intellij.openapi.actionSystem.{AnActionEvent, Presentation}
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import intellij.haskell.external.component.StackProjectManager
import intellij.haskell.settings.HaskellSettingsState
import intellij.haskell.util.{HaskellEditorUtil, HaskellFileUtil}
class HaskellReformatAction extends ReformatCodeAction {
@ -32,7 +33,7 @@ class HaskellReformatAction extends ReformatCodeAction {
ActionUtil.findActionContext(actionEvent).foreach(actionContext => {
val psiFile = actionContext.psiFile
if (HaskellFileUtil.isHaskellFile(psiFile)) {
HaskellEditorUtil.enableExternalAction(actionEvent, (project: Project) => StackProjectManager.isStylishHaskellAvailable(project) && StackProjectManager.isHindentAvailable(project))
HaskellEditorUtil.enableExternalAction(actionEvent, (project: Project) => (StackProjectManager.isStylishHaskellAvailable(project) && StackProjectManager.isHindentAvailable(project)) || HaskellReformatAction.reformatByOrmolu)
} else {
super.update(actionEvent)
}
@ -45,10 +46,10 @@ class HaskellReformatAction extends ReformatCodeAction {
if (HaskellFileUtil.isHaskellFile(psiFile)) {
val selectionModel = actionContext.selectionModel
selectionModel match {
case Some(_) =>
case Some(_) if StackProjectManager.isHindentAvailable(actionEvent.getProject) =>
HindentReformatAction.format(psiFile, selectionModel.map(m =>
HindentReformatAction.translateSelectionModelToSelectionContext(m)))
case None => HaskellReformatAction.reformatFile(psiFile)
case _ => HaskellReformatAction.reformatFile(psiFile)
}
} else {
super.actionPerformed(actionEvent)
@ -59,9 +60,17 @@ class HaskellReformatAction extends ReformatCodeAction {
object HaskellReformatAction {
def reformatByOrmolu: Boolean = {
HaskellSettingsState.ormoluPath.isDefined
}
def reformatFile(psiFile: PsiFile): Boolean = {
HindentReformatAction.format(psiFile)
StylishHaskellReformatAction.format(psiFile)
HaskellSettingsState.ormoluPath match {
case Some(p) => OrmoluReformatAction.format(psiFile, p)
case None =>
HindentReformatAction.format(psiFile)
StylishHaskellReformatAction.format(psiFile)
}
true
}
}

View File

@ -26,7 +26,6 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import intellij.haskell.external.component.StackProjectManager
import intellij.haskell.external.execution.CommandLine
import intellij.haskell.settings.HaskellSettingsState
import intellij.haskell.util._
import intellij.haskell.{GlobalInfo, HTool, HaskellLanguage, HaskellNotificationGroup}
@ -52,7 +51,7 @@ class HindentReformatAction extends AnAction {
}
object HindentReformatAction {
private def hindentPath = HaskellSettingsState.hindentPath.getOrElse(GlobalInfo.defaultHindentPath.toString)
private def hindentPath = GlobalInfo.defaultHindentPath.toString
def format(psiFile: PsiFile, selectionContext: Option[SelectionContext] = None): Boolean = {
val lineLength = CodeStyle.getSettings(psiFile.getProject).getRightMargin(HaskellLanguage.Instance)

View File

@ -0,0 +1,77 @@
/*
* Copyright 2014-2019 Rik van der Kleij
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package intellij.haskell.action
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.actionSystem.{AnAction, AnActionEvent}
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import intellij.haskell.external.execution.CommandLine
import intellij.haskell.settings.HaskellSettingsState
import intellij.haskell.util._
import intellij.haskell.{HTool, HaskellNotificationGroup}
sealed case class SelectionContext(start: Int, end: Int, text: String)
class OrmoluReformatAction extends AnAction {
override def update(actionEvent: AnActionEvent): Unit = {
HaskellEditorUtil.enableExternalAction(actionEvent, (project: Project) => HaskellReformatAction.reformatByOrmolu)
}
override def actionPerformed(actionEvent: AnActionEvent): Unit = {
ActionUtil.findActionContext(actionEvent).foreach { actionContext =>
val psiFile = actionContext.psiFile
HaskellSettingsState.ormoluPath match {
case Some(p) => OrmoluReformatAction.format(psiFile, p)
case None => ()
}
}
}
}
object OrmoluReformatAction {
def format(psiFile: PsiFile, ormoluPath: String): Unit = {
val project = psiFile.getProject
HaskellFileUtil.saveFile(psiFile)
HaskellFileUtil.getAbsolutePath(psiFile) match {
case Some(path) =>
val processOutputFuture = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.callable[ProcessOutput] {
CommandLine.run(project, ormoluPath, Seq(path))
})
FutureUtil.waitForValue(project, processOutputFuture, s"reformatting by ${HTool.Ormolu.name}") match {
case None => ()
case Some(processOutput) =>
if (processOutput.getStderrLines.isEmpty) {
HaskellFileUtil.saveFileWithNewContent(psiFile, processOutput.getStdout)
} else {
HaskellNotificationGroup.logInfoEvent(project, s"Error while reformatting by `${HTool.Ormolu.name}`. Error: ${processOutput.getStderr}")
}
}
case None => HaskellNotificationGroup.logWarningBalloonEvent(psiFile.getProject, s"Can not reformat file because could not determine path for file `${psiFile.getName}`. File exists only in memory")
}
}
def versionInfo(project: Project): String = {
HaskellSettingsState.ormoluPath.map(p => CommandLine.run(project, p, Seq("--version")).getStdout).getOrElse("-")
}
}

View File

@ -23,7 +23,6 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import intellij.haskell.external.component.StackProjectManager
import intellij.haskell.external.execution.CommandLine
import intellij.haskell.settings.HaskellSettingsState
import intellij.haskell.util.{FutureUtil, HaskellEditorUtil, HaskellFileUtil, ScalaUtil}
import intellij.haskell.{GlobalInfo, HTool, HaskellNotificationGroup}
@ -41,7 +40,7 @@ class StylishHaskellReformatAction extends AnAction {
}
object StylishHaskellReformatAction {
private def stylishHaskellPath = HaskellSettingsState.stylishHaskellPath.getOrElse(GlobalInfo.defaultStylishHaskellPath.toString)
private def stylishHaskellPath = GlobalInfo.defaultStylishHaskellPath.toString
def versionInfo(project: Project): String = {
if (StackProjectManager.isStylishHaskellAvailable(project)) {

View File

@ -75,7 +75,7 @@ object CommandLine {
val commandLine = createCommandLine(workDir, commandPath, arguments)
if (!logOutput) {
HaskellNotificationGroup.logInfoEvent(project, s"Executing: `${commandLine.getCommandLineString}`")
HaskellNotificationGroup.logInfoEvent(project, s"Executing: ${commandLine.getCommandLineString} ")
}
val processHandler = createProcessHandler(project, commandLine, logOutput)

View File

@ -34,6 +34,7 @@ class HaskellConfigurable extends Configurable {
private val hindentPathField = new JTextField
private val hlintPathField = new JTextField
private val hooglePathField = new JTextField
private val ormoluPathField = new JTextField
private val stylishHaskellPathField = new JTextField
private val useCustomToolsToggle = new JCheckBox
private val extraStackArgumentsField = new JTextField
@ -54,6 +55,7 @@ class HaskellConfigurable extends Configurable {
hlintPathField.setVisible(visible)
hooglePathField.setVisible(visible)
stylishHaskellPathField.setVisible(visible)
ormoluPathField.setVisible(visible)
}
toggleToolPathsVisibility()
@ -68,10 +70,9 @@ class HaskellConfigurable extends Configurable {
hlintOptionsField.getDocument.addDocumentListener(docListener)
replTimeoutField.getDocument.addDocumentListener(docListener)
newProjectTemplateNameField.getDocument.addDocumentListener(docListener)
hindentPathField.getDocument.addDocumentListener(docListener)
hlintPathField.getDocument.addDocumentListener(docListener)
hooglePathField.getDocument.addDocumentListener(docListener)
stylishHaskellPathField.getDocument.addDocumentListener(docListener)
ormoluPathField.getDocument.addDocumentListener(docListener)
useSystemGhcToggle.addChangeListener { _ =>
isModifiedByUser = true
}
@ -126,10 +127,9 @@ class HaskellConfigurable extends Configurable {
(new JLabel(NewProjectTemplateName), newProjectTemplateNameField),
(new JLabel(BuildToolsUsingSystemGhc), useSystemGhcToggle),
(new JLabel(UseCustomTool), useCustomToolsToggle),
(new JLabel(HindentPath), hindentPathField),
(new JLabel(HlintPath), hlintPathField),
(new JLabel(HooglePath), hooglePathField),
(new JLabel(StylishHaskellPath), stylishHaskellPathField),
(new JLabel(OrmoluPath), ormoluPathField),
(new JLabel(""), afterRestartLabel)
)
@ -163,10 +163,9 @@ class HaskellConfigurable extends Configurable {
state.hlintOptions = hlintOptionsField.getText
state.useSystemGhc = useSystemGhcToggle.isSelected
state.newProjectTemplateName = newProjectTemplateNameField.getText
state.hindentPath = hindentPathField.getText
state.hlintPath = hlintPathField.getText
state.hooglePath = hooglePathField.getText
state.stylishHaskellPath = stylishHaskellPathField.getText
state.ormoluPath = ormoluPathField.getText
state.customTools = useCustomToolsToggle.isSelected
state.extraStackArguments = extraStackArgumentsField.getText
}
@ -192,17 +191,16 @@ class HaskellConfigurable extends Configurable {
private def validateCustomTools(): Unit = {
if (useCustomToolsToggle.isSelected) {
if (hindentPathField.getText.trim.isEmpty ||
hlintPathField.getText.trim.isEmpty ||
hooglePathField.getText.trim.isEmpty ||
stylishHaskellPathField.getText.trim.isEmpty) {
if (
ormoluPathField.getText.trim.isEmpty ||
hlintPathField.getText.trim.isEmpty ||
hooglePathField.getText.trim.isEmpty) {
throw new ConfigurationException(s"All Haskell tools paths have to be set")
}
checkFileExists(hindentPathField.getText)
checkFileExists(hlintPathField.getText)
checkFileExists(hooglePathField.getText)
checkFileExists(stylishHaskellPathField.getText)
checkFileExists(ormoluPathField.getText)
}
}
@ -217,10 +215,9 @@ class HaskellConfigurable extends Configurable {
useSystemGhcToggle.setSelected(state.useSystemGhc)
replTimeoutField.setText(state.replTimeout.toString)
newProjectTemplateNameField.setText(state.newProjectTemplateName)
hindentPathField.setText(state.hindentPath)
hlintPathField.setText(state.hlintPath)
hooglePathField.setText(state.hooglePath)
stylishHaskellPathField.setText(state.stylishHaskellPath)
ormoluPathField.setText(state.ormoluPath)
useCustomToolsToggle.setSelected(state.customTools)
extraStackArgumentsField.setText(state.extraStackArguments)
}
@ -235,6 +232,7 @@ object HaskellConfigurable {
final val HlintPath = "Hlint path"
final val HooglePath = "Hoogle path"
final val StylishHaskellPath = "Stylish Haskell path"
final val OrmoluPath = "Ormolu path"
final val UseCustomTool = "Use custom Haskell tools *"
final val CustomToolPathWarning =
"""WARNING! Specifying a path for a Haskell tool will override the default

View File

@ -54,10 +54,9 @@ public class HaskellSettingsPersistentStateComponent implements PersistentStateC
public Boolean reformatCodeBeforeCommit = false;
public Boolean optimizeImportsBeforeCommit = false;
public String newProjectTemplateName = "new-template";
public String hindentPath = "";
public String hlintPath = "";
public String hooglePath = "";
public String stylishHaskellPath = "";
public String ormoluPath = "";
public Boolean customTools = false;
public String extraStackArguments = "";
}

View File

@ -55,10 +55,6 @@ object HaskellSettingsState {
state.customTools
}
def hindentPath: Option[String] = {
Option.when(customTools && state.hindentPath.nonEmpty)(state.hindentPath)
}
def hlintPath: Option[String] = {
Option.when(customTools && state.hlintPath.nonEmpty)(state.hlintPath)
}
@ -67,8 +63,8 @@ object HaskellSettingsState {
Option.when(customTools && state.hooglePath.nonEmpty)(state.hooglePath)
}
def stylishHaskellPath: Option[String] = {
Option.when(customTools && state.stylishHaskellPath.nonEmpty)(state.stylishHaskellPath)
def ormoluPath: Option[String] = {
Option.when(customTools && state.ormoluPath.nonEmpty)(state.ormoluPath)
}
def useCustomTools: Boolean = {