Last active
March 7, 2018 16:11
-
-
Save dkandalov/5557393 to your computer and use it in GitHub Desktop.
IntelliJ micro-plugin to wrap selected text to the column width (copied from https://github.com/abrookins/WrapToColumn)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.intellij.openapi.actionSystem.ActionPlaces | |
import com.intellij.openapi.actionSystem.AnActionEvent | |
import com.intellij.openapi.actionSystem.DataContext | |
import com.intellij.openapi.actionSystem.LangDataKeys | |
import com.intellij.openapi.application.ApplicationManager | |
import com.intellij.openapi.diagnostic.Logger | |
import com.intellij.openapi.editor.Document | |
import com.intellij.openapi.editor.Editor | |
import com.intellij.openapi.editor.SelectionModel | |
import com.intellij.openapi.editor.actionSystem.EditorAction | |
import com.intellij.openapi.editor.actionSystem.EditorActionHandler | |
import com.intellij.openapi.project.Project | |
import com.intellij.psi.codeStyle.CodeStyleSettings | |
import com.intellij.psi.codeStyle.CodeStyleSettingsManager | |
import junit.framework.Assert | |
import junit.framework.TestSuite | |
import org.apache.commons.lang.WordUtils | |
import org.junit.Before | |
import org.junit.Test | |
import org.junit.internal.TextListener | |
import org.junit.runner.JUnitCore | |
import org.junit.runner.notification.Failure | |
import java.io.StringReader | |
import java.util.ArrayList | |
import java.util.regex.Matcher | |
import java.util.regex.Pattern | |
import static liveplugin.PluginUtil.* | |
import static org.junit.Assert.* | |
// Wraps selected text or, if no text is selected, the current line to the column width | |
// specified in the editor's "Right Margin (columns)" setting. | |
// | |
// This is almost direct copy-paste of this plugin https://github.com/abrookins/WrapToColumn | |
// (Note that this code was meant to be run within this plugin https://github.com/dkandalov/live-plugin) | |
if (!runUnitTests(project, CodeWrapperTest.class)) return | |
def id = "com.andrewbrookins.idea.wrap.WrapAction" | |
registerAction(id, "shift ctrl meta W", "EditMenu", "Wrap to Column") { event -> | |
new WrapAction().actionPerformed(event) | |
} | |
show("loaded WrapAction") | |
class WrapAction extends EditorAction { | |
private static final Logger log = Logger.getInstance(WrapAction.class) | |
WrapAction() { | |
super(new WrapHandler()) | |
} | |
@Override void update(AnActionEvent e) { | |
super.update(e) | |
if (ActionPlaces.isPopupPlace(e.place)) { | |
e.presentation.visible = e.presentation.enabled | |
} | |
} | |
private static class WrapHandler extends EditorActionHandler { | |
@Override void execute(final Editor editor, final DataContext dataContext) { | |
ApplicationManager.application.runWriteAction(new Runnable() { | |
@Override void run() { | |
def project = LangDataKeys.PROJECT.getData(dataContext) | |
def selectionModel = editor.selectionModel | |
if (!selectionModel.hasSelection()) { | |
selectionModel.selectLineAtCaret() | |
} | |
final String text = selectionModel.getSelectedText() | |
CodeWrapper wrapper = new CodeWrapper(CodeStyleSettingsManager.getSettings(project).RIGHT_MARGIN) | |
String wrappedText = wrapper.fillParagraphs(text) | |
editor.document.replaceString(selectionModel.selectionStart, selectionModel.selectionEnd, wrappedText) | |
} | |
}) | |
} | |
} | |
} | |
class CodeWrapper { | |
private static class Options { | |
// A string with a newline above and below it is a paragraph. | |
String paragraphSeparatorPattern = "(\\n|\\r\\n)\\s*(\\n|\\r\\n)" | |
// A string containing a comment or empty space is considered an indent. | |
String indentPattern = "^\\s*(#+|//+|;+)?\\s*" | |
// New lines appended to text during wrapping will use this character. | |
String lineSeparator = System.getProperty("line.separator") | |
// The column width to wrap text to. | |
Integer width = 80 | |
} | |
/* | |
Data about a line that has been split into two pieces: the indent portion | |
of the string, if one exists, and the rest of the string. | |
*/ | |
private static class LineData { | |
String indent = "" | |
String rest = "" | |
LineData(String indent, String rest) { | |
this.indent = indent | |
this.rest = rest | |
} | |
} | |
private Options options | |
CodeWrapper(Options options = new Options()) { | |
this.options = options | |
} | |
CodeWrapper(Integer columnWidth) { | |
options = new Options() | |
options.width = columnWidth | |
} | |
/** | |
* Fill multiple paragraphs | |
* | |
* Assume that paragraphs are separated by empty lines. Preserve | |
* the amount of white space between paragraphs. | |
* | |
* @param text the text to fill, which may contain multiple paragraphs. | |
* @return text filled to set column width. | |
*/ | |
String fillParagraphs(String text) { | |
StringBuilder result = new StringBuilder() | |
Pattern pattern = Pattern.compile(options.paragraphSeparatorPattern) | |
Matcher matcher = pattern.matcher(text) | |
Integer textLength = text.length() | |
Integer location = 0 | |
while (matcher.find()) { | |
String paragraph = text.substring(location, matcher.start()) | |
result.append(fill(paragraph)) | |
result.append(matcher.group()) | |
location = matcher.end() | |
} | |
if (location < textLength) { | |
result.append(fill(text.substring(location, textLength))) | |
} | |
String builtResult = result.toString() | |
// Keep trailing text newline. | |
if (text.endsWith(options.lineSeparator)) { | |
builtResult += options.lineSeparator | |
} | |
return builtResult | |
} | |
/** | |
* Fill paragraph by joining wrapped lines | |
* | |
* @param text the text to fill | |
* @return text filled with current width | |
*/ | |
private String fill(String text) { | |
StringBuilder result = new StringBuilder() | |
ArrayList<String> wrappedParagraphs = wrap(text) | |
int size = wrappedParagraphs.size() | |
for (int i = 0; i < size; i++) { | |
String paragraph = wrappedParagraphs.get(i) | |
// If this is a multi-paragraph list and we aren't at the end, | |
// add a new line. | |
if (size > 0 && i < size - 1) { | |
paragraph += options.lineSeparator | |
} | |
result.append(paragraph) | |
} | |
return result.toString() | |
} | |
/** | |
* Wrap code, and comments in a smart way | |
* | |
* Reformat the single paragraph in 'text' so it fits in lines of | |
* no more than 'width' columns, and return a list of wrapped | |
* lines. | |
* | |
* @param text single paragraph of text | |
* @return lines filled with current width | |
*/ | |
private ArrayList<String> wrap(String text) { | |
text = dewrap(text) | |
LineData firstLineData = splitIndent(text) | |
Integer width = options.width - firstLineData.indent.length() | |
String[] lines = WordUtils.wrap(text, width).split(options.lineSeparator) | |
ArrayList<String> result = new ArrayList<String>() | |
for (int i = 0; i < lines.length; i++) { | |
String line = lines[i] | |
if (i == 0) { | |
LineData lineData = splitIndent(line) | |
line = lineData.rest | |
} | |
// Use indent from the first line on it and all subsequent lines. | |
result.add(firstLineData.indent + line) | |
} | |
return result | |
} | |
/** | |
* Convert hard wrapped paragraph to one line. | |
* | |
* The indentation and comments of the first line are preserved, | |
* subsequent lines indent and comments characters are striped. | |
* | |
* @param text one paragraph of text, possibly hard-wrapped | |
* @return one line of text | |
*/ | |
private String dewrap(String text) { | |
if (text.isEmpty()) { | |
return text | |
} | |
String[] lines = text.split("[\\r\\n]+") | |
StringBuilder result = new StringBuilder() | |
// Add first line as is, keeping indent | |
result.append(lines[0]) | |
for (int i = 0; i < lines.length; i++) { | |
if (i == 0) { | |
continue | |
} | |
String unindentedLine = ' ' + splitIndent(lines[i]).rest | |
// Add rest of lines removing indent | |
result.append(unindentedLine) | |
} | |
return result.toString() | |
} | |
/** | |
* Split text on indent, including comments characters | |
* | |
* Example (parsed from left margin): | |
* // Comment -> ' // ', 'Comment' | |
* | |
* @param text text to remove indents from | |
* @return indent string, rest | |
*/ | |
private LineData splitIndent(String text) { | |
Pattern pattern = Pattern.compile(options.indentPattern) | |
Matcher matcher = pattern.matcher(text) | |
LineData lineData = new LineData("", text) | |
// Only break on the first indent-worthy sequence found, to avoid any | |
// weirdness with comments-embedded-in-comments. | |
if (matcher.find()) { | |
lineData.indent = matcher.group() | |
lineData.rest = text.substring(matcher.end(), text.length()) | |
} | |
return lineData | |
} | |
} | |
static def runUnitTests(Project project, Class... classes) { | |
JUnitCore junit = new JUnitCore() | |
def output = new ByteArrayOutputStream() | |
boolean hasFailures = false | |
junit.addListener(new TextListener(new PrintStream(output)) { | |
@Override void testFailure(Failure failure) { | |
super.testFailure(failure) | |
hasFailures = true | |
} | |
}) | |
classes.each { junit.run(it) } | |
if (hasFailures) showInConsole(output.toString(), project) | |
!hasFailures | |
} | |
class CodeWrapperTest { | |
CodeWrapper wrapper | |
@Before void initialize() { | |
wrapper = new CodeWrapper() | |
} | |
@Test void testCreateWithoutOptions() throws Exception { | |
String original = "// This is my text.\n// This is my text.\n" | |
String text = wrapper.fillParagraphs(original) | |
assertEquals("// This is my text. This is my text.\n", text) | |
} | |
@Test void testFillParagraphsOneLongLine() throws Exception { | |
String text = wrapper.fillParagraphs("// This is my very long line of text. " + | |
"This is my very long line of text. This is my very long line of text.\n") | |
assertEquals("// This is my very long line of text. This is my very long line of text. This\n" + | |
"// is my very long line of text.\n", text) | |
} | |
@Test void testFillParagraphsRetainsSeparateParagraphs() throws Exception { | |
String text = wrapper.fillParagraphs("// This is my very long line of text. " + | |
"This is my very long line of text. This is my very long line of text.\n\n" + | |
"// This is a second paragraph.\n") | |
assertEquals("// This is my very long line of text. This is my very long line of text. This\n" + | |
"// is my very long line of text.\n\n// This is a second paragraph.\n", text) | |
} | |
@Test void testFillParagraphsWorksWithWindowsNewlines() throws Exception { | |
String text = wrapper.fillParagraphs("// This is my very long line of text. " + | |
"This is my very long line of text. This is my very long line of text.\r\n\r\n" + | |
"// This is a second paragraph.\r\n") | |
assertEquals("// This is my very long line of text. This is my very long line of text. This\n" + | |
"// is my very long line of text.\r\n\r\n// This is a second paragraph.\n", text) | |
} | |
@Test void testFillParagraphsDoesNotCombineTwoShortLines() throws Exception { | |
String text = wrapper.fillParagraphs("// This is my text.\n// This is my text.") | |
assertEquals("// This is my text. This is my text.", text) | |
} | |
@Test void testFillParagraphsFillsMultiLineOpener() throws Exception { | |
// This could be more graceful, I suppose. | |
String text = wrapper.fillParagraphs("/** This is my text This is my long multi-" + | |
"line comment opener text. More text please.") | |
assertEquals("/** This is my text This is my long multi-" + | |
"line comment opener text. More text\nplease.", text) | |
} | |
@Test void testFillParagraphsRetainsSpaceIndent() throws Exception { | |
String text = wrapper.fillParagraphs(" This is my long indented " + | |
"string. It's too long to fit on one line, uh oh! What will happen?") | |
assertEquals(" This is my long indented string. It's too long to fit " + | |
"on one line, uh oh!\n What will happen?", text) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment