- * Note: a side-effect of this method is that it converts all line-breaks
- * into the local system's line-breaks. E.g. on Windows, \n will become \r\n
- *
- * @param text
- * The text to format
- * @param lineWidth
- * The number of columns in a line. Typically 78 or 80.
- * @param respectLeadingCharacters
- * Can be null. If set, the specified leading characters will be
- * copied if the line is split. Use with " \t" to keep indented
- * paragraphs properly indented. Use with "> \t" to also handle
- * email-style quoting. Note that respected leading characters
- * receive no special treatment when they are used inside a
- * paragraph.
- * @return A copy of text, formatted to the given line-width.
- *
- * TODO: recognise paragraphs by changes in the respected leading
- * characters
- */
- public static String format(String text, int lineWidth, int tabWidth,
- String respectLeadingCharacters) {
- // Switch to Linux line breaks for easier internal workings
- text = convertLineBreaks(text, "\n");
- // Find paragraphs
- List paras = format2_splitParagraphs(text,
- respectLeadingCharacters);
- // Rebuild text
- StringBuilder sb = new StringBuilder(text.length() + 10);
- for (String p : paras) {
- String fp = format3_oneParagraph(p, lineWidth, tabWidth,
- respectLeadingCharacters);
- sb.append(fp);
- // Paragraphs end with a double line break
- sb.append("\n\n");
- }
- // Pop the last line breaks
- sb.delete(sb.length() - 2, sb.length());
- // Convert line breaks to system ones
- text = convertLineBreaks(sb.toString());
- // Done
- return text;
- }
-
- private static List format2_splitParagraphs(String text,
- String respectLeadingCharacters) {
- List paras = new ArrayList();
- Mutable.Int index = new Mutable.Int(0);
- // TODO The characters prefacing this paragraph
- String leadingChars = "";
- while (index.value < text.length()) {
- // One paragraph
- boolean inSpace = false;
- int start = index.value;
- while (index.value < text.length()) {
- char c = text.charAt(index.value);
- index.value++;
- if (!Character.isWhitespace(c)) {
- inSpace = false;
- continue;
- }
- // Line end?
- if (c == '\r' || c == '\n') {
- // // Handle MS Windows 2 character \r\n line breaks
- // if (index.value < text.length()) {
- // char c2 = text.charAt(index.value);
- // if (c=='\r' && c2=='\n') index.value++; // Push on past
- // the 2nd line break char
- // }
- // Double line end - indicating a paragraph break
- if (inSpace)
- break;
- inSpace = true;
- }
- // TODO Other paragraph markers, spotted by a change in
- // leadingChars
- }
- String p = text.substring(start, index.value);
- paras.add(p);
- }
- // Done
- return paras;
- }
-
- /**
- * Format a block of text to fit the given line width
- *
- * @param p
- * @param lineWidth
- * @param tabWidth
- * @param respectLeadingCharacters
- * @return
- */
- private static String format3_oneParagraph(String p, int lineWidth,
- int tabWidth, String respectLeadingCharacters) {
- // Collect the reformatted paragraph
- StringBuilder sb = new StringBuilder(p.length() + 10); // Allow for
- // some extra
- // line-breaks
- // Get respected leading chars
- String leadingChars = format4_getLeadingChars(p,
- respectLeadingCharacters);
- // First Line
- sb.append(leadingChars);
- int lineLength = leadingChars.length();
- int index = leadingChars.length();
- // Loop
- while (index < p.length()) {
- // Get the next word
- StringBuilder word = new StringBuilder();
- char c = p.charAt(index);
- index++;
- while (!Character.isWhitespace(c)) {
- word.append(c);
- if (index == p.length())
- break;
- c = p.charAt(index);
- index++;
- }
- // Break the line if the word will not fit
- if (lineLength + word.length() > lineWidth && lineLength != 0) {
- trimEnd(sb);
- sb.append('\n'); // lineEnd(sb);
- // New line
- sb.append(leadingChars);
- lineLength = leadingChars.length();
- }
- // Add word
- sb.append(word);
- lineLength += word.length();
- // Add the whitespace
- if (index != p.length() && lineLength < lineWidth) {
- if (c == '\n') {
- c = ' ';
- }
- sb.append(c);
- lineLength += (c == '\t') ? tabWidth : 1;
- }
- }
- // A final trim
- trimEnd(sb);
- // Done
- return sb.toString();
- }
-
- /**
- *
- * @param text
- * @param respectLeadingCharacters
- * Can be null
- * @return The characters at the beginning of text which are respected. E.g.
- * ("> Hello", " \t>") --> "> "
- */
- private static String format4_getLeadingChars(String text,
- String respectLeadingCharacters) {
- if (respectLeadingCharacters == null)
- return "";
- // Line-breaks cannot be respected
- assert respectLeadingCharacters.indexOf('\n') == -1;
- // Look for the first non-respected char
- for (int i = 0; i < text.length(); i++) {
- char c = text.charAt(i);
- if (respectLeadingCharacters.indexOf(c) == -1) {
- // Return the previous chars
- return text.substring(0, i);
- }
- }
- // All chars are respected
- return text;
- }
-
- /**
- * Ensure that line ends with the right line-end character(s)
- */
- public static final String lineEnd(String line) {
- // strip possibly inappropriate line-endings
- if (line.endsWith("\n")) {
- line = line.substring(0, line.length() - 1);
- }
- if (line.endsWith("\r\n")) {
- line = line.substring(0, line.length() - 2);
- }
- if (line.endsWith("\r")) {
- line = line.substring(0, line.length() - 1);
- }
- // add in proper line end
- if (!line.endsWith(LINEEND)) {
- line += LINEEND;
- }
- return line;
- }
-
- /**
- * Ensure that line ends with the right line-end character(s). This is more
- * efficient than the version for Strings.
- *
- * @param line
- */
- public static final void lineEnd(final StringBuilder line) {
- if (line.length() == 0) {
- line.append(LINEEND);
- return;
- }
- // strip possibly inappropriate line-endings
- final char last = line.charAt(line.length() - 1);
- if (last == '\n') {
- if ((line.length() > 1) && (line.charAt(line.length() - 2) == '\r')) {
- // \r\n
- line.replace(line.length() - 2, line.length(), LINEEND);
- return;
- }
- line.replace(line.length() - 1, line.length(), LINEEND);
- return;
- }
- if (last == '\r') {
- line.replace(line.length() - 1, line.length(), LINEEND);
- return;
- }
- line.append(LINEEND);
- return;
- }
-
-
-
- /**
- * @param string
- * @return the MD5 sum of the string using the default charset. Null if
- * there was an error in calculating the hash.
- * @author Sam Halliday
- */
- public static String md5Hash(String string) {
- MessageDigest md5 = null;
- try {
- md5 = MessageDigest.getInstance("MD5");
- } catch (NoSuchAlgorithmException e) {
- // ignore this exception, we know MD5 exists
- }
- md5.update(string.getBytes());
- BigInteger hash = new BigInteger(1, md5.digest());
- return hash.toString(16);
- }
-
- /**
- * Removes HTML-style tags from a string.
- *
- * @param s
- * a String from which to remove tags
- * @return a string with all instances of <.*> removed.
- */
- public static String removeTags(String s) {
- StringBuffer sb = new StringBuffer();
- boolean inTag = false;
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if (c == '<')
- inTag = true;
- if (!inTag)
- sb.append(c);
- if (c == '>')
- inTag = false;
- }
- return sb.toString();
- }
-
- /**
- * Repeat a character.
- *
- * @param c
- * @param i
- * @return A String consisting of i x c.
- * @example assert repeat('-', 5).equals("-----");
- */
- public static String repeat(Character c, int i) {
- StringBuilder dashes = new StringBuilder(i);
- for (int j = 0; j < i; j++)
- dashes.append(c);
- return dashes.toString();
- }
-
- /**
- * Split a piece of text into separate lines. The line breaks are left at
- * the end of each line.
- *
- * @param text
- * @return The individual lines in the text.
- */
- public static List splitLines(String text) {
- List lines = new ArrayList();
- // Search for lines
- int start = 0;
- for (int i = 0; i < text.length(); i++) {
- char c = text.charAt(i);
- if (c == '\r' || c == '\n') {
- // Handle MS Windows 2 character \r\n line breaks
- if (i + 1 < text.length()) {
- char c2 = text.charAt(i + 1);
- if (c == '\r' && c2 == '\n')
- i++;
- }
- // Get the line, with the line break
- String line = text.substring(start, i + 1);
- lines.add(line);
- start = i + 1;
- }
- }
- // Last one
- if (start != text.length()) {
- String line = text.substring(start);
- lines.add(line);
- }
- return lines;
- }
-
- /**
- * Remove trailing whitespace. c.f. String#trim() which removes
- * leading and trailing whitespace.
- *
- * @param sb
- */
- private static void trimEnd(StringBuilder sb) {
- while (true) {
- // Get the last character
- int i = sb.length() - 1;
- if (i == -1)
- return; // Quit if sb is empty
- char c = sb.charAt(i);
- if (!Character.isWhitespace(c))
- return; // Finish?
- sb.deleteCharAt(i); // Remove and continue
- }
- }
-
- /**
- * Returns true if the string is just whitespace, or empty, or null.
- *
- * @param s
- */
- public static final boolean whitespace(final String s) {
- if (s == null) {
- return true;
- }
- for (int i = 0; i < s.length(); i++) {
- final char c = s.charAt(i);
- if (!Character.isWhitespace(c)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * @param text
- * @return the number of words in text. Uses a crude whitespace
- * measure.
- */
- public static int wordCount(String text) {
- String[] bits = text.split("\\W+");
- int wc = 0;
- for (String string : bits) {
- if (!whitespace(string)) wc++;
- }
- return wc;
- }
-
-}
+/**
+ * Basic String manipulation utilities.
+ * (c) Winterwell 2010 and ThinkTank Mathematics 2007
+ */
+package winterwell.markdown;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+import winterwell.markdown.util.Mutable;
+import winterwell.markdown.util.Pair;
+
+/**
+ * A collection of general-purpose String handling methods.
+ *
+ * @author daniel.winterstein
+ */
+public final class StringMethods {
+
+ /**
+ * Removes xml tags, comment blocks and script blocks.
+ *
+ * @param page
+ * @return the page with all xml tags removed.
+ */
+ public static String stripTags(String page) {
+ // This code is rather ugly, but it does the job
+ StringBuilder stripped = new StringBuilder(page.length());
+ boolean inTag = false;
+ // Comment blocks and script blocks are given special treatment
+ boolean inComment = false;
+ boolean inScript = false;
+ // Go through the text
+ for (int i = 0; i < page.length(); i++) {
+ char c = page.charAt(i);
+ // First check whether we are ignoring text
+ if (inTag) {
+ if (c == '>') inTag = false;
+ } else if (inComment) {
+ if (c == '>' && page.charAt(i - 1) == '-' && page.charAt(i - 1) == '-') {
+ inComment = false;
+ }
+ } else if (inScript) {
+ if (c == '>' && page.substring(i - 7, i).equals("/script")) {
+ inScript = false;
+ }
+ } else {
+ // Check for the start of a tag - looks for '<' followed by any
+ // non-whitespace character
+ if (c == '<' && !Character.isWhitespace(page.charAt(i + 1))) {
+ // Comment, script-block or tag?
+ if (page.charAt(i + 1) == '!' && page.charAt(i + 2) == '-' && page.charAt(i + 3) == '-') {
+ inComment = true;
+ } else if (i + 8 < page.length() && page.substring(i + 1, i + 7).equals("script")) {
+ inScript = true;
+ i += 7;
+ } else
+ inTag = true; // Normal tag by default
+ } else {
+ // Append all non-tag chars
+ stripped.append(c);
+ }
+ } // end if...
+ }
+ return stripped.toString();
+ }
+
+ /**
+ * The local line-end string. \n on unix, \r\n on windows, \r on mac.
+ */
+ public static final String LINEEND = System.getProperty("line.separator");
+
+ /**
+ * @param s
+ * @return A version of s where the first letter is uppercase and all others are lowercase
+ */
+ public static final String capitalise(final String s) {
+ return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
+ }
+
+ /**
+ * Convert all line breaks into the system line break.
+ */
+ public static final String convertLineBreaks(String text) {
+ return convertLineBreaks(text, LINEEND);
+ }
+
+ /**
+ * Convert all line breaks into the specified line break.
+ */
+ public static final String convertLineBreaks(String text, String br) {
+ text = text.replaceAll("\r\n", br);
+ text = text.replaceAll("\r", br);
+ text = text.replaceAll("\n", br);
+ return text;
+ }
+
+ /**
+ * @param string
+ * @param character
+ * @return the number of times character appears in the string
+ * @author Sam Halliday
+ */
+ static public int countCharsInString(String string, char character) {
+ int count = 0;
+ for (char c : string.toCharArray()) {
+ if (c == character) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * E.g. findEnclosingRegion("text with a [region] inside", 15, '[', ']') is (??,??)
+ *
+ * @param text
+ * @param offset
+ * @param start
+ * @param end
+ * @return the smallest enclosed region (including start and end chars, the 1st number is
+ * inclusive, the 2nd exclusive), or null if none. So text.subString(start,end) is the
+ * specified region
+ */
+ public static Pair findEnclosingRegion(String text, int offset, char startMarker, char endMarker) {
+ // Forward
+ int end = findEnclosingRegion2(text, offset, endMarker, 1);
+ if (end == -1) return null;
+ end++; // end is exclusive
+ // Backward
+ int start = findEnclosingRegion2(text, offset, startMarker, -1);
+ if (start == -1) return null;
+ // Sanity
+ assert text.substring(start, end).charAt(0) == startMarker;
+ assert text.substring(start, end).endsWith("" + endMarker);
+ // Done
+ return new Pair(start, end);
+ }
+
+ private static int findEnclosingRegion2(String text, int offset, char endMarker, int direction) {
+ while (offset > -1 && offset < text.length()) {
+ char c = text.charAt(offset);
+ if (c == endMarker) return offset;
+ offset += direction;
+ }
+ return -1;
+ }
+
+ /**
+ * A convenience wrapper for {@link #findEnclosingRegion(String, int, char, char)} E.g.
+ findEnclosingRegion("text with a [region] inside", 15, '[', ']') .equals("[region]");
+
+ *
+ * @param text
+ * @param offset
+ * @param start
+ * @param end
+ * @return the smallest enclosed region (including start and end chars), or null if none.
+ */
+ public static String findEnclosingText(String text, int offset, char startMarker, char endMarker) {
+ Pair region = findEnclosingRegion(text, offset, startMarker, endMarker);
+ if (region == null) return null;
+ String s = text.substring(region.first, region.second);
+ return s;
+ }
+
+ /**
+ * Format a block of text to use the given line-width. I.e. adjust the line breaks. Also known
+ * as hard line-wrapping. Paragraphs are recognised by a line of blank space between them
+ * (e.g. two returns).
+ *
+ * Note: a side-effect of this method is that it converts all line-breaks into the local
+ * system's line-breaks. E.g. on Windows, \n will become \r\n
+ *
+ * @param text The text to format
+ * @param lineWidth The number of columns in a line. Typically 78 or 80.
+ * @param respectLeadingCharacters Can be null. If set, the specified leading characters will be
+ * copied if the line is split. Use with " \t" to keep indented paragraphs properly
+ * indented. Use with "> \t" to also handle email-style quoting. Note that respected
+ * leading characters receive no special treatment when they are used inside a
+ * paragraph.
+ * @return A copy of text, formatted to the given line-width.
+ *
+ * TODO: recognise paragraphs by changes in the respected leading characters
+ */
+ public static String format(String text, int lineWidth, int tabWidth, String respectLeadingCharacters) {
+ // Switch to Linux line breaks for easier internal workings
+ text = convertLineBreaks(text, "\n");
+ // Find paragraphs
+ List paras = format2_splitParagraphs(text, respectLeadingCharacters);
+ // Rebuild text
+ StringBuilder sb = new StringBuilder(text.length() + 10);
+ for (String p : paras) {
+ String fp = format3_oneParagraph(p, lineWidth, tabWidth, respectLeadingCharacters);
+ sb.append(fp);
+ // Paragraphs end with a double line break
+ sb.append("\n\n");
+ }
+ // Pop the last line breaks
+ sb.delete(sb.length() - 2, sb.length());
+ // Convert line breaks to system ones
+ text = convertLineBreaks(sb.toString());
+ // Done
+ return text;
+ }
+
+ private static List format2_splitParagraphs(String text, String respectLeadingCharacters) {
+ List paras = new ArrayList();
+ Mutable.Int index = new Mutable.Int(0);
+ // TODO The characters prefacing this paragraph
+ // String leadingChars = "";
+ while (index.value < text.length()) {
+ // One paragraph
+ boolean inSpace = false;
+ int start = index.value;
+ while (index.value < text.length()) {
+ char c = text.charAt(index.value);
+ index.value++;
+ if (!Character.isWhitespace(c)) {
+ inSpace = false;
+ continue;
+ }
+ // Line end?
+ if (c == '\r' || c == '\n') {
+ // // Handle MS Windows 2 character \r\n line breaks
+ // if (index.value < text.length()) {
+ // char c2 = text.charAt(index.value);
+ // if (c=='\r' && c2=='\n') index.value++; // Push on past
+ // the 2nd line break char
+ // }
+ // Double line end - indicating a paragraph break
+ if (inSpace) break;
+ inSpace = true;
+ }
+ // TODO Other paragraph markers, spotted by a change in
+ // leadingChars
+ }
+ String p = text.substring(start, index.value);
+ paras.add(p);
+ }
+ // Done
+ return paras;
+ }
+
+ /**
+ * Format a block of text to fit the given line width
+ *
+ * @param p
+ * @param lineWidth
+ * @param tabWidth
+ * @param respectLeadingCharacters
+ * @return
+ */
+ private static String format3_oneParagraph(String p, int lineWidth, int tabWidth, String respectLeadingCharacters) {
+ // Collect the reformatted paragraph
+ StringBuilder sb = new StringBuilder(p.length() + 10); // Allow for
+ // some extra
+ // line-breaks
+ // Get respected leading chars
+ String leadingChars = format4_getLeadingChars(p, respectLeadingCharacters);
+ // First Line
+ sb.append(leadingChars);
+ int lineLength = leadingChars.length();
+ int index = leadingChars.length();
+ // Loop
+ while (index < p.length()) {
+ // Get the next word
+ StringBuilder word = new StringBuilder();
+ char c = p.charAt(index);
+ index++;
+ while (!Character.isWhitespace(c)) {
+ word.append(c);
+ if (index == p.length()) break;
+ c = p.charAt(index);
+ index++;
+ }
+ // Break the line if the word will not fit
+ if (lineLength + word.length() > lineWidth && lineLength != 0) {
+ trimEnd(sb);
+ sb.append('\n'); // lineEnd(sb);
+ // New line
+ sb.append(leadingChars);
+ lineLength = leadingChars.length();
+ }
+ // Add word
+ sb.append(word);
+ lineLength += word.length();
+ // Add the whitespace
+ if (index != p.length() && lineLength < lineWidth) {
+ if (c == '\n') {
+ c = ' ';
+ }
+ sb.append(c);
+ lineLength += (c == '\t') ? tabWidth : 1;
+ }
+ }
+ // A final trim
+ trimEnd(sb);
+ // Done
+ return sb.toString();
+ }
+
+ /**
+ * @param text
+ * @param respectLeadingCharacters Can be null
+ * @return The characters at the beginning of text which are respected. E.g. ("> Hello", " \t>")
+ * --> "> "
+ */
+ private static String format4_getLeadingChars(String text, String respectLeadingCharacters) {
+ if (respectLeadingCharacters == null) return "";
+ // Line-breaks cannot be respected
+ assert respectLeadingCharacters.indexOf('\n') == -1;
+ // Look for the first non-respected char
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (respectLeadingCharacters.indexOf(c) == -1) {
+ // Return the previous chars
+ return text.substring(0, i);
+ }
+ }
+ // All chars are respected
+ return text;
+ }
+
+ /**
+ * Ensure that line ends with the right line-end character(s)
+ */
+ public static final String lineEnd(String line) {
+ // strip possibly inappropriate line-endings
+ if (line.endsWith("\n")) {
+ line = line.substring(0, line.length() - 1);
+ }
+ if (line.endsWith("\r\n")) {
+ line = line.substring(0, line.length() - 2);
+ }
+ if (line.endsWith("\r")) {
+ line = line.substring(0, line.length() - 1);
+ }
+ // add in proper line end
+ if (!line.endsWith(LINEEND)) {
+ line += LINEEND;
+ }
+ return line;
+ }
+
+ /**
+ * Ensure that line ends with the right line-end character(s). This is more efficient than the
+ * version for Strings.
+ *
+ * @param line
+ */
+ public static final void lineEnd(final StringBuilder line) {
+ if (line.length() == 0) {
+ line.append(LINEEND);
+ return;
+ }
+ // strip possibly inappropriate line-endings
+ final char last = line.charAt(line.length() - 1);
+ if (last == '\n') {
+ if ((line.length() > 1) && (line.charAt(line.length() - 2) == '\r')) {
+ // \r\n
+ line.replace(line.length() - 2, line.length(), LINEEND);
+ return;
+ }
+ line.replace(line.length() - 1, line.length(), LINEEND);
+ return;
+ }
+ if (last == '\r') {
+ line.replace(line.length() - 1, line.length(), LINEEND);
+ return;
+ }
+ line.append(LINEEND);
+ return;
+ }
+
+ /**
+ * @param string
+ * @return the MD5 sum of the string using the default charset. Null if there was an error in
+ * calculating the hash.
+ * @author Sam Halliday
+ */
+ public static String md5Hash(String string) {
+ MessageDigest md5 = null;
+ try {
+ md5 = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ // ignore this exception, we know MD5 exists
+ }
+ md5.update(string.getBytes());
+ BigInteger hash = new BigInteger(1, md5.digest());
+ return hash.toString(16);
+ }
+
+ /**
+ * Removes HTML-style tags from a string.
+ *
+ * @param s a String from which to remove tags
+ * @return a string with all instances of <.*> removed.
+ */
+ public static String removeTags(String s) {
+ StringBuffer sb = new StringBuffer();
+ boolean inTag = false;
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c == '<') inTag = true;
+ if (!inTag) sb.append(c);
+ if (c == '>') inTag = false;
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Repeat a character.
+ *
+ * @param c
+ * @param i
+ * @return A String consisting of i x c.
+ * @example assert repeat('-', 5).equals("-----");
+ */
+ public static String repeat(Character c, int i) {
+ StringBuilder dashes = new StringBuilder(i);
+ for (int j = 0; j < i; j++)
+ dashes.append(c);
+ return dashes.toString();
+ }
+
+ /**
+ * Split a piece of text into separate lines. The line breaks are left at the end of each line.
+ *
+ * @param text
+ * @return The individual lines in the text.
+ */
+ public static List splitLines(String text) {
+ List lines = new ArrayList();
+ // Search for lines
+ int start = 0;
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (c == '\r' || c == '\n') {
+ // Handle MS Windows 2 character \r\n line breaks
+ if (i + 1 < text.length()) {
+ char c2 = text.charAt(i + 1);
+ if (c == '\r' && c2 == '\n') i++;
+ }
+ // Get the line, with the line break
+ String line = text.substring(start, i + 1);
+ lines.add(line);
+ start = i + 1;
+ }
+ }
+ // Last one
+ if (start != text.length()) {
+ String line = text.substring(start);
+ lines.add(line);
+ }
+ return lines;
+ }
+
+ /**
+ * Remove trailing whitespace. c.f. String#trim() which removes leading and trailing
+ * whitespace.
+ *
+ * @param sb
+ */
+ private static void trimEnd(StringBuilder sb) {
+ while (true) {
+ // Get the last character
+ int i = sb.length() - 1;
+ if (i == -1) return; // Quit if sb is empty
+ char c = sb.charAt(i);
+ if (!Character.isWhitespace(c)) return; // Finish?
+ sb.deleteCharAt(i); // Remove and continue
+ }
+ }
+
+ /**
+ * Returns true if the string is just whitespace, or empty, or null.
+ *
+ * @param s
+ */
+ public static final boolean whitespace(final String s) {
+ if (s == null) {
+ return true;
+ }
+ for (int i = 0; i < s.length(); i++) {
+ final char c = s.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param text
+ * @return the number of words in text. Uses a crude whitespace measure.
+ */
+ public static int wordCount(String text) {
+ String[] bits = text.split("\\W+");
+ int wc = 0;
+ for (String string : bits) {
+ if (!whitespace(string)) wc++;
+ }
+ return wc;
+ }
+
+}
diff --git a/plugin/src/winterwell/markdown/WritableChainedPreferenceStore.java b/plugin/src/winterwell/markdown/WritableChainedPreferenceStore.java
new file mode 100644
index 0000000..328b455
--- /dev/null
+++ b/plugin/src/winterwell/markdown/WritableChainedPreferenceStore.java
@@ -0,0 +1,88 @@
+package winterwell.markdown;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.ui.texteditor.ChainedPreferenceStore;
+
+/**
+ * Provides a chained preference store where the first of the chained stores is available for writes.
+ */
+public class WritableChainedPreferenceStore extends ChainedPreferenceStore {
+
+ private IPreferenceStore writeStore;
+
+ public WritableChainedPreferenceStore(IPreferenceStore[] preferenceStores) {
+ super(preferenceStores);
+ this.writeStore = preferenceStores[0];
+ }
+
+ @Override
+ public boolean needsSaving() {
+ return writeStore.needsSaving();
+ }
+
+ @Override
+ public void setValue(String name, double value) {
+ writeStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, float value) {
+ writeStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, int value) {
+ writeStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, long value) {
+ writeStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, String value) {
+ writeStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, boolean value) {
+ writeStore.setValue(name, value);
+ }
+
+ @Override
+ public void setToDefault(String name) {
+ writeStore.setToDefault(name);
+ }
+
+ @Override
+ public void setDefault(String name, boolean value) {
+
+ writeStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, double value) {
+ writeStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, float value) {
+ writeStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, int value) {
+ writeStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, long value) {
+ writeStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, String defaultObject) {
+ writeStore.setDefault(name, defaultObject);
+ }
+}
diff --git a/plugin/src/winterwell/markdown/editors/ExportHTMLAction.java b/plugin/src/winterwell/markdown/commands/ExportHTML.java
similarity index 76%
rename from plugin/src/winterwell/markdown/editors/ExportHTMLAction.java
rename to plugin/src/winterwell/markdown/commands/ExportHTML.java
index 3c9d26e..7d57df2 100755
--- a/plugin/src/winterwell/markdown/editors/ExportHTMLAction.java
+++ b/plugin/src/winterwell/markdown/commands/ExportHTML.java
@@ -1,4 +1,4 @@
-package winterwell.markdown.editors;
+package winterwell.markdown.commands;
import java.io.File;
@@ -8,13 +8,16 @@
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPathEditorInput;
-import winterwell.utils.io.FileUtils;
+import winterwell.markdown.editors.ActionBarContributor;
+import winterwell.markdown.editors.MarkdownEditor;
+import winterwell.markdown.util.FileUtils;
+public class ExportHTML extends Action {
-public class ExportHTMLAction extends Action {
- public ExportHTMLAction() {
+ public ExportHTML() {
super("Export to HTML");
}
+
@Override
public void run() {
IEditorPart ed = ActionBarContributor.getActiveEditor();
@@ -33,5 +36,4 @@ public void run() {
FileUtils.write(file, html);
}
}
-
}
diff --git a/plugin/src/winterwell/markdown/editors/FormatAction.java b/plugin/src/winterwell/markdown/commands/FormatParagraph.java
similarity index 74%
rename from plugin/src/winterwell/markdown/editors/FormatAction.java
rename to plugin/src/winterwell/markdown/commands/FormatParagraph.java
index cd16589..ba2ffb8 100755
--- a/plugin/src/winterwell/markdown/editors/FormatAction.java
+++ b/plugin/src/winterwell/markdown/commands/FormatParagraph.java
@@ -1,4 +1,4 @@
-package winterwell.markdown.editors;
+package winterwell.markdown.commands;
import java.util.List;
@@ -14,25 +14,27 @@
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.ISourceViewer;
+import winterwell.markdown.Log;
+import winterwell.markdown.editors.ActionBarContributor;
+import winterwell.markdown.editors.MarkdownEditor;
import winterwell.markdown.pagemodel.MarkdownFormatter;
import winterwell.markdown.pagemodel.MarkdownPage;
import winterwell.markdown.pagemodel.MarkdownPage.KLineType;
-import winterwell.utils.containers.IntRange;
+import winterwell.markdown.util.IntRange;
/**
* TODO An action for formatting text (via hard wrapping, i.e. inserting returns).
*
- *
* @author daniel
*/
-public class FormatAction extends Action implements IHandler {
+public class FormatParagraph extends Action implements IHandler {
- public FormatAction() {
+ public FormatParagraph() {
super("&Format paragraph");
setActionDefinitionId("winterwell.markdown.formatParagraphCommand");
setToolTipText("Format the paragraph under the caret by inserting/removing line-breaks");
}
-
+
@Override
public void run() {
try {
@@ -52,9 +54,9 @@ public void run() {
// Get a paragraph region
MarkdownPage page = ed.getMarkdownPage();
IRegion pRegion = getParagraph(page, lineNum, ed.getDocument());
- if (pRegion==null) {
+ if (pRegion == null) {
// Not in a paragraph - so give up
- // TODO tell the user why we've given up
+ // TODO tell the user why we've given up
return;
}
String paragraph = ed.getDocument().get(pRegion.getOffset(), pRegion.getLength());
@@ -65,91 +67,86 @@ public void run() {
ed.getDocument().replace(pRegion.getOffset(), pRegion.getLength(), formatted);
// Done
} catch (Exception ex) {
- System.out.println(ex);
+ Log.error(ex);
}
}
- private void formatSelectedRegion(MarkdownEditor ed, ITextSelection s, int cols)
- throws BadLocationException {
+ private void formatSelectedRegion(MarkdownEditor ed, ITextSelection s, int cols) throws BadLocationException {
int start = s.getStartLine();
int end = s.getEndLine();
IDocument doc = ed.getDocument();
int soff = doc.getLineOffset(start);
- int eoff = lineEndOffset(end, doc);
- IntRange editedRegion = new IntRange(soff, eoff);
+ int eoff = lineEndOffset(end, doc);
+ IntRange editedRegion = new IntRange(soff, eoff);
MarkdownPage page = ed.getMarkdownPage();
StringBuilder sb = new StringBuilder(s.getLength());
- for(int i=start; i<=end; i++) {
+ for (int i = start; i <= end; i++) {
IRegion para = getParagraph(page, i, ed.getDocument());
- if (para==null) {
+ if (para == null) {
sb.append(page.getText().get(i));
continue;
}
String paragraph = ed.getDocument().get(para.getOffset(), para.getLength());
-// int lines = StrUtils.splitLines(paragraph).length;
+ // int lines = Strings.splitLines(paragraph).length;
String formatted = MarkdownFormatter.format(paragraph, cols);
// append formatted and move forward
sb.append(formatted);
CharSequence le = lineEnd(i, doc);
sb.append(le);
- int pEnd = doc.getLineOfOffset(para.getOffset()+para.getLength());
+ int pEnd = doc.getLineOfOffset(para.getOffset() + para.getLength());
i = pEnd;
// Adjust edited region?
- IntRange pr = new IntRange(para.getOffset(),
- para.getOffset()+para.getLength()+le.length());
- editedRegion = new IntRange(Math.min(pr.low, editedRegion.low),
- Math.max(pr.high, editedRegion.high));
- }
+ IntRange pr = new IntRange(para.getOffset(), para.getOffset() + para.getLength() + le.length());
+ editedRegion = new IntRange(Math.min(pr.low, editedRegion.low), Math.max(pr.high, editedRegion.high));
+ }
// Replace the unformatted region with the new formatted one
String old = doc.get(editedRegion.low, editedRegion.size());
String newText = sb.toString();
if (old.equals(newText)) return;
- ed.getDocument().replace(editedRegion.low, editedRegion.size(), newText);
+ ed.getDocument().replace(editedRegion.low, editedRegion.size(), newText);
}
private CharSequence lineEnd(int line, IDocument doc) throws BadLocationException {
- int eoff = doc.getLineOffset(line) + doc.getLineInformation(line).getLength();
+ int eoff = doc.getLineOffset(line) + doc.getLineInformation(line).getLength();
char c = doc.getChar(eoff);
- if (c=='\r' && doc.getLength() > eoff+1
- && doc.getChar(eoff+1) =='\n') return "\r\n";
- return ""+c;
+ if (c == '\r' && doc.getLength() > eoff + 1 && doc.getChar(eoff + 1) == '\n') return "\r\n";
+ return "" + c;
}
- private int lineEndOffset(int end, IDocument doc)
- throws BadLocationException {
+ private int lineEndOffset(int end, IDocument doc) throws BadLocationException {
int eoff = doc.getLineOffset(end) + doc.getLineInformation(end).getLength();
// Include line end
char c = doc.getChar(eoff);
- if (c=='\r' && doc.getLength() > eoff+1
- && doc.getChar(eoff+1) =='\n') eoff += 2;
- else eoff += 1;
+ if (c == '\r' && doc.getLength() > eoff + 1 && doc.getChar(eoff + 1) == '\n')
+ eoff += 2;
+ else
+ eoff += 1;
return eoff;
}
/**
- *
* @param page
* @param lineNum
* @param doc
* @return region of paragraph containing this line, or null
* @throws BadLocationException
*/
- private IRegion getParagraph(MarkdownPage page, int lineNum, IDocument doc)
- throws BadLocationException {
+ private IRegion getParagraph(MarkdownPage page, int lineNum, IDocument doc) throws BadLocationException {
// Get doc info
List lines = page.getText();
List lineInfo = page.getLineTypes();
// Check we are in a paragraph or list
KLineType pType = lineInfo.get(lineNum);
- switch(pType) {
- case NORMAL: break;
- default: // Not in a paragraph, so we cannot format.
- return null;
+ switch (pType) {
+ case NORMAL:
+ break;
+ default: // Not in a paragraph, so we cannot format.
+ return null;
}
// Work out the paragraph
// Beginning
int start;
- for(start=lineNum; start>-1; start--) {
+ for (start = lineNum; start > -1; start--) {
if (lineInfo.get(start) != pType) {
start++;
break;
@@ -157,7 +154,7 @@ private IRegion getParagraph(MarkdownPage page, int lineNum, IDocument doc)
}
// End
int end;
- for(end=lineNum; end fColorTable = new HashMap(10);
public void dispose() {
- Iterator e = fColorTable.values().iterator();
+ Iterator e = fColorTable.values().iterator();
while (e.hasNext())
- ((Color) e.next()).dispose();
+ e.next().dispose();
}
+
public Color getColor(RGB rgb) {
- Color color = (Color) fColorTable.get(rgb);
+ Color color = fColorTable.get(rgb);
if (color == null) {
color = new Color(Display.getCurrent(), rgb);
fColorTable.put(rgb, color);
diff --git a/plugin/src/winterwell/markdown/editors/EmphasisRule.java b/plugin/src/winterwell/markdown/editors/EmphasisRule.java
index 06f1420..7224acf 100755
--- a/plugin/src/winterwell/markdown/editors/EmphasisRule.java
+++ b/plugin/src/winterwell/markdown/editors/EmphasisRule.java
@@ -1,112 +1,111 @@
-/**
- * Copyright winterwell Mathematics Ltd.
- * @author Daniel Winterstein
- * 11 Jan 2007
- */
-package winterwell.markdown.editors;
-
-import org.eclipse.core.runtime.Assert;
-import org.eclipse.jface.text.rules.ICharacterScanner;
-import org.eclipse.jface.text.rules.IRule;
-import org.eclipse.jface.text.rules.IToken;
-import org.eclipse.jface.text.rules.MultiLineRule;
-import org.eclipse.jface.text.rules.Token;
-
-/**
- *
- *
- * @author Daniel Winterstein
- */
-public class EmphasisRule implements IRule {
- private static char[][] fDelimiters = null;
- private char[] fSequence;
- protected IToken fToken;
-
-
- public EmphasisRule(String marker, IToken token) {
- assert marker.equals("*") || marker.equals("_") || marker.equals("**")
- || marker.equals("***") || marker.equals("`") || marker.equals("``");
- Assert.isNotNull(token);
- fSequence = marker.toCharArray();
- fToken = token;
- }
-
- // Copied from org.eclipse.jface.text.rules.PatternRule
- protected boolean sequenceDetected(ICharacterScanner scanner, char[] sequence, boolean eofAllowed) {
- for (int i = 1; i < sequence.length; i++) {
- int c = scanner.read();
- if (c == ICharacterScanner.EOF && eofAllowed) {
- return true;
- } else if (c != sequence[i]) {
- // Non-matching character detected, rewind the scanner back to
- // the start.
- // Do not unread the first character.
- for (int j = i; j > 0; j--)
- scanner.unread();
- return false;
- }
- }
- return true;
- }
-
- /*
- * @see IRule#evaluate(ICharacterScanner)
- *
- * @since 2.0
- */
- public IToken evaluate(ICharacterScanner scanner) {
- // Should be connected only on the right side
- scanner.unread();
- boolean sawSpaceBefore = Character.isWhitespace(scanner.read());
- if (!sawSpaceBefore && scanner.getColumn() != 0) {
- return Token.UNDEFINED;
- }
-
- int c = scanner.read();
- // Should be connected only on right side
- if (c != fSequence[0] || !sequenceDetected(scanner, fSequence, false)) {
- scanner.unread();
- return Token.UNDEFINED;
- }
- int readCount = fSequence.length;
- if (fDelimiters == null) {
- fDelimiters = scanner.getLegalLineDelimiters();
- }
- // Start sequence detected
- int delimiterFound = 0;
- // Is it a list item marker, or just a floating *?
- if (sawSpaceBefore) {
- boolean after = Character.isWhitespace(scanner.read());
- scanner.unread();
- if (after)
- delimiterFound = 2;
- }
-
- while (delimiterFound < 2
- && (c = scanner.read()) != ICharacterScanner.EOF) {
- readCount++;
-
- if (!sawSpaceBefore && c == fSequence[0]
- && sequenceDetected(scanner, fSequence, false)) {
- return fToken;
- }
-
- int i;
- for (i = 0; i < fDelimiters.length; i++) {
- if (c == fDelimiters[i][0]
- && sequenceDetected(scanner, fDelimiters[i], true)) {
- delimiterFound++;
- break;
- }
- }
- if (i == fDelimiters.length)
- delimiterFound = 0;
- sawSpaceBefore = Character.isWhitespace(c);
- }
- // Reached ICharacterScanner.EOF
- for (; readCount > 0; readCount--)
- scanner.unread();
- return Token.UNDEFINED;
- }
-
-}
+/**
+ * Copyright winterwell Mathematics Ltd.
+ * @author Daniel Winterstein
+ * 11 Jan 2007
+ */
+package winterwell.markdown.editors;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.text.rules.ICharacterScanner;
+import org.eclipse.jface.text.rules.IRule;
+import org.eclipse.jface.text.rules.IToken;
+import org.eclipse.jface.text.rules.Token;
+
+/**
+ *
+ *
+ * @author Daniel Winterstein
+ */
+public class EmphasisRule implements IRule {
+ private static char[][] fDelimiters = null;
+ private char[] fSequence;
+ protected IToken fToken;
+
+
+ public EmphasisRule(String marker, IToken token) {
+ assert marker.equals("*") || marker.equals("_") || marker.equals("**")
+ || marker.equals("***") || marker.equals("`") || marker.equals("``");
+ Assert.isNotNull(token);
+ fSequence = marker.toCharArray();
+ fToken = token;
+ }
+
+ // Copied from org.eclipse.jface.text.rules.PatternRule
+ protected boolean sequenceDetected(ICharacterScanner scanner, char[] sequence, boolean eofAllowed) {
+ for (int i = 1; i < sequence.length; i++) {
+ int c = scanner.read();
+ if (c == ICharacterScanner.EOF && eofAllowed) {
+ return true;
+ } else if (c != sequence[i]) {
+ // Non-matching character detected, rewind the scanner back to
+ // the start.
+ // Do not unread the first character.
+ for (int j = i; j > 0; j--)
+ scanner.unread();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * @see IRule#evaluate(ICharacterScanner)
+ *
+ * @since 2.0
+ */
+ public IToken evaluate(ICharacterScanner scanner) {
+ // Should be connected only on the right side
+ scanner.unread();
+ boolean sawSpaceBefore = Character.isWhitespace(scanner.read());
+ if (!sawSpaceBefore && scanner.getColumn() != 0) {
+ return Token.UNDEFINED;
+ }
+
+ int c = scanner.read();
+ // Should be connected only on right side
+ if (c != fSequence[0] || !sequenceDetected(scanner, fSequence, false)) {
+ scanner.unread();
+ return Token.UNDEFINED;
+ }
+ int readCount = fSequence.length;
+ if (fDelimiters == null) {
+ fDelimiters = scanner.getLegalLineDelimiters();
+ }
+ // Start sequence detected
+ int delimiterFound = 0;
+ // Is it a list item marker, or just a floating *?
+ if (sawSpaceBefore) {
+ boolean after = Character.isWhitespace(scanner.read());
+ scanner.unread();
+ if (after)
+ delimiterFound = 2;
+ }
+
+ while (delimiterFound < 2
+ && (c = scanner.read()) != ICharacterScanner.EOF) {
+ readCount++;
+
+ if (!sawSpaceBefore && c == fSequence[0]
+ && sequenceDetected(scanner, fSequence, false)) {
+ return fToken;
+ }
+
+ int i;
+ for (i = 0; i < fDelimiters.length; i++) {
+ if (c == fDelimiters[i][0]
+ && sequenceDetected(scanner, fDelimiters[i], true)) {
+ delimiterFound++;
+ break;
+ }
+ }
+ if (i == fDelimiters.length)
+ delimiterFound = 0;
+ sawSpaceBefore = Character.isWhitespace(c);
+ }
+ // Reached ICharacterScanner.EOF
+ for (; readCount > 0; readCount--)
+ scanner.unread();
+ return Token.UNDEFINED;
+ }
+
+}
diff --git a/plugin/src/winterwell/markdown/editors/LinkRule.java b/plugin/src/winterwell/markdown/editors/LinkRule.java
index 4beeb68..f91702c 100644
--- a/plugin/src/winterwell/markdown/editors/LinkRule.java
+++ b/plugin/src/winterwell/markdown/editors/LinkRule.java
@@ -5,11 +5,11 @@
*/
package winterwell.markdown.editors;
+import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.rules.ICharacterScanner;
+import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
-import org.eclipse.jface.text.rules.IRule;
-import org.eclipse.core.runtime.Assert;
/**
*
diff --git a/plugin/src/winterwell/markdown/editors/ListRule.java b/plugin/src/winterwell/markdown/editors/ListRule.java
index 11ff3f0..78c4f07 100644
--- a/plugin/src/winterwell/markdown/editors/ListRule.java
+++ b/plugin/src/winterwell/markdown/editors/ListRule.java
@@ -1,77 +1,70 @@
-/**
- * Copyright winterwell Mathematics Ltd.
- * @author Daniel Winterstein
- * 11 Jan 2007
- */
-package winterwell.markdown.editors;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Arrays;
-
-import org.eclipse.core.runtime.Assert;
-import org.eclipse.jface.text.rules.ICharacterScanner;
-import org.eclipse.jface.text.rules.IRule;
-import org.eclipse.jface.text.rules.IToken;
-import org.eclipse.jface.text.rules.Token;
-
-/**
- *
- *
- * @author Daniel Winterstein
- */
-public class ListRule implements IRule {
- private ArrayList markerList;
- protected IToken fToken;
-
- public ListRule(IToken token) {
- Assert.isNotNull(token);
- fToken= token;
- }
-
-
- /*
- * @see IRule#evaluate(ICharacterScanner)
- * @since 2.0
- */
- public IToken evaluate(ICharacterScanner scanner) {
- if (scanner.getColumn() != 0) {
- return Token.UNDEFINED;
- }
-// // Fast mode
-// if (scanner.read() != '-') {
-// scanner.unread();
-// return Token.UNDEFINED;
-// }
-// if (Character.isWhitespace(scanner.read())) {
-// return fToken;
-// }
-// scanner.unread();
-// scanner.unread();
-// return Token.UNDEFINED;
-// // Fast mode
- int readCount = 0;
- int c;
- while ((c = scanner.read()) != ICharacterScanner.EOF) {
- readCount++;
- if( !Character.isWhitespace( c ) ) {
- int after = scanner.read();
-// readCount++;
- scanner.unread();
-// if ( markerList.contains(c) && Character.isWhitespace( after ) ) {
- if ( (c == '-' || c == '+' || c == '*')
- && Character.isWhitespace( after ) ) {
- return fToken;
- } else {
- for (; readCount > 0; readCount--)
- scanner.unread();
- return Token.UNDEFINED;
- }
- }
- }
- // Reached ICharacterScanner.EOF
- for (; readCount > 0; readCount--)
- scanner.unread();
- return Token.UNDEFINED;
- }
-}
+/**
+ * Copyright winterwell Mathematics Ltd.
+ * @author Daniel Winterstein
+ * 11 Jan 2007
+ */
+package winterwell.markdown.editors;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.text.rules.ICharacterScanner;
+import org.eclipse.jface.text.rules.IRule;
+import org.eclipse.jface.text.rules.IToken;
+import org.eclipse.jface.text.rules.Token;
+
+/**
+ * @author Daniel Winterstein
+ */
+public class ListRule implements IRule {
+
+ // private ArrayList markerList;
+ protected IToken fToken;
+
+ public ListRule(IToken token) {
+ Assert.isNotNull(token);
+ fToken = token;
+ }
+
+ /*
+ * @see IRule#evaluate(ICharacterScanner)
+ * @since 2.0
+ */
+ public IToken evaluate(ICharacterScanner scanner) {
+ if (scanner.getColumn() != 0) {
+ return Token.UNDEFINED;
+ }
+ // // Fast mode
+ // if (scanner.read() != '-') {
+ // scanner.unread();
+ // return Token.UNDEFINED;
+ // }
+ // if (Character.isWhitespace(scanner.read())) {
+ // return fToken;
+ // }
+ // scanner.unread();
+ // scanner.unread();
+ // return Token.UNDEFINED;
+ // // Fast mode
+ int readCount = 0;
+ int c;
+ while ((c = scanner.read()) != ICharacterScanner.EOF) {
+ readCount++;
+ if (!Character.isWhitespace(c)) {
+ int after = scanner.read();
+ // readCount++;
+ scanner.unread();
+ // if ( markerList.contains(c) && Character.isWhitespace( after ) ) {
+ if ((c == '-' || c == '+' || c == '*') && Character.isWhitespace(after)) {
+ return fToken;
+ } else {
+ for (; readCount > 0; readCount--)
+ scanner.unread();
+ return Token.UNDEFINED;
+ }
+ }
+ }
+ // Reached ICharacterScanner.EOF
+ for (; readCount > 0; readCount--)
+ scanner.unread();
+ return Token.UNDEFINED;
+ }
+}
diff --git a/plugin/src/winterwell/markdown/editors/MDConfiguration.java b/plugin/src/winterwell/markdown/editors/MDConfiguration.java
index f20a642..63fe0b7 100755
--- a/plugin/src/winterwell/markdown/editors/MDConfiguration.java
+++ b/plugin/src/winterwell/markdown/editors/MDConfiguration.java
@@ -1,81 +1,66 @@
package winterwell.markdown.editors;
-import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
-import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
-import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconciler;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.MonoReconciler;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
+import org.eclipse.ui.texteditor.spelling.SpellingReconcileStrategy;
+import org.eclipse.ui.texteditor.spelling.SpellingService;
+
+import winterwell.markdown.preferences.Prefs;
public class MDConfiguration extends TextSourceViewerConfiguration {
+
private ColorManager colorManager;
- public MDConfiguration(ColorManager colorManager, IPreferenceStore prefStore) {
- super(prefStore);
+ public MDConfiguration(ColorManager colorManager, IPreferenceStore store) {
+ super(store);
this.colorManager = colorManager;
}
@Override
public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
MDScanner scanner = new MDScanner(colorManager);
- PresentationReconciler pr = (PresentationReconciler) super.getPresentationReconciler(sourceViewer); // FIXME
+ PresentationReconciler pr = (PresentationReconciler) super.getPresentationReconciler(sourceViewer);
DefaultDamagerRepairer ddr = new DefaultDamagerRepairer(scanner);
pr.setRepairer(ddr, IDocument.DEFAULT_CONTENT_TYPE);
pr.setDamager(ddr, IDocument.DEFAULT_CONTENT_TYPE);
return pr;
}
-
@Override
- public IReconciler getReconciler(ISourceViewer sourceViewer) {
- // This awful mess adds in update support
- // Get super strategy
- IReconciler rs = super.getReconciler(sourceViewer);
- if (true) return rs; // Seems to work fine?!
- final IReconcilingStrategy fsuperStrategy = rs==null? null : rs.getReconcilingStrategy("text");
- // Add our own
- IReconcilingStrategy strategy = new IReconcilingStrategy() {
- private IDocument doc;
- public void reconcile(IRegion partition) {
- MarkdownEditor ed = MarkdownEditor.getEditor(doc);
- if (ed != null) ed.updatePage(partition);
- if (fsuperStrategy!=null) fsuperStrategy.reconcile(partition);
- }
- public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
- MarkdownEditor ed = MarkdownEditor.getEditor(doc);
- if (ed != null) ed.updatePage(subRegion);
- if (fsuperStrategy!=null) fsuperStrategy.reconcile(dirtyRegion, subRegion);
+ public IReconciler getReconciler(ISourceViewer viewer) {
+ boolean local = fPreferenceStore.getBoolean(Prefs.PREF_SPELLING_ENABLED);
+ if (local) {
+
+ // use the combined preference store
+ SpellingService service = new SpellingService(fPreferenceStore);
+ if (service.getActiveSpellingEngineDescriptor(fPreferenceStore) == null) {
+ return super.getReconciler(viewer); // bail
}
- public void setDocument(IDocument document) {
- this.doc = document;
- if (fsuperStrategy!=null) fsuperStrategy.setDocument(document);
- }
- };
- // Make a reconciler
- MonoReconciler m2 = new MonoReconciler(strategy, true);
- m2.setIsIncrementalReconciler(true);
- m2.setProgressMonitor(new NullProgressMonitor());
- m2.setDelay(500);
- // Done
- return m2;
+
+ IReconcilingStrategy strategy = new SpellingReconcileStrategy(viewer, service);
+ MonoReconciler reconciler = new MonoReconciler(strategy, false);
+ reconciler.setDelay(500);
+ return reconciler;
+ }
+
+ // default; uses just the PlatformUI store
+ return super.getReconciler(viewer);
}
-
+
@SuppressWarnings("unused")
@Override
- public ITextHover getTextHover(ISourceViewer sourceViewer,
- String contentType) {
+ public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {
if (true) return super.getTextHover(sourceViewer, contentType);
// Add hover support for images
return new MDTextHover();
}
}
-
-
diff --git a/plugin/src/winterwell/markdown/editors/MDScanner.java b/plugin/src/winterwell/markdown/editors/MDScanner.java
index 60a307b..5cd27c1 100755
--- a/plugin/src/winterwell/markdown/editors/MDScanner.java
+++ b/plugin/src/winterwell/markdown/editors/MDScanner.java
@@ -1,61 +1,59 @@
-/**
- * Copyright winterwell Mathematics Ltd.
- * @author Daniel Winterstein
- * 13 Jan 2007
- */
-package winterwell.markdown.editors;
-
-import org.eclipse.jface.preference.IPreferenceStore;
-import org.eclipse.jface.preference.PreferenceConverter;
-import org.eclipse.jface.text.TextAttribute;
-import org.eclipse.jface.text.rules.IRule;
-import org.eclipse.jface.text.rules.IWhitespaceDetector;
-import org.eclipse.jface.text.rules.MultiLineRule;
-import org.eclipse.jface.text.rules.RuleBasedScanner;
-import org.eclipse.jface.text.rules.Token;
-import org.eclipse.jface.text.rules.WhitespaceRule;
-import org.eclipse.swt.SWT;
-
-import winterwell.markdown.Activator;
-import winterwell.markdown.preferences.MarkdownPreferencePage;
-
-/**
- *
- *
- * @author Daniel Winterstein
- */
-public class MDScanner extends RuleBasedScanner {
- ColorManager cm;
- public MDScanner(ColorManager cm) {
- this.cm = cm;
- IPreferenceStore pStore = Activator.getDefault().getPreferenceStore();
- Token heading = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_HEADER)), null, SWT.BOLD));
- Token comment = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_COMMENT))));
- Token emphasis = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_DEFUALT)), null, SWT.ITALIC));
- Token list = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_HEADER)), null, SWT.BOLD));
- Token link = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_LINK)), null, TextAttribute.UNDERLINE));
- Token code = new Token(new TextAttribute(
- cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_CODE)),
- cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_CODE_BG)),
- SWT.NORMAL));
- setRules(new IRule[] {
- new LinkRule(link),
- new HeaderRule(heading),
- new HeaderWithUnderlineRule(heading),
- new ListRule(list),
- new EmphasisRule("_", emphasis),
- new EmphasisRule("***", emphasis),
- new EmphasisRule("**", emphasis),
- new EmphasisRule("*", emphasis),
- new EmphasisRule("``", code),
- new EmphasisRule("`", code),
- new MultiLineRule("", comment),
- // WhitespaceRule messes up with the rest of rules
-// new WhitespaceRule(new IWhitespaceDetector() {
-// public boolean isWhitespace(char c) {
-// return Character.isWhitespace(c);
-// }
-// }),
- });
- }
-}
+/**
+ * Copyright winterwell Mathematics Ltd.
+ * @author Daniel Winterstein
+ * 13 Jan 2007
+ */
+package winterwell.markdown.editors;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceConverter;
+import org.eclipse.jface.text.TextAttribute;
+import org.eclipse.jface.text.rules.IRule;
+import org.eclipse.jface.text.rules.MultiLineRule;
+import org.eclipse.jface.text.rules.RuleBasedScanner;
+import org.eclipse.jface.text.rules.Token;
+import org.eclipse.swt.SWT;
+
+import winterwell.markdown.MarkdownUI;
+import winterwell.markdown.preferences.PrefPageGeneral;
+
+/**
+ *
+ *
+ * @author Daniel Winterstein
+ */
+public class MDScanner extends RuleBasedScanner {
+ ColorManager cm;
+ public MDScanner(ColorManager cm) {
+ this.cm = cm;
+ IPreferenceStore pStore = MarkdownUI.getDefault().getPreferenceStore();
+ Token heading = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, PrefPageGeneral.PREF_HEADER)), null, SWT.BOLD));
+ Token comment = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, PrefPageGeneral.PREF_COMMENT))));
+ Token emphasis = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, PrefPageGeneral.PREF_DEFAULT)), null, SWT.ITALIC));
+ Token list = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, PrefPageGeneral.PREF_HEADER)), null, SWT.BOLD));
+ Token link = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, PrefPageGeneral.PREF_LINK)), null, TextAttribute.UNDERLINE));
+ Token code = new Token(new TextAttribute(
+ cm.getColor(PreferenceConverter.getColor(pStore, PrefPageGeneral.PREF_CODE)),
+ cm.getColor(PreferenceConverter.getColor(pStore, PrefPageGeneral.PREF_CODE_BG)),
+ SWT.NORMAL));
+ setRules(new IRule[] {
+ new LinkRule(link),
+ new HeaderRule(heading),
+ new HeaderWithUnderlineRule(heading),
+ new ListRule(list),
+ new EmphasisRule("_", emphasis),
+ new EmphasisRule("***", emphasis),
+ new EmphasisRule("**", emphasis),
+ new EmphasisRule("*", emphasis),
+ new EmphasisRule("``", code),
+ new EmphasisRule("`", code),
+ new MultiLineRule("", comment),
+ // WhitespaceRule messes up with the rest of rules
+// new WhitespaceRule(new IWhitespaceDetector() {
+// public boolean isWhitespace(char c) {
+// return Character.isWhitespace(c);
+// }
+// }),
+ });
+ }
+}
diff --git a/plugin/src/winterwell/markdown/editors/MDTextHover.java b/plugin/src/winterwell/markdown/editors/MDTextHover.java
index 04377a6..92f9940 100755
--- a/plugin/src/winterwell/markdown/editors/MDTextHover.java
+++ b/plugin/src/winterwell/markdown/editors/MDTextHover.java
@@ -10,32 +10,23 @@
import org.eclipse.jface.text.Region;
import winterwell.markdown.StringMethods;
-import winterwell.utils.containers.Pair;
+import winterwell.markdown.util.Pair;
/**
- *
- *
* @author daniel
*/
-public class MDTextHover implements ITextHover //, ITextHoverExtension
-{
+public class MDTextHover implements ITextHover {
- /* (non-Javadoc)
- * @see org.eclipse.jface.text.ITextHover#getHoverInfo(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion)
- */
public String getHoverInfo(ITextViewer textViewer, IRegion region) {
try {
IDocument doc = textViewer.getDocument();
String text = doc.get(region.getOffset(), region.getLength());
- return ""+text+"";
+ return "" + text + "";
} catch (Exception e) {
return null;
- }
+ }
}
- /* (non-Javadoc)
- * @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int)
- */
public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
try {
IDocument doc = textViewer.getDocument();
@@ -45,37 +36,36 @@ public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
String text = doc.get(lineOffset, lineLength);
// Look for image tags
Pair altRegion;
- Pair urlRegion =
- StringMethods.findEnclosingRegion(text, offset-lineOffset, '(', ')');
- if (urlRegion==null) {
- altRegion = StringMethods.findEnclosingRegion(text, offset-lineOffset, '[', ']');
+ Pair urlRegion = StringMethods.findEnclosingRegion(text, offset - lineOffset, '(', ')');
+ if (urlRegion == null) {
+ altRegion = StringMethods.findEnclosingRegion(text, offset - lineOffset, '[', ']');
if (altRegion == null) return null;
urlRegion = StringMethods.findEnclosingRegion(text, altRegion.second, '(', ')');
} else {
- altRegion = StringMethods.findEnclosingRegion(text, urlRegion.first-1, '[', ']');
+ altRegion = StringMethods.findEnclosingRegion(text, urlRegion.first - 1, '[', ']');
}
- if (urlRegion==null || altRegion==null) return null;
+ if (urlRegion == null || altRegion == null) return null;
// Is it an image link?
- if (text.charAt(altRegion.first-1) != '!') return null;
- Region r = new Region(urlRegion.first+1+lineOffset, urlRegion.second-urlRegion.first-2);
+ if (text.charAt(altRegion.first - 1) != '!') return null;
+ Region r = new Region(urlRegion.first + 1 + lineOffset, urlRegion.second - urlRegion.first - 2);
return r;
} catch (Exception ex) {
return null;
}
}
-// public IInformationControlCreator getHoverControlCreator() {
-// return new IInformationControlCreator() {
-// public IInformationControl createInformationControl(Shell parent) {
-// int style= fIsFocusable ? SWT.V_SCROLL | SWT.H_SCROLL : SWT.NONE;
-//
-// if (BrowserInformationControl.isAvailable(parent)) {
-// final int shellStyle= SWT.TOOL | (fIsFocusable ? SWT.RESIZE : SWT.NO_TRIM);
-// return new BrowserInformationControl(parent, shellStyle, style, null);
-// }
-// return new DefaultInformationControl(parent, style, new HTMLTextPresenter());
-// }
-// };
-// }
+ // public IInformationControlCreator getHoverControlCreator() {
+ // return new IInformationControlCreator() {
+ // public IInformationControl createInformationControl(Shell parent) {
+ // int style= fIsFocusable ? SWT.V_SCROLL | SWT.H_SCROLL : SWT.NONE;
+ //
+ // if (BrowserInformationControl.isAvailable(parent)) {
+ // final int shellStyle= SWT.TOOL | (fIsFocusable ? SWT.RESIZE : SWT.NO_TRIM);
+ // return new BrowserInformationControl(parent, shellStyle, style, null);
+ // }
+ // return new DefaultInformationControl(parent, style, new HTMLTextPresenter());
+ // }
+ // };
+ // }
}
diff --git a/plugin/src/winterwell/markdown/editors/MarkdownEditor.java b/plugin/src/winterwell/markdown/editors/MarkdownEditor.java
index 86699c4..05b96ec 100755
--- a/plugin/src/winterwell/markdown/editors/MarkdownEditor.java
+++ b/plugin/src/winterwell/markdown/editors/MarkdownEditor.java
@@ -30,167 +30,173 @@
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
-import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.IDocumentProvider;
-import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
-import winterwell.markdown.Activator;
+import winterwell.markdown.Log;
+import winterwell.markdown.MarkdownUI;
import winterwell.markdown.pagemodel.MarkdownPage;
import winterwell.markdown.pagemodel.MarkdownPage.Header;
-import winterwell.markdown.preferences.MarkdownPreferencePage;
-import winterwell.markdown.views.MarkdownPreview;
-
+import winterwell.markdown.preferences.PrefPageGeneral;
+import winterwell.markdown.preferences.Prefs;
/**
* Text editor with markdown support.
+ *
* @author Daniel Winterstein
*/
-public class MarkdownEditor extends TextEditor implements IDocumentListener
-{
+public class MarkdownEditor extends TextEditor {
- /**
- * Maximum length for a task tag message
- */
+ public static final String ID = "winterwell.markdown.editors.MarkdownEditor";
+ public static final String ID2 = "org.nodeclipse.ui.editors.LitCoffeeEditor";
+
+ /** Maximum length for a task tag message */
private static final int MAX_TASK_MSG_LENGTH = 80;
+ private static final Annotation[] ANNOTATION_ARRAY = new Annotation[0];
+ private static final Position[] POSITION_ARRAY = new Position[0];
+
+ private MarkdownOutlinePage fOutlinePage;
private ColorManager colorManager;
- private MarkdownContentOutlinePage fOutlinePage = null;
-
- IDocument oldDoc = null;
-
private MarkdownPage page;
-
-
private boolean pageDirty = true;
-
+
+ private boolean haveRunFolding = false;
private ProjectionSupport projectionSupport;
- private final IPreferenceStore pStore;
- private IPropertyChangeListener prefChangeListener;
-
+ private Map oldAnnotations = new HashMap(0);
+
+ private final IDocumentListener docListener = new IDocumentListener() {
+
+ @Override
+ public void documentChanged(DocumentEvent event) {
+ pageDirty = true;
+ }
+
+ @Override
+ public void documentAboutToBeChanged(DocumentEvent event) {}
+ };
+
+ private final IPropertyChangeListener prefChangeListener = new IPropertyChangeListener() {
+
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ if (event.getProperty().equals(PrefPageGeneral.PREF_WORD_WRAP)) {
+ getViewer().getTextWidget().setWordWrap(isWordWrap());
+ }
+ }
+ };
public MarkdownEditor() {
super();
- pStore = Activator.getDefault().getPreferenceStore();
+ initCombinedPreferenceStore();
colorManager = new ColorManager();
setSourceViewerConfiguration(new MDConfiguration(colorManager, getPreferenceStore()));
}
-
@Override
public void createPartControl(Composite parent) {
- // Over-ride to add code-folding support
+ // add code-folding support
super.createPartControl(parent);
if (getSourceViewer() instanceof ProjectionViewer) {
- ProjectionViewer viewer =(ProjectionViewer)getSourceViewer();
- projectionSupport = new ProjectionSupport(viewer,getAnnotationAccess(),getSharedColors());
- projectionSupport.install();
- //turn projection mode on
- viewer.doOperation(ProjectionViewer.TOGGLE);
+ ProjectionViewer viewer = (ProjectionViewer) getSourceViewer();
+ projectionSupport = new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors());
+ projectionSupport.install();
+
+ // turn projection mode on
+ viewer.doOperation(ProjectionViewer.TOGGLE);
}
}
-
+
/**
- * Returns the editor's source viewer. May return null before the editor's part has been created and after disposal.
+ * Returns the editor's source viewer. May return null before the editor's part has been created
+ * and after disposal.
*/
public ISourceViewer getViewer() {
return getSourceViewer();
}
-
+
@Override
- protected ISourceViewer createSourceViewer(Composite parent,
- IVerticalRuler ruler, int styles) {
-// if (true) return super.createSourceViewer(parent, ruler, styles);
+ protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
// Create with code-folding
- ISourceViewer viewer = new ProjectionViewer(parent, ruler,
- getOverviewRuler(), isOverviewRulerVisible(), styles);
+ ISourceViewer viewer = new ProjectionViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(),
+ styles);
+
// ensure decoration support has been created and configured.
- SourceViewerDecorationSupport decSupport = getSourceViewerDecorationSupport(viewer);
-// SourceViewer viewer = (SourceViewer) super.createSourceViewer(parent, ruler, styles);
- // Setup word-wrapping
- final StyledText widget = viewer.getTextWidget();
- // Listen to pref changes
- prefChangeListener = new IPropertyChangeListener() {
- public void propertyChange(PropertyChangeEvent event) {
- if (event.getProperty().equals(MarkdownPreferencePage.PREF_WORD_WRAP)) {
- widget.setWordWrap(MarkdownPreferencePage.wordWrap());
- }
- }
- };
- pStore.addPropertyChangeListener(prefChangeListener);
- // Switch on word-wrapping
- if (MarkdownPreferencePage.wordWrap()) {
- widget.setWordWrap(true);
- }
- return viewer;
+ getSourceViewerDecorationSupport(viewer);
+
+ // initialize word-wrapping
+ viewer.getTextWidget().setWordWrap(isWordWrap());
+
+ return viewer;
+ }
+
+ private boolean isWordWrap() {
+ return getPreferenceStore().getBoolean(Prefs.PREF_WORD_WRAP);
}
-
+
public void dispose() {
- if (pStore != null) {
- pStore.removePropertyChangeListener(prefChangeListener);
- }
+ removePreferenceStoreListener();
colorManager.dispose();
- super.dispose();
- }
- public void documentAboutToBeChanged(DocumentEvent event) {
+ colorManager = null;
+ super.dispose();
}
- public void documentChanged(DocumentEvent event) {
- pageDirty = true;
- }
-
@Override
protected void doSetInput(IEditorInput input) throws CoreException {
- // Detach from old
- if (oldDoc!= null) {
- oldDoc.removeDocumentListener(this);
- if (doc2editor.get(oldDoc) == this) doc2editor.remove(oldDoc);
- }
- // Set
- super.doSetInput(input);
- // Attach as a listener to new doc
- IDocument doc = getDocument();
- oldDoc = doc;
- if (doc==null) return;
- doc.addDocumentListener(this);
- doc2editor.put(doc, this);
- // Initialise code folding
+
+ // Remove old doc listener
+ if (getDocument() != null) getDocument().removeDocumentListener(docListener);
+
+ super.doSetInput(input);
+
+ // Attach listener to new doc
+ getDocument().addDocumentListener(docListener);
+
+ // Initialize code folding
haveRunFolding = false;
updateSectionFoldingAnnotations(null);
}
- @Override
- protected void editorSaved() {
- if (MarkdownPreview.preview != null) {
- // Update the preview when the file is saved
- MarkdownPreview.preview.update();
+ /**
+ * Initializes the preference store for this editor. The constucted store represents the
+ * combined values of the MarkdownUI, EditorsUI, and PlatformUI stores.
+ */
+ private void initCombinedPreferenceStore() {
+ IPreferenceStore store = MarkdownUI.getDefault().getCombinedPreferenceStore();
+ store.addPropertyChangeListener(prefChangeListener);
+ setPreferenceStore(store);
+ }
+
+ private void removePreferenceStoreListener() {
+ if (getPreferenceStore() != null) {
+ getPreferenceStore().removePropertyChangeListener(prefChangeListener);
}
}
+ @SuppressWarnings({ "unchecked", "rawtypes" })
public Object getAdapter(Class required) {
if (IContentOutlinePage.class.equals(required)) {
if (fOutlinePage == null) {
- fOutlinePage= new MarkdownContentOutlinePage(getDocumentProvider(), this);
- if (getEditorInput() != null)
- fOutlinePage.setInput(getEditorInput());
+ fOutlinePage = new MarkdownOutlinePage(getDocumentProvider(), this);
+ if (getEditorInput() != null) fOutlinePage.setInput(getEditorInput());
}
return fOutlinePage;
}
return super.getAdapter(required);
}
+
public IDocument getDocument() {
IEditorInput input = getEditorInput();
- IDocumentProvider docProvider = getDocumentProvider();
- return docProvider==null? null : docProvider.getDocument(input);
+ IDocumentProvider docProvider = getDocumentProvider();
+ return docProvider == null ? null : docProvider.getDocument(input);
}
+
/**
- *
- * @return The {@link MarkdownPage} for the document being edited, or null
- * if unavailable.
+ * @return The {@link MarkdownPage} for the document being edited, or null if unavailable.
*/
public MarkdownPage getMarkdownPage() {
if (pageDirty) updateMarkdownPage();
@@ -198,147 +204,109 @@ public MarkdownPage getMarkdownPage() {
}
public int getPrintColumns() {
- return getPreferenceStore().getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN);
+ return getPreferenceStore().getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN);
}
/**
- * @return The text of the editor's document, or null if unavailable.
+ * Gets the text of the editor's current document, or null if unavailable.
*/
public String getText() {
IDocument doc = getDocument();
- return doc==null? null : doc.get();
+ return doc == null ? null : doc.get();
}
private void updateMarkdownPage() {
String text = getText();
- if (text==null) text="";
+ if (text == null) text = "";
page = new MarkdownPage(text);
pageDirty = false;
}
void updateTaskTags(IRegion region) {
- try {
- boolean useTags = pStore.getBoolean(MarkdownPreferencePage.PREF_TASK_TAGS);
- if (!useTags) return;
- // Get task tags
-// IPreferenceStore peuistore = EditorsUI.getPreferenceStore();
-//// IPreferenceStore pStore_jdt = org.eclipse.jdt.core.compiler.getDefault().getPreferenceStore();
-// String tagString = peuistore.getString("org.eclipse.jdt.core.compiler.taskTags");
- String tagString = pStore.getString(MarkdownPreferencePage.PREF_TASK_TAGS_DEFINED);
- List tags = Arrays.asList(tagString.split(","));
- // Get resource for editor
- IFile docFile = getResource(this);
- // Get existing tasks
- IMarker[] taskMarkers = docFile.findMarkers(IMarker.TASK, true, IResource.DEPTH_INFINITE);
- List markers = new ArrayList(Arrays.asList(taskMarkers));
-// Collections.sort(markers, c) sort for efficiency
- // Find tags in doc
- List text = getMarkdownPage().getText();
- for(int i=1; i<=text.size(); i++) {
- String line = text.get(i-1); // wierd off-by-one bug
- for (String tag : tags) {
- tag = tag.trim();
- int tagIndex = line.indexOf(tag);
- if (tagIndex == -1) continue;
- IMarker exists = updateTaskTags2_checkExisting(i, tagIndex, line, markers);
- if (exists!=null) {
- markers.remove(exists);
- continue;
+ try {
+ boolean useTags = getPreferenceStore().getBoolean(PrefPageGeneral.PREF_TASK_TAGS);
+ if (!useTags) return;
+ // Get task tags
+ String tagString = getPreferenceStore().getString(PrefPageGeneral.PREF_TASK_TAGS_DEFINED);
+ List tags = Arrays.asList(tagString.split(","));
+ // Get resource for editor
+ IFile docFile = getResource(this);
+ // Get existing tasks
+ IMarker[] taskMarkers = docFile.findMarkers(IMarker.TASK, true, IResource.DEPTH_INFINITE);
+ List markers = new ArrayList(Arrays.asList(taskMarkers));
+ // Find tags in doc
+ List text = getMarkdownPage().getText();
+ for (int i = 1; i <= text.size(); i++) {
+ String line = text.get(i - 1); // wierd off-by-one bug
+ for (String tag : tags) {
+ tag = tag.trim();
+ int tagIndex = line.indexOf(tag);
+ if (tagIndex == -1) continue;
+ IMarker exists = updateTaskTags2_checkExisting(i, tagIndex, line, markers);
+ if (exists != null) {
+ markers.remove(exists);
+ continue;
+ }
+ IMarker marker = docFile.createMarker(IMarker.TASK);
+ // Once we have a marker object, we can set its attributes
+ marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_NORMAL);
+ String msg = line.substring(line.indexOf(tag),
+ Math.min(tagIndex + MAX_TASK_MSG_LENGTH, line.length() - 1));
+ marker.setAttribute(IMarker.MESSAGE, msg);
+ marker.setAttribute(IMarker.LINE_NUMBER, i);
}
- IMarker marker = docFile.createMarker(IMarker.TASK);
- //Once we have a marker object, we can set its attributes
- marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_NORMAL);
- String msg = line.substring(line.indexOf(tag), Math.min(tagIndex+MAX_TASK_MSG_LENGTH, line.length()-1));
- marker.setAttribute(IMarker.MESSAGE, msg);
- marker.setAttribute(IMarker.LINE_NUMBER, i);
}
- }
- // Remove old markers
- for (IMarker m : markers) {
- try {
- m.delete();
- } catch (Exception ex) {
- //
- }
- }
- } catch (Exception ex) {
- //
- }
+ // Remove old markers
+ for (IMarker m : markers) {
+ try {
+ m.delete();
+ } catch (Exception e) {}
+ }
+ } catch (Exception e) {}
}
/**
* Find an existing marker, if there is one.
- * @param i
- * @param tagIndex
- * @param line
- * @param markers
- * @return
*/
- private IMarker updateTaskTags2_checkExisting(int i, int tagIndex,
- String line, List markers) {
+ private IMarker updateTaskTags2_checkExisting(int i, int tagIndex, String line, List markers) {
String tagMessage = line.substring(tagIndex).trim();
for (IMarker marker : markers) {
try {
- Integer lineNum = (Integer) marker.getAttribute(IMarker.LINE_NUMBER);
- if (i != lineNum) continue;
- String txt = ((String) marker.getAttribute(IMarker.MESSAGE)).trim();
- if (tagMessage.equals(txt)) return marker;
- } catch (Exception ex) {
- // Ignore
- }
+ Integer lineNum = (Integer) marker.getAttribute(IMarker.LINE_NUMBER);
+ if (i != lineNum) continue;
+ String txt = ((String) marker.getAttribute(IMarker.MESSAGE)).trim();
+ if (tagMessage.equals(txt)) return marker;
+ } catch (Exception ex) {}
}
return null;
}
-
private IFile getResource(MarkdownEditor markdownEditor) {
IPathEditorInput input = (IPathEditorInput) getEditorInput();
- IPath path = input.getPath();
+ IPath path = input.getPath();
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot root = workspace.getRoot();
- IFile[] files = root.findFilesForLocation(path);
+ IFile[] files = root.findFilesForLocationURI(path.toFile().toURI());
if (files.length != 1) return null;
- IFile docFile = files[0];
+ IFile docFile = files[0];
return docFile;
}
-
- /**
- * @param doc
- * @return
- */
- public static MarkdownEditor getEditor(IDocument doc) {
- return doc2editor.get(doc);
- }
-
- private static final Map doc2editor = new HashMap();
-
-
/**
- * @param region
- *
+ * @param region
*/
public void updatePage(IRegion region) {
-// if (!pageDirty) return;
updateTaskTags(region);
updateSectionFoldingAnnotations(region);
}
-
-
- private static final Annotation[] ANNOTATION_ARRAY = new Annotation[0];
-
- private static final Position[] POSITION_ARRAY = new Position[0];
-
- private boolean haveRunFolding = false;
- private Map oldAnnotations = new HashMap(0);
/**
* @param region can be null
*/
private void updateSectionFoldingAnnotations(IRegion region) {
if (!haveRunFolding) region = null; // Do the whole doc
- if ( ! (getSourceViewer() instanceof ProjectionViewer)) return;
- ProjectionViewer viewer = ((ProjectionViewer)getSourceViewer());
+ if (!(getSourceViewer() instanceof ProjectionViewer)) return;
+ ProjectionViewer viewer = ((ProjectionViewer) getSourceViewer());
MarkdownPage mPage = getMarkdownPage();
List headers = mPage.getHeadings(null);
// this will hold the new annotations along
@@ -347,9 +315,9 @@ private void updateSectionFoldingAnnotations(IRegion region) {
IDocument doc = getDocument();
updateSectionFoldingAnnotations2(doc, headers, annotations, doc.getLength());
// Filter existing ones
- Position[] newValues = annotations.values().toArray(POSITION_ARRAY);
+ Position[] newValues = annotations.values().toArray(POSITION_ARRAY);
List deletedAnnotations = new ArrayList();
- for(Entry ae : oldAnnotations.entrySet()) {
+ for (Entry ae : oldAnnotations.entrySet()) {
Position oldp = ae.getValue();
boolean stillExists = false;
for (Position newp : newValues) {
@@ -364,54 +332,52 @@ private void updateSectionFoldingAnnotations(IRegion region) {
}
}
// Filter out-of-region ones
- for(Annotation a : annotations.keySet().toArray(ANNOTATION_ARRAY)) {
+ for (Annotation a : annotations.keySet().toArray(ANNOTATION_ARRAY)) {
Position p = annotations.get(a);
- if (!intersectsRegion(p , region)) annotations.remove(a);
+ if (!intersectsRegion(p, region)) annotations.remove(a);
}
// Adjust the page
- ProjectionAnnotationModel annotationModel = viewer.getProjectionAnnotationModel();
- if (annotationModel==null) return;
+ ProjectionAnnotationModel annotationModel = viewer.getProjectionAnnotationModel();
+ if (annotationModel == null) return;
annotationModel.modifyAnnotations(deletedAnnotations.toArray(ANNOTATION_ARRAY), annotations, null);
// Remember old values
oldAnnotations.putAll(annotations);
for (Annotation a : deletedAnnotations) {
- oldAnnotations.remove(a);
- }
+ oldAnnotations.remove(a);
+ }
haveRunFolding = true;
}
-
/**
* @param p
* @param region
* @return true if p overlaps with region, or if region is null
*/
private boolean intersectsRegion(Position p, IRegion region) {
- if (region==null) return true;
- if (p.offset > region.getOffset()+region.getLength()) return false;
- if (p.offset+p.length < region.getOffset()) return false;
+ if (region == null) return true;
+ if (p.offset > region.getOffset() + region.getLength()) return false;
+ if (p.offset + p.length < region.getOffset()) return false;
return true;
}
-
/**
* Calculate where to fold, sticking the info into newAnnotations
- * @param doc
+ *
+ * @param doc
* @param headers
* @param newAnnotations
* @param endParent
*/
private void updateSectionFoldingAnnotations2(IDocument doc, List headers,
Map newAnnotations, int endParent) {
- for (int i=0; i subHeaders = header.getSubHeaders();
@@ -419,37 +385,8 @@ private void updateSectionFoldingAnnotations2(IDocument doc, List header
updateSectionFoldingAnnotations2(doc, subHeaders, newAnnotations, end);
}
} catch (Exception ex) {
- System.out.println(ex);
- }
- }
+ Log.error(ex);
+ }
+ }
}
-
-
}
-
-
-
-/*
-
-
--
- ${control}.getDisplay().syncExec(new Runnable() { public void run() { ${control}.${cursor} } });
- ${viewType} ${view} = null; IWorkbenchWindow ${window} = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (${window} != null) { IWorkbenchPage ${activePage} = ${window}.getActivePage(); if (${activePage} != null) ${view} = (${viewType}) ${activePage}.findView("${viewID}"); } if (${view} != null) { ${cursor}//${todo}: Add operations for opened view }
- IEditorPart ${editor} = null; IWorkbenchWindow ${window} = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (${window} != null) { IWorkbenchPage ${activePage} = ${window}.getActivePage(); if (${activePage} != null) ${editor} = ${activePage}.getActiveEditor(); } if (${editor} != null) { ${cursor}//${todo}: Add operations for active editor }
- IEditorPart editor = null;
- IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
- if (window != null) {
- IWorkbenchPage activePage = window.getActivePage();
- if (activePage != null) editor = activePage.getActiveEditor();
- }
- if (editor != null) {
- // todo: Add operations for active editor
- }
-
- ${dialogType} ${dialog} = new ${dialogType}(${cursor}); ${dialog}.create(); //${todo}: Complete dialog creation if (${dialog}.open() == Dialog.OK) { //${todo}: Perform actions on success };
- IExtensionRegistry ${registry} = Platform.getExtensionRegistry(); IExtensionPoint ${point} = ${registry}.getExtensionPoint(${pluginId}, ${expointId}); IExtension[] ${extensions} = ${point}.getExtensions(); for (int ${index} = 0; ${index} < ${extensions}.length; ${index}++) { IConfigurationElement[] ${elements} = ${extensions}[${index}].getConfigurationElements(); for (int ${index2} = 0; ${index2} < ${elements}.length; ${index2}++) { IConfigurationElement ${element} = ${elements}[${index2}]; String ${attValue} = ${element}.getAttribute(${attName}); ${cursor}//${todo}: Implement processing for configuration element } }
- ${viewType} ${view} = null; IWorkbenchWindow ${window} = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (${window} != null) { IWorkbenchPage ${activePage} = ${window}.getActivePage(); if (${activePage} != null) try { ${view} = (${viewType}) ${activePage}.showView("${viewID}"); } catch (${Exception} e) { // ${todo}: handle exception } } if (${view} != null) { ${cursor} }
- IEditorActionBarContributor ${contributor} = ${editor}.getEditorSite().getActionBarContributor(); if (${contributor} instanceof EditorActionBarContributor) { IActionBars ${actionBars} = ((EditorActionBarContributor) ${contributor}).getActionBars(); if (${actionBars} != null) { IStatusLineManager ${manager} = ${actionBars}.getStatusLineManager(); if (${manager} != null) ${manager}.setErrorMessage(msg); } }
-
-
-*/
\ No newline at end of file
diff --git a/plugin/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java b/plugin/src/winterwell/markdown/editors/MarkdownOutlinePage.java
similarity index 67%
rename from plugin/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java
rename to plugin/src/winterwell/markdown/editors/MarkdownOutlinePage.java
index 445a322..6716b74 100755
--- a/plugin/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java
+++ b/plugin/src/winterwell/markdown/editors/MarkdownOutlinePage.java
@@ -1,538 +1,503 @@
-/**
- * Copyright winterwell Mathematics Ltd.
- * @author Daniel Winterstein
- * 11 Jan 2007
- */
-package winterwell.markdown.editors;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-
-import org.eclipse.jface.action.Action;
-import org.eclipse.jface.action.IAction;
-import org.eclipse.jface.action.IToolBarManager;
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.jface.text.BadLocationException;
-import org.eclipse.jface.text.DocumentEvent;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.jface.text.IDocumentListener;
-import org.eclipse.jface.text.IRegion;
-import org.eclipse.jface.text.Region;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.LabelProvider;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.jface.viewers.TreeViewer;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.ui.IActionBars;
-import org.eclipse.ui.part.IPageSite;
-import org.eclipse.ui.texteditor.IDocumentProvider;
-import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
-
-import winterwell.markdown.pagemodel.MarkdownPage;
-import winterwell.markdown.pagemodel.MarkdownPage.Header;
-import winterwell.markdown.pagemodel.MarkdownPage.KLineType;
-import winterwell.utils.StrUtils;
-import winterwell.utils.Utils;
-import winterwell.utils.web.WebUtils;
-
-/**
- *
- *
- * @author Daniel Winterstein
- */
-public final class MarkdownContentOutlinePage extends ContentOutlinePage {
-
- /**
- *
- *
- * @author Daniel Winterstein
- */
- public final class ContentProvider implements ITreeContentProvider,
- IDocumentListener {
-
- // protected final static String SEGMENTS= "__md_segments";
- // //$NON-NLS-1$
- // protected IPositionUpdater fPositionUpdater= new
- // DefaultPositionUpdater(SEGMENTS);
- private MarkdownPage fContent;
- // protected List fContent= new ArrayList(10);
- private MarkdownEditor fTextEditor;
-
- private void parse() {
- fContent = fTextEditor.getMarkdownPage();
- }
-
- /*
- * @see IContentProvider#inputChanged(Viewer, Object, Object)
- */
- public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
- // Detach from old
- if (oldInput != null) {
- IDocument document = fDocumentProvider.getDocument(oldInput);
- if (document != null) {
- document.removeDocumentListener(this);
- }
- }
- fContent = null;
- // Attach to new
- if (newInput == null)
- return;
- IDocument document = fDocumentProvider.getDocument(newInput);
- if (document == null)
- return;
- fTextEditor = MarkdownEditor.getEditor(document);
- document.addDocumentListener(this);
- parse();
- }
-
- /*
- * @see IContentProvider#dispose
- */
- public void dispose() {
- fContent = null;
- }
-
- /*
- * @see IContentProvider#isDeleted(Object)
- */
- public boolean isDeleted(Object element) {
- return false;
- }
-
- /*
- * @see IStructuredContentProvider#getElements(Object)
- */
- public Object[] getElements(Object element) {
- return fContent.getHeadings(null).toArray();
- }
-
- /*
- * @see ITreeContentProvider#hasChildren(Object)
- */
- public boolean hasChildren(Object element) {
- if (element == fInput) {
- return true;
- }
- if (element instanceof MarkdownPage.Header) {
- MarkdownPage.Header header = (MarkdownPage.Header) element;
- return header.getSubHeaders().size() > 0;
- }
- ;
- return false;
- }
-
- /*
- * @see ITreeContentProvider#getParent(Object)
- */
- public Object getParent(Object element) {
- if (!(element instanceof MarkdownPage.Header))
- return null;
- return ((MarkdownPage.Header) element).getParent();
- }
-
- /*
- * @see ITreeContentProvider#getChildren(Object)
- */
- public Object[] getChildren(Object element) {
- if (element == fInput) {
- return fContent.getHeadings(null).toArray();
- }
- if (!(element instanceof MarkdownPage.Header))
- return null;
- return ((MarkdownPage.Header) element).getSubHeaders().toArray();
- }
-
- public void documentAboutToBeChanged(DocumentEvent event) {
- // nothing
- }
-
- public void documentChanged(DocumentEvent event) {
- parse();
- update();
- }
- }
-
- private Object fInput = null;
- private final IDocumentProvider fDocumentProvider;
- private final MarkdownEditor fTextEditor;
- protected boolean showWordCounts;
- private List selectedHeaders;
-
- /**
- * @param documentProvider
- * @param mdEditor
- */
- public MarkdownContentOutlinePage(IDocumentProvider documentProvider,
- MarkdownEditor mdEditor) {
- fDocumentProvider = documentProvider;
- fTextEditor = mdEditor;
- }
-
- /*
- * (non-Javadoc) Method declared on ContentOutlinePage
- */
- @Override
- public void createControl(Composite parent) {
- super.createControl(parent);
- TreeViewer viewer = getTreeViewer();
- viewer.setContentProvider(new ContentProvider());
- // Add word count annotations
- viewer.setLabelProvider(new LabelProvider() {
- @Override
- public String getText(Object element) {
- if (!(element instanceof MarkdownPage.Header))
- return super.getText(element);
- Header header = ((MarkdownPage.Header) element);
- String hText = header.toString();
- if (!showWordCounts)
- return hText;
- IRegion region = getRegion(header);
- String text;
- try {
- text = fTextEditor.getDocument().get(region.getOffset(),
- region.getLength());
- text = WebUtils.stripTags(text);
- text = text.replaceAll("#", "").trim();
- assert text.startsWith(hText);
- text = text.substring(hText.length());
- int wc = StrUtils.wordCount(text);
- return hText + " (" + wc + ":" + text.length() + ")";
- } catch (BadLocationException e) {
- return hText;
- }
- }
- });
- viewer.addSelectionChangedListener(this);
-
- if (fInput != null)
- viewer.setInput(fInput);
-
- // Buttons
- IPageSite site = getSite();
- IActionBars bars = site.getActionBars();
- IToolBarManager toolbar = bars.getToolBarManager();
- // Word count action
- Action action = new Action("123", IAction.AS_CHECK_BOX) {
- @Override
- public void run() {
- showWordCounts = isChecked();
- update();
- }
- };
- action.setToolTipText("Show/hide section word:character counts");
- toolbar.add(action);
- // +/- actions
- action = new Action("<") {
- @Override
- public void run() {
- doPromoteDemote(-1);
- }
- };
- action.setToolTipText("Promote the selected section\n -- move it up a level.");
- toolbar.add(action);
- //
- action = new Action(">") {
- @Override
- public void run() {
- doPromoteDemote(1);
- }
- };
- action.setToolTipText("Demote the selected section\n -- move it down a level.");
- toolbar.add(action);
- // up/down actions
- action = new Action("/\\") {
- @Override
- public void run() {
- try {
- doMove(-1);
- } catch (BadLocationException e) {
- throw Utils.runtime(e);
- }
- }
- };
- action.setToolTipText("Move the selected section earlier");
- toolbar.add(action);
- //
- action = new Action("\\/") {
- @Override
- public void run() {
- try {
- doMove(1);
- } catch (BadLocationException e) {
- throw Utils.runtime(e);
- }
- }
- };
- action.setToolTipText("Move the selected section later");
- toolbar.add(action);
- // Collapse
- ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif");
- action = new Action("collapse", id) {
- @Override
- public void run() {
- doCollapseAll();
- }
- };
- action.setImageDescriptor(id);
- action.setToolTipText("Collapse outline tree");
- toolbar.add(action);
- // Sync
- id = ImageDescriptor.createFromFile(getClass(), "synced.gif");
- action = new Action("sync") {
- @Override
- public void run() {
- try {
- doSyncToEditor();
- } catch (BadLocationException e) {
- throw Utils.runtime(e);
- }
- }
- };
- action.setImageDescriptor(id);
- action.setToolTipText("Link with editor");
- toolbar.add(action);
- // Add edit ability
- viewer.getControl().addKeyListener(new KeyListener() {
- public void keyPressed(KeyEvent e) {
- if (e.keyCode==SWT.F2) {
- doEditHeader();
- }
- }
- public void keyReleased(KeyEvent e) {
- //
- }
- });
- }
-
- /**
- * @throws BadLocationException
- *
- */
- protected void doSyncToEditor() throws BadLocationException {
- TreeViewer viewer = getTreeViewer();
- if (viewer == null) return;
- // Get header
- MarkdownPage page = fTextEditor.getMarkdownPage();
- int caretOffset = fTextEditor.getViewer().getTextWidget().getCaretOffset();
- IDocument doc = fTextEditor.getDocument();
- int line = doc.getLineOfOffset(caretOffset);
- List lineTypes = page.getLineTypes();
- for(; line>-1; line--) {
- KLineType lt = lineTypes.get(line);
- if (lt.toString().startsWith("H")) break;
- }
- if (line<0) return;
- Header header = (Header) page.getPageObject(line);
- // Set
- IStructuredSelection selection = new StructuredSelection(header);
- viewer.setSelection(selection , true);
- }
-
- void doEditHeader() {
- TreeViewer viewer = getTreeViewer();
- viewer.editElement(selectedHeaders.get(0), 0);
- }
-
- protected void doCollapseAll() {
- TreeViewer viewer = getTreeViewer();
- if (viewer == null) return;
-// Control control = viewer.getControl();
-// if (control != null && !control.isDisposed()) {
-// control.setRedraw(false);
- viewer.collapseAll();
-// control.setRedraw(true);
-// }
- }
-
- /**
- * Move the selected sections up/down
- * @param i 1 or -1. 1==move later, -1=earlier
- * @throws BadLocationException
- */
- protected void doMove(int i) throws BadLocationException {
- assert i==1 || i==-1;
- if (selectedHeaders == null || selectedHeaders.size() == 0)
- return;
- // Get text region to move
- MarkdownPage.Header first = selectedHeaders.get(0);
- MarkdownPage.Header last = selectedHeaders.get(selectedHeaders.size()-1);
- int start = fTextEditor.getDocument().getLineOffset(
- first.getLineNumber());
- IRegion r = getRegion(last);
- int end = r.getOffset() + r.getLength();
- int length = end - start;
- // Get new insertion point
- int insert;
- if (i==1) {
- Header nextSection = last.getNext();
- if (nextSection==null) return;
- IRegion nr = getRegion(nextSection);
- insert = nr.getOffset()+nr.getLength();
- } else {
- Header prevSection = first.getPrevious();
- if (prevSection==null) return;
- IRegion nr = getRegion(prevSection);
- insert = nr.getOffset();
- }
- // Get text
- String text = fTextEditor.getDocument().get();
- // Move text
- String section = text.substring(start, end);
- String pre, post;
- if (i==1) {
- pre = text.substring(0, start) + text.substring(end, insert);
- post = text.substring(insert);
- } else {
- pre = text.substring(0, insert);
- post = text.substring(insert,start)+text.substring(end);
- }
- text = pre + section + post;
- assert text.length() == fTextEditor.getDocument().get().length() :
- text.length()-fTextEditor.getDocument().get().length()+" chars gained/lost";
- // Update doc
- fTextEditor.getDocument().set(text);
- }
-
- /**
- * Does not support -------- / ========= underlining, only # headers
- * @param upDown 1 for demote (e.g. h2 -> h3), -1 for promote (e.g. h2 -> h1)
- */
- protected void doPromoteDemote(int upDown) {
- assert upDown==1 || upDown==-1;
- if (selectedHeaders == null || selectedHeaders.size() == 0)
- return;
- HashSet toAdjust = new HashSet(selectedHeaders);
- HashSet adjusted = new HashSet();
- // Adjust
- MarkdownPage mdPage = fTextEditor.getMarkdownPage();
- List lines = new ArrayList(mdPage.getText());
- while(toAdjust.size() != 0) {
- Header h = toAdjust.iterator().next();
- toAdjust.remove(h);
- adjusted.add(h);
- String line = lines.get(h.getLineNumber());
- if (upDown==-1) {
- if (h.getLevel() == 1) return; // Level 1; can't promote
- if (line.startsWith("##")) line = line.substring(1);
- else {
- return; // TODO support for ------ / ========
- }
- } else line = "#" + line;
- int ln = h.getLineNumber();
- lines.set(ln, line);
- // kids
- ArrayList kids = new ArrayList(h.getSubHeaders());
- for (Header header : kids) {
- if ( ! adjusted.contains(header)) toAdjust.add(header);
- }
- }
- // Set
- StringBuilder sb = new StringBuilder();
- for (String line : lines) {
- sb.append(line);
- }
- fTextEditor.getDocument().set(sb.toString());
- }
-
- /**
- * The region of text for this header. This includes the header itself.
- * @param header
- * @return
- * @throws BadLocationException
- */
- protected IRegion getRegion(Header header) {
- try {
- IDocument doc = fTextEditor.getDocument();
- // Line numbers
- int start = header.getLineNumber();
- Header next = header.getNext();
- int end;
- if (next != null) {
- end = next.getLineNumber() - 1;
- } else {
- end = doc.getNumberOfLines() - 1;
- }
- int offset = doc.getLineOffset(start);
- IRegion ei = doc.getLineInformation(end);
- int length = ei.getOffset() + ei.getLength() - offset;
- return new Region(offset, length);
- } catch (BadLocationException ex) {
- throw Utils.runtime(ex);
- }
- }
-
- /*
- * (non-Javadoc) Method declared on ContentOutlinePage
- */
- @Override
- public void selectionChanged(SelectionChangedEvent event) {
- super.selectionChanged(event);
- selectedHeaders = null;
- ISelection selection = event.getSelection();
- if (selection.isEmpty())
- return;
- if (!(selection instanceof IStructuredSelection))
- return;
- try {
- IStructuredSelection strucSel = (IStructuredSelection) selection;
- Object[] sections = strucSel.toArray();
- selectedHeaders = (List) Arrays.asList(sections);
- MarkdownPage.Header first = (Header) sections[0];
- MarkdownPage.Header last = (Header) sections[sections.length - 1];
- int start = fTextEditor.getDocument().getLineOffset(
- first.getLineNumber());
- int length;
- if (first == last) {
- length = fTextEditor.getDocument().getLineLength(
- first.getLineNumber());
- } else {
- IRegion r = getRegion(last);
- int end = r.getOffset() + r.getLength();
- length = end - start;
- }
- fTextEditor.setHighlightRange(start, length, true);
- } catch (Exception x) {
- System.out.println(x.getStackTrace());
- fTextEditor.resetHighlightRange();
- }
- }
-
- /**
- * Sets the input of the outline page
- *
- * @param input
- * the input of this outline page
- */
- public void setInput(Object input) {
- fInput = input;
- update();
- }
-
- /**
- * Updates the outline page.
- */
- public void update() {
- TreeViewer viewer = getTreeViewer();
-
- if (viewer != null) {
- Control control = viewer.getControl();
- if (control != null && !control.isDisposed()) {
- control.setRedraw(false);
- viewer.setInput(fInput);
- viewer.expandAll();
- control.setRedraw(true);
- }
- }
- }
-
-}
+/**
+ * Copyright winterwell Mathematics Ltd.
+ * @author Daniel Winterstein
+ * 11 Jan 2007
+ */
+package winterwell.markdown.editors;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextListener;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextEvent;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.part.IPageSite;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
+
+import winterwell.markdown.Log;
+import winterwell.markdown.pagemodel.MarkdownPage;
+import winterwell.markdown.pagemodel.MarkdownPage.Header;
+import winterwell.markdown.pagemodel.MarkdownPage.KLineType;
+import winterwell.markdown.util.Strings;
+
+/**
+ * @author Daniel Winterstein
+ */
+public final class MarkdownOutlinePage extends ContentOutlinePage {
+
+ public final class ContentProvider implements ITreeContentProvider, IDocumentListener {
+
+ private MarkdownPage page;
+
+ private void parse() {
+ this.page = editor.getMarkdownPage();
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+
+ if (oldInput != null) {
+ IDocument document = fDocumentProvider.getDocument(oldInput);
+ if (document != null) {
+ document.removeDocumentListener(this);
+ }
+ }
+
+ page = null;
+ if (newInput != null) {
+ IDocument document = fDocumentProvider.getDocument(newInput);
+ if (document != null) {
+ document.addDocumentListener(this);
+ parse();
+ }
+ }
+ }
+
+ public void dispose() {
+ page = null;
+ }
+
+ public boolean isDeleted(Object element) {
+ return false;
+ }
+
+ public Object[] getElements(Object element) {
+ return page.getHeadings(null).toArray();
+ }
+
+ public boolean hasChildren(Object element) {
+ if (element == fInput) {
+ return true;
+ }
+ if (element instanceof MarkdownPage.Header) {
+ MarkdownPage.Header header = (MarkdownPage.Header) element;
+ return header.getSubHeaders().size() > 0;
+ } ;
+ return false;
+ }
+
+ public Object getParent(Object element) {
+ if (!(element instanceof MarkdownPage.Header)) return null;
+ return ((MarkdownPage.Header) element).getParent();
+ }
+
+ public Object[] getChildren(Object element) {
+ if (element == fInput) {
+ return page.getHeadings(null).toArray();
+ }
+ if (!(element instanceof MarkdownPage.Header)) return null;
+ return ((MarkdownPage.Header) element).getSubHeaders().toArray();
+ }
+
+ public void documentAboutToBeChanged(DocumentEvent event) {}
+
+ public void documentChanged(DocumentEvent event) {
+ parse();
+ update();
+ }
+ }
+
+ private Object fInput = null;
+ private final IDocumentProvider fDocumentProvider;
+ private final MarkdownEditor editor;
+ protected boolean showWordCounts;
+ private List selectedHeaders;
+
+ /**
+ * @param documentProvider
+ * @param editor
+ */
+ public MarkdownOutlinePage(IDocumentProvider documentProvider, MarkdownEditor editor) {
+ this.fDocumentProvider = documentProvider;
+ this.editor = editor;
+
+ editor.getViewer().addTextListener(new ITextListener() {
+
+ @Override
+ public void textChanged(TextEvent event) {}
+ });
+ }
+
+ @Override
+ public void createControl(Composite parent) {
+ super.createControl(parent);
+ TreeViewer viewer = getTreeViewer();
+ viewer.setContentProvider(new ContentProvider());
+ // Add word count annotations
+ viewer.setLabelProvider(new LabelProvider() {
+
+ @Override
+ public String getText(Object element) {
+ if (!(element instanceof MarkdownPage.Header)) return super.getText(element);
+ Header header = ((MarkdownPage.Header) element);
+ String hText = header.toString();
+ if (!showWordCounts) return hText;
+ IRegion region = getRegion(header);
+ String text;
+ try {
+ text = editor.getDocument().get(region.getOffset(), region.getLength());
+ text = Strings.stripTags(text);
+ text = text.replaceAll("#", "").trim();
+ assert text.startsWith(hText);
+ text = text.substring(hText.length());
+ int wc = Strings.wordCount(text);
+ return hText + " (" + wc + ":" + text.length() + ")";
+ } catch (BadLocationException e) {
+ return hText;
+ }
+ }
+ });
+ viewer.addSelectionChangedListener(this);
+
+ if (fInput != null) viewer.setInput(fInput);
+
+ // Buttons
+ IPageSite site = getSite();
+ IActionBars bars = site.getActionBars();
+ IToolBarManager toolbar = bars.getToolBarManager();
+ // Word count action
+ Action action = new Action("123", IAction.AS_CHECK_BOX) {
+
+ @Override
+ public void run() {
+ showWordCounts = isChecked();
+ update();
+ }
+ };
+ action.setToolTipText("Show/hide section word:character counts");
+ toolbar.add(action);
+ // +/- actions
+ action = new Action("<") {
+
+ @Override
+ public void run() {
+ doPromoteDemote(-1);
+ }
+ };
+ action.setToolTipText("Promote the selected section\n -- move it up a level.");
+ toolbar.add(action);
+ //
+ action = new Action(">") {
+
+ @Override
+ public void run() {
+ doPromoteDemote(1);
+ }
+ };
+ action.setToolTipText("Demote the selected section\n -- move it down a level.");
+ toolbar.add(action);
+ // up/down actions
+ action = new Action("/\\") {
+
+ @Override
+ public void run() {
+ try {
+ doMove(-1);
+ } catch (BadLocationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ action.setToolTipText("Move the selected section earlier");
+ toolbar.add(action);
+ //
+ action = new Action("\\/") {
+
+ @Override
+ public void run() {
+ try {
+ doMove(1);
+ } catch (BadLocationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ action.setToolTipText("Move the selected section later");
+ toolbar.add(action);
+ // Collapse
+ ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif");
+ action = new Action("collapse", id) {
+
+ @Override
+ public void run() {
+ doCollapseAll();
+ }
+ };
+ action.setImageDescriptor(id);
+ action.setToolTipText("Collapse outline tree");
+ toolbar.add(action);
+ // Sync
+ id = ImageDescriptor.createFromFile(getClass(), "synced.gif");
+ action = new Action("sync") {
+
+ @Override
+ public void run() {
+ try {
+ doSyncToEditor();
+ } catch (BadLocationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ action.setImageDescriptor(id);
+ action.setToolTipText("Link with editor");
+ toolbar.add(action);
+ // Add edit ability
+ viewer.getControl().addKeyListener(new KeyListener() {
+
+ public void keyPressed(KeyEvent e) {
+ if (e.keyCode == SWT.F2) {
+ doEditHeader();
+ }
+ }
+
+ public void keyReleased(KeyEvent e) {
+ //
+ }
+ });
+ }
+
+ /**
+ * @throws BadLocationException
+ */
+ protected void doSyncToEditor() throws BadLocationException {
+ TreeViewer viewer = getTreeViewer();
+ if (viewer == null) return;
+ // Get header
+ MarkdownPage page = editor.getMarkdownPage();
+ int caretOffset = editor.getViewer().getTextWidget().getCaretOffset();
+ IDocument doc = editor.getDocument();
+ int line = doc.getLineOfOffset(caretOffset);
+ List lineTypes = page.getLineTypes();
+ for (; line > -1; line--) {
+ KLineType lt = lineTypes.get(line);
+ if (lt.toString().startsWith("H")) break;
+ }
+ if (line < 0) return;
+ Header header = (Header) page.getPageObject(line);
+ // Set
+ IStructuredSelection selection = new StructuredSelection(header);
+ viewer.setSelection(selection, true);
+ }
+
+ void doEditHeader() {
+ TreeViewer viewer = getTreeViewer();
+ viewer.editElement(selectedHeaders.get(0), 0);
+ }
+
+ protected void doCollapseAll() {
+ TreeViewer viewer = getTreeViewer();
+ if (viewer == null) return;
+ // Control control = viewer.getControl();
+ // if (control != null && !control.isDisposed()) {
+ // control.setRedraw(false);
+ viewer.collapseAll();
+ // control.setRedraw(true);
+ // }
+ }
+
+ /**
+ * Move the selected sections up/down
+ *
+ * @param i 1 or -1. 1==move later, -1=earlier
+ * @throws BadLocationException
+ */
+ protected void doMove(int i) throws BadLocationException {
+ assert i == 1 || i == -1;
+ if (selectedHeaders == null || selectedHeaders.size() == 0) return;
+ // Get text region to move
+ MarkdownPage.Header first = selectedHeaders.get(0);
+ MarkdownPage.Header last = selectedHeaders.get(selectedHeaders.size() - 1);
+ int start = editor.getDocument().getLineOffset(first.getLineNumber());
+ IRegion r = getRegion(last);
+ int end = r.getOffset() + r.getLength();
+ // int length = end - start;
+ // Get new insertion point
+ int insert;
+ if (i == 1) {
+ Header nextSection = last.getNext();
+ if (nextSection == null) return;
+ IRegion nr = getRegion(nextSection);
+ insert = nr.getOffset() + nr.getLength();
+ } else {
+ Header prevSection = first.getPrevious();
+ if (prevSection == null) return;
+ IRegion nr = getRegion(prevSection);
+ insert = nr.getOffset();
+ }
+ // Get text
+ String text = editor.getDocument().get();
+ // Move text
+ String section = text.substring(start, end);
+ String pre, post;
+ if (i == 1) {
+ pre = text.substring(0, start) + text.substring(end, insert);
+ post = text.substring(insert);
+ } else {
+ pre = text.substring(0, insert);
+ post = text.substring(insert, start) + text.substring(end);
+ }
+ text = pre + section + post;
+ assert text.length() == editor.getDocument().get().length() : text.length()
+ - editor.getDocument().get().length() + " chars gained/lost";
+ // Update doc
+ editor.getDocument().set(text);
+ }
+
+ /**
+ * Does not support -------- / ========= underlining, only # headers
+ *
+ * @param upDown 1 for demote (e.g. h2 -> h3), -1 for promote (e.g. h2 -> h1)
+ */
+ protected void doPromoteDemote(int upDown) {
+ assert upDown == 1 || upDown == -1;
+ if (selectedHeaders == null || selectedHeaders.size() == 0) return;
+ HashSet toAdjust = new HashSet(selectedHeaders);
+ HashSet adjusted = new HashSet();
+ // Adjust
+ MarkdownPage mdPage = editor.getMarkdownPage();
+ List lines = new ArrayList(mdPage.getText());
+ while (toAdjust.size() != 0) {
+ Header h = toAdjust.iterator().next();
+ toAdjust.remove(h);
+ adjusted.add(h);
+ String line = lines.get(h.getLineNumber());
+ if (upDown == -1) {
+ if (h.getLevel() == 1) return; // Level 1; can't promote
+ if (line.startsWith("##"))
+ line = line.substring(1);
+ else {
+ return; // TODO support for ------ / ========
+ }
+ } else
+ line = "#" + line;
+ int ln = h.getLineNumber();
+ lines.set(ln, line);
+ // kids
+ ArrayList kids = new ArrayList(h.getSubHeaders());
+ for (Header header : kids) {
+ if (!adjusted.contains(header)) toAdjust.add(header);
+ }
+ }
+ // Set
+ StringBuilder sb = new StringBuilder();
+ for (String line : lines) {
+ sb.append(line);
+ }
+ editor.getDocument().set(sb.toString());
+ }
+
+ /**
+ * The region of text for this header. This includes the header itself.
+ *
+ * @param header
+ * @return
+ * @throws BadLocationException
+ */
+ protected IRegion getRegion(Header header) {
+ try {
+ IDocument doc = editor.getDocument();
+ // Line numbers
+ int start = header.getLineNumber();
+ Header next = header.getNext();
+ int end;
+ if (next != null) {
+ end = next.getLineNumber() - 1;
+ } else {
+ end = doc.getNumberOfLines() - 1;
+ }
+ int offset = doc.getLineOffset(start);
+ IRegion ei = doc.getLineInformation(end);
+ int length = ei.getOffset() + ei.getLength() - offset;
+ return new Region(offset, length);
+ } catch (BadLocationException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ /*
+ * (non-Javadoc) Method declared on ContentOutlinePage
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ super.selectionChanged(event);
+ selectedHeaders = null;
+ ISelection selection = event.getSelection();
+ if (selection.isEmpty()) return;
+ if (!(selection instanceof IStructuredSelection)) return;
+ try {
+ IStructuredSelection strucSel = (IStructuredSelection) selection;
+ Object[] sections = strucSel.toArray();
+ selectedHeaders = (List) Arrays.asList(sections);
+ MarkdownPage.Header first = (Header) sections[0];
+ MarkdownPage.Header last = (Header) sections[sections.length - 1];
+ int start = editor.getDocument().getLineOffset(first.getLineNumber());
+ int length;
+ if (first == last) {
+ length = editor.getDocument().getLineLength(first.getLineNumber());
+ } else {
+ IRegion r = getRegion(last);
+ int end = r.getOffset() + r.getLength();
+ length = end - start;
+ }
+ editor.setHighlightRange(start, length, true);
+ } catch (Exception e) {
+ Log.error(e);
+ editor.resetHighlightRange();
+ }
+ }
+
+ /**
+ * Sets the input of the outline page
+ *
+ * @param input the input of this outline page
+ */
+ public void setInput(Object input) {
+ fInput = input;
+ update();
+ }
+
+ /**
+ * Updates the outline page.
+ */
+ public void update() {
+ TreeViewer viewer = getTreeViewer();
+
+ if (viewer != null) {
+ Control control = viewer.getControl();
+ if (control != null && !control.isDisposed()) {
+ control.setRedraw(false);
+ viewer.setInput(fInput);
+ viewer.expandAll();
+ control.setRedraw(true);
+ }
+ }
+ }
+
+}
diff --git a/plugin/src/winterwell/markdown/pagemodel/MarkdownFormatter.java b/plugin/src/winterwell/markdown/pagemodel/MarkdownFormatter.java
index 4c38f19..4277a64 100755
--- a/plugin/src/winterwell/markdown/pagemodel/MarkdownFormatter.java
+++ b/plugin/src/winterwell/markdown/pagemodel/MarkdownFormatter.java
@@ -1,351 +1,316 @@
-
package winterwell.markdown.pagemodel;
import java.util.List;
-import winterwell.utils.StrUtils;
+import winterwell.markdown.util.Strings;
/**
- * Formats a string that is compatible with the Markdown syntax.
- * Strings must not include headers.
+ * Formats a string that is compatible with the Markdown syntax. Strings must not include headers.
*
* @author Howard Abrams
*/
-public class MarkdownFormatter
-{
- // Expect everyone to simply use the public static methods...
- private MarkdownFormatter ()
- {
- }
-
- /**
- * Formats a collection of lines to a particular width and honors typical
- * Markdown syntax and formatting.
- *
- * The method assumes that if the first line ends with a line
- * termination character, all the other lines will as well.
- *
- * @param lines A list of strings that should be formatted and wrapped.
- * @param lineWidth The width of the page
- * @return A string containing each
- */
- public static String format (List lines, int lineWidth)
- {
- if (lines == null)
- return null; // Should we return an empty string?
-
- final String lineEndings;
- if ( lines.get(0).endsWith ("\r\n") )
- lineEndings = "\r\n";
- else if ( lines.get(0).endsWith ("\r") )
- lineEndings = "\r";
- else
- lineEndings = StrUtils.LINEEND;
-
- final StringBuilder buf = new StringBuilder();
- for (String line : lines) {
- buf.append (line);
- buf.append (' '); // We can add extra spaces with impunity, and this
- // makes sure our lines don't run together.
- }
- return format ( buf.toString(), lineWidth, lineEndings );
- }
-
-
- /**
- * Formats a string of text. The formatting does line wrapping at the
- * lineWidth boundary, but it also honors the formatting
- * of initial paragraph lines, allowing indentation of the entire
- * paragraph.
- *
- * @param text The line of text to format
- * @param lineWidth The width of the lines
- * @return A string containing the formatted text.
- */
- public static String format ( final String text, final int lineWidth)
- {
- return format(text, lineWidth, StrUtils.LINEEND);
- }
-
- /**
- * Formats a string of text. The formatting does line wrapping at the
- * lineWidth boundary, but it also honors the formatting
- * of initial paragraph lines, allowing indentation of the entire
- * paragraph.
- *
- * @param text The line of text to format
- * @param lineWidth The width of the lines
- * @param lineEnding The line ending that overrides the default System value
- * @return A string containing the formatted text.
- */
- public static String format (final String text, final int lineWidth, final String lineEnding)
- {
- return new String( format(text.toCharArray (), lineWidth, lineEnding));
- }
-
- /**
- * The available cursor position states as it sits in the buffer.
- */
- private enum StatePosition {
- /** The beginning of a paragraph ... the start of the buffer */
- BEGIN_FIRST_LINE,
-
- /** The beginning of the next line, which may be completely ignored. */
- BEGIN_OTHER_LINE,
-
- /** The beginning of a new line that will not be ignored, but appended. */
- BEGIN_NEW_LINE,
-
- /** The middle of a line. */
- MIDDLE_OF_LINE
- }
-
- /**
- * The method that does the work of formatting a string of text. The text,
- * however, is a character array, which is more efficient to work with.
- *
- * TODO: Should we make the format(char[]) method public?
- *
- * @param text The line of text to format
- * @param lineWidth The width of the lines
- * @param lineEnding The line ending that overrides the default System value
- * @return A string containing the formatted text.
- */
- static char[] format ( final char[] text, final int lineWidth, final String lineEnding )
- {
- final StringBuilder word = new StringBuilder();
- final StringBuilder indent = new StringBuilder();
- final StringBuilder buffer = new StringBuilder(text.length + 10);
-
- StatePosition state = StatePosition.BEGIN_FIRST_LINE;
- int lineLength = 0;
-
- // There are times when we will run across a character(s) that will
- // cause us to stop doing word wrap until we get to the
- // "end of non-wordwrap" character(s).
- //
- // If this string is set to null, it tells us to "do" word-wrapping.
- char endWordwrap1 = 0;
- char endWordwrap2 = 0;
-
- // We loop one character past the end of the loop, and when we get to
- // this position, we assign 'c' to be 0 ... as a marker for the end of
- // the string...
-
- for (int i = 0; i <= text.length; i++)
- {
- final char c;
- if (i < text.length)
- c = text[i];
- else
- c = 0;
-
- final char nextChar;
- if (i+1 < text.length)
- nextChar = text[i+1];
- else
- nextChar = 0;
-
- // Are we actually word-wrapping?
- if (endWordwrap1 != 0) {
- // Did we get the ending sequence of the non-word-wrap?
- if ( ( endWordwrap2 == 0 && c == endWordwrap1 ) ||
- ( c == endWordwrap1 && nextChar == endWordwrap2 ) )
- endWordwrap1 = 0;
- buffer.append (c);
- lineLength++;
-
- if (endWordwrap1 == 0 && endWordwrap2 != 0) {
- buffer.append (nextChar);
- lineLength++;
- i++;
- }
- continue;
- }
-
- // Check to see if we got one of our special non-word-wrapping
- // character sequences ...
-
- if ( c == '[' ) { // [Hyperlink]
- endWordwrap1 = ']';
- }
- else if ( c == '*' && nextChar == '*' ) { // **Bold**
- endWordwrap1 = '*';
- endWordwrap2 = '*';
- } // *Italics*
- else if ( c == '*' && state == StatePosition.MIDDLE_OF_LINE ) {
- endWordwrap1 = '*';
- }
- else if ( c == '`' ) { // `code`
- endWordwrap1 = '`';
- }
- else if ( c == '(' && nextChar == '(' ) { // ((Footnote))
- endWordwrap1 = ')';
- endWordwrap2 = ')';
- }
- else if ( c == '!' && nextChar == '[' ) { // ![Image]
- endWordwrap1 = ')';
- }
-
- // We are no longer doing word-wrapping, so tidy the situation up...
- if (endWordwrap1 != 0) {
- if (word.length() > 0)
- lineLength = addWordToBuffer (lineWidth, lineEnding, word, indent, buffer, lineLength);
- else if (buffer.length() > 0 && buffer.charAt (buffer.length()-1) != ']' )
- buffer.append(' ');
- // We are adding an extra space for most situations, unless we get a
- // [link][ref] where we want them to be together without a space.
-
- buffer.append (c);
- lineLength++;
- continue;
- }
-
- // Normal word-wrapping processing continues ...
-
- if (state == StatePosition.BEGIN_FIRST_LINE)
- {
- if ( c == '\n' || c == '\r' ) { // Keep, but ignore initial line feeds
- buffer.append (c);
- lineLength = 0;
- continue;
- }
-
- if (Character.isWhitespace (c))
- indent.append (c);
- else if ( (c == '*' || c == '-' || c == '.' ) &&
- Character.isWhitespace (nextChar) )
- indent.append (' ');
- else if ( Character.isDigit (c) && nextChar == '.' &&
- Character.isWhitespace (text[i+2]))
- indent.append (' ');
- else if ( c == '>' )
- indent.append ('>');
- else
- state = StatePosition.MIDDLE_OF_LINE;
-
- // If we are still in the initial state, then put 'er in...
- if (state == StatePosition.BEGIN_FIRST_LINE) {
- buffer.append (c);
- lineLength++;
- }
- }
-
- // While it would be more accurate to explicitely state the range of
- // possibilities, with something like:
- // EnumSet.range (StatePosition.BEGIN_OTHER_LINE, StatePosition.MIDDLE_OF_LINE ).contains (state)
- // We know that what is left is just the BEGIN_FIRST_LINE ...
-
- if ( state != StatePosition.BEGIN_FIRST_LINE )
- {
- // If not the middle of the line, then it must be at the first of a line
- // Either BEGIN_OTHER_LINE or BEGIN_NEW_LINE
- if (state != StatePosition.MIDDLE_OF_LINE)
- {
- if ( Character.isWhitespace(c) || c == '>' || c == '.' )
- word.append (c);
- else if ( ( ( c == '*' || c == '-' ) && Character.isWhitespace (nextChar) ) ||
- ( Character.isDigit(c) && nextChar == '.' && Character.isWhitespace( text[i+2] ) ) ) {
- word.append (c);
- state = StatePosition.BEGIN_NEW_LINE;
- }
- else {
- if (state == StatePosition.BEGIN_NEW_LINE) {
- buffer.append (word);
- lineLength = word.substring ( word.indexOf("\n")+1 ).length();
- }
- word.setLength (0);
- state = StatePosition.MIDDLE_OF_LINE;
- }
- }
-
- if (state == StatePosition.MIDDLE_OF_LINE)
- {
- // Are we at the end of a word? Then we need to calculate whether
- // to wrap the line or not.
- //
- // This condition does double duty, in that is also serves to
- // ignore multiple spaces and special characters that may be at
- // the beginning of the line.
- if ( Character.isWhitespace(c) || c == 0 )
- {
- if ( word.length() > 0) {
- lineLength = addWordToBuffer (lineWidth, lineEnding, word, indent, buffer, lineLength);
- }
- // Do we we two spaces at the end of the line? Honor this...
- else if ( c == ' ' && ( nextChar == '\r' || nextChar == '\n' ) &&
- state != StatePosition.BEGIN_OTHER_LINE ) {
- buffer.append (" ");
- buffer.append (lineEnding);
- lineLength = 0;
- }
-
- if ( c == '\r' || c == '\n' ) {
- state = StatePosition.BEGIN_OTHER_LINE;
- word.append(c);
- }
-
- // Linefeeds are completely ignored and just treated as whitespace,
- // unless, of course, there are two of 'em... and of course, end of
- // lines are simply evil on Windows machines.
-
- if ( (c == '\n' && nextChar == '\n') || // Unix-style line-ends
- ( c == '\r' && nextChar == '\n' && // Windows-style line-ends
- text[i+2] == '\r' && text[i+3] == '\n' ) )
- {
- state = StatePosition.BEGIN_FIRST_LINE;
- word.setLength(0);
- indent.setLength (0);
- lineLength = 0;
-
- if (c == '\r') { // If we are dealing with Windows-style line-ends,
- i++; // we need to skip past the next character...
- buffer.append("\r\n");
- } else
- buffer.append(c);
- }
-
- } else {
- word.append (c);
- state = StatePosition.MIDDLE_OF_LINE;
- }
- }
- }
- }
-
- return buffer.toString().toCharArray();
- }
-
- /**
- * Adds a word to the buffer, performing word wrap if necessary.
- * @param lineWidth The current width of the line
- * @param lineEnding The line ending to append, if necessary
- * @param word The word to append
- * @param indent The indentation string to insert, if necesary
- * @param buffer The buffer to perform all this stuff to
- * @param lineLength The current length of the current line
- * @return The new length of the current line
- */
- private static int addWordToBuffer (final int lineWidth, final String lineEnding,
- final StringBuilder word,
- final StringBuilder indent,
- final StringBuilder buffer, int lineLength)
- {
- if ( word.length() + lineLength + 1 > lineWidth )
- {
- buffer.append (lineEnding);
- buffer.append (indent);
- buffer.append (word);
-
- lineLength = indent.length() + word.length();
- }
- else {
- if ( lineLength > indent.length() )
- buffer.append (' ');
- buffer.append (word);
- lineLength += word.length() + 1;
- }
- word.setLength (0);
- return lineLength;
- }
+public class MarkdownFormatter {
+
+ // Expect everyone to simply use the public static methods...
+ private MarkdownFormatter() {}
+
+ /**
+ * Formats a collection of lines to a particular width and honors typical Markdown syntax and
+ * formatting. The method assumes that if the first line ends with a line termination
+ * character, all the other lines will as well.
+ *
+ * @param lines A list of strings that should be formatted and wrapped.
+ * @param lineWidth The width of the page
+ * @return A string containing each
+ */
+ public static String format(List lines, int lineWidth) {
+ if (lines == null) return null; // Should we return an empty string?
+
+ final String lineEndings;
+ if (lines.get(0).endsWith("\r\n"))
+ lineEndings = "\r\n";
+ else if (lines.get(0).endsWith("\r"))
+ lineEndings = "\r";
+ else
+ lineEndings = Strings.EOL;
+
+ final StringBuilder buf = new StringBuilder();
+ for (String line : lines) {
+ buf.append(line);
+ buf.append(' '); // We can add extra spaces with impunity, and this
+ // makes sure our lines don't run together.
+ }
+ return format(buf.toString(), lineWidth, lineEndings);
+ }
+
+ /**
+ * Formats a string of text. The formatting does line wrapping at the lineWidth
+ * boundary, but it also honors the formatting of initial paragraph lines, allowing indentation
+ * of the entire paragraph.
+ *
+ * @param text The line of text to format
+ * @param lineWidth The width of the lines
+ * @return A string containing the formatted text.
+ */
+ public static String format(final String text, final int lineWidth) {
+ return format(text, lineWidth, Strings.EOL);
+ }
+
+ /**
+ * Formats a string of text. The formatting does line wrapping at the lineWidth
+ * boundary, but it also honors the formatting of initial paragraph lines, allowing indentation
+ * of the entire paragraph.
+ *
+ * @param text The line of text to format
+ * @param lineWidth The width of the lines
+ * @param lineEnding The line ending that overrides the default System value
+ * @return A string containing the formatted text.
+ */
+ public static String format(final String text, final int lineWidth, final String lineEnding) {
+ return new String(format(text.toCharArray(), lineWidth, lineEnding));
+ }
+
+ /**
+ * The available cursor position states as it sits in the buffer.
+ */
+ private enum StatePosition {
+ /** The beginning of a paragraph ... the start of the buffer */
+ BEGIN_FIRST_LINE,
+
+ /** The beginning of the next line, which may be completely ignored. */
+ BEGIN_OTHER_LINE,
+
+ /** The beginning of a new line that will not be ignored, but appended. */
+ BEGIN_NEW_LINE,
+
+ /** The middle of a line. */
+ MIDDLE_OF_LINE
+ }
+
+ /**
+ * The method that does the work of formatting a string of text. The text, however, is a
+ * character array, which is more efficient to work with. TODO: Should we make the
+ * format(char[]) method public?
+ *
+ * @param text The line of text to format
+ * @param lineWidth The width of the lines
+ * @param lineEnding The line ending that overrides the default System value
+ * @return A string containing the formatted text.
+ */
+ static char[] format(final char[] text, final int lineWidth, final String lineEnding) {
+ final StringBuilder word = new StringBuilder();
+ final StringBuilder indent = new StringBuilder();
+ final StringBuilder buffer = new StringBuilder(text.length + 10);
+
+ StatePosition state = StatePosition.BEGIN_FIRST_LINE;
+ int lineLength = 0;
+
+ // There are times when we will run across a character(s) that will
+ // cause us to stop doing word wrap until we get to the
+ // "end of non-wordwrap" character(s).
+ //
+ // If this string is set to null, it tells us to "do" word-wrapping.
+ char endWordwrap1 = 0;
+ char endWordwrap2 = 0;
+
+ // We loop one character past the end of the loop, and when we get to
+ // this position, we assign 'c' to be 0 ... as a marker for the end of
+ // the string...
+
+ for (int i = 0; i <= text.length; i++) {
+ final char c;
+ if (i < text.length)
+ c = text[i];
+ else
+ c = 0;
+
+ final char nextChar;
+ if (i + 1 < text.length)
+ nextChar = text[i + 1];
+ else
+ nextChar = 0;
+
+ // Are we actually word-wrapping?
+ if (endWordwrap1 != 0) {
+ // Did we get the ending sequence of the non-word-wrap?
+ if ((endWordwrap2 == 0 && c == endWordwrap1) || (c == endWordwrap1 && nextChar == endWordwrap2))
+ endWordwrap1 = 0;
+ buffer.append(c);
+ lineLength++;
+
+ if (endWordwrap1 == 0 && endWordwrap2 != 0) {
+ buffer.append(nextChar);
+ lineLength++;
+ i++;
+ }
+ continue;
+ }
+
+ // Check to see if we got one of our special non-word-wrapping
+ // character sequences ...
+
+ if (c == '[') { // [Hyperlink]
+ endWordwrap1 = ']';
+ } else if (c == '*' && nextChar == '*') { // **Bold**
+ endWordwrap1 = '*';
+ endWordwrap2 = '*';
+ } // *Italics*
+ else if (c == '*' && state == StatePosition.MIDDLE_OF_LINE) {
+ endWordwrap1 = '*';
+ } else if (c == '`') { // `code`
+ endWordwrap1 = '`';
+ } else if (c == '(' && nextChar == '(') { // ((Footnote))
+ endWordwrap1 = ')';
+ endWordwrap2 = ')';
+ } else if (c == '!' && nextChar == '[') { // ![Image]
+ endWordwrap1 = ')';
+ }
+
+ // We are no longer doing word-wrapping, so tidy the situation up...
+ if (endWordwrap1 != 0) {
+ if (word.length() > 0)
+ lineLength = addWordToBuffer(lineWidth, lineEnding, word, indent, buffer, lineLength);
+ else if (buffer.length() > 0 && buffer.charAt(buffer.length() - 1) != ']') buffer.append(' ');
+ // We are adding an extra space for most situations, unless we get a
+ // [link][ref] where we want them to be together without a space.
+
+ buffer.append(c);
+ lineLength++;
+ continue;
+ }
+
+ // Normal word-wrapping processing continues ...
+
+ if (state == StatePosition.BEGIN_FIRST_LINE) {
+ if (c == '\n' || c == '\r') { // Keep, but ignore initial line feeds
+ buffer.append(c);
+ lineLength = 0;
+ continue;
+ }
+
+ if (Character.isWhitespace(c))
+ indent.append(c);
+ else if ((c == '*' || c == '-' || c == '.') && Character.isWhitespace(nextChar))
+ indent.append(' ');
+ else if (Character.isDigit(c) && nextChar == '.' && Character.isWhitespace(text[i + 2]))
+ indent.append(' ');
+ else if (c == '>')
+ indent.append('>');
+ else
+ state = StatePosition.MIDDLE_OF_LINE;
+
+ // If we are still in the initial state, then put 'er in...
+ if (state == StatePosition.BEGIN_FIRST_LINE) {
+ buffer.append(c);
+ lineLength++;
+ }
+ }
+
+ // While it would be more accurate to explicitely state the range of
+ // possibilities, with something like:
+ // EnumSet.range (StatePosition.BEGIN_OTHER_LINE, StatePosition.MIDDLE_OF_LINE
+ // ).contains (state)
+ // We know that what is left is just the BEGIN_FIRST_LINE ...
+
+ if (state != StatePosition.BEGIN_FIRST_LINE) {
+ // If not the middle of the line, then it must be at the first of a line
+ // Either BEGIN_OTHER_LINE or BEGIN_NEW_LINE
+ if (state != StatePosition.MIDDLE_OF_LINE) {
+ if (Character.isWhitespace(c) || c == '>' || c == '.')
+ word.append(c);
+ else if (((c == '*' || c == '-') && Character.isWhitespace(nextChar))
+ || (Character.isDigit(c) && nextChar == '.' && Character.isWhitespace(text[i + 2]))) {
+ word.append(c);
+ state = StatePosition.BEGIN_NEW_LINE;
+ } else {
+ if (state == StatePosition.BEGIN_NEW_LINE) {
+ buffer.append(word);
+ lineLength = word.substring(word.indexOf("\n") + 1).length();
+ }
+ word.setLength(0);
+ state = StatePosition.MIDDLE_OF_LINE;
+ }
+ }
+
+ if (state == StatePosition.MIDDLE_OF_LINE) {
+ // Are we at the end of a word? Then we need to calculate whether
+ // to wrap the line or not.
+ //
+ // This condition does double duty, in that is also serves to
+ // ignore multiple spaces and special characters that may be at
+ // the beginning of the line.
+ if (Character.isWhitespace(c) || c == 0) {
+ if (word.length() > 0) {
+ lineLength = addWordToBuffer(lineWidth, lineEnding, word, indent, buffer, lineLength);
+ }
+ // Do we we two spaces at the end of the line? Honor this...
+ else if (c == ' ' && (nextChar == '\r' || nextChar == '\n')
+ && state != StatePosition.BEGIN_OTHER_LINE) {
+ buffer.append(" ");
+ buffer.append(lineEnding);
+ lineLength = 0;
+ }
+
+ if (c == '\r' || c == '\n') {
+ state = StatePosition.BEGIN_OTHER_LINE;
+ word.append(c);
+ }
+
+ // Linefeeds are completely ignored and just treated as whitespace,
+ // unless, of course, there are two of 'em... and of course, end of
+ // lines are simply evil on Windows machines.
+
+ if ((c == '\n' && nextChar == '\n') || // Unix-style line-ends
+ (c == '\r' && nextChar == '\n' && // Windows-style line-ends
+ text[i + 2] == '\r' && text[i + 3] == '\n')) {
+ state = StatePosition.BEGIN_FIRST_LINE;
+ word.setLength(0);
+ indent.setLength(0);
+ lineLength = 0;
+
+ if (c == '\r') { // If we are dealing with Windows-style line-ends,
+ i++; // we need to skip past the next character...
+ buffer.append("\r\n");
+ } else
+ buffer.append(c);
+ }
+
+ } else {
+ word.append(c);
+ state = StatePosition.MIDDLE_OF_LINE;
+ }
+ }
+ }
+ }
+
+ return buffer.toString().toCharArray();
+ }
+
+ /**
+ * Adds a word to the buffer, performing word wrap if necessary.
+ *
+ * @param lineWidth The current width of the line
+ * @param lineEnding The line ending to append, if necessary
+ * @param word The word to append
+ * @param indent The indentation string to insert, if necesary
+ * @param buffer The buffer to perform all this stuff to
+ * @param lineLength The current length of the current line
+ * @return The new length of the current line
+ */
+ private static int addWordToBuffer(final int lineWidth, final String lineEnding, final StringBuilder word,
+ final StringBuilder indent, final StringBuilder buffer, int lineLength) {
+ if (word.length() + lineLength + 1 > lineWidth) {
+ buffer.append(lineEnding);
+ buffer.append(indent);
+ buffer.append(word);
+
+ lineLength = indent.length() + word.length();
+ } else {
+ if (lineLength > indent.length()) buffer.append(' ');
+ buffer.append(word);
+ lineLength += word.length() + 1;
+ }
+ word.setLength(0);
+ return lineLength;
+ }
}
diff --git a/plugin/src/winterwell/markdown/pagemodel/MarkdownPage.java b/plugin/src/winterwell/markdown/pagemodel/MarkdownPage.java
index 7390452..68e1098 100755
--- a/plugin/src/winterwell/markdown/pagemodel/MarkdownPage.java
+++ b/plugin/src/winterwell/markdown/pagemodel/MarkdownPage.java
@@ -1,626 +1,568 @@
-/**
- * Copyright winterwell Mathematics Ltd.
- * @author Daniel Winterstein
- * 11 Jan 2007
- */
-package winterwell.markdown.pagemodel;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.jface.preference.IPreferenceStore;
-
-import winterwell.markdown.Activator;
-import winterwell.markdown.StringMethods;
-import winterwell.markdown.preferences.MarkdownPreferencePage;
-import winterwell.utils.FailureException;
-import winterwell.utils.Process;
-import winterwell.utils.StrUtils;
-import winterwell.utils.Utils;
-import winterwell.utils.io.FileUtils;
-
-import com.petebevin.markdown.MarkdownProcessor;
-
-/**
- * Understands Markdown syntax.
- *
- * @author Daniel Winterstein
- */
-public class MarkdownPage {
-
- /**
- * Strip leading and trailing #s and whitespace
- *
- * @param line
- * @return cleaned up line
- */
- private String cleanHeader(String line) {
- for (int j = 0; j < line.length(); j++) {
- char c = line.charAt(j);
- if (c != '#' && !Character.isWhitespace(c)) {
- line = line.substring(j);
- break;
- }
- }
- for (int j = line.length() - 1; j > 0; j--) {
- char c = line.charAt(j);
- if (c != '#' && !Character.isWhitespace(c)) {
- line = line.substring(0, j + 1);
- break;
- }
- }
- return line;
- }
-
- /**
- * Represents information about a section header. E.g. ## Misc Warblings
- *
- * @author daniel
- */
- public class Header {
- /**
- * 1 = top-level (i.e. #), 2= 2nd-level (i.e. ##), etc.
- */
- final int level;
- /**
- * The text of the Header
- */
- final String heading;
- /**
- * Sub-sections, if any
- */
- final List subHeaders = new ArrayList();
- /**
- * The line on which this header occurs.
- */
- final int lineNumber;
-
- public int getLineNumber() {
- return lineNumber;
- }
-
- /**
- *
- * @return the next section (at this depth if possible), null if none
- */
- public Header getNext() {
- if (parent == null) {
- int ti = level1Headers.indexOf(this);
- if (ti == -1 || ti == level1Headers.size() - 1)
- return null;
- return level1Headers.get(ti + 1);
- }
- int i = parent.subHeaders.indexOf(this);
- assert i != -1 : this;
- if (i == parent.subHeaders.size() - 1)
- return parent.getNext();
- return parent.subHeaders.get(i + 1);
- }
- /**
- *
- * @return the next section (at this depth if possible), null if none
- */
- public Header getPrevious() {
- if (parent == null) {
- int ti = level1Headers.indexOf(this);
- if (ti == -1 || ti == 0)
- return null;
- return level1Headers.get(ti - 1);
- }
- int i = parent.subHeaders.indexOf(this);
- assert i != -1 : this;
- if (i == 0)
- return parent.getPrevious();
- return parent.subHeaders.get(i - 1);
- }
-
-
- /**
- * The parent section. Can be null.
- */
- private Header parent;
-
- /**
- * Create a marker for a section Header
- *
- * @param level
- * 1 = top-level (i.e. #), 2= 2nd-level (i.e. ##), etc.
- * @param lineNumber
- * The line on which this header occurs
- * @param heading
- * The text of the Header, trimmed of #s
- * @param currentHeader
- * The previous Header. This is used to find the parent
- * section if there is one. Can be null.
- */
- Header(int level, int lineNumber, String heading, Header currentHeader) {
- this.lineNumber = lineNumber;
- this.level = level;
- this.heading = cleanHeader(heading);
- // Heading Tree
- setParent(currentHeader);
- }
-
- private void setParent(Header currentHeader) {
- if (currentHeader == null) {
- parent = null;
- return;
- }
- if (currentHeader.level < level) {
- parent = currentHeader;
- parent.subHeaders.add(this);
- return;
- }
- setParent(currentHeader.parent);
- }
-
- public Header getParent() {
- return parent;
- }
-
- /**
- * Sub-sections. May be zero-length, never null.
- */
- public List getSubHeaders() {
- return subHeaders;
- }
-
- @Override
- public String toString() {
- return heading;
- }
-
- public int getLevel() {
- return level;
- }
- }
-
- /**
- * The raw text, broken up into individual lines.
- */
- private List lines;
-
- /**
- * The raw text, broken up into individual lines.
- */
- public List getText() {
- return Collections.unmodifiableList(lines);
- }
-
- public enum KLineType {
- NORMAL, H1, H2, H3, H4, H5, H6, BLANK,
- // TODO LIST, BLOCKQUOTE,
- /** A line marking Markdown info about the preceding line, e.g. ====== */
- MARKER,
- /** A line containing meta-data, e.g. title: My Page */
- META
- }
-
- /**
- * Information about each line.
- */
- private List lineTypes;
- private Map pageObjects = new HashMap();
-
- // TODO meta-data, footnotes, tables, link & image attributes
- private static Pattern multiMarkdownTag = Pattern.compile("^([\\w].*):(.*)");
- private Map multiMarkdownTags = new HashMap();
-
- // Regular expression for Github support
- private static Pattern githubURLDetection = Pattern.compile("((https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|])");
-
- /**
- * The top-level headers. FIXME handle documents which have a 2nd level
- * header before any 1st level ones
- */
- private final List level1Headers = new ArrayList();
- private final IPreferenceStore pStore;
-
- /**
- * Create a page.
- *
- * @param text
- */
- public MarkdownPage(String text) {
- pStore = Activator.getDefault().getPreferenceStore();
- setText(text);
- }
-
- /**
- * Reset the text for this page.
- *
- * @param text
- */
- private void setText(String text) {
- // Get lines
- lines = StringMethods.splitLines(text);
- // Clean out old
- level1Headers.clear();
- lineTypes = new ArrayList(lines.size());
- pageObjects.clear();
- // Dummy level-1 header in case there are none
- Header dummyTopHeader = new Header(1, 0, "", null);
- level1Headers.add(dummyTopHeader);
- Header currentHeader = dummyTopHeader;
- // Identify line types
- int lineNum = 0;
-
- // Check if we should support the Multi-Markdown Metadata
- boolean multiMarkdownMetadataSupport =
- pStore.getBoolean(MarkdownPreferencePage.PREF_MULTIMARKDOWN_METADATA);
-
- // Multi-markdown header
- if (multiMarkdownMetadataSupport) {
- // The key is the text before the colon, and the data is the text
- // after the
- // colon. In the above example, notice that there are two lines of
- // information
- // for the Author key. If you end a line with “space-space-newline”,
- // the newline
- // will be included when converted to other formats.
- //
- // There must not be any whitespace above the metadata, and the
- // metadata block
- // ends with the first whitespace only line. The metadata is
- // stripped from the
- // document before it is passed on to the syntax parser.
-
- //
- // Check if the Metdatas are valid
- //
- boolean validMetadata = true;
- for (lineNum = 0; lineNum < lines.size(); lineNum++) {
- String line = lines.get(lineNum);
- if (Utils.isBlank(line)) {
- break;
- }
- Matcher m = multiMarkdownTag.matcher(line);
- if (!m.find()) {
- if (lineNum == 0) {
- // No MultiMarkdown metadata
- validMetadata = false;
- break;
- } else if (!line.matches("^\\s.*\n")) {
- // The next line was not intended (ie. it does not start
- // with a whitespace)
- validMetadata = false;
- break;
- }
- }
- }
-
- // Valid Metadatas have been found. We need to retrieve these keys/values.
- if (validMetadata) {
- String data = "";
- String tag = "";
- for (lineNum = 0; lineNum < lines.size(); lineNum++) {
- String line = lines.get(lineNum);
- if (Utils.isBlank(line)) {
- break;
- }
- Matcher m = multiMarkdownTag.matcher(line);
- if (!m.find()) {
- if (lineNum == 0) {
- break;
- }
- // Multi-line tag
- lineTypes.add(KLineType.META);
- data += StrUtils.LINEEND + line.trim();
- multiMarkdownTags.put(tag, data);
- } else {
- lineTypes.add(KLineType.META);
- tag = m.group(0);
- data = m.group(1).trim();
- if (m.group(1).endsWith(line))
- multiMarkdownTags.put(tag, data);
- }
- }
- } else {
- lineNum = 0;
- }
- }
-
- boolean githubSyntaxSupport =
- pStore.getBoolean(MarkdownPreferencePage.PREF_GITHUB_SYNTAX);
-
- boolean inCodeBlock = false;
-
- for (; lineNum < lines.size(); lineNum++) {
- String line = lines.get(lineNum);
- // Code blocks
- if (githubSyntaxSupport && line.startsWith("```")) {
- inCodeBlock = !inCodeBlock;
- }
- if (!inCodeBlock) {
- // Headings
- int h = numHash(line);
- String hLine = line;
- int hLineNum = lineNum;
- int underline = -1;
- if (lineNum != 0) {
- underline = just(line, '=') ? 1 : just(line, '-') ? 2 : -1;
- }
- if (underline != -1) {
- h = underline;
- hLineNum = lineNum - 1;
- hLine = lines.get(lineNum - 1);
- lineTypes.set(hLineNum, KLineType.values()[h]);
- lineTypes.add(KLineType.MARKER);
- }
- // Create a Header object
- if (h > 0) {
- if (underline == -1)
- lineTypes.add(KLineType.values()[h]);
- Header header = new Header(h, hLineNum, hLine, currentHeader);
- if (h == 1) {
- level1Headers.add(header);
- }
- pageObjects.put(hLineNum, header);
- currentHeader = header;
- continue;
- }
- }
- // TODO List
- // TODO Block quote
- // Blank line
- if (Utils.isBlank(line)) {
- lineTypes.add(KLineType.BLANK);
- continue;
- }
- // Normal
- lineTypes.add(KLineType.NORMAL);
- } // end line-loop
- // Remove dummy header?
- if (dummyTopHeader.getSubHeaders().size() == 0) {
- level1Headers.remove(dummyTopHeader);
- }
- if (githubSyntaxSupport) {
- /*
- * Support Code block
- */
- inCodeBlock = false;
- for (lineNum = 0; lineNum < lines.size(); lineNum++) {
- String line = lines.get(lineNum);
- // Found the start or end of a code block
- if (line.matches("^```.*\n")) {
- // We reverse the boolean value
- inCodeBlock = !inCodeBlock;
-
- // We force the line to be blank. But we mark it as normal
- // to prevent to be stripped
- lines.set(lineNum, "\n");
- lineTypes.set(lineNum, KLineType.NORMAL);
- continue;
- }
- if (inCodeBlock) {
- lines.set(lineNum, " " + line);
- }
- }
-
- /*
- * Support for URL Detection
- * We search for links that are not captured by Markdown syntax
- */
- for (lineNum = 0; lineNum < lines.size(); lineNum++) {
- String line = lines.get(lineNum);
- // When a link has been replaced we need to scan again the string
- // as the offsets have changed (we add '<' and '>' to the link to
- // be interpreted by the markdown library)
- boolean urlReplaced;
-
- do {
- urlReplaced = false;
- Matcher m = githubURLDetection.matcher(line);
- while (m.find()) {
- // Ignore the URL following the format
- if ((m.start() - 1 >= 0) && (m.end() < line.length()) &&
- (line.charAt(m.start() - 1) == '<') &&
- (line.charAt(m.end()) == '>'))
- {
- continue;
- }
-
- // Ignore the URL following the format [description](link)
- if ((m.start() - 2 >= 0) && (m.end() < line.length()) &&
- (line.charAt(m.start() - 2) == ']') &&
- (line.charAt(m.start() - 1) == '(') &&
- (line.charAt(m.end()) == ')'))
- {
- continue;
- }
-
- // Ignore the URL following the format [description](link "title")
- if ((m.start() - 2 >= 0) && (m.end() + 1 < line.length()) &&
- (line.charAt(m.start() - 2) == ']') &&
- (line.charAt(m.start() - 1) == '(') &&
- (line.charAt(m.end()) == ' ') &&
- (line.charAt(m.end() + 1) == '"'))
- {
- continue;
- }
-
- if (m.start() - 1 >= 0) {
- // Case when the link is at the beginning of the string
- line = line.substring(0, m.start()) + "<" + m.group(0) + ">" + line.substring(m.end());
- } else {
- line = "<" + m.group(0) + ">" + line.substring(m.end());
- }
-
- // We replaced the string in the array
- lines.set(lineNum, line);
- urlReplaced = true;
- break;
- }
- } while (urlReplaced);
- }
- }
- }
-
- /**
- * @param line
- * @param c
- * @return true if line is just cs (and whitespace at the start/end)
- */
- boolean just(String line, char c) {
- return line.matches("\\s*"+c+"+\\s*");
- }
-
- /**
- * @param line
- * @return The number of # symbols prepending the line.
- */
- private int numHash(String line) {
- for (int i = 0; i < line.length(); i++) {
- if (line.charAt(i) != '#')
- return i;
- }
- return line.length();
- }
-
- /**
- *
- * @param parent
- * Can be null for top-level
- * @return List of sub-headers. Never null. FIXME handle documents which
- * have a 2nd level header before any 1st level ones
- */
- public List getHeadings(Header parent) {
- if (parent == null) {
- return Collections.unmodifiableList(level1Headers);
- }
- return Collections.unmodifiableList(parent.subHeaders);
- }
-
- // public WebPage getWebPage() {
- // WebPage page = new WebPage();
- // // Add the lines, one by one
- // boolean inParagraph = false;
- // for (int i=0; i");
- // line = cleanHeader(line);
- // page.addText("<"+type+">"+line+""+type+">");
- // continue;
- // case MARKER: // Ignore
- // continue;
- // // TODO List?
- // // TODO Block quote?
- // }
- // // Paragraph end?
- // if (Utils.isBlank(line)) {
- // if (inParagraph) page.addText("
");
+ // inParagraph = true;
+ // }
+ // // Plain text
+ // page.addText(line);
+ // }
+ // return page;
+ // }
+
+ /**
+ * Get the HTML for this page.
+ */
+ public String html() {
+ // Section numbers??
+ boolean sectionNumbers = pStore.getBoolean(PrefPageGeneral.PREF_SECTION_NUMBERS);
+ // Chop out multi-markdown header
+ StringBuilder sb = new StringBuilder();
+ assert lines.size() == lineTypes.size();
+ for (int i = 0, n = lines.size(); i < n; i++) {
+ KLineType type = lineTypes.get(i);
+ if (type == KLineType.META) continue;
+ String line = lines.get(i);
+ if (sectionNumbers && isHeader(type) && line.contains("$section")) {
+ // TODO Header section = headers.get(i);
+ // String secNum = section.getSectionNumber();
+ // line.replace("$section", secNum);
+ }
+ sb.append(line);
+ }
+ String text = sb.toString();
+
+ MdConverter converter = new MdConverter();
+ return converter.convert(text);
+ }
+
+ private boolean isHeader(KLineType type) {
+ return type == KLineType.H1 || type == KLineType.H2 || type == KLineType.H3 || type == KLineType.H4
+ || type == KLineType.H5 || type == KLineType.H6;
+ }
+
+ /**
+ * Return the raw text of this page.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (String line : lines) {
+ sb.append(line);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Line type information for the raw text.
+ *
+ * @return
+ */
+ public List getLineTypes() {
+ return Collections.unmodifiableList(lineTypes);
+ }
+
+ public Object getPageObject(int line) {
+ return pageObjects.get(line);
+ }
+}
diff --git a/plugin/src/winterwell/markdown/pagemodel/MarkdownPageTest.java b/plugin/src/winterwell/markdown/pagemodel/MarkdownPageTest.java
index 244d437..bcc2b72 100755
--- a/plugin/src/winterwell/markdown/pagemodel/MarkdownPageTest.java
+++ b/plugin/src/winterwell/markdown/pagemodel/MarkdownPageTest.java
@@ -4,22 +4,18 @@
import java.util.List;
import winterwell.markdown.pagemodel.MarkdownPage.Header;
-import winterwell.utils.io.FileUtils;
+import winterwell.markdown.util.FileUtils;
-
-
-public class MarkdownPageTest //extends TestCase
-{
+public class MarkdownPageTest /* extends TestCase */ {
public static void main(String[] args) {
MarkdownPageTest mpt = new MarkdownPageTest();
mpt.testGetHeadings();
}
-
+
public void testGetHeadings() {
// problem caused by a line beginning --, now fixed
- String txt = FileUtils.read(new File(
- "/home/daniel/winterwell/companies/DTC/projects/DTC-bayes/report1.txt"));
+ String txt = FileUtils.read(new File("/home/daniel/winterwell/companies/DTC/projects/DTC-bayes/report1.txt"));
MarkdownPage p = new MarkdownPage(txt);
List h1s = p.getHeadings(null);
Header h1 = h1s.get(0);
diff --git a/plugin/src/winterwell/markdown/pagemodel/MdConverter.java b/plugin/src/winterwell/markdown/pagemodel/MdConverter.java
new file mode 100644
index 0000000..ae3c8c1
--- /dev/null
+++ b/plugin/src/winterwell/markdown/pagemodel/MdConverter.java
@@ -0,0 +1,101 @@
+package winterwell.markdown.pagemodel;
+
+import java.io.File;
+
+import org.commonmark.node.Node;
+import org.commonmark.parser.Parser;
+import org.commonmark.renderer.html.HtmlRenderer;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.markdownj.MarkdownProcessor;
+import org.pegdown.PegDownProcessor;
+
+import com.github.rjeschke.txtmark.Configuration;
+import com.github.rjeschke.txtmark.Configuration.Builder;
+import com.github.rjeschke.txtmark.Processor;
+
+import winterwell.markdown.MarkdownUI;
+import winterwell.markdown.preferences.PrefPageGeneral;
+import winterwell.markdown.preferences.Prefs;
+import winterwell.markdown.util.FailureException;
+import winterwell.markdown.util.FileUtils;
+import winterwell.markdown.util.Process;
+import winterwell.markdown.util.Strings;
+
+public class MdConverter {
+
+ private IPreferenceStore store;
+
+ public MdConverter() {
+ super();
+ store = MarkdownUI.getDefault().getPreferenceStore();
+ }
+
+ public String convert(String text) {
+ switch (store.getString(Prefs.PREF_MD_CONVERTER)) {
+ case Prefs.KEY_MARDOWNJ:
+ return useMarkDownJ(text);
+ case Prefs.KEY_PEGDOWN:
+ return usePegDown(text);
+ case Prefs.KEY_COMMONMARK:
+ return useCommonMark(text);
+ case Prefs.KEY_TXTMARK:
+ return useTxtMark(text);
+ case Prefs.PREF_EXTERNAL_COMMAND:
+ return useExternalCli(text);
+ }
+ return "";
+ }
+
+ // Use MarkdownJ
+ private String useMarkDownJ(String text) {
+ MarkdownProcessor markdown = new MarkdownProcessor();
+ return markdown.markdown(text);
+ }
+
+ // Use PegDown
+ private String usePegDown(String text) {
+ PegDownProcessor pegdown = new PegDownProcessor();
+ return pegdown.markdownToHtml(text);
+ }
+
+ // Use CommonMark
+ private String useCommonMark(String text) {
+ Parser parser = Parser.builder().build();
+ Node document = parser.parse(text);
+ HtmlRenderer renderer = HtmlRenderer.builder().build();
+ return renderer.render(document);
+ }
+
+ // Use TxtMark
+ private String useTxtMark(String text) {
+ boolean safeMode = store.getBoolean(Prefs.PREF_TXTMARK_SAFEMODE);
+ boolean extended = store.getBoolean(Prefs.PREF_TXTMARK_EXTENDED);
+
+ Builder builder = Configuration.builder();
+ if (safeMode) builder.enableSafeMode();
+ if (extended) builder.forceExtentedProfile();
+ Configuration config = builder.build();
+ return Processor.process(text, config);
+ }
+
+ // Run external command
+ private String useExternalCli(String text) {
+ String cmd = store.getString(PrefPageGeneral.PREF_EXTERNAL_COMMAND);
+ if (Strings.isBlank(cmd) || (cmd.startsWith("(") && cmd.contains("MarkdownJ"))) {
+ return "No external markdown converter specified; update preferences.";
+ }
+
+ try {
+ final File md = File.createTempFile("tmp", ".md");
+ FileUtils.write(md, text);
+ Process process = new Process(cmd + " " + md.getAbsolutePath());
+ process.run();
+ int ok = process.waitFor(10000);
+ if (ok != 0) throw new FailureException(cmd + " failed:\n" + process.getError());
+ String html = process.getOutput();
+ FileUtils.delete(md);
+ return html;
+ } catch (Exception e) {}
+ return "External markdown convertion failed; update preferences.";
+ }
+}
diff --git a/plugin/src/winterwell/markdown/preferences/MarkdownPreferencePage.java b/plugin/src/winterwell/markdown/preferences/MarkdownPreferencePage.java
deleted file mode 100755
index 36dadbc..0000000
--- a/plugin/src/winterwell/markdown/preferences/MarkdownPreferencePage.java
+++ /dev/null
@@ -1,214 +0,0 @@
-package winterwell.markdown.preferences;
-
-import org.eclipse.jface.preference.BooleanFieldEditor;
-import org.eclipse.jface.preference.ColorFieldEditor;
-import org.eclipse.jface.preference.FieldEditor;
-import org.eclipse.jface.preference.FieldEditorPreferencePage;
-import org.eclipse.jface.preference.IPreferenceStore;
-import org.eclipse.jface.preference.PreferenceConverter;
-import org.eclipse.jface.preference.StringFieldEditor;
-import org.eclipse.swt.graphics.RGB;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.ui.IWorkbench;
-import org.eclipse.ui.IWorkbenchPreferencePage;
-
-import winterwell.markdown.Activator;
-
-/**
- * This class represents a preference page that
- * is contributed to the Preferences dialog. By
- * subclassing FieldEditorPreferencePage, we
- * can use the field support built into JFace that allows
- * us to create a page that is small and knows how to
- * save, restore and apply itself.
- *
- * This page is used to modify preferences only. They
- * are stored in the preference store that belongs to
- * the main plug-in class. That way, preferences can
- * be accessed directly via the preference store.
- */
-
-public class MarkdownPreferencePage
- extends FieldEditorPreferencePage
- implements IWorkbenchPreferencePage {
-
- public static final String PREF_FOLDING = "Pref_Folding";
- public static final String PREF_WORD_WRAP = "Pref_WordWrap";
- public static final String PREF_TASK_TAGS = "Pref_TaskTagsOn";
- public static final String PREF_TASK_TAGS_DEFINED = "Pref_TaskTags";
- public static final String PREF_SECTION_NUMBERS = "Pref_SectionNumbers";
-
- public static final String PREF_MARKDOWN_COMMAND = "Pref_Markdown_Command";
- private static final String MARKDOWNJ = "(use built-in MarkdownJ converter)";
-
-
- public static final String PREF_DEFUALT = "Pref_Default";
- public static final String PREF_COMMENT = "Pref_Comment";
- public static final String PREF_HEADER = "Pref_Header";
- public static final String PREF_LINK = "Pref_Link";
- public static final String PREF_CODE = "Pref_Code";
- public static final String PREF_CODE_BG = "Pref_Code_Background";
-
- public static final String PREF_GITHUB_SYNTAX = "Pref_Github_Syntax";
- public static final String PREF_MULTIMARKDOWN_METADATA = "Pref_MultiMarkdown_Metadata";
-
- private static final RGB DEF_DEFAULT = new RGB(0, 0, 0);
- private static final RGB DEF_COMMENT = new RGB(128, 0, 0);
- private static final RGB DEF_HEADER = new RGB(0, 128, 0);
- private static final RGB DEF_LINK = new RGB(106, 131, 199);
- private static final RGB DEF_CODE = new RGB(0, 0, 0);
- private static final RGB DEF_CODE_BG = new RGB(244,244,244);
-
- public MarkdownPreferencePage() {
- super(GRID);
- IPreferenceStore pStore = Activator.getDefault().getPreferenceStore();
- setDefaultPreferences(pStore);
- setPreferenceStore(pStore);
- setDescription("Settings for the Markdown text editor. See also the general text editor preferences.");
- }
-
- public static void setDefaultPreferences(IPreferenceStore pStore) {
- pStore.setDefault(PREF_WORD_WRAP, false);
- pStore.setDefault(PREF_FOLDING, true);
- pStore.setDefault(PREF_TASK_TAGS, true);
- pStore.setDefault(PREF_TASK_TAGS_DEFINED, "TODO,FIXME,??");
- pStore.setDefault(PREF_MARKDOWN_COMMAND, MARKDOWNJ);
- pStore.setDefault(PREF_SECTION_NUMBERS, true);
- pStore.setDefault(PREF_GITHUB_SYNTAX, true);
- pStore.setDefault(PREF_MULTIMARKDOWN_METADATA, false);
-
- PreferenceConverter.setDefault(pStore, PREF_DEFUALT, DEF_DEFAULT);
- PreferenceConverter.setDefault(pStore, PREF_COMMENT, DEF_COMMENT);
- PreferenceConverter.setDefault(pStore, PREF_HEADER, DEF_HEADER);
- PreferenceConverter.setDefault(pStore, PREF_LINK, DEF_LINK);
- PreferenceConverter.setDefault(pStore, PREF_CODE, DEF_CODE);
- PreferenceConverter.setDefault(pStore, PREF_CODE_BG, DEF_CODE_BG);
- }
-
- /**
- * Creates the field editors. Field editors are abstractions of
- * the common GUI blocks needed to manipulate various types
- * of preferences. Each field editor knows how to save and
- * restore itself.
- */
- @Override
- public void createFieldEditors() {
- // Word wrap
- BooleanFieldEditor fd = new BooleanFieldEditor(PREF_WORD_WRAP,
- "Soft word wrapping \r\n"
-+"Note: may cause line numbers and related \r\n" +
- "functionality to act a bit strangely",
- getFieldEditorParent());
- addField(fd);
- // Task tags
- fd = new BooleanFieldEditor(PREF_TASK_TAGS,
- "Manage tasks using task tags \r\n" +
- "If true, this will add and delete tags in sync with edits.",
- getFieldEditorParent());
- addField(fd);
- StringFieldEditor tags = new StringFieldEditor(PREF_TASK_TAGS_DEFINED,
- "Task tags\nComma separated list of recognised task tags.", getFieldEditorParent());
- addField(tags);
- // Code folding
- fd = new BooleanFieldEditor(PREF_FOLDING,
- "Document folding, a.k.a. outline support",
- getFieldEditorParent());
- addField(fd);
- // Command line
-// addField(new DummyField() {
-// protected void makeComponent(Composite parent) {
-// Label label = new Label(parent, 0);
-// label.setText("Hello!");
-// GridData gd = new GridData(100, 20);
-// label.setLayoutData(gd);
-// }
-// });
- StringFieldEditor cmd = new StringFieldEditor(PREF_MARKDOWN_COMMAND,
- "UNSTABLE: Command-line to run Markdown.\r\n" +
- "This should take in a file and output to std-out.\n" +
- "Leave blank to use the built-in Java converter.", getFieldEditorParent());
- addField(cmd);
-
- ColorFieldEditor def = new ColorFieldEditor(PREF_DEFUALT, "Default text", getFieldEditorParent());
- addField(def);
-
- ColorFieldEditor com = new ColorFieldEditor(PREF_COMMENT, "Comment", getFieldEditorParent());
- addField(com);
-
- ColorFieldEditor link = new ColorFieldEditor(PREF_LINK, "Link", getFieldEditorParent());
- addField(link);
-
- ColorFieldEditor head = new ColorFieldEditor(PREF_HEADER, "Header and List indicator", getFieldEditorParent());
- addField(head);
-
- ColorFieldEditor code = new ColorFieldEditor(PREF_CODE, "Code", getFieldEditorParent());
- addField(code);
-
- ColorFieldEditor codeBg = new ColorFieldEditor(PREF_CODE_BG, "Code Background", getFieldEditorParent());
- addField(codeBg);
-
- /*
- * Fields for the preview window
- */
-
- // Github Syntax support
- fd = new BooleanFieldEditor(PREF_GITHUB_SYNTAX,
- "Support Github Syntax",
- getFieldEditorParent());
- addField(fd);
-
- // Multi-Markdown support
- fd = new BooleanFieldEditor(PREF_MULTIMARKDOWN_METADATA,
- "Support Multi-Markdown Metadata",
- getFieldEditorParent());
- addField(fd);
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
- */
- public void init(IWorkbench workbench) {
-
- }
-
- public static boolean wordWrap() {
- IPreferenceStore pStore = Activator.getDefault().getPreferenceStore();
- if (! pStore.contains(MarkdownPreferencePage.PREF_WORD_WRAP)) {
- return false;
- }
- return pStore.getBoolean(MarkdownPreferencePage.PREF_WORD_WRAP);
- }
-
-}
-
-abstract class DummyField extends FieldEditor {
- @Override
- protected void adjustForNumColumns(int numColumns) {
- // do nothing
- }
- @Override
- protected void doFillIntoGrid(Composite parent, int numColumns) {
- makeComponent(parent);
- }
- abstract protected void makeComponent(Composite parent);
-
- @Override
- protected void doLoad() {
- //
- }
- @Override
- protected void doLoadDefault() {
- //
- }
-
- @Override
- protected void doStore() {
- //
- }
-
- @Override
- public int getNumberOfControls() {
- return 1;
- }
-
-}
\ No newline at end of file
diff --git a/plugin/src/winterwell/markdown/preferences/PrefPageColoring.java b/plugin/src/winterwell/markdown/preferences/PrefPageColoring.java
new file mode 100644
index 0000000..1ff1eed
--- /dev/null
+++ b/plugin/src/winterwell/markdown/preferences/PrefPageColoring.java
@@ -0,0 +1,47 @@
+package winterwell.markdown.preferences;
+
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.preference.ColorFieldEditor;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+import winterwell.markdown.MarkdownUI;
+
+public class PrefPageColoring extends FieldEditorPreferencePage implements IWorkbenchPreferencePage, Prefs {
+
+ public PrefPageColoring() {
+ super(GRID);
+ setDescription("");
+ }
+
+ public void init(IWorkbench workbench) {
+ setPreferenceStore(MarkdownUI.getDefault().getPreferenceStore());
+ }
+
+ /** Creates the field editors. */
+ @Override
+ public void createFieldEditors() {
+ Composite parent = getFieldEditorParent();
+
+ Group frame = new Group(parent, SWT.NONE);
+ GridDataFactory.fillDefaults().indent(0, 6).grab(true, false).span(2, 1).applyTo(frame);
+ GridLayoutFactory.fillDefaults().margins(6, 6).applyTo(frame);
+ frame.setText("Highlight Elements");
+
+ Composite internal = new Composite(frame, SWT.NONE);
+ GridDataFactory.fillDefaults().indent(0, 4).grab(true, false).applyTo(internal);
+ GridLayoutFactory.fillDefaults().applyTo(internal);
+
+ addField(new ColorFieldEditor(PREF_DEFAULT, "Default text", internal));
+ addField(new ColorFieldEditor(PREF_COMMENT, "Comment", internal));
+ addField(new ColorFieldEditor(PREF_LINK, "Link", internal));
+ addField(new ColorFieldEditor(PREF_HEADER, "Header and List indicator", internal));
+ addField(new ColorFieldEditor(PREF_CODE, "Code", internal));
+ addField(new ColorFieldEditor(PREF_CODE_BG, "Code Background", internal));
+ }
+}
diff --git a/plugin/src/winterwell/markdown/preferences/PrefPageGeneral.java b/plugin/src/winterwell/markdown/preferences/PrefPageGeneral.java
new file mode 100755
index 0000000..d75d9fa
--- /dev/null
+++ b/plugin/src/winterwell/markdown/preferences/PrefPageGeneral.java
@@ -0,0 +1,136 @@
+package winterwell.markdown.preferences;
+
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.preference.BooleanFieldEditor;
+import org.eclipse.jface.preference.ComboFieldEditor;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.StringFieldEditor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+import winterwell.markdown.MarkdownUI;
+
+public class PrefPageGeneral extends FieldEditorPreferencePage implements IWorkbenchPreferencePage, Prefs {
+
+ private String[][] converters;
+ private Group txtGroup;
+ private Group extGroup;
+
+ private BooleanFieldEditor safeMode;
+ private BooleanFieldEditor extended;
+ private StringFieldEditor extField;
+
+ private class ComboFieldEditor2 extends ComboFieldEditor {
+
+ public ComboFieldEditor2(String name, String labelText, String[][] entryNamesAndValues, Composite parent) {
+ super(name, labelText, entryNamesAndValues, parent);
+ }
+
+ @Override
+ protected void fireValueChanged(String property, Object oldValue, Object newValue) {
+ super.fireValueChanged(property, oldValue, newValue);
+ update((String) newValue);
+ }
+ }
+
+ public PrefPageGeneral() {
+ super(GRID);
+ setDescription("");
+ }
+
+ public void init(IWorkbench workbench) {
+ setPreferenceStore(MarkdownUI.getDefault().getPreferenceStore());
+ }
+
+ /** Create fields controlling general editor behavior */
+ @Override
+ public void createFieldEditors() {
+ Composite parent = getFieldEditorParent();
+
+ Group frame = new Group(parent, SWT.NONE);
+ GridDataFactory.fillDefaults().indent(0, 6).grab(true, false).applyTo(frame);
+ GridLayoutFactory.fillDefaults().margins(6, 6).applyTo(frame);
+ frame.setText("General");
+
+ Composite internal = new Composite(frame, SWT.NONE);
+ GridDataFactory.fillDefaults().indent(0, 4).grab(true, false).applyTo(internal);
+
+ // Word wrap
+ addField(new BooleanFieldEditor(PREF_WORD_WRAP, "Soft word wrapping", internal));
+
+ // Code folding
+ addField(new BooleanFieldEditor(PREF_FOLDING, "Document folding", internal));
+
+ // Task tags
+ addField(new BooleanFieldEditor(PREF_TASK_TAGS, "Use task tags ", internal));
+ addField(new StringFieldEditor(PREF_TASK_TAGS_DEFINED, "Task tags defined:", internal));
+
+ // Converter selection
+ addField(new ComboFieldEditor2(PREF_MD_CONVERTER, "Markdown Converter:", converters(), internal));
+
+ // Converter related options
+ txtGroup = new Group(internal, SWT.NONE);
+ txtGroup.setText("TxtMark Options");
+
+ GridDataFactory.fillDefaults().indent(2, 6).grab(true, false).span(2, 1).applyTo(txtGroup);
+ GridLayoutFactory.fillDefaults().margins(6, 6).applyTo(txtGroup);
+
+ safeMode = new BooleanFieldEditor(PREF_TXTMARK_SAFEMODE, "Use safe mode", txtGroup);
+ addField(safeMode);
+ extended = new BooleanFieldEditor(PREF_TXTMARK_EXTENDED, "Use extended profile", txtGroup);
+ addField(extended);
+
+ // External cli
+ extGroup = new Group(internal, SWT.NONE);
+ extGroup.setText("External Run Command");
+
+ GridDataFactory.fillDefaults().indent(2, 6).grab(true, false).span(2, 1).applyTo(extGroup);
+ GridLayoutFactory.fillDefaults().margins(6, 6).applyTo(extGroup);
+
+ extField = new StringFieldEditor(PREF_EXTERNAL_COMMAND, "", extGroup);
+
+ ((GridLayout) internal.getLayout()).numColumns = 2;
+ update(getPreferenceStore().getString(PREF_MD_CONVERTER)); // init visibility
+ }
+
+ private void update(String value) {
+ switch (value) {
+ case KEY_TXTMARK:
+ safeMode.setEnabled(true, txtGroup);
+ extended.setEnabled(true, txtGroup);
+ extField.setEnabled(false, extGroup);
+ break;
+ case KEY_USE_EXTERNAL:
+ safeMode.setEnabled(false, txtGroup);
+ extended.setEnabled(false, txtGroup);
+ extField.setEnabled(true, extGroup);
+ break;
+ default:
+ safeMode.setEnabled(false, txtGroup);
+ extended.setEnabled(false, txtGroup);
+ extField.setEnabled(false, extGroup);
+ }
+ }
+
+ private String[][] converters() {
+ if (converters == null) {
+ converters = new String[5][2];
+ converters[0][0] = "MarkdownJ";
+ converters[0][1] = KEY_MARDOWNJ;
+ converters[1][0] = "Commonmark";
+ converters[1][1] = KEY_COMMONMARK;
+ converters[2][0] = "PegDown";
+ converters[2][1] = KEY_PEGDOWN;
+ converters[3][0] = "TxtMark";
+ converters[3][1] = KEY_TXTMARK;
+ converters[4][0] = "External converter";
+ converters[4][1] = KEY_USE_EXTERNAL;
+ }
+ return converters;
+ }
+}
diff --git a/plugin/src/winterwell/markdown/preferences/PrefPageSpeller.java b/plugin/src/winterwell/markdown/preferences/PrefPageSpeller.java
new file mode 100644
index 0000000..35e6ce3
--- /dev/null
+++ b/plugin/src/winterwell/markdown/preferences/PrefPageSpeller.java
@@ -0,0 +1,59 @@
+package winterwell.markdown.preferences;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.ui.editors.text.ITextEditorHelpContextIds;
+import org.eclipse.ui.texteditor.spelling.IPreferenceStatusMonitor;
+
+import winterwell.markdown.MarkdownUI;
+import winterwell.markdown.spelling.AbstractConfigurationBlockPreferencePage;
+import winterwell.markdown.spelling.IPreferenceConfigurationBlock;
+import winterwell.markdown.spelling.OverlayPreferenceStore;
+import winterwell.markdown.spelling.SpellingConfigurationBlock;
+import winterwell.markdown.spelling.StatusUtil;
+
+/**
+ * Spelling preference page for options specific to Markdown.
+ */
+public class PrefPageSpeller extends AbstractConfigurationBlockPreferencePage {
+
+ /** Status monitor */
+ private class StatusMonitor implements IPreferenceStatusMonitor {
+
+ @Override
+ public void statusChanged(IStatus status) {
+ handleStatusChanged(status);
+ }
+ }
+
+ public PrefPageSpeller() {
+ super();
+ }
+
+ @Override
+ protected IPreferenceConfigurationBlock createConfigurationBlock(OverlayPreferenceStore overlayPreferenceStore) {
+ return new SpellingConfigurationBlock(overlayPreferenceStore, new StatusMonitor());
+ }
+
+ /**
+ * Handles status changes.
+ *
+ * @param status the new status
+ */
+ protected void handleStatusChanged(IStatus status) {
+ setValid(!status.matches(IStatus.ERROR));
+ StatusUtil.applyToStatusLine(this, status);
+ }
+
+ @Override
+ protected void setDescription() {}
+
+ @Override
+ protected void setPreferenceStore() {
+ setPreferenceStore(MarkdownUI.getDefault().getCombinedPreferenceStore());
+ }
+
+ @Override
+ protected String getHelpId() {
+ return ITextEditorHelpContextIds.SPELLING_PREFERENCE_PAGE;
+ }
+}
diff --git a/plugin/src/winterwell/markdown/preferences/PrefPageStyles.java b/plugin/src/winterwell/markdown/preferences/PrefPageStyles.java
new file mode 100644
index 0000000..bd20700
--- /dev/null
+++ b/plugin/src/winterwell/markdown/preferences/PrefPageStyles.java
@@ -0,0 +1,99 @@
+package winterwell.markdown.preferences;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.preference.BooleanFieldEditor;
+import org.eclipse.jface.preference.ComboFieldEditor;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.FileFieldEditor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.osgi.framework.Bundle;
+
+import winterwell.markdown.MarkdownUI;
+
+public class PrefPageStyles extends FieldEditorPreferencePage implements IWorkbenchPreferencePage, Prefs {
+
+ public PrefPageStyles() {
+ super(GRID);
+ setDescription("");
+ }
+
+ public void init(IWorkbench workbench) {
+ setPreferenceStore(MarkdownUI.getDefault().getPreferenceStore());
+ }
+
+ /** Creates the field editors. */
+ @Override
+ public void createFieldEditors() {
+ Composite parent = getFieldEditorParent();
+
+ Group frame = new Group(parent, SWT.NONE);
+ GridDataFactory.fillDefaults().indent(0, 6).grab(true, false).span(2, 1).applyTo(frame);
+ GridLayoutFactory.fillDefaults().numColumns(3).margins(6, 6).applyTo(frame);
+ frame.setText("Stylesheets");
+
+ Composite internal = new Composite(frame, SWT.NONE);
+ GridDataFactory.fillDefaults().indent(0, 4).grab(true, false).applyTo(internal);
+ GridLayoutFactory.fillDefaults().numColumns(3).applyTo(internal);
+
+ // Github Syntax support
+ addField(new BooleanFieldEditor(PREF_GITHUB_SYNTAX, "Support Github Syntax", internal));
+
+ // Multi-Markdown support
+ addField(new BooleanFieldEditor(PREF_MULTIMARKDOWN_METADATA, "Support Multi-Markdown Metadata", internal));
+
+ // Browser CSS
+ addField(new ComboFieldEditor(PREF_CSS_DEFAULT, "Default Stylesheet", builtins(), internal));
+ addField(new FileFieldEditor(PREF_CSS_CUSTOM, "Custom Stylesheet", internal));
+ }
+
+ // build list of builtin stylesheets
+ // key=name, value=bundle cache URL as string
+ private String[][] builtins() {
+ Bundle bundle = Platform.getBundle(MarkdownUI.PLUGIN_ID);
+ URL url = bundle.getEntry("resources/");
+ File dir = null;
+ try {
+ url = FileLocator.toFileURL(url); // extracts to bundle cache
+ dir = new File(url.toURI());
+ } catch (IOException | URISyntaxException e) {
+ String[][] values = new String[1][2];
+ values[0][0] = "";
+ values[0][1] = "";
+ return values;
+ }
+ List cssNames = new ArrayList<>();
+ if (dir.isDirectory()) {
+ for (String name : dir.list()) {
+ if (name.endsWith("." + CSS)) {
+ cssNames.add(name);
+ }
+ }
+ }
+
+ String[][] values = new String[cssNames.size()][2];
+ for (int idx = 0; idx < cssNames.size(); idx++) {
+ String cssName = cssNames.get(idx);
+ values[idx][0] = cssName;
+ try {
+ values[idx][1] = url.toURI().resolve(cssName).toString();
+ } catch (URISyntaxException e) {
+ values[idx][0] = cssName + " ";
+ }
+ }
+ return values;
+ }
+}
diff --git a/plugin/src/winterwell/markdown/preferences/Prefs.java b/plugin/src/winterwell/markdown/preferences/Prefs.java
new file mode 100644
index 0000000..c6fa5e9
--- /dev/null
+++ b/plugin/src/winterwell/markdown/preferences/Prefs.java
@@ -0,0 +1,46 @@
+package winterwell.markdown.preferences;
+
+public interface Prefs {
+
+ // preference related values
+ public static final String DEF_MDCSS = "markdown.css";
+ public static final String CSS = "css";
+
+ // preference store keys
+ public static final String PREF_FOLDING = "Pref_Folding";
+ public static final String PREF_WORD_WRAP = "Pref_WordWrap";
+ public static final String PREF_TASK_TAGS = "Pref_TaskTagsOn";
+ public static final String PREF_TASK_TAGS_DEFINED = "Pref_TaskTags";
+ public static final String PREF_SECTION_NUMBERS = "Pref_SectionNumbers";
+
+ public static final String PREF_MD_CONVERTER = "Pref_Converter_Selection";
+
+ public static final String KEY_MARDOWNJ = "Pref_MarkdownJ";
+ public static final String KEY_COMMONMARK = "Pref_Commonmark";
+ public static final String KEY_PEGDOWN = "Pref_PegDown";
+ public static final String KEY_TXTMARK = "Pref_TxtMark";
+ public static final String KEY_USE_EXTERNAL = "Pref_ExtMark";
+
+ public static final String PREF_TXTMARK_SAFEMODE = "Pref_TxtMark_SafeMode";
+ public static final String PREF_TXTMARK_EXTENDED = "Pref_TxtMark_ExtendedMode";
+
+ public static final String PREF_EXTERNAL_COMMAND = "Pref_Markdown_Command";
+
+ public static final String PREF_DEFAULT = "Pref_Default";
+ public static final String PREF_COMMENT = "Pref_Comment";
+ public static final String PREF_HEADER = "Pref_Header";
+ public static final String PREF_LINK = "Pref_Link";
+ public static final String PREF_CODE = "Pref_Code";
+ public static final String PREF_CODE_BG = "Pref_Code_Background";
+
+ public static final String PREF_GITHUB_SYNTAX = "Pref_Github_Syntax";
+ public static final String PREF_MULTIMARKDOWN_METADATA = "Pref_MultiMarkdown_Metadata";
+
+ public static final String PREF_CSS_DEFAULT = "Pref_Markdown_Css";
+ public static final String PREF_CSS_CUSTOM = "";
+
+ public static final String PREF_SPELLING_ENABLED = "Pref_Spelling_Enabled";
+
+ public static final String PREF_UPDATE_DELAY = "Pref_Update_Delay";
+
+}
diff --git a/plugin/src/winterwell/markdown/preferences/PrefsInit.java b/plugin/src/winterwell/markdown/preferences/PrefsInit.java
new file mode 100644
index 0000000..17720ce
--- /dev/null
+++ b/plugin/src/winterwell/markdown/preferences/PrefsInit.java
@@ -0,0 +1,73 @@
+package winterwell.markdown.preferences;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceConverter;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.ui.texteditor.spelling.SpellingService;
+import org.osgi.framework.Bundle;
+
+import winterwell.markdown.MarkdownUI;
+
+/**
+ * Initialize default preference values
+ */
+public class PrefsInit extends AbstractPreferenceInitializer implements Prefs {
+
+ private static final RGB DEF_DEFAULT = new RGB(0, 0, 0);
+ private static final RGB DEF_COMMENT = new RGB(128, 0, 0);
+ private static final RGB DEF_HEADER = new RGB(0, 128, 0);
+ private static final RGB DEF_LINK = new RGB(106, 131, 199);
+ private static final RGB DEF_CODE = new RGB(0, 0, 0);
+ private static final RGB DEF_CODE_BG = new RGB(244, 244, 244);
+
+ public void initializeDefaultPreferences() {
+ IPreferenceStore store = MarkdownUI.getDefault().getPreferenceStore();
+
+ store.setDefault(PREF_WORD_WRAP, false);
+ store.setDefault(PREF_FOLDING, true);
+ store.setDefault(PREF_TASK_TAGS, true);
+ store.setDefault(PREF_TASK_TAGS_DEFINED, "TODO,FIXME,??");
+
+ store.setDefault(PREF_MD_CONVERTER, KEY_MARDOWNJ);
+ store.setDefault(PREF_TXTMARK_SAFEMODE, false);
+ store.setDefault(PREF_TXTMARK_EXTENDED, true);
+
+ store.setDefault(PREF_EXTERNAL_COMMAND, "");
+ store.setDefault(PREF_SECTION_NUMBERS, true);
+
+ store.setDefault(PREF_CSS_DEFAULT, cssDefault());
+ store.setDefault(PREF_CSS_CUSTOM, "");
+ store.setDefault(PREF_GITHUB_SYNTAX, true);
+ store.setDefault(PREF_MULTIMARKDOWN_METADATA, false);
+
+ // hides the corresponding PlatformUI preference value
+ store.setDefault(SpellingService.PREFERENCE_SPELLING_ENABLED, true);
+ store.setDefault(PREF_SPELLING_ENABLED, true);
+
+ PreferenceConverter.setDefault(store, PREF_DEFAULT, DEF_DEFAULT);
+ PreferenceConverter.setDefault(store, PREF_COMMENT, DEF_COMMENT);
+ PreferenceConverter.setDefault(store, PREF_HEADER, DEF_HEADER);
+ PreferenceConverter.setDefault(store, PREF_LINK, DEF_LINK);
+ PreferenceConverter.setDefault(store, PREF_CODE, DEF_CODE);
+ PreferenceConverter.setDefault(store, PREF_CODE_BG, DEF_CODE_BG);
+ }
+
+ // create bundle cache URL for the default stylesheet
+ private String cssDefault() {
+ Bundle bundle = Platform.getBundle(MarkdownUI.PLUGIN_ID);
+ URL url = FileLocator.find(bundle, new Path("resources/" + DEF_MDCSS), null);
+ try {
+ url = FileLocator.toFileURL(url);
+ return url.toURI().toString();
+ } catch (IOException | URISyntaxException e) {}
+ return DEF_MDCSS; // really an error
+ }
+}
diff --git a/plugin/src/winterwell/markdown/spelling/AbstractConfigurationBlockPreferencePage.java b/plugin/src/winterwell/markdown/spelling/AbstractConfigurationBlockPreferencePage.java
new file mode 100644
index 0000000..1a2b01d
--- /dev/null
+++ b/plugin/src/winterwell/markdown/spelling/AbstractConfigurationBlockPreferencePage.java
@@ -0,0 +1,96 @@
+package winterwell.markdown.spelling;
+
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.preference.PreferencePage;
+
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * Abstract preference page which is used to wrap a
+ * {@link org.eclipse.ui.internal.editors.text.IPreferenceConfigurationBlock}.
+ *
+ * @since 3.0
+ */
+public abstract class AbstractConfigurationBlockPreferencePage extends PreferencePage
+ implements IWorkbenchPreferencePage {
+
+ private IPreferenceConfigurationBlock fConfigurationBlock;
+ private OverlayPreferenceStore fOverlayStore;
+
+ /**
+ * Creates a new preference page.
+ */
+ public AbstractConfigurationBlockPreferencePage() {
+ setDescription();
+ setPreferenceStore();
+ fOverlayStore = new OverlayPreferenceStore(getPreferenceStore(), new OverlayPreferenceStore.OverlayKey[] {});
+ fConfigurationBlock = createConfigurationBlock(fOverlayStore);
+ }
+
+ protected abstract IPreferenceConfigurationBlock createConfigurationBlock(
+ OverlayPreferenceStore overlayPreferenceStore);
+
+ protected abstract String getHelpId();
+
+ protected abstract void setDescription();
+
+ protected abstract void setPreferenceStore();
+
+ @Override
+ public void init(IWorkbench workbench) {}
+
+ @Override
+ public void createControl(Composite parent) {
+ super.createControl(parent);
+ PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), getHelpId());
+ }
+
+ @Override
+ protected Control createContents(Composite parent) {
+ fOverlayStore.load();
+ fOverlayStore.start();
+ Control content = fConfigurationBlock.createControl(parent);
+ initialize();
+ Dialog.applyDialogFont(content);
+ return content;
+ }
+
+ private void initialize() {
+ fConfigurationBlock.initialize();
+ }
+
+ @Override
+ public boolean performOk() {
+ if (!fConfigurationBlock.canPerformOk()) return false;
+ fConfigurationBlock.performOk();
+ fOverlayStore.propagate(); // TODO: chained store cannot propogate!!!
+ return true;
+ }
+
+ @Override
+ public void performDefaults() {
+ fOverlayStore.loadDefaults();
+ fConfigurationBlock.performDefaults();
+ super.performDefaults();
+ }
+
+ @Override
+ public void dispose() {
+ fConfigurationBlock.dispose();
+ if (fOverlayStore != null) {
+ fOverlayStore.stop();
+ fOverlayStore = null;
+ }
+ super.dispose();
+ }
+
+ @Override
+ public void applyData(Object data) {
+ fConfigurationBlock.applyData(data);
+ }
+}
diff --git a/plugin/src/winterwell/markdown/spelling/IEditorsStatusConstants.java b/plugin/src/winterwell/markdown/spelling/IEditorsStatusConstants.java
new file mode 100644
index 0000000..cfdf018
--- /dev/null
+++ b/plugin/src/winterwell/markdown/spelling/IEditorsStatusConstants.java
@@ -0,0 +1,18 @@
+package winterwell.markdown.spelling;
+
+/**
+ * Defines plug-in-specific status codes.
+ *
+ * @see org.eclipse.core.runtime.IStatus#getCode()
+ * @see org.eclipse.core.runtime.Status#Status(int, java.lang.String, int, java.lang.String,
+ * java.lang.Throwable)
+ * @since 2.1
+ */
+public interface IEditorsStatusConstants {
+
+ /**
+ * Status constant indicating that an internal error occurred. Value: 1001
+ */
+ public static final int INTERNAL_ERROR = 10001;
+
+}
diff --git a/plugin/src/winterwell/markdown/spelling/IPreferenceConfigurationBlock.java b/plugin/src/winterwell/markdown/spelling/IPreferenceConfigurationBlock.java
new file mode 100644
index 0000000..61e2c9e
--- /dev/null
+++ b/plugin/src/winterwell/markdown/spelling/IPreferenceConfigurationBlock.java
@@ -0,0 +1,74 @@
+package winterwell.markdown.spelling;
+
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.jface.preference.PreferencePage;
+
+/**
+ * Interface for preference configuration blocks which can either be wrapped by a
+ * {@link org.eclipse.ui.internal.editors.text.AbstractConfigurationBlockPreferencePage} or be
+ * included some preference page.
+ *
+ * Clients may implement this interface.
+ *
+ *
+ * @since 3.0
+ */
+public interface IPreferenceConfigurationBlock {
+
+ /**
+ * Creates the preference control.
+ *
+ * @param parent the parent composite to which to add the preferences control
+ * @return the control that was added to parent
+ */
+ Control createControl(Composite parent);
+
+ /**
+ * Called after creating the control. Implementations should load the preferences values and
+ * update the controls accordingly.
+ */
+ void initialize();
+
+ /**
+ * Called when the OK button is pressed on the preference page. Implementations
+ * should commit the configured preference settings into their form of preference storage.
+ */
+ void performOk();
+
+ /**
+ * Called when the OK button is pressed on the preference page. Implementations can
+ * abort the 'OK' operation by returning false.
+ *
+ * @return true iff the 'OK' operation can be performed
+ * @since 3.1
+ */
+ boolean canPerformOk();
+
+ /**
+ * Called when the Defaults button is pressed on the preference page.
+ * Implementation should reset any preference settings to their default values and adjust the
+ * controls accordingly.
+ */
+ void performDefaults();
+
+ /**
+ * Called when the preference page is being disposed. Implementations should free any resources
+ * they are holding on to.
+ */
+ void dispose();
+
+ /**
+ * Applies the given data.
+ *
+ * It is up to the implementor to define whether it supports this and which kind of data it
+ * accepts.
+ *
+ *
+ * @param data the data which is specified by each configuration block
+ * @see PreferencePage#applyData(Object)
+ * @since 3.4
+ */
+ void applyData(Object data);
+}
diff --git a/plugin/src/winterwell/markdown/spelling/OverlayPreferenceStore.java b/plugin/src/winterwell/markdown/spelling/OverlayPreferenceStore.java
new file mode 100644
index 0000000..2f3b336
--- /dev/null
+++ b/plugin/src/winterwell/markdown/spelling/OverlayPreferenceStore.java
@@ -0,0 +1,438 @@
+package winterwell.markdown.spelling;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceStore;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+
+/**
+ * An overlaying preference store.
+ */
+public class OverlayPreferenceStore implements IPreferenceStore {
+
+ /**
+ * Descriptor used to denote data types.
+ */
+ public static final class TypeDescriptor {
+
+ private TypeDescriptor() {}
+ }
+
+ public static final TypeDescriptor BOOLEAN = new TypeDescriptor();
+ public static final TypeDescriptor DOUBLE = new TypeDescriptor();
+ public static final TypeDescriptor FLOAT = new TypeDescriptor();
+ public static final TypeDescriptor INT = new TypeDescriptor();
+ public static final TypeDescriptor LONG = new TypeDescriptor();
+ public static final TypeDescriptor STRING = new TypeDescriptor();
+
+ /**
+ * Data structure for the overlay key.
+ */
+ public static class OverlayKey {
+
+ TypeDescriptor fDescriptor;
+ String fKey;
+
+ public OverlayKey(TypeDescriptor descriptor, String key) {
+ fDescriptor = descriptor;
+ fKey = key;
+ }
+ }
+
+ /*
+ * @see IPropertyChangeListener
+ */
+ private class PropertyListener implements IPropertyChangeListener {
+
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ OverlayKey key = findOverlayKey(event.getProperty());
+ if (key != null) propagateProperty(fParent, key, fStore);
+ }
+ }
+
+ /** The parent preference store. */
+ private IPreferenceStore fParent;
+ /** This store. */
+ private IPreferenceStore fStore;
+ /** The keys of this store. */
+ private OverlayKey[] fOverlayKeys;
+ /** The property listener. */
+ private PropertyListener fPropertyListener;
+ private boolean fLoaded;
+
+ /**
+ * Creates and returns a new overlay preference store.
+ *
+ * @param parent the parent preference store
+ * @param overlayKeys the overlay keys
+ */
+ public OverlayPreferenceStore(IPreferenceStore parent, OverlayKey[] overlayKeys) {
+ fParent = parent;
+ fOverlayKeys = overlayKeys;
+ fStore = new PreferenceStore();
+ }
+
+ /**
+ * Tries to find and return the overlay key for the given preference key string.
+ *
+ * @param key the preference key string
+ * @return the overlay key or null if none can be found
+ */
+ private OverlayKey findOverlayKey(String key) {
+ for (OverlayKey fOverlayKey : fOverlayKeys) {
+ if (fOverlayKey.fKey.equals(key)) return fOverlayKey;
+ }
+ return null;
+ }
+
+ /**
+ * Tells whether the given preference key string is covered by this overlay store.
+ *
+ * @param key the preference key string
+ * @return true if this overlay store covers the given key
+ */
+ private boolean covers(String key) {
+ return (findOverlayKey(key) != null);
+ }
+
+ /**
+ * Propagates the given overlay key from the origin to the target preference store.
+ *
+ * @param origin the source preference store
+ * @param key the overlay key
+ * @param target the preference store to which the key is propagated
+ */
+ private void propagateProperty(IPreferenceStore origin, OverlayKey key, IPreferenceStore target) {
+
+ if (origin.isDefault(key.fKey)) {
+ if (!target.isDefault(key.fKey)) target.setToDefault(key.fKey);
+ return;
+ }
+
+ TypeDescriptor d = key.fDescriptor;
+ if (BOOLEAN == d) {
+
+ boolean originValue = origin.getBoolean(key.fKey);
+ boolean targetValue = target.getBoolean(key.fKey);
+ if (targetValue != originValue) target.setValue(key.fKey, originValue);
+
+ } else if (DOUBLE == d) {
+
+ double originValue = origin.getDouble(key.fKey);
+ double targetValue = target.getDouble(key.fKey);
+ if (targetValue != originValue) target.setValue(key.fKey, originValue);
+
+ } else if (FLOAT == d) {
+
+ float originValue = origin.getFloat(key.fKey);
+ float targetValue = target.getFloat(key.fKey);
+ if (targetValue != originValue) target.setValue(key.fKey, originValue);
+
+ } else if (INT == d) {
+
+ int originValue = origin.getInt(key.fKey);
+ int targetValue = target.getInt(key.fKey);
+ if (targetValue != originValue) target.setValue(key.fKey, originValue);
+
+ } else if (LONG == d) {
+
+ long originValue = origin.getLong(key.fKey);
+ long targetValue = target.getLong(key.fKey);
+ if (targetValue != originValue) target.setValue(key.fKey, originValue);
+
+ } else if (STRING == d) {
+
+ String originValue = origin.getString(key.fKey);
+ String targetValue = target.getString(key.fKey);
+ if (targetValue != null && originValue != null && !targetValue.equals(originValue))
+ target.setValue(key.fKey, originValue);
+
+ }
+ }
+
+ /**
+ * Propagates all overlay keys from this store to the parent store.
+ */
+ public void propagate() {
+ for (OverlayKey fOverlayKey : fOverlayKeys)
+ propagateProperty(fStore, fOverlayKey, fParent);
+ }
+
+ /**
+ * Loads the given key from the origin into the target.
+ *
+ * @param origin the source preference store
+ * @param key the overlay key
+ * @param target the preference store to which the key is propagated
+ * @param forceInitialization if true the value in the target gets initialized
+ * before loading
+ */
+ private void loadProperty(IPreferenceStore origin, OverlayKey key, IPreferenceStore target,
+ boolean forceInitialization) {
+ TypeDescriptor d = key.fDescriptor;
+ if (BOOLEAN == d) {
+
+ if (forceInitialization) target.setValue(key.fKey, true);
+ target.setValue(key.fKey, origin.getBoolean(key.fKey));
+ target.setDefault(key.fKey, origin.getDefaultBoolean(key.fKey));
+
+ } else if (DOUBLE == d) {
+
+ if (forceInitialization) target.setValue(key.fKey, 1.0D);
+ target.setValue(key.fKey, origin.getDouble(key.fKey));
+ target.setDefault(key.fKey, origin.getDefaultDouble(key.fKey));
+
+ } else if (FLOAT == d) {
+
+ if (forceInitialization) target.setValue(key.fKey, 1.0F);
+ target.setValue(key.fKey, origin.getFloat(key.fKey));
+ target.setDefault(key.fKey, origin.getDefaultFloat(key.fKey));
+
+ } else if (INT == d) {
+
+ if (forceInitialization) target.setValue(key.fKey, 1);
+ target.setValue(key.fKey, origin.getInt(key.fKey));
+ target.setDefault(key.fKey, origin.getDefaultInt(key.fKey));
+
+ } else if (LONG == d) {
+
+ if (forceInitialization) target.setValue(key.fKey, 1L);
+ target.setValue(key.fKey, origin.getLong(key.fKey));
+ target.setDefault(key.fKey, origin.getDefaultLong(key.fKey));
+
+ } else if (STRING == d) {
+
+ if (forceInitialization) target.setValue(key.fKey, "1"); //$NON-NLS-1$
+ target.setValue(key.fKey, origin.getString(key.fKey));
+ target.setDefault(key.fKey, origin.getDefaultString(key.fKey));
+
+ }
+ }
+
+ /**
+ * Loads the values from the parent into this store.
+ */
+ public void load() {
+ for (OverlayKey fOverlayKey : fOverlayKeys)
+ loadProperty(fParent, fOverlayKey, fStore, true);
+
+ fLoaded = true;
+ }
+
+ /**
+ * Loads the default values.
+ */
+ public void loadDefaults() {
+ for (OverlayKey fOverlayKey : fOverlayKeys)
+ setToDefault(fOverlayKey.fKey);
+ }
+
+ /**
+ * Starts to listen for changes.
+ */
+ public void start() {
+ if (fPropertyListener == null) {
+ fPropertyListener = new PropertyListener();
+ fParent.addPropertyChangeListener(fPropertyListener);
+ }
+ }
+
+ /**
+ * Stops to listen for changes.
+ */
+ public void stop() {
+ if (fPropertyListener != null) {
+ fParent.removePropertyChangeListener(fPropertyListener);
+ fPropertyListener = null;
+ }
+ }
+
+ @Override
+ public void addPropertyChangeListener(IPropertyChangeListener listener) {
+ fStore.addPropertyChangeListener(listener);
+ }
+
+ @Override
+ public void removePropertyChangeListener(IPropertyChangeListener listener) {
+ fStore.removePropertyChangeListener(listener);
+ }
+
+ @Override
+ public void firePropertyChangeEvent(String name, Object oldValue, Object newValue) {
+ fStore.firePropertyChangeEvent(name, oldValue, newValue);
+ }
+
+ @Override
+ public boolean contains(String name) {
+ return fStore.contains(name);
+ }
+
+ @Override
+ public boolean getBoolean(String name) {
+ return fStore.getBoolean(name);
+ }
+
+ @Override
+ public boolean getDefaultBoolean(String name) {
+ return fStore.getDefaultBoolean(name);
+ }
+
+ @Override
+ public double getDefaultDouble(String name) {
+ return fStore.getDefaultDouble(name);
+ }
+
+ @Override
+ public float getDefaultFloat(String name) {
+ return fStore.getDefaultFloat(name);
+ }
+
+ @Override
+ public int getDefaultInt(String name) {
+ return fStore.getDefaultInt(name);
+ }
+
+ @Override
+ public long getDefaultLong(String name) {
+ return fStore.getDefaultLong(name);
+ }
+
+ @Override
+ public String getDefaultString(String name) {
+ return fStore.getDefaultString(name);
+ }
+
+ @Override
+ public double getDouble(String name) {
+ return fStore.getDouble(name);
+ }
+
+ @Override
+ public float getFloat(String name) {
+ return fStore.getFloat(name);
+ }
+
+ @Override
+ public int getInt(String name) {
+ return fStore.getInt(name);
+ }
+
+ @Override
+ public long getLong(String name) {
+ return fStore.getLong(name);
+ }
+
+ @Override
+ public String getString(String name) {
+ return fStore.getString(name);
+ }
+
+ @Override
+ public boolean isDefault(String name) {
+ return fStore.isDefault(name);
+ }
+
+ @Override
+ public boolean needsSaving() {
+ return fStore.needsSaving();
+ }
+
+ @Override
+ public void putValue(String name, String value) {
+ if (covers(name)) fStore.putValue(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, double value) {
+ if (covers(name)) fStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, float value) {
+ if (covers(name)) fStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, int value) {
+ if (covers(name)) fStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, long value) {
+ if (covers(name)) fStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, String value) {
+ if (covers(name)) fStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setDefault(String name, boolean value) {
+ if (covers(name)) fStore.setDefault(name, value);
+ }
+
+ @Override
+ public void setToDefault(String name) {
+ fStore.setToDefault(name);
+ }
+
+ @Override
+ public void setValue(String name, double value) {
+ if (covers(name)) fStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, float value) {
+ if (covers(name)) fStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, int value) {
+ if (covers(name)) fStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, long value) {
+ if (covers(name)) fStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, String value) {
+ if (covers(name)) fStore.setValue(name, value);
+ }
+
+ @Override
+ public void setValue(String name, boolean value) {
+ if (covers(name)) fStore.setValue(name, value);
+ }
+
+ /**
+ * The keys to add to the list of overlay keys.
+ *
+ * Note: This method must be called before {@link #load()} is called.
+ *
+ *
+ * @param keys an array with overlay keys
+ * @since 3.0
+ */
+ public void addKeys(OverlayKey[] keys) {
+ Assert.isTrue(!fLoaded);
+ Assert.isNotNull(keys);
+
+ int overlayKeysLength = fOverlayKeys.length;
+ OverlayKey[] result = new OverlayKey[keys.length + overlayKeysLength];
+
+ for (int i = 0, length = overlayKeysLength; i < length; i++)
+ result[i] = fOverlayKeys[i];
+
+ for (int i = 0, length = keys.length; i < length; i++)
+ result[overlayKeysLength + i] = keys[i];
+
+ fOverlayKeys = result;
+
+ if (fLoaded) load();
+ }
+}
diff --git a/plugin/src/winterwell/markdown/spelling/SpellingConfigurationBlock.java b/plugin/src/winterwell/markdown/spelling/SpellingConfigurationBlock.java
new file mode 100644
index 0000000..555d110
--- /dev/null
+++ b/plugin/src/winterwell/markdown/spelling/SpellingConfigurationBlock.java
@@ -0,0 +1,567 @@
+package winterwell.markdown.spelling;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.layout.PixelConverter;
+import org.eclipse.jface.viewers.ComboViewer;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StackLayout;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.editors.text.EditorsUI;
+import org.eclipse.ui.texteditor.spelling.IPreferenceStatusMonitor;
+import org.eclipse.ui.texteditor.spelling.ISpellingPreferenceBlock;
+import org.eclipse.ui.texteditor.spelling.SpellingEngineDescriptor;
+import org.eclipse.ui.texteditor.spelling.SpellingService;
+
+import winterwell.markdown.Log;
+import winterwell.markdown.spelling.OverlayPreferenceStore.OverlayKey;
+
+/**
+ * Configures spelling preferences specific to Markdown.
+ */
+public class SpellingConfigurationBlock implements IPreferenceConfigurationBlock {
+
+ /** Error preferences block. */
+ private static class ErrorPreferences implements ISpellingPreferenceBlock {
+
+ /** Error message */
+ private String fMessage;
+
+ /** Error label */
+ private Label fLabel;
+
+ /**
+ * Initialize with the given error message.
+ *
+ * @param message the error message
+ */
+ protected ErrorPreferences(String message) {
+ fMessage = message;
+ }
+
+ @Override
+ public Control createControl(Composite composite) {
+ Composite inner = new Composite(composite, SWT.NONE);
+ inner.setLayout(new FillLayout(SWT.VERTICAL));
+
+ fLabel = new Label(inner, SWT.CENTER);
+ fLabel.setText(fMessage);
+
+ return inner;
+ }
+
+ @Override
+ public void initialize(IPreferenceStatusMonitor statusMonitor) {}
+
+ @Override
+ public boolean canPerformOk() {
+ return true;
+ }
+
+ @Override
+ public void performOk() {}
+
+ @Override
+ public void performDefaults() {}
+
+ @Override
+ public void performRevert() {}
+
+ @Override
+ public void dispose() {}
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ fLabel.setEnabled(enabled);
+ }
+ }
+
+ /**
+ * Forwarding status monitor for accessing the current status.
+ */
+ private static class ForwardingStatusMonitor implements IPreferenceStatusMonitor {
+
+ /** Status monitor to which status changes are forwarded */
+ private IPreferenceStatusMonitor fForwardedMonitor;
+
+ /** Latest reported status */
+ private IStatus fStatus;
+
+ /**
+ * Initializes with the given status monitor to which status changes are forwarded.
+ *
+ * @param forwardedMonitor the status monitor to which changes are forwarded
+ */
+ public ForwardingStatusMonitor(IPreferenceStatusMonitor forwardedMonitor) {
+ fForwardedMonitor = forwardedMonitor;
+ }
+
+ @Override
+ public void statusChanged(IStatus status) {
+ fStatus = status;
+ fForwardedMonitor.statusChanged(status);
+ }
+
+ /**
+ * Returns the latest reported status.
+ *
+ * @return the latest reported status, can be null
+ */
+ public IStatus getStatus() {
+ return fStatus;
+ }
+ }
+
+ /** The overlay preference store. */
+ private final OverlayPreferenceStore fStore;
+
+ /* The controls */
+ private Combo fProviderCombo;
+ private Button fEnablementCheckbox;
+ private ComboViewer fProviderViewer;
+ private Composite fComboGroup;
+ private Composite fGroup;
+ private StackLayout fStackLayout;
+
+ /* the model */
+ private final Map fProviderDescriptors;
+ private final Map fProviderPreferences;
+ private final Map fProviderControls;
+
+ private ForwardingStatusMonitor fStatusMonitor;
+
+ private ISpellingPreferenceBlock fCurrentBlock;
+
+ public SpellingConfigurationBlock(OverlayPreferenceStore store, IPreferenceStatusMonitor statusMonitor) {
+ Assert.isNotNull(store);
+ fStore = store;
+ fStore.addKeys(createOverlayStoreKeys());
+ fStatusMonitor = new ForwardingStatusMonitor(statusMonitor);
+ fProviderDescriptors = createListModel();
+ fProviderPreferences = new HashMap<>();
+ fProviderControls = new HashMap<>();
+ }
+
+ private Map createListModel() {
+ SpellingEngineDescriptor[] descs = EditorsUI.getSpellingService().getSpellingEngineDescriptors();
+ Map map = new HashMap<>();
+ for (SpellingEngineDescriptor desc : descs) {
+ map.put(desc.getId(), desc);
+ }
+ return map;
+ }
+
+ private OverlayPreferenceStore.OverlayKey[] createOverlayStoreKeys() {
+
+ ArrayList overlayKeys = new ArrayList<>();
+
+ overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN,
+ SpellingService.PREFERENCE_SPELLING_ENABLED));
+ overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING,
+ SpellingService.PREFERENCE_SPELLING_ENGINE));
+
+ OverlayPreferenceStore.OverlayKey[] keys = new OverlayPreferenceStore.OverlayKey[overlayKeys.size()];
+ overlayKeys.toArray(keys);
+ return keys;
+ }
+
+ /**
+ * Creates page for spelling preferences.
+ *
+ * @param parent the parent composite
+ * @return the control for the preference page
+ */
+ @Override
+ public Control createControl(Composite parent) {
+
+ Composite composite = new Composite(parent, SWT.NULL);
+ // assume parent page uses grid-data
+ GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_CENTER | GridData.VERTICAL_ALIGN_FILL);
+ composite.setLayoutData(gd);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ layout.marginHeight = 0;
+ layout.marginWidth = 0;
+
+ PixelConverter pc = new PixelConverter(composite);
+ layout.verticalSpacing = pc.convertHeightInCharsToPixels(1) / 2;
+ composite.setLayout(layout);
+
+ if (EditorsUI.getSpellingService().getSpellingEngineDescriptors().length == 0) {
+ Label label = new Label(composite, SWT.NONE);
+ label.setText(TextEditorMessages.SpellingConfigurationBlock_error_not_installed);
+ return composite;
+ }
+
+ /* check box for new editors */
+ fEnablementCheckbox = new Button(composite, SWT.CHECK);
+ fEnablementCheckbox.setText(TextEditorMessages.SpellingConfigurationBlock_enable);
+ gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING);
+ fEnablementCheckbox.setLayoutData(gd);
+ fEnablementCheckbox.addSelectionListener(new SelectionListener() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ boolean enabled = fEnablementCheckbox.getSelection();
+ fStore.setValue(SpellingService.PREFERENCE_SPELLING_ENABLED, enabled);
+ updateCheckboxDependencies();
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {}
+ });
+
+ Label label = new Label(composite, SWT.CENTER);
+ gd = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_BEGINNING);
+ label.setLayoutData(gd);
+
+ if (fProviderDescriptors.size() > 1) {
+ fComboGroup = new Composite(composite, SWT.NONE);
+ gd = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_BEGINNING);
+ gd.horizontalIndent = 10;
+ fComboGroup.setLayoutData(gd);
+ GridLayout gridLayout = new GridLayout(2, false);
+ gridLayout.marginWidth = 0;
+ fComboGroup.setLayout(gridLayout);
+
+ Label comboLabel = new Label(fComboGroup, SWT.CENTER);
+ gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_CENTER);
+ comboLabel.setLayoutData(gd);
+ comboLabel.setText(TextEditorMessages.SpellingConfigurationBlock_combo_caption);
+
+ label = new Label(composite, SWT.CENTER);
+ gd = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_BEGINNING);
+ label.setLayoutData(gd);
+
+ fProviderCombo = new Combo(fComboGroup, SWT.READ_ONLY | SWT.DROP_DOWN);
+ gd = new GridData(GridData.HORIZONTAL_ALIGN_END | GridData.VERTICAL_ALIGN_CENTER);
+ fProviderCombo.setLayoutData(gd);
+
+ fProviderViewer = createProviderViewer();
+ }
+
+ Composite groupComp = new Composite(composite, SWT.NONE);
+ gd = new GridData(GridData.FILL_BOTH);
+ gd.horizontalSpan = 2;
+ groupComp.setLayoutData(gd);
+ GridLayout gridLayout = new GridLayout(1, false);
+ gridLayout.marginWidth = 0;
+ groupComp.setLayout(gridLayout);
+
+ /* contributed provider preferences. */
+ fGroup = new Composite(groupComp, SWT.NONE);
+ gd = new GridData(SWT.FILL, SWT.FILL, true, true);
+ gd.horizontalIndent = 10;
+ fGroup.setLayoutData(gd);
+ fStackLayout = new StackLayout();
+ fGroup.setLayout(fStackLayout);
+
+ return composite;
+ }
+
+ @Override
+ public void applyData(Object data) {}
+
+ private ComboViewer createProviderViewer() {
+ /* list viewer */
+ final ComboViewer viewer = new ComboViewer(fProviderCombo);
+ viewer.setContentProvider(new IStructuredContentProvider() {
+
+ @Override
+ public void dispose() {}
+
+ @Override
+ public void inputChanged(Viewer v, Object oldInput, Object newInput) {}
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ return fProviderDescriptors.values().toArray();
+ }
+ });
+ viewer.setLabelProvider(new LabelProvider() {
+
+ @Override
+ public Image getImage(Object element) {
+ return null;
+ }
+
+ @Override
+ public String getText(Object element) {
+ return ((SpellingEngineDescriptor) element).getLabel();
+ }
+ });
+ viewer.addSelectionChangedListener(new ISelectionChangedListener() {
+
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection sel = (IStructuredSelection) event.getSelection();
+ if (sel.isEmpty()) return;
+ if (fCurrentBlock != null && fStatusMonitor.getStatus() != null
+ && fStatusMonitor.getStatus().matches(IStatus.ERROR))
+ if (isPerformRevert()) {
+ ISafeRunnable runnable = new ISafeRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ fCurrentBlock.performRevert();
+ }
+
+ @Override
+ public void handleException(Throwable x) {}
+ };
+ SafeRunner.run(runnable);
+ } else {
+ revertSelection();
+ return;
+ }
+ fStore.setValue(SpellingService.PREFERENCE_SPELLING_ENGINE,
+ ((SpellingEngineDescriptor) sel.getFirstElement()).getId());
+ updateListDependencies();
+ }
+
+ private boolean isPerformRevert() {
+ Shell shell = viewer.getControl().getShell();
+ MessageDialog dialog = new MessageDialog(shell,
+ TextEditorMessages.SpellingConfigurationBlock_error_title, null,
+ TextEditorMessages.SpellingConfigurationBlock_error_message, MessageDialog.QUESTION,
+ new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL }, 1);
+ return dialog.open() == 0;
+ }
+
+ private void revertSelection() {
+ try {
+ viewer.removeSelectionChangedListener(this);
+ SpellingEngineDescriptor desc = EditorsUI.getSpellingService()
+ .getActiveSpellingEngineDescriptor(fStore);
+ if (desc != null) viewer.setSelection(new StructuredSelection(desc), true);
+ } finally {
+ viewer.addSelectionChangedListener(this);
+ }
+ }
+ });
+ viewer.setInput(fProviderDescriptors);
+ viewer.refresh();
+
+ return viewer;
+ }
+
+ private void updateCheckboxDependencies() {
+ final boolean enabled = fEnablementCheckbox.getSelection();
+ if (fComboGroup != null) setEnabled(fComboGroup, enabled);
+ SpellingEngineDescriptor desc = EditorsUI.getSpellingService().getActiveSpellingEngineDescriptor(fStore);
+ String id = desc != null ? desc.getId() : ""; //$NON-NLS-1$
+ final ISpellingPreferenceBlock preferenceBlock = fProviderPreferences.get(id);
+ if (preferenceBlock != null) {
+ ISafeRunnable runnable = new ISafeRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ preferenceBlock.setEnabled(enabled);
+ }
+
+ @Override
+ public void handleException(Throwable x) {}
+ };
+ SafeRunner.run(runnable);
+ }
+ }
+
+ private void setEnabled(Control control, boolean enabled) {
+ if (control instanceof Composite) {
+ Control[] children = ((Composite) control).getChildren();
+ for (Control child : children)
+ setEnabled(child, enabled);
+ }
+ control.setEnabled(enabled);
+ }
+
+ void updateListDependencies() {
+ SpellingEngineDescriptor desc = EditorsUI.getSpellingService().getActiveSpellingEngineDescriptor(fStore);
+ String id;
+ if (desc == null) {
+ // safety in case there is no such descriptor
+ id = ""; //$NON-NLS-1$
+ String message = TextEditorMessages.SpellingConfigurationBlock_error_not_exist;
+ Log.log(new Status(IStatus.WARNING, EditorsUI.PLUGIN_ID, IStatus.OK, message, null));
+ fCurrentBlock = new ErrorPreferences(message);
+ } else {
+ id = desc.getId();
+ fCurrentBlock = fProviderPreferences.get(id);
+ if (fCurrentBlock == null) {
+ try {
+ fCurrentBlock = desc.createPreferences();
+ fProviderPreferences.put(id, fCurrentBlock);
+ } catch (CoreException e) {
+ Log.error(e);
+ fCurrentBlock = new ErrorPreferences(e.getLocalizedMessage());
+ }
+ }
+ }
+
+ Control control = fProviderControls.get(id);
+ if (control == null) {
+ final Control[] result = new Control[1];
+ ISafeRunnable runnable = new ISafeRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ result[0] = fCurrentBlock.createControl(fGroup);
+ }
+
+ @Override
+ public void handleException(Throwable x) {}
+ };
+ SafeRunner.run(runnable);
+ control = result[0];
+ if (control == null) {
+ String message = TextEditorMessages.SpellingConfigurationBlock_info_no_preferences;
+ Log.log(IStatus.WARNING, IStatus.OK, message, null);
+ control = new ErrorPreferences(message).createControl(fGroup);
+ } else {
+ fProviderControls.put(id, control);
+ }
+ }
+ Dialog.applyDialogFont(control);
+ fStackLayout.topControl = control;
+ control.pack();
+ fGroup.layout();
+ fGroup.getParent().layout();
+
+ fStatusMonitor.statusChanged(new StatusInfo());
+ ISafeRunnable runnable = new ISafeRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ fCurrentBlock.initialize(fStatusMonitor);
+ }
+
+ @Override
+ public void handleException(Throwable x) {}
+ };
+ SafeRunner.run(runnable);
+ }
+
+ @Override
+ public void initialize() {
+ restoreFromPreferences();
+ }
+
+ @Override
+ public boolean canPerformOk() {
+ SpellingEngineDescriptor desc = EditorsUI.getSpellingService().getActiveSpellingEngineDescriptor(fStore);
+ String id = desc != null ? desc.getId() : ""; //$NON-NLS-1$
+ final ISpellingPreferenceBlock block = fProviderPreferences.get(id);
+ if (block == null) return true;
+
+ final Boolean[] result = new Boolean[] { Boolean.TRUE };
+ ISafeRunnable runnable = new ISafeRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ result[0] = Boolean.valueOf(block.canPerformOk());
+ }
+
+ @Override
+ public void handleException(Throwable x) {}
+ };
+ SafeRunner.run(runnable);
+ return result[0].booleanValue();
+ }
+
+ @Override
+ public void performOk() {
+ for (ISpellingPreferenceBlock block : fProviderPreferences.values()) {
+ ISafeRunnable runnable = new ISafeRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ block.performOk();
+ }
+
+ @Override
+ public void handleException(Throwable x) {}
+ };
+ SafeRunner.run(runnable);
+ }
+ }
+
+ @Override
+ public void performDefaults() {
+ restoreFromPreferences();
+ for (ISpellingPreferenceBlock block : fProviderPreferences.values()) {
+ ISafeRunnable runnable = new ISafeRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ block.performDefaults();
+ }
+
+ @Override
+ public void handleException(Throwable x) {}
+ };
+ SafeRunner.run(runnable);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ for (ISpellingPreferenceBlock block : fProviderPreferences.values()) {
+ ISafeRunnable runnable = new ISafeRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ block.dispose();
+ }
+
+ @Override
+ public void handleException(Throwable x) {}
+ };
+ SafeRunner.run(runnable);
+ }
+ }
+
+ private void restoreFromPreferences() {
+ if (fEnablementCheckbox == null) return;
+
+ boolean enabled = fStore.getBoolean(SpellingService.PREFERENCE_SPELLING_ENABLED);
+ fEnablementCheckbox.setSelection(enabled);
+
+ if (fProviderViewer == null)
+ updateListDependencies();
+ else {
+ SpellingEngineDescriptor desc = EditorsUI.getSpellingService().getActiveSpellingEngineDescriptor(fStore);
+ if (desc != null) fProviderViewer.setSelection(new StructuredSelection(desc), true);
+ }
+
+ updateCheckboxDependencies();
+ }
+}
diff --git a/plugin/src/winterwell/markdown/spelling/StatusInfo.java b/plugin/src/winterwell/markdown/spelling/StatusInfo.java
new file mode 100644
index 0000000..d576c33
--- /dev/null
+++ b/plugin/src/winterwell/markdown/spelling/StatusInfo.java
@@ -0,0 +1,177 @@
+package winterwell.markdown.spelling;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IStatus;
+
+import org.eclipse.ui.editors.text.EditorsUI;
+
+/**
+ * A settable IStatus.
+ * Can be an error, warning, info or ok. For error, info and warning states,
+ * a message describes the problem.
+ *
+ * @since 2.1
+ */
+class StatusInfo implements IStatus {
+
+ /** The message of this status. */
+ private String fStatusMessage;
+ /** The severity of this status. */
+ private int fSeverity;
+
+ /**
+ * Creates a status set to OK (no message).
+ */
+ public StatusInfo() {
+ this(OK, null);
+ }
+
+ /**
+ * Creates a status with the given severity and message.
+ *
+ * @param severity the severity of this status: ERROR, WARNING, INFO and OK.
+ * @param message the message of this status. Applies only for ERROR,
+ * WARNING and INFO.
+ */
+ public StatusInfo(int severity, String message) {
+ fStatusMessage= message;
+ fSeverity= severity;
+ }
+
+ @Override
+ public boolean isOK() {
+ return fSeverity == IStatus.OK;
+ }
+
+ /**
+ * Returns whether this status indicates a warning.
+ *
+ * @return true if this status has severity
+ * {@link IStatus#WARNING} and false otherwise
+ */
+ public boolean isWarning() {
+ return fSeverity == IStatus.WARNING;
+ }
+
+ /**
+ * Returns whether this status indicates an info.
+ *
+ * @return true if this status has severity
+ * {@link IStatus#INFO} and false otherwise
+ */
+ public boolean isInfo() {
+ return fSeverity == IStatus.INFO;
+ }
+
+ /**
+ * Returns whether this status indicates an error.
+ *
+ * @return true if this status has severity
+ * {@link IStatus#ERROR} and false otherwise
+ */
+ public boolean isError() {
+ return fSeverity == IStatus.ERROR;
+ }
+
+ @Override
+ public String getMessage() {
+ return fStatusMessage;
+ }
+
+ /**
+ * Sets the status to ERROR.
+ *
+ * @param errorMessage the error message which can be an empty string, but not null
+ */
+ public void setError(String errorMessage) {
+ Assert.isNotNull(errorMessage);
+ fStatusMessage= errorMessage;
+ fSeverity= IStatus.ERROR;
+ }
+
+ /**
+ * Sets the status to WARNING.
+ *
+ * @param warningMessage the warning message which can be an empty string, but not null
+ */
+ public void setWarning(String warningMessage) {
+ Assert.isNotNull(warningMessage);
+ fStatusMessage= warningMessage;
+ fSeverity= IStatus.WARNING;
+ }
+
+ /**
+ * Sets the status to INFO.
+ *
+ * @param infoMessage the info message which can be an empty string, but not null
+ */
+ public void setInfo(String infoMessage) {
+ Assert.isNotNull(infoMessage);
+ fStatusMessage= infoMessage;
+ fSeverity= IStatus.INFO;
+ }
+
+ /**
+ * Sets the status to OK.
+ */
+ public void setOK() {
+ fStatusMessage= null;
+ fSeverity= IStatus.OK;
+ }
+
+ @Override
+ public boolean matches(int severityMask) {
+ return (fSeverity & severityMask) != 0;
+ }
+
+ /**
+ * Returns always false.
+ *
+ * @see IStatus#isMultiStatus()
+ */
+ @Override
+ public boolean isMultiStatus() {
+ return false;
+ }
+
+ @Override
+ public int getSeverity() {
+ return fSeverity;
+ }
+
+ @Override
+ public String getPlugin() {
+ return EditorsUI.PLUGIN_ID;
+ }
+
+ /**
+ * Returns always null.
+ *
+ * @see IStatus#getException()
+ */
+ @Override
+ public Throwable getException() {
+ return null;
+ }
+
+ /**
+ * Returns always the error severity.
+ *
+ * @see IStatus#getCode()
+ */
+ @Override
+ public int getCode() {
+ return fSeverity;
+ }
+
+ /**
+ * Returns always null.
+ *
+ * @see IStatus#getChildren()
+ */
+ @Override
+ public IStatus[] getChildren() {
+ return new IStatus[0];
+ }
+
+}
diff --git a/plugin/src/winterwell/markdown/spelling/StatusUtil.java b/plugin/src/winterwell/markdown/spelling/StatusUtil.java
new file mode 100644
index 0000000..e4c6bed
--- /dev/null
+++ b/plugin/src/winterwell/markdown/spelling/StatusUtil.java
@@ -0,0 +1,81 @@
+package winterwell.markdown.spelling;
+
+import org.eclipse.core.runtime.IStatus;
+
+import org.eclipse.jface.dialogs.DialogPage;
+import org.eclipse.jface.dialogs.IMessageProvider;
+
+/**
+ * A utility class to work with IStatus.
+ *
+ * @since 3.1
+ */
+public class StatusUtil {
+
+ /**
+ * Compares two instances of {@link IStatus}. The more severe is returned: An error is more
+ * severe than a warning, and a warning is more severe than OK. If the two statuses have the
+ * same severity, the second is returned.
+ *
+ * @param s1 a status object
+ * @param s2 a status object
+ * @return the more severe status
+ */
+ public static IStatus getMoreSevere(IStatus s1, IStatus s2) {
+ if (s1.getSeverity() > s2.getSeverity()) return s1;
+
+ return s2;
+ }
+
+ /**
+ * Finds the most severe status from a array of statuses. An error is more severe than a
+ * warning, and a warning is more severe than OK.
+ *
+ * @param status an array with status objects
+ * @return the most severe status object
+ */
+ public static IStatus getMostSevere(IStatus[] status) {
+ IStatus max = null;
+ for (int i = 0; i < status.length; i++) {
+ IStatus curr = status[i];
+ if (curr.matches(IStatus.ERROR)) {
+ return curr;
+ }
+ if (max == null || curr.getSeverity() > max.getSeverity()) {
+ max = curr;
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Applies the status to the status line of a dialog page.
+ *
+ * @param page the dialog page
+ * @param status the status
+ */
+ public static void applyToStatusLine(DialogPage page, IStatus status) {
+ String message = status.getMessage();
+ switch (status.getSeverity()) {
+ case IStatus.OK:
+ page.setMessage(message, IMessageProvider.NONE);
+ page.setErrorMessage(null);
+ break;
+ case IStatus.WARNING:
+ page.setMessage(message, IMessageProvider.WARNING);
+ page.setErrorMessage(null);
+ break;
+ case IStatus.INFO:
+ page.setMessage(message, IMessageProvider.INFORMATION);
+ page.setErrorMessage(null);
+ break;
+ default:
+ if (message.length() == 0) {
+ message = null;
+ }
+ page.setMessage(null);
+ page.setErrorMessage(message);
+ break;
+ }
+ }
+}
diff --git a/plugin/src/winterwell/markdown/spelling/TextEditorMessages.java b/plugin/src/winterwell/markdown/spelling/TextEditorMessages.java
new file mode 100644
index 0000000..92500f0
--- /dev/null
+++ b/plugin/src/winterwell/markdown/spelling/TextEditorMessages.java
@@ -0,0 +1,153 @@
+package winterwell.markdown.spelling;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Helper class to get NLSed messages.
+ *
+ * @since 2.1
+ */
+public final class TextEditorMessages extends NLS {
+
+ private static final String BUNDLE_NAME = TextEditorMessages.class.getName();
+
+ private TextEditorMessages() {
+ // Do not instantiate
+ }
+
+ public static String AnnotationsConfigurationBlock_DASHED_BOX;
+ public static String EditorsPlugin_additionalInfo_affordance;
+ public static String EditorsPlugin_internal_error;
+ public static String LinkedModeConfigurationBlock_DASHED_BOX;
+ public static String TextEditorPreferencePage_displayedTabWidth;
+ public static String TextEditorPreferencePage_enableWordWrap;
+ public static String TextEditorPreferencePage_convertTabsToSpaces;
+ public static String TextEditorPreferencePage_undoHistorySize;
+ public static String TextEditorPreferencePage_printMarginColumn;
+ public static String TextEditorPreferencePage_showLineNumbers;
+ public static String TextEditorPreferencePage_highlightCurrentLine;
+ public static String TextEditorPreferencePage_showPrintMargin;
+ public static String TextEditorPreferencePage_color;
+ public static String TextEditorPreferencePage_appearanceOptions;
+ public static String TextEditorPreferencePage_lineNumberForegroundColor;
+ public static String TextEditorPreferencePage_currentLineHighlighColor;
+ public static String TextEditorPreferencePage_printMarginColor;
+ public static String TextEditorPreferencePage_foregroundColor;
+ public static String TextEditorPreferencePage_backgroundColor;
+ public static String TextEditorPreferencePage_findScopeColor;
+ public static String TextEditorPreferencePage_accessibility_disableCustomCarets;
+ public static String TextEditorPreferencePage_accessibility_wideCaret;
+ public static String TextEditorPreferencePage_accessibility_useSaturatedColorsInOverviewRuler;
+ public static String TextEditorPreferencePage_showAffordance;
+ public static String TextEditorPreferencePage_selectionForegroundColor;
+ public static String TextEditorPreferencePage_selectionBackgroundColor;
+ public static String TextEditorPreferencePage_systemDefault;
+ public static String TextEditorPreferencePage_invalidInput;
+ public static String TextEditorPreferencePage_invalidRange;
+ public static String TextEditorPreferencePage_emptyInput;
+ public static String TextEditorPreferencePage_colorsAndFonts_link;
+ public static String TextEditorPreferencePage_Font_link;
+ public static String QuickDiffConfigurationBlock_description;
+ public static String QuickDiffConfigurationBlock_referenceProviderTitle;
+ public static String QuickDiffConfigurationBlock_referenceProviderNoteMessage;
+ public static String QuickDiffConfigurationBlock_referenceProviderNoteTitle;
+ public static String QuickDiffConfigurationBlock_characterMode;
+ public static String QuickDiffConfigurationBlock_showForNewEditors;
+ public static String QuickDiffConfigurationBlock_showInOverviewRuler;
+ public static String QuickDiffConfigurationBlock_colorTitle;
+ public static String QuickDiffConfigurationBlock_changeColor;
+ public static String QuickDiffConfigurationBlock_additionColor;
+ public static String QuickDiffConfigurationBlock_deletionColor;
+ public static String NewTextEditorAction_namePrefix;
+ public static String AnnotationsConfigurationBlock_description;
+ public static String AnnotationsConfigurationBlock_showInText;
+ public static String AnnotationsConfigurationBlock_showInOverviewRuler;
+ public static String AnnotationsConfigurationBlock_showInVerticalRuler;
+ public static String AnnotationsConfigurationBlock_isNavigationTarget;
+ public static String AnnotationsConfigurationBlock_annotationPresentationOptions;
+ public static String AnnotationsConfigurationBlock_SQUIGGLES;
+ public static String AnnotationsConfigurationBlock_PROBLEM_UNDERLINE;
+ public static String AnnotationsConfigurationBlock_UNDERLINE;
+ public static String AnnotationsConfigurationBlock_BOX;
+ public static String AnnotationsConfigurationBlock_IBEAM;
+ public static String AnnotationsConfigurationBlock_HIGHLIGHT;
+ public static String AnnotationsConfigurationBlock_labels_showIn;
+ public static String AnnotationsConfigurationBlock_color;
+ public static String HyperlinkDetectorsConfigurationBlock_description;
+ public static String HyperlinkDetectorTable_nameColumn;
+ public static String HyperlinkDetectorTable_modifierKeysColumn;
+ public static String HyperlinkDetectorTable_targetNameColumn;
+ public static String SelectResourcesDialog_filterSelection;
+ public static String SelectResourcesDialog_deselectAll;
+ public static String SelectResourcesDialog_selectAll;
+ public static String SelectResourcesDialog_noFilesSelected;
+ public static String SelectResourcesDialog_oneFileSelected;
+ public static String SelectResourcesDialog_nFilesSelected;
+ public static String ConvertLineDelimitersAction_convert_all;
+ public static String ConvertLineDelimitersAction_convert_text;
+ public static String ConvertLineDelimitersAction_default_label;
+ public static String ConvertLineDelimitersAction_dialog_title;
+ public static String ConvertLineDelimitersToWindows_label;
+ public static String ConvertLineDelimitersToUnix_label;
+ public static String ConvertLineDelimitersAction_dialog_description;
+ public static String ConvertLineDelimitersAction_nontext_selection;
+ public static String ConvertLineDelimitersAction_show_only_text_files;
+ public static String RemoveTrailingWhitespaceHandler_dialog_title;
+ public static String RemoveTrailingWhitespaceHandler_dialog_description;
+ public static String HyperlinksEnabled_label;
+ public static String HyperlinkColor_label;
+ public static String HyperlinkKeyModifier_label;
+ public static String HyperlinkDefaultKeyModifier_label;
+ public static String HyperlinkKeyModifier_error_modifierIsNotValid;
+ public static String HyperlinkKeyModifier_error_shiftIsDisabled;
+ public static String HyperlinkKeyModifier_delimiter;
+ public static String HyperlinkKeyModifier_concatModifierStrings;
+ public static String HyperlinkKeyModifier_insertDelimiterAndModifier;
+ public static String HyperlinkKeyModifier_insertDelimiterAndModifierAndDelimiter;
+ public static String HyperlinkKeyModifier_insertModifierAndDelimiter;
+ public static String AccessibilityPreferencePage_accessibility_title;
+ public static String SpellingConfigurationBlock_enable;
+ public static String SpellingConfigurationBlock_combo_caption;
+ public static String SpellingConfigurationBlock_info_no_preferences;
+ public static String SpellingConfigurationBlock_error_not_installed;
+ public static String SpellingConfigurationBlock_error_not_exist;
+ public static String SpellingConfigurationBlock_error_title;
+ public static String SpellingConfigurationBlock_error_message;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, TextEditorMessages.class);
+ }
+
+ public static String TextEditorDefaultsPreferencePage_carriageReturn;
+ public static String TextEditorDefaultsPreferencePage_transparencyLevel;
+ public static String TextEditorDefaultsPreferencePage_configureWhitespaceCharacterPainterProperties;
+ public static String TextEditorDefaultsPreferencePage_enclosed;
+ public static String TextEditorDefaultsPreferencePage_enrichHoverMode;
+ public static String TextEditorDefaultsPreferencePage_enrichHover_immediately;
+ public static String TextEditorDefaultsPreferencePage_enrichHover_afterDelay;
+ public static String TextEditorDefaultsPreferencePage_enrichHover_disabled;
+ public static String TextEditorDefaultsPreferencePage_enrichHover_onClick;
+ public static String TextEditorDefaultsPreferencePage_ideographicSpace;
+ public static String TextEditorDefaultsPreferencePage_leading;
+ public static String TextEditorDefaultsPreferencePage_lineFeed;
+ public static String TextEditorDefaultsPreferencePage_range_indicator;
+ public static String TextEditorDefaultsPreferencePage_smartHomeEnd;
+ public static String TextEditorDefaultsPreferencePage_warn_if_derived;
+ public static String TextEditorDefaultsPreferencePage_showWhitespaceCharacters;
+ public static String TextEditorDefaultsPreferencePage_showWhitespaceCharactersLinkText;
+ public static String TextEditorDefaultsPreferencePage_showWhitespaceCharactersDialogInvalidInput;
+ public static String TextEditorDefaultsPreferencePage_showWhitespaceCharactersDialogTitle;
+ public static String TextEditorDefaultsPreferencePage_space;
+ public static String TextEditorDefaultsPreferencePage_tab;
+ public static String TextEditorDefaultsPreferencePage_textDragAndDrop;
+ public static String TextEditorDefaultsPreferencePage_trailing;
+ public static String LinkedModeConfigurationBlock_annotationPresentationOptions;
+ public static String LinkedModeConfigurationBlock_SQUIGGLES;
+ public static String LinkedModeConfigurationBlock_UNDERLINE;
+ public static String LinkedModeConfigurationBlock_BOX;
+ public static String LinkedModeConfigurationBlock_IBEAM;
+ public static String LinkedModeConfigurationBlock_HIGHLIGHT;
+ public static String LinkedModeConfigurationBlock_labels_showIn;
+ public static String LinkedModeConfigurationBlock_color;
+ public static String LinkedModeConfigurationBlock_linking_title;
+}
diff --git a/plugin/src/winterwell/markdown/spelling/TextEditorMessages.properties b/plugin/src/winterwell/markdown/spelling/TextEditorMessages.properties
new file mode 100644
index 0000000..053cf9f
--- /dev/null
+++ b/plugin/src/winterwell/markdown/spelling/TextEditorMessages.properties
@@ -0,0 +1,160 @@
+###############################################################################
+# Copyright (c) 2000, 2015 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+###############################################################################
+
+EditorsPlugin_additionalInfo_affordance=Press 'Tab' from proposal table or click for focus
+EditorsPlugin_internal_error=Internal Error
+
+TextEditorPreferencePage_displayedTabWidth=Displayed &tab width:
+TextEditorPreferencePage_enableWordWrap=&Enable word wrap when opening an editor
+TextEditorPreferencePage_convertTabsToSpaces=&Insert spaces for tabs
+TextEditorPreferencePage_undoHistorySize=&Undo history size:
+TextEditorPreferencePage_printMarginColumn=Print margin colu&mn:
+TextEditorPreferencePage_showLineNumbers=Show line num&bers
+TextEditorPreferencePage_highlightCurrentLine=Hi&ghlight current line
+TextEditorPreferencePage_showPrintMargin=Sho&w print margin
+TextEditorPreferencePage_color=&Color:
+TextEditorPreferencePage_appearanceOptions=Appearance co&lor options:
+TextEditorPreferencePage_lineNumberForegroundColor=Line number foreground
+TextEditorPreferencePage_currentLineHighlighColor=Current line highlight
+TextEditorPreferencePage_printMarginColor=Print margin
+TextEditorPreferencePage_foregroundColor=Foreground color
+TextEditorPreferencePage_backgroundColor=Background color
+TextEditorPreferencePage_findScopeColor=Find scope
+TextEditorPreferencePage_accessibility_disableCustomCarets= Use &custom caret
+TextEditorPreferencePage_accessibility_wideCaret= &Enable thick caret
+TextEditorPreferencePage_accessibility_useSaturatedColorsInOverviewRuler=U&se saturated colors in overview ruler
+TextEditorDefaultsPreferencePage_carriageReturn=Carriage Return ( \u00a4 )
+TextEditorDefaultsPreferencePage_transparencyLevel=&Transparency level (0 is transparent and 255 is opaque):
+TextEditorDefaultsPreferencePage_configureWhitespaceCharacterPainterProperties=Configure visibility of whitespace characters in different regions of a line of text:
+TextEditorDefaultsPreferencePage_enclosed=Enclosed
+TextEditorDefaultsPreferencePage_enrichHoverMode=When mouse mo&ved into hover:
+TextEditorDefaultsPreferencePage_enrichHover_afterDelay=Enrich after delay
+TextEditorDefaultsPreferencePage_enrichHover_disabled=Close hover
+TextEditorDefaultsPreferencePage_enrichHover_immediately=Enrich immediately
+TextEditorDefaultsPreferencePage_enrichHover_onClick=Enrich on click
+TextEditorDefaultsPreferencePage_ideographicSpace=Ideographic space ( \u00b0 )
+TextEditorDefaultsPreferencePage_leading=Leading
+TextEditorDefaultsPreferencePage_lineFeed=Line Feed ( \u00b6 )
+TextEditorDefaultsPreferencePage_range_indicator=Show &range indicator
+TextEditorDefaultsPreferencePage_warn_if_derived= War&n before editing a derived file
+TextEditorDefaultsPreferencePage_smartHomeEnd= &Smart caret positioning at line start and end
+TextEditorDefaultsPreferencePage_showWhitespaceCharacters= Sh&ow whitespace characters
+TextEditorDefaultsPreferencePage_showWhitespaceCharactersLinkText= (configure visibility)
+TextEditorDefaultsPreferencePage_showWhitespaceCharactersDialogInvalidInput=''{0}'' is not a valid input.
+TextEditorDefaultsPreferencePage_showWhitespaceCharactersDialogTitle=Show Whitespace Characters
+TextEditorDefaultsPreferencePage_space=Space ( \u00b7 )
+TextEditorDefaultsPreferencePage_tab=Tab ( \u00bb )
+TextEditorDefaultsPreferencePage_textDragAndDrop= Enable drag and dro&p of text
+TextEditorDefaultsPreferencePage_trailing=Trailing
+TextEditorPreferencePage_colorsAndFonts_link= More colors can be configured on the 'Colors and Fonts' preference page.
+TextEditorPreferencePage_Font_link= Some editors may not honor all of these settings.\n\
+\n\
+See 'Colors and Fonts' to configure the font.
+
+TextEditorPreferencePage_selectionForegroundColor= Selection foreground color
+TextEditorPreferencePage_selectionBackgroundColor= Selection background color
+TextEditorPreferencePage_systemDefault= System De&fault
+
+TextEditorPreferencePage_invalidInput= ''{0}'' is not a valid input.
+TextEditorPreferencePage_invalidRange= Value must be in between ''{0}'' and ''{1}''.
+TextEditorPreferencePage_emptyInput= Empty input.
+
+TextEditorPreferencePage_showAffordance= S&how affordance in hover on how to make it sticky
+
+
+QuickDiffConfigurationBlock_description= General Quick Diff settings.
+QuickDiffConfigurationBlock_referenceProviderTitle= &Use this reference source:
+QuickDiffConfigurationBlock_referenceProviderNoteMessage= Changing the reference source does not update open editors.
+QuickDiffConfigurationBlock_referenceProviderNoteTitle= Note:
+QuickDiffConfigurationBlock_characterMode= &Use characters to show changes in vertical ruler
+QuickDiffConfigurationBlock_showForNewEditors= &Enable quick diff
+QuickDiffConfigurationBlock_showInOverviewRuler= Show differences in &overview ruler
+QuickDiffConfigurationBlock_colorTitle= Colo&rs
+QuickDiffConfigurationBlock_changeColor= C&hanges:
+QuickDiffConfigurationBlock_additionColor= Addi&tions:
+QuickDiffConfigurationBlock_deletionColor= De&letions:
+
+NewTextEditorAction_namePrefix=Untitled
+
+AnnotationsConfigurationBlock_description= General annotation settings.
+AnnotationsConfigurationBlock_showInText=&Text as
+AnnotationsConfigurationBlock_DASHED_BOX=Dashed Box
+AnnotationsConfigurationBlock_showInOverviewRuler=&Overview ruler
+AnnotationsConfigurationBlock_showInVerticalRuler=&Vertical ruler
+AnnotationsConfigurationBlock_isNavigationTarget=&Include in next/previous navigation
+AnnotationsConfigurationBlock_annotationPresentationOptions=Annotation ty&pes:
+AnnotationsConfigurationBlock_SQUIGGLES=Squiggly Line
+AnnotationsConfigurationBlock_PROBLEM_UNDERLINE=Native Problem Underline
+AnnotationsConfigurationBlock_UNDERLINE=Underlined
+AnnotationsConfigurationBlock_BOX=Box
+AnnotationsConfigurationBlock_IBEAM=Vertical Bar
+AnnotationsConfigurationBlock_HIGHLIGHT=Highlighted
+AnnotationsConfigurationBlock_labels_showIn=Show in
+AnnotationsConfigurationBlock_color=&Color:
+
+HyperlinkDetectorsConfigurationBlock_description= On demand hyperlinks are shown when moving the mouse in the editor while the specified modifier is pressed. The hyperlinks appear on mouse move when no modifier is specified.\n
+HyperlinksEnabled_label= &Enable on demand hyperlink style navigation
+HyperlinkDetectorTable_nameColumn= Link Kind
+HyperlinkDetectorTable_modifierKeysColumn= Modifier Keys
+HyperlinkDetectorTable_targetNameColumn= Available In
+HyperlinkColor_label=Hyperlink
+HyperlinkDefaultKeyModifier_label= De&fault modifier key:
+HyperlinkKeyModifier_label= &Modifier keys for selected detector:
+HyperlinkKeyModifier_error_modifierIsNotValid= Modifier ''{0}'' is not valid.
+HyperlinkKeyModifier_error_shiftIsDisabled= The modifier 'Shift' is not allowed because 'Shift' + click sets a new selection.
+HyperlinkKeyModifier_delimiter= +
+HyperlinkKeyModifier_concatModifierStrings= {0} + {1}
+HyperlinkKeyModifier_insertDelimiterAndModifier= \ + {0}
+# The following two property values need to end with a space
+HyperlinkKeyModifier_insertDelimiterAndModifierAndDelimiter= \ + {0} +\
+HyperlinkKeyModifier_insertModifierAndDelimiter= \ {0} +\
+
+SelectResourcesDialog_filterSelection= &Filter Selection...
+SelectResourcesDialog_deselectAll= &Deselect All
+SelectResourcesDialog_selectAll= &Select All
+SelectResourcesDialog_noFilesSelected= No file selected.
+SelectResourcesDialog_oneFileSelected= 1 file selected.
+SelectResourcesDialog_nFilesSelected= {0} files selected.
+
+ConvertLineDelimitersAction_convert_all=Convert &All
+ConvertLineDelimitersAction_convert_text=Convert &Text Files
+ConvertLineDelimitersAction_default_label=\ [default]
+ConvertLineDelimitersAction_dialog_title=Convert Line Delimiters to {0}
+ConvertLineDelimitersToWindows_label=&Windows (CRLF, \\r\\n, 0D0A, \u00A4\u00B6)
+ConvertLineDelimitersToUnix_label=&Unix (LF, \\n, 0A, \u00B6)
+ConvertLineDelimitersAction_dialog_description=Select files to convert:
+ConvertLineDelimitersAction_nontext_selection=The selection contains files that could have binary content. Do you want to convert all files or only text files?
+ConvertLineDelimitersAction_show_only_text_files=Show only &text files
+RemoveTrailingWhitespaceHandler_dialog_title=Remove Trailing Whitespace
+RemoveTrailingWhitespaceHandler_dialog_description=Select files:
+
+
+AccessibilityPreferencePage_accessibility_title=Accessibility
+
+SpellingConfigurationBlock_enable= &Enable spell checking
+SpellingConfigurationBlock_combo_caption= Select spelling engine to &use:
+SpellingConfigurationBlock_info_no_preferences= The selected spelling engine did not provide a preference control
+SpellingConfigurationBlock_error_not_installed= The spelling service is not installed.
+SpellingConfigurationBlock_error_not_exist= The selected spelling engine does not exist
+SpellingConfigurationBlock_error_title=Dismiss Changes
+SpellingConfigurationBlock_error_message=The currently displayed spelling engine preferences contain invalid values. Dismiss changes?
+
+# linked mode
+LinkedModeConfigurationBlock_annotationPresentationOptions= &Ranges:
+LinkedModeConfigurationBlock_SQUIGGLES=Squiggles
+LinkedModeConfigurationBlock_UNDERLINE=Underlined
+LinkedModeConfigurationBlock_BOX=Box
+LinkedModeConfigurationBlock_IBEAM=Vertical Bar
+LinkedModeConfigurationBlock_HIGHLIGHT=Highlighted
+LinkedModeConfigurationBlock_DASHED_BOX=Dashed Box
+LinkedModeConfigurationBlock_labels_showIn=&Show in text as:
+LinkedModeConfigurationBlock_color= C&olor:
+LinkedModeConfigurationBlock_linking_title=Lin&ked Mode
diff --git a/plugin/src/winterwell/markdown/util/Environment.java b/plugin/src/winterwell/markdown/util/Environment.java
new file mode 100644
index 0000000..ffdd1f2
--- /dev/null
+++ b/plugin/src/winterwell/markdown/util/Environment.java
@@ -0,0 +1,114 @@
+package winterwell.markdown.util;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Stack;
+
+public final class Environment implements IProperties {
+
+ private static final Map defaultProperties = new HashMap();
+ private static final Environment dflt = new Environment();
+ private final ThreadLocal