Command line formatter (IDEA-99267)

This commit is contained in:
Rustam Vishnyakov
2016-07-11 12:06:41 +03:00
parent c1743bd0e9
commit b327fd7257
11 changed files with 476 additions and 0 deletions
@@ -0,0 +1,9 @@
package com.company;
public class Test {
class Foo {
void foo() {
//other comment
}
}
}
@@ -0,0 +1,9 @@
package com.company.subdir;
public class Test2 {
class Foo {
void foo() {
//other comment
}
}
}
@@ -0,0 +1,5 @@
package com.company;
public class Test {class Foo {
void foo() {
//other comment
}}}
@@ -0,0 +1,5 @@
package com.company.subdir;
public class Test2 {class Foo {
void foo() {
//other comment
}}}
@@ -0,0 +1,74 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* 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 com.intellij.formatting.commandLine;
import com.intellij.JavaTestUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightPlatformTestCase;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
public class FileSetFormatterTest extends LightPlatformTestCase {
private static final String BASE_PATH = JavaTestUtil.getJavaTestDataPath() + "/psi/formatter/commandLine";
public void testFormat() throws IOException {
File sourceDir = createSourceDir("original");
String fileSpec = sourceDir.getCanonicalPath() + File.separator + "**.java";
MessageOutput messageOutput = new MessageOutput(new PrintWriter(System.out), new PrintWriter(System.err));
FileSetFormatter formatter = new FileSetFormatter(fileSpec, messageOutput);
formatter.processFiles();
compareDirs(new File(BASE_PATH + File.separator + "expected"), sourceDir);
}
private static File createSourceDir(@NotNull String subDir) throws IOException {
File sourceDir = FileUtil.createTempDirectory("unitTest", "_format");
FileUtil.copyDir(new File(BASE_PATH + File.separator + subDir), sourceDir);
return sourceDir;
}
private static void compareDirs(@NotNull File expectedDir, @NotNull File resultDir) throws IOException {
assertTrue(expectedDir.isDirectory() && resultDir.isDirectory());
File[] childFiles = expectedDir.listFiles();
if (childFiles != null) {
for (File file : childFiles) {
File resultEntry = new File(resultDir.getCanonicalPath() + File.separator + file.getName());
assertTrue("Cannot find expected" + resultEntry.getPath(), resultEntry.exists());
if (!file.isDirectory()) {
assertContentEquals(resultEntry, file);
}
else {
compareDirs(file, resultEntry);
}
}
}
}
private static void assertContentEquals(@NotNull File expectedFile, @NotNull File actualFile) throws IOException {
VirtualFile expectedVFile = VfsUtil.findFileByIoFile(expectedFile, true);
assertNotNull(expectedVFile);
VirtualFile actualVFile = VfsUtil.findFileByIoFile(actualFile, true);
assertNotNull(actualVFile);
String expected = VfsUtilCore.loadText(expectedVFile);
String actual = VfsUtilCore.loadText(actualVFile);
assertEquals(expected, actual);
}
}
@@ -133,6 +133,7 @@ public class Main {
Comparing.strEqual(firstArg, "duplocate") ||
Comparing.strEqual(firstArg, "traverseUI") ||
Comparing.strEqual(firstArg, "buildAppcodeCache") ||
Comparing.strEqual(firstArg, "format") ||
(firstArg.length() < 20 && firstArg.endsWith("inspect"));
}
@@ -0,0 +1,134 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* 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 com.intellij.formatting.commandLine;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.util.PlatformUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
class FileSetFormatter extends FileSetProcessor {
private final static String PROJECT_DIR_PREFIX = PlatformUtils.getPlatformPrefix() + ".format.";
private final static String PROJECT_DIR_SUFFIX = ".tmp";
private final @NotNull String myProjectUID;
private @Nullable Project myProject;
private MessageOutput myMessageOutput;
FileSetFormatter(@NotNull String fileSpec, @NotNull MessageOutput messageOutput) {
super(fileSpec);
myMessageOutput = messageOutput;
myProjectUID = UUID.randomUUID().toString();
}
@Nullable
private File createProject() throws IOException {
ProjectManagerEx projectManager = (ProjectManagerEx)ProjectManager.getInstance();
File projectDir = createProjectDir();
myProject = projectManager.createProject(myProjectUID, projectDir.getPath());
if (myProject != null) {
projectManager.openProject(myProject);
return projectDir;
}
return null;
}
private File createProjectDir() throws IOException {
File tempDir = FileUtil.createTempDirectory(PROJECT_DIR_PREFIX, myProjectUID + PROJECT_DIR_SUFFIX);
File projectDir = new File(tempDir.getPath() + File.separator + ".idea");
if (projectDir.mkdirs()) {
return projectDir;
}
throw new IOException("Cannot create a temporary project at " + projectDir);
}
private void closeProject() {
if (myProject != null) {
ProjectManager.getInstance().closeProject(myProject);
AccessToken writeToken = ApplicationManager.getApplication().acquireWriteActionLock(this.getClass());
Disposer.dispose(myProject);
writeToken.finish();
}
}
@Override
public void processFiles() throws IOException {
File projectDir = createProject();
if (projectDir != null) {
super.processFiles();
closeProject();
}
}
@Override
protected void processFile(@NotNull VirtualFile virtualFile) {
if (myProject != null) {
VfsUtil.markDirtyAndRefresh(false, false, false, virtualFile);
myMessageOutput.info("Formatting " + virtualFile.getCanonicalPath() + "...");
Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
if (document != null) {
PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
NonProjectFileWritingAccessProvider.allowWriting(virtualFile);
if (psiFile != null) {
reformatFile(myProject, psiFile, document);
}
FileDocumentManager.getInstance().saveDocument(document);
}
FileEditorManager editorManager = FileEditorManager.getInstance(myProject);
VirtualFile[] openFiles = editorManager.getOpenFiles();
for (VirtualFile openFile : openFiles) {
editorManager.closeFile(openFile);
}
myMessageOutput.info("OK\n");
}
}
private void reformatFile(@NotNull Project project, @NotNull PsiFile file, @NotNull Document document) {
AccessToken writeToken = ApplicationManager.getApplication().acquireWriteActionLock(this.getClass());
try {
CommandProcessor.getInstance().executeCommand(
myProject,
() -> {
CodeStyleManager.getInstance(project).reformatText(file, 0, file.getTextLength());
PsiDocumentManager.getInstance(project).commitDocument(document);
}, null, null);
}
finally {
writeToken.finish();
}
}
}
@@ -0,0 +1,102 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* 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 com.intellij.formatting.commandLine;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
public abstract class FileSetProcessor {
private File myRoot;
private String myPattern;
FileSetProcessor(@NotNull String fileSpec) {
setRootAndPattern(fileSpec);
}
public void processFiles() throws IOException {
processEntry(myRoot);
}
private void processEntry(@NotNull File entry) throws IOException {
if (entry.exists()) {
if (entry.isDirectory()) {
File[] subEntries = entry.listFiles();
if (subEntries != null) {
for (File subEntry : subEntries) {
processEntry(subEntry);
}
}
}
else {
if (myPattern == null || entry.getCanonicalPath().matches(myPattern)) {
VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(entry);
if (virtualFile == null) {
throw new IOException("Can not find " + entry.getPath());
}
processFile(virtualFile);
}
}
}
}
protected abstract void processFile(@NotNull VirtualFile virtualFile);
private void setRootAndPattern(@NotNull String fileSpec) {
String rootPath = fileSpec;
int starPos = fileSpec.indexOf("*");
int questionPos = fileSpec.indexOf("?");
int wildcardPos = starPos >= 0 ? questionPos >= 0 ? Math.min(starPos, questionPos) : starPos : questionPos;
if (wildcardPos >= 0) rootPath = rootPath.substring(0, wildcardPos);
int lastSlash = Math.max(rootPath.lastIndexOf("/"), rootPath.lastIndexOf("\\"));
rootPath = lastSlash >= 0 ? rootPath.substring(0, lastSlash) : "";
myPattern = fileSpecToRegExp(fileSpec);
myRoot = new File(rootPath);
}
private static String fileSpecToRegExp(@NotNull String fileSpec) {
StringBuilder result = new StringBuilder();
char[] fileSpecChars = fileSpec.toCharArray();
for (int i = 0; i < fileSpecChars.length; i ++) {
char c = fileSpecChars[i];
switch (c) {
case '.':
result.append("\\.");
break;
case '*':
char next = i + 1 < fileSpecChars.length ? fileSpecChars[i + 1] : 0;
result.append(next == '*' ? ".*" : "[^\\\\]*");
break;
case '?':
result.append(".");
break;
case '/':
result.append("[\\\\/]");
break;
case '\\':
result.append("[\\\\/]");
break;
default:
result.append(c);
break;
}
}
return result.toString();
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* 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 com.intellij.formatting.commandLine;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationStarterEx;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.PrintWriter;
/**
* A launcher class for command-line formatter.
*/
public class FormatterStarter extends ApplicationStarterEx {
public static final String FORMAT_COMMAND_NAME = "format";
@Override
public boolean isHeadless() {
return true;
}
@Override
public String getCommandName() {
return FORMAT_COMMAND_NAME;
}
@Override
public void premain(String[] args) {
}
@Override
public void main(String[] args) {
@SuppressWarnings("UseOfSystemOutOrSystemErr")
MessageOutput messageOutput = new MessageOutput(
new PrintWriter(System.out),
new PrintWriter(System.err));
messageOutput.info(getAppInfo() + " Formatter\n");
for (int i = 1; i < args.length; i ++) {
if (args[i].startsWith("-")) {
if (checkOption(args[i], "-h", "--help")) {
showUsageInfo(messageOutput);
}
else {
messageOutput.error("Unknown option " + args[i]);
}
}
else {
FileSetFormatter fileSetFormatter = new FileSetFormatter(args[i], messageOutput);
try {
fileSetFormatter.processFiles();
}
catch (IOException e) {
messageOutput.error("FAILED: " + e.getLocalizedMessage() + "\n");
System.exit(1);
}
}
}
((ApplicationEx)ApplicationManager.getApplication()).exit(true, true);
}
private static boolean checkOption(@NotNull String arg, String... variants) {
for (String variant: variants) {
if (variant.equals(arg)) return true;
}
return false;
}
private static String getAppInfo() {
ApplicationInfoImpl appInfo = (ApplicationInfoImpl)ApplicationInfoEx.getInstanceEx();
return String.format("%s, build %s", appInfo.getFullApplicationName(), appInfo.getBuild().asString());
}
private static void showUsageInfo(@NotNull MessageOutput messageOutput) {
messageOutput.info("Usage: format fileSpec...");
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* 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 com.intellij.formatting.commandLine;
import org.jetbrains.annotations.NotNull;
import java.io.PrintWriter;
class MessageOutput {
private final PrintWriter myErrorOutput;
private final PrintWriter myInfoOutput;
public MessageOutput(PrintWriter infoOutput, PrintWriter errorOuput) {
myInfoOutput = infoOutput;
myErrorOutput = errorOuput;
}
public void info(@NotNull String message) {
myInfoOutput.print(message);
myInfoOutput.flush();
}
public void error(@NotNull String message) {
myErrorOutput.print(message);
myErrorOutput.flush();
}
}
@@ -2,6 +2,7 @@
<extensions defaultExtensionNs="com.intellij">
<appStarter implementation="com.intellij.codeInspection.InspectionMain"/>
<appStarter implementation="com.intellij.ide.ui.search.TraverseUIStarter"/>
<appStarter implementation="com.intellij.formatting.commandLine.FormatterStarter"/>
<referencesSearch implementation="com.intellij.psi.impl.search.CachesBasedRefSearcher"/>
<referencesSearch implementation="com.intellij.psi.impl.search.NonPhysicalReferenceSearcher"/>