/** * Justify It ! * * This macro will justify a text (either a docblock comment, either * anything you want). The advantage of justifying your text is * simple : you will have a fixed length paragraph that will fit your * screen. * * Your comment will also be more readable. * * Note: when you are in docblock section, the script will get the * docBlock indent and try to add a start (*) before each line. * * @author Baldurien * @version 0.1 * * Bug fixed in 0.1 : * - buffer.getText() throw an exception when you are at end of * buffer. Now, {@link getSelectedOffset()} will determine the * length of current buffer, and then do appropriate change. * * @todo try to recognize list in docblock, and indent list starting * with '-' with three spaces on left (like the 'bug fixed' list) */ /** * The number of char per line */ int marginLength = 80; /** * The padding */ int paddingLength = 10; /** * The minLength : we must have at least 20 char per line to * justify */ int minLength = 20; /** * Are we in docBlock mode? */ boolean docBlock = false; /** * The suffixe of parserRuleName for docBlock reco. * must begin be a valid regexp */ String docParserRuleName = ".*:(JAVA|JS|PHP)DOC"; /** * Return the selected text's offset * * If the selection is set, then we will use it to determine * the selected text. Else we will use the {@link * Textarea.getCaretLine()} method. * * @return int[] */ int[] getSelectedOffset() { sel = textArea.getSelection(); int[] offset = new int[2]; if ( sel.length != 0 ) { offset[0] = buffer.getLineStartOffset(sel[0].getStartLine()); docBlock = getIsInDocBlock(offset[0]); offset[1] = buffer.getLineEndOffset(sel[0].getEndLine()); } else { offset[0] = buffer.getLineStartOffset(textArea.getCaretLine()); docBlock = getIsInDocBlock(offset[0]); int line = textArea.getCaretLine() + 1; while ( line < buffer.getLineCount() ) { String s = buffer.getLineText(line).trim(); if ( s.length() == 0 ) break; if ( docBlock && s.indexOf("*/") != -1 ) break; if ( docBlock && s.length() == 1 && s.charAt(0) == '*') break; line++; } if ( line >= buffer.getLineCount() ) line = buffer.getLineCount() - 1; else line--; offset[1] = buffer.getLineEndOffset(line); } if ( offset[1] >= buffer.getLength() ) offset[1] = buffer.getLength() - 1; offset[1] -= offset[0]; // Macros.message(view, buffer.getText(offset[0], offset[1])); return offset; } /** * Determine if we are in docBlock or not * * @param int startOffset an offset, where we will read data * @return boolean return true if we are in a docBlock, false else */ boolean getIsInDocBlock(int startOffset) { return buffer.getRuleSetAtOffset(startOffset). getName().matches(docParserRuleName); } /** * Return the length of space before the first alpha char in * text. * * @param String text the text * @return int the space length */ int getLeadingSpaceLength(String text) { int length = 0; boolean test = true; for ( int i = 0; i < text.length(); i++ ) if ( text.charAt(i) == ' ') { length++; if ( docBlock && !test ) break; } else if ( text.charAt(i) == '\t' ) length += buffer.getTabSize(); else if ( docBlock && test && text.charAt(i) == '*' ) { length++; test = false; } else break; return length; } /** * Return the text splitted by space * * @param String text * @return String[] */ String[] getWord(String text) { ArrayList list = new ArrayList(); for ( String s : text.trim().split("\\s+") ) if ( s.length() != 0 ) list.add(s); return list.toArray(new String[0]); } /** * Return the text splitted by space, in docBlock mode. * * @param String text * @param int spaceLength the number of space to remove * @return String[] */ String[] getWordInDoc(String text, int spaceLength) { ArrayList list = new ArrayList(); String[] s = text.split("\n"); for ( int i = 0; i < s.length; i++ ) { s[i] = s[i].trim(); if ( s[i].length() == 0 ) continue; if ( s[i].charAt(0) == '*' ) s[i] = s[i].substring(1); for ( String t : s[i].split("\\s+") ) if ( t.length() != 0 ) list.add(t); } return list.toArray(new String[0]); } /** * Return n space into a string * * @param int n the number of space to return * @return String a string of space */ String getSpace(int n) { char[] space = new char[n]; for ( int i = 0; i < n; i++ ) space[i] = ' '; if ( n > 2 && docBlock ) space[n-2] = '*'; return new String(space); } /** * Return a list of word that fit in maxLength char * @param String[] word the array of non-empty word. * @param int maxLength the maxlength per line * @param int offset the offset, where we will start */ String[] getFittingWordAtOffset(String[] word, int maxLength, int offset) { if ( offset >= word.length ) throw new IllegalArgumentException( "offset (" + String.valueOf(offset) + " >= " + String.valueOf(word.length)); ArrayList list = new ArrayList(); /** * The first word is always in */ int length = word[offset].length(); list.add(word[offset]); offset++; for ( ; offset < word.length; offset++ ) { if ( (length + word[offset].length()+1) < maxLength ) { list.add(word[offset]); length += word[offset].length() + 1; } else break; } return list.toArray(new String[0]); } /** * Get the list of fitting word per maxlength * * @param String[] word the list of word * @param int maxLength the max length per line * @return String[][] an array of string array. It looks like String[row][word] */ String[][] getFittingWord(String[] word, int maxLength) { ArrayList list = new ArrayList(); for ( int offset = 0; offset < word.length; ) { String[] fit = getFittingWordAtOffset(word, maxLength, offset ); list.add(fit); offset += fit.length; } return list.toArray(new String[0][0]); } /** * Return an array containg the length of each row of fit * * @param String[][] fit an array returned by {@link getFittingWord()} * @return int[] an array of int. */ int[] getFittingLength(String[][] fit) { int[] length = new int[fit.length]; for ( int i = 0; i < fit.length; i++ ) { length[i] = 0; for ( String s : fit[i] ) length[i] += s.length(); } return length; } /** * Justify the text * * @param String text the text to justify * @param int charPerLine the number of char per line (the margin, etc) * @param int minLength the min length * @param int padding the padding (on the right) * @return String the result justified */ String justify(String text, int charPerLine, int minLength, int padding) { int spaceLength = getLeadingSpaceLength(text); String[] word = docBlock ? getWordInDoc(text, spaceLength):getWord(text); if ( word.length == 0 ) return ""; int maxLength = charPerLine - padding - spaceLength; if ( maxLength <= minLength ) return text; String space = getSpace(spaceLength); String[][] fittingWord = getFittingWord(word, maxLength); int[] fittingLength = getFittingLength(fittingWord); StringBuilder result = new StringBuilder(); for ( int i = 0; i < fittingWord.length; i++ ) { int missingSpace = maxLength - fittingLength[i]; result.append(space); /* do nothing for the last line or if we do have only one word */ if ( fittingWord[i].length == 0 ) Macros.message(view, "Empty array : " + StringL.valueOf(i)); else if ( fittingWord[i].length == 1 || i + 1 == fittingWord.length ) { for ( int j = 0; j < fittingWord[i].length - 1; j++ ) result.append(fittingWord[i][j]).append( " " ); result.append(fittingWord[i][fittingWord[i].length - 1]); } else { StringBuilder[] missing = new StringBuilder[fittingWord[i].length-1]; /* we must have at least one space so it work */ for ( int j = 0; j < missing.length; j++ ) missing[j] = new StringBuilder(" "); missingSpace -= missing.length; /* so add space */ while ( missingSpace > 0 ) for ( int j = 0; j < missing.length && missingSpace > 0; j++, missingSpace-- ) missing[j].append(" "); /* so add word */ for ( int j = 0; j < missing.length; j++ ) result.append(fittingWord[i][j]).append(missing[j].toString()); result.append(fittingWord[i][missing.length]); } result.append("\n"); } return result.toString(); } int[] bufferOffset = getSelectedOffset(); String justifiedText = justify( buffer.getText(bufferOffset[0], bufferOffset[1]), marginLength, minLength, paddingLength ); if ( justifiedText.length() > 0 ) { Selection sel = new Selection.Range( bufferOffset[0], bufferOffset[0] + bufferOffset[1] ); textArea.setSelection(sel); textArea.setSelectedText(justifiedText); textArea.selectNone(); }