1 package winterwell.markdown.editors;
5 import org.eclipse.core.commands.ExecutionEvent;
6 import org.eclipse.core.commands.ExecutionException;
7 import org.eclipse.core.commands.IHandler;
8 import org.eclipse.core.commands.IHandlerListener;
9 import org.eclipse.jface.action.Action;
10 import org.eclipse.jface.text.BadLocationException;
11 import org.eclipse.jface.text.IDocument;
12 import org.eclipse.jface.text.IRegion;
13 import org.eclipse.jface.text.ITextSelection;
14 import org.eclipse.jface.text.Region;
15 import org.eclipse.jface.text.source.ISourceViewer;
17 import winterwell.markdown.pagemodel.MarkdownFormatter;
18 import winterwell.markdown.pagemodel.MarkdownPage;
19 import winterwell.markdown.pagemodel.MarkdownPage.KLineType;
20 import winterwell.utils.containers.IntRange;
23 * TODO An action for formatting text (via hard wrapping, i.e. inserting returns).
28 public class FormatAction extends Action implements IHandler {
30 public FormatAction() {
31 super("&Format paragraph");
32 setActionDefinitionId("winterwell.markdown.formatParagraphCommand");
33 setToolTipText("Format the paragraph under the caret by inserting/removing line-breaks");
39 MarkdownEditor ed = (MarkdownEditor) ActionBarContributor.getActiveEditor();
40 if (ed == null) return; // The active editor is not a markdown editor.
41 int cols = ed.getPrintColumns();
42 // Do we have a selection?
43 ITextSelection s = (ITextSelection) ed.getSelectionProvider().getSelection();
44 if (s != null && s.getLength() > 0) {
45 formatSelectedRegion(ed, s, cols);
48 // Where is the caret?
49 ISourceViewer viewer = ed.getViewer();
50 int caretOffset = viewer.getTextWidget().getCaretOffset();
51 int lineNum = ed.getDocument().getLineOfOffset(caretOffset);
52 // Get a paragraph region
53 MarkdownPage page = ed.getMarkdownPage();
54 IRegion pRegion = getParagraph(page, lineNum, ed.getDocument());
56 // Not in a paragraph - so give up
57 // TODO tell the user why we've given up
60 String paragraph = ed.getDocument().get(pRegion.getOffset(), pRegion.getLength());
62 String formatted = MarkdownFormatter.format(paragraph, cols);
63 if (formatted.equals(paragraph)) return; // No change!
64 // Replace the unformatted region with the new formatted one
65 ed.getDocument().replace(pRegion.getOffset(), pRegion.getLength(), formatted);
67 } catch (Exception ex) {
68 System.out.println(ex);
72 private void formatSelectedRegion(MarkdownEditor ed, ITextSelection s, int cols)
73 throws BadLocationException {
74 int start = s.getStartLine();
75 int end = s.getEndLine();
76 IDocument doc = ed.getDocument();
77 int soff = doc.getLineOffset(start);
78 int eoff = lineEndOffset(end, doc);
79 IntRange editedRegion = new IntRange(soff, eoff);
80 MarkdownPage page = ed.getMarkdownPage();
81 StringBuilder sb = new StringBuilder(s.getLength());
82 for(int i=start; i<=end; i++) {
83 IRegion para = getParagraph(page, i, ed.getDocument());
85 sb.append(page.getText().get(i));
88 String paragraph = ed.getDocument().get(para.getOffset(), para.getLength());
89 // int lines = StrUtils.splitLines(paragraph).length;
90 String formatted = MarkdownFormatter.format(paragraph, cols);
91 // append formatted and move forward
93 CharSequence le = lineEnd(i, doc);
95 int pEnd = doc.getLineOfOffset(para.getOffset()+para.getLength());
97 // Adjust edited region?
98 IntRange pr = new IntRange(para.getOffset(),
99 para.getOffset()+para.getLength()+le.length());
100 editedRegion = new IntRange(Math.min(pr.low, editedRegion.low),
101 Math.max(pr.high, editedRegion.high));
103 // Replace the unformatted region with the new formatted one
104 String old = doc.get(editedRegion.low, editedRegion.size());
105 String newText = sb.toString();
106 if (old.equals(newText)) return;
107 ed.getDocument().replace(editedRegion.low, editedRegion.size(), newText);
110 private CharSequence lineEnd(int line, IDocument doc) throws BadLocationException {
111 int eoff = doc.getLineOffset(line) + doc.getLineInformation(line).getLength();
112 char c = doc.getChar(eoff);
113 if (c=='\r' && doc.getLength() > eoff+1
114 && doc.getChar(eoff+1) =='\n') return "\r\n";
118 private int lineEndOffset(int end, IDocument doc)
119 throws BadLocationException {
120 int eoff = doc.getLineOffset(end) + doc.getLineInformation(end).getLength();
122 char c = doc.getChar(eoff);
123 if (c=='\r' && doc.getLength() > eoff+1
124 && doc.getChar(eoff+1) =='\n') eoff += 2;
134 * @return region of paragraph containing this line, or null
135 * @throws BadLocationException
137 private IRegion getParagraph(MarkdownPage page, int lineNum, IDocument doc)
138 throws BadLocationException {
140 List<String> lines = page.getText();
141 List<KLineType> lineInfo = page.getLineTypes();
142 // Check we are in a paragraph or list
143 KLineType pType = lineInfo.get(lineNum);
146 default: // Not in a paragraph, so we cannot format.
149 // Work out the paragraph
152 for(start=lineNum; start>-1; start--) {
153 if (lineInfo.get(start) != pType) {
160 for(end=lineNum; end<lines.size(); end++) {
161 if (lineInfo.get(end) != pType) {
167 int sOff = doc.getLineOffset(start);
168 IRegion endLine = doc.getLineInformation(end); // exclude final line end
169 int eOff = endLine.getOffset()+endLine.getLength();
170 return new Region(sOff, eOff-sOff);
173 public void addHandlerListener(IHandlerListener handlerListener) {
177 public void dispose() {
181 public Object execute(ExecutionEvent event) throws ExecutionException {
186 public void removeHandlerListener(IHandlerListener handlerListener) {