Skip to content

Commit

Permalink
Updates to the rider plugin
Browse files Browse the repository at this point in the history
Use DOTNET_NOLOGO - fixes #533 and #549
Use a custom tools path to allow csharpier to be updated/installed while running - fixes #563 and #531
Use the solution version of csharpier - fixes #493
  • Loading branch information
belav committed Jan 23, 2022
1 parent cc6fbbe commit a267474
Show file tree
Hide file tree
Showing 23 changed files with 523 additions and 124 deletions.
20 changes: 20 additions & 0 deletions Docs/ExtensionTestCases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
No installs at all
- show message to install globaly or in solution

Install globaly
- should format after
- can uninstall globaly after process running

Install solution
- works with existing config
- works with no config
- should format after

Formatting
- Existing open documents get warmed
- Edit warms
- Uses correct version - adding lines is easy to test, 0.12 vs 0.14

Action
- is not shown on files that aren't c#
- run on save works
5 changes: 5 additions & 0 deletions Src/CSharpier.Rider/.run/Run IDE with Plugin.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
<configuration default="false" name="Run Plugin" type="GradleRunConfiguration" factoryName="Gradle">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="DEBUG" value="1" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.intellij.csharpier;

import com.intellij.openapi.diagnostic.Logger;
import org.apache.log4j.Level;

public class CSharpierLogger {
private final static Logger logger = createLogger();

private static Logger createLogger() {
Logger logger = Logger.getInstance(CSharpierLogger.class);
if ("1".equals(System.getenv("DEBUG"))) {
logger.setLevel(Level.DEBUG);
}

return logger;
}

public static Logger getInstance() {
return logger;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import java.util.concurrent.atomic.AtomicBoolean;

public class CSharpierProcessPipeMultipleFiles implements ICSharpierProcess, Disposable {
Logger logger = Logger.getInstance(CSharpierProcessPipeMultipleFiles.class);
Logger logger = CSharpierLogger.getInstance();
String csharpierPath;

Process process = null;
Expand All @@ -21,8 +21,9 @@ public class CSharpierProcessPipeMultipleFiles implements ICSharpierProcess, Dis
public CSharpierProcessPipeMultipleFiles(String csharpierPath, boolean useUtf8) {
this.csharpierPath = csharpierPath;
try {
this.process = new ProcessBuilder("dotnet", csharpierPath, "--pipe-multiple-files")
.start();
ProcessBuilder processBuilder = new ProcessBuilder(csharpierPath, "--pipe-multiple-files");
processBuilder.environment().put("DOTNET_NOLOGO", "1");
this.process = processBuilder.start();

String charset = useUtf8 ? "utf-8" : Charset.defaultCharset().toString();

Expand All @@ -33,6 +34,9 @@ public CSharpierProcessPipeMultipleFiles(String csharpierPath, boolean useUtf8)
this.logger.error("error", e);
}

this.logger.debug("Warm CSharpier with initial format");
// warm by formatting a file twice, the 3rd time is when it gets really fast
this.formatFile("public class ClassName { }", "Test.cs");
this.formatFile("public class ClassName { }", "Test.cs");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package com.intellij.csharpier;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.intellij.notification.NotificationGroupManager;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

public class CSharpierProcessProvider implements DocumentListener, @NotNull Disposable, IProcessKiller {
private final CustomPathInstaller customPathInstaller = new CustomPathInstaller();
private final Logger logger = CSharpierLogger.getInstance();
private final Project project;

private boolean warnedForOldVersion;
private final HashMap<String, Boolean> warmingByDirectory = new HashMap<>();
private final HashMap<String, String> csharpierVersionByDirectory = new HashMap<>();
private final HashMap<String, ICSharpierProcess> csharpierProcessesByVersion = new HashMap<>();

public CSharpierProcessProvider(@NotNull Project project) {
this.project = project;

for (FileEditor fileEditor : FileEditorManager.getInstance(project).getAllEditors()) {
this.findAndWarmProcess(fileEditor.getFile().getPath());
}

EditorFactory.getInstance().getEventMulticaster().addDocumentListener(this, this);
}

@Override
public void documentChanged(@NotNull DocumentEvent event) {
Document document = event.getDocument();
VirtualFile file = FileDocumentManager.getInstance().getFile(document);

if (file == null
|| file.getExtension() == null
|| !file.getExtension().equalsIgnoreCase("cs")
) {
return;
}
String filePath = file.getPath();
this.findAndWarmProcess(filePath);
}

@NotNull
static CSharpierProcessProvider getInstance(@NotNull Project project) {
return project.getService(CSharpierProcessProvider.class);
}

private void findAndWarmProcess(String filePath) {
String directory = Path.of(filePath).getParent().toString();
if (this.warmingByDirectory.getOrDefault(directory, false)) {
return;
}
this.logger.debug("Ensure there is a csharpier process for " + directory);
this.warmingByDirectory.put(directory, true);
String version = this.csharpierVersionByDirectory.getOrDefault(directory, null);
if (version == null) {
version = this.getCSharpierVersion(directory);
if (version == null || version.isEmpty()) {
InstallerService.getInstance(this.project).displayInstallNeededMessage(directory, this);
}
this.csharpierVersionByDirectory.put(directory, version);
}

if (!this.csharpierProcessesByVersion.containsKey(version)) {
this.csharpierProcessesByVersion.put(version, this.setupCSharpierProcess(
directory,
version
));
}

this.warmingByDirectory.remove(directory);
}

public ICSharpierProcess getProcessFor(String filePath) {
String directory = Path.of(filePath).getParent().toString();
String version = this.csharpierVersionByDirectory.getOrDefault(directory, null);
if (version == null) {
this.findAndWarmProcess(filePath);
version = this.csharpierVersionByDirectory.get(directory);
}

if (version == null || !this.csharpierProcessesByVersion.containsKey(version)) {
// this shouldn't really happen, but just in case
return new NullCSharpierProcess();
}

return this.csharpierProcessesByVersion.get(version);
}

;

private String getCSharpierVersion(String directoryThatContainsFile) {
Path currentDirectory = Path.of(directoryThatContainsFile);
try {
while (true) {
Path configPath = Path.of(currentDirectory.toString(), ".config/dotnet-tools.json");
String dotnetToolsPath = configPath.toString();
File file = new File(dotnetToolsPath);
this.logger.debug("Looking for " + dotnetToolsPath);
if (file.exists()) {
String data = new String(Files.readAllBytes(configPath));
JsonObject configData = new Gson().fromJson(data, JsonObject.class);
JsonObject tools = configData.getAsJsonObject("tools");
if (tools != null) {
JsonObject csharpier = tools.getAsJsonObject("csharpier");
if (csharpier != null) {
String version = csharpier.get("version").getAsString();
if (version != null) {
this.logger.debug("Found version " + version + " in " + dotnetToolsPath);
return version;
}
}
}
}

if (currentDirectory.getParent() == null) {
break;
}
currentDirectory = currentDirectory.getParent();
}
} catch (Exception ex) {
this.logger.error(ex);
}

this.logger.debug(
"Unable to find dotnet-tools.json, falling back to running dotnet csharpier --version"
);

Map<String, String> env = new HashMap<>();
env.put("DOTNET_NOLOGO", "1");

String[] command = {"dotnet", "csharpier", "--version"};

String version = ProcessHelper.ExecuteCommand(command, env, new File(directoryThatContainsFile));

this.logger.debug("dotnet csharpier --version output: " + version);

return version == null ? "" : version;
}


private ICSharpierProcess setupCSharpierProcess(String directory, String version) {
if (version == null || version.equals("")) {
return new NullCSharpierProcess();
}

this.customPathInstaller.ensureVersionInstalled(version);
String customPath = this.customPathInstaller.getPathForVersion(version);
try {
this.logger.debug("Adding new version " + version + " process for " + directory);

ComparableVersion installedVersion = new ComparableVersion(version);
ComparableVersion pipeFilesVersion = new ComparableVersion("0.12.0");
ComparableVersion utf8Version = new ComparableVersion("0.14.0");

if (installedVersion.compareTo(pipeFilesVersion) < 0) {
if (!this.warnedForOldVersion) {
String content = "Please upgrade to CSharpier >= 0.12.0 for bug fixes and improved formatting speed.";
NotificationGroupManager.getInstance().getNotificationGroup("CSharpier")
.createNotification(content, NotificationType.INFORMATION)
.notify(this.project);

this.warnedForOldVersion = true;
}


return new CSharpierProcessSingleFile(customPath);
}

boolean useUtf8 = installedVersion.compareTo(utf8Version) >= 0;

return new CSharpierProcessPipeMultipleFiles(customPath, useUtf8);

} catch (Exception ex) {
this.logger.error(ex);
}

return new NullCSharpierProcess();
}

@Override
public void dispose() {
this.killRunningProcesses();
}

public void killRunningProcesses() {
for (String key : this.csharpierProcessesByVersion.keySet()) {
this.logger.debug(
"disposing of process for version " + (key == "" ? "null" : key)
);
this.csharpierProcessesByVersion.get(key).dispose();
}
this.warmingByDirectory.clear();
this.csharpierVersionByDirectory.clear();
this.csharpierProcessesByVersion.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.io.OutputStream;

public class CSharpierProcessSingleFile implements ICSharpierProcess {
Logger logger = Logger.getInstance(CSharpierProcessSingleFile.class);
Logger logger = CSharpierLogger.getInstance();
String csharpierPath;

public CSharpierProcessSingleFile(String csharpierPath) {
Expand All @@ -18,7 +18,9 @@ public CSharpierProcessSingleFile(String csharpierPath) {
@Override
public String formatFile(String content, String fileName) {
try {
ProcessBuilder processBuilder = new ProcessBuilder("dotnet", this.csharpierPath, "--write-stdout");
this.logger.debug("Running " + this.csharpierPath + " --write-stdout");
ProcessBuilder processBuilder = new ProcessBuilder(this.csharpierPath, "--write-stdout");
processBuilder.environment().put("DOTNET_NOLOGO", "1");
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();

Expand All @@ -32,7 +34,6 @@ public String formatFile(String content, String fileName) {

var nextCharacter = stdOut.read();
while (nextCharacter != -1) {
this.logger.info("Got Output " + nextCharacter);
output.append((char)nextCharacter);
nextCharacter = stdOut.read();
}
Expand All @@ -51,4 +52,9 @@ public String formatFile(String content, String fileName) {

return "";
}

@Override
public void dispose() {

}
}
Loading

0 comments on commit a267474

Please sign in to comment.