├── .gitignore ├── .project ├── .travis.yml ├── README.md ├── feature ├── .project ├── build.properties ├── feature.xml └── pom.xml ├── license.txt ├── overview.png ├── plugin ├── .classpath ├── .project ├── META-INF │ └── MANIFEST.MF ├── build.properties ├── icons │ ├── coffee.png │ ├── github-cat_yellow.png │ ├── notepad.gif │ └── settings16_yellow.png ├── lib │ ├── markdownj-1.0.2b4-0.3.0.jar │ ├── net.sf.paperclips_1.0.1.jar │ └── winterwell.utils.jar ├── plugin.xml ├── pom.xml └── src │ └── winterwell │ └── markdown │ ├── Activator.java │ ├── LogUtil.java │ ├── StringMethods.java │ ├── commands │ ├── OpenGfmView.java │ ├── OpenMdView.java │ └── Preferences.java │ ├── editors │ ├── ActionBarContributor.java │ ├── ColorManager.java │ ├── EmphasisRule.java │ ├── ExportHTMLAction.java │ ├── FormatAction.java │ ├── HeaderRule.java │ ├── HeaderWithUnderlineRule.java │ ├── LinkRule.java │ ├── ListRule.java │ ├── MDColorConstants.java │ ├── MDConfiguration.java │ ├── MDScanner.java │ ├── MDTextHover.java │ ├── MarkdownContentOutlinePage.java │ ├── MarkdownEditor.java │ ├── PrintAction.java │ ├── collapseall.gif │ └── synced.gif │ ├── pagemodel │ ├── MarkdownFormatter.java │ ├── MarkdownFormatterTest.java │ ├── MarkdownPage.java │ └── MarkdownPageTest.java │ ├── preferences │ └── MarkdownPreferencePage.java │ └── views │ └── MarkdownPreview.java ├── pom.xml ├── site ├── category.xml ├── pom.xml └── src │ └── main │ └── resources │ └── index.html └── updatesite ├── index.html ├── site.xml └── web ├── site.css └── site.xsl /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Eclipse autogenerated files 2 | #.classpath 3 | #.project 4 | .settings/ 5 | bin/ 6 | target/ 7 | 8 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Eclipse-Markdown-Editor 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | notifications: 4 | email: false -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eclipse Markdown Editor Plugin 2 | 3 | [![Build Status](https://secure.travis-ci.org/winterstein/Eclipse-Markdown-Editor-Plugin.png)](http://travis-ci.org/winterstein/Eclipse-Markdown-Editor-Plugin) 4 | 6 | 7 | 8 | 9 | Edit .md and .txt files with syntax highlighting. 10 | Provides Outline and Preview HTML views. 11 | 12 | Please see the website for information: 13 | 14 | 15 | Eclipse Marketplace entry: 16 | 17 | or install with [Nodeclipse CLI Installer](https://github.com/Nodeclipse/nodeclipse-1/tree/master/org.nodeclipse.ui/templates) `nodeclipse install markdown` 18 | 19 | There is also a complementary GitHub Flavoured Markdown Viewer 20 | 21 | 22 | For dedicated AsciiDoc (`.adoc`) support, see 23 | 24 | 25 | 26 | ## Usage 27 | 28 | ![](http://marketplace.eclipse.org/sites/default/files/Markdown-Editor-1.1.0.PNG) 29 | 30 | Note that for HTML preview OS the internal browser is used, e.g. Internet Explorer on Windows; if you use Ubuntu or other Linux distros, we recommend the WebKit browser; see: 31 | 32 | - 33 | - 34 | - 35 | 36 | ## Eclipse Dev Details 37 | 38 | You need Eclipse with PDE, e.g. Eclipse Standard 39 | 40 | 41 | 42 | ![](overview.png) 43 | 44 | Main Editor class `winterwell.markdown.editors.MarkdownEditor` defined as 45 | 46 | 53 | 54 | 55 | ### Build 56 | 57 | mvn package 58 | 59 | then check `site\target` directory for update site archive `markdown.editor.site-x.x.x.zip` and p2 repository. 60 | Use Help -> Install New Software... -> Add... -> Archive to istall from .zip file. 61 | 62 | Increase version 63 | 64 | mvn -Dtycho.mode=maven org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=1.2.0-SNAPSHOT 65 | 66 | ### History 67 | 68 | - 1.0 69 | - 1.1 (24 Feb 2014) by Telmo Brugnara @tbrugz #40 70 | - Rich color preferences #35 #37 71 | - 1.2 (Jan 2015) by Olivier Martin @oliviermartin #52 72 | - Update preview when the file is saved #48 73 | - MultiMarkdown metadata #49 74 | - GitHub code blocks #50 75 | - detecting links #51 76 | - open GFM View from Markdown View #53 77 | 78 | with-Eclipse logo 79 | -------------------------------------------------------------------------------- /feature/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | markdown.editor.feature 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.pde.FeatureBuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.m2e.core.maven2Nature 21 | org.eclipse.pde.FeatureNature 22 | 23 | 24 | -------------------------------------------------------------------------------- /feature/build.properties: -------------------------------------------------------------------------------- 1 | bin.includes = feature.xml 2 | src.includes = feature.xml,\ 3 | build.properties,\ 4 | .project 5 | -------------------------------------------------------------------------------- /feature/feature.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | Support for editing the text-markup language Markdown. Also provides 10 | some nice enhancements to the text editor. 11 | <p> 12 | Markdown Features: 13 | 14 | - Content outline 15 | - Section folding 16 | - Export to HTML and HTML Preview 17 | <p> 18 | General Text Features: 19 | - Word wrapping 20 | - TODO task tags 21 | - Format paragraph command 22 | 23 | The download includes all source code and project files. 24 | 25 | 26 | 27 | Developed by Daniel Winterstein. 28 | Except where otherwise stated, this program and all related material are copyright (c) Winterwell Associates Ltd, 2008-2012 and ThinkTank Mathematics Ltd, 2007. 29 | 30 | Contains code from the MarkdownJ project, released under a BSD license. See http://sourceforge.net/projects/markdownj/ for more information. 31 | 32 | The Markdown syntax is copyright (c) 2004, John Gruber. It is used under a BSD-style license. See http://daringfireball.net/projects/markdown/license for more information. 33 | 34 | 35 | 36 | Eclipse Public License 37 | 38 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /feature/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.winterwell.markdown 9 | markdown.editor.parent 10 | 1.2.0-SNAPSHOT 11 | 12 | 13 | markdown.editor.feature 14 | eclipse-feature 15 | 16 | Markdown Editor (feature) 17 | Markdown Editor (feature) 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | 2 | # Eclipse Public License - v 1.0 3 | 4 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 5 | 6 | ## 1. DEFINITIONS 7 | 8 | "Contribution" means: 9 | 10 | a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and 11 | b) in the case of each subsequent Contributor: 12 | i) changes to the Program, and 13 | ii) additions to the Program; 14 | where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. 15 | "Contributor" means any person or entity that distributes the Program. 16 | "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 17 | "Program" means the Contributions distributed in accordance with this Agreement. 18 | "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 19 | 20 | ## 2. GRANT OF RIGHTS 21 | 22 | a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. 23 | b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 24 | c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 25 | d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 26 | 27 | ## 3. REQUIREMENTS 28 | 29 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 30 | 31 | a) it complies with the terms and conditions of this Agreement; and 32 | b) its license agreement: 33 | i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 34 | ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 35 | iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and 36 | iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. 37 | 38 | When the Program is made available in source code form: 39 | 40 | a) it must be made available under this Agreement; and 41 | b) a copy of this Agreement must be included with each copy of the Program. 42 | Contributors may not remove or alter any copyright notices contained within the Program. 43 | Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 44 | 45 | ## 4. COMMERCIAL DISTRIBUTION 46 | 47 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 48 | 49 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 50 | 51 | ## 5. NO WARRANTY 52 | 53 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 54 | 55 | ## 6. DISCLAIMER OF LIABILITY 56 | 57 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 58 | 59 | ## 7. GENERAL 60 | 61 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 62 | 63 | If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 64 | 65 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 66 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. 67 | This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. -------------------------------------------------------------------------------- /overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/overview.png -------------------------------------------------------------------------------- /plugin/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /plugin/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | winterwell.markdown 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.pde.ManifestBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.pde.SchemaBuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.m2e.core.maven2Nature 31 | org.eclipse.pde.PluginNature 32 | org.eclipse.jdt.core.javanature 33 | 34 | 35 | -------------------------------------------------------------------------------- /plugin/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Bundle-ManifestVersion: 2 3 | Bundle-Name: Markdown 4 | Bundle-SymbolicName: winterwell.markdown;singleton:=true 5 | Bundle-Version: 1.2.0.qualifier 6 | Bundle-Activator: winterwell.markdown.Activator 7 | Bundle-Vendor: Winterwell Associates Ltd 8 | Require-Bundle: org.eclipse.ui, 9 | org.eclipse.core.runtime, 10 | org.eclipse.ui.editors, 11 | org.eclipse.jface.text, 12 | org.eclipse.core.resources, 13 | org.eclipse.ui.views, 14 | org.eclipse.jface, 15 | org.eclipse.swt, 16 | org.eclipse.ui.workbench 17 | Bundle-RequiredExecutionEnvironment: JavaSE-1.6 18 | Bundle-ActivationPolicy: lazy 19 | Import-Package: org.eclipse.core.internal.resources, 20 | org.eclipse.jface.text, 21 | org.eclipse.ui.texteditor 22 | Bundle-ClassPath: .,target/classes,lib/markdownj-1.0.2b4-0.3.0.jar, 23 | lib/winterwell.utils.jar, 24 | lib/net.sf.paperclips_1.0.1.jar 25 | Export-Package: com.petebevin.markdown, 26 | com.petebevin.markdown.test, 27 | net.sf.paperclips, 28 | net.sf.paperclips.decorator, 29 | winterwell.markdown, 30 | winterwell.markdown.editors, 31 | winterwell.markdown.pagemodel, 32 | winterwell.markdown.preferences, 33 | winterwell.markdown.views, 34 | winterwell.utils, 35 | winterwell.utils.containers, 36 | winterwell.utils.gui, 37 | winterwell.utils.io, 38 | winterwell.utils.reporting, 39 | winterwell.utils.threads, 40 | winterwell.utils.time, 41 | winterwell.utils.web 42 | -------------------------------------------------------------------------------- /plugin/build.properties: -------------------------------------------------------------------------------- 1 | source.. = src/ 2 | bin.includes = META-INF/,\ 3 | plugin.xml,\ 4 | icons/,\ 5 | .,\ 6 | lib/ 7 | src.includes = src/,\ 8 | pom.xml,\ 9 | plugin.xml,\ 10 | icons/,\ 11 | lib/,\ 12 | .project,\ 13 | .classpath,\ 14 | META-INF/,\ 15 | build.properties 16 | -------------------------------------------------------------------------------- /plugin/icons/coffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/plugin/icons/coffee.png -------------------------------------------------------------------------------- /plugin/icons/github-cat_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/plugin/icons/github-cat_yellow.png -------------------------------------------------------------------------------- /plugin/icons/notepad.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/plugin/icons/notepad.gif -------------------------------------------------------------------------------- /plugin/icons/settings16_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/plugin/icons/settings16_yellow.png -------------------------------------------------------------------------------- /plugin/lib/markdownj-1.0.2b4-0.3.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/plugin/lib/markdownj-1.0.2b4-0.3.0.jar -------------------------------------------------------------------------------- /plugin/lib/net.sf.paperclips_1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/plugin/lib/net.sf.paperclips_1.0.1.jar -------------------------------------------------------------------------------- /plugin/lib/winterwell.utils.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/plugin/lib/winterwell.utils.jar -------------------------------------------------------------------------------- /plugin/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 15 | 16 | 17 | 25 | 26 | 27 | 28 | 29 | 31 | 34 | 38 | 39 | 43 | 44 | 45 | 48 | 50 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 63 | 67 | 68 | 71 | 72 | 76 | 77 | 78 | 79 | 81 | 84 | 90 | 91 | 93 | 98 | 100 | 101 | 102 | 103 | 105 | 108 | 109 | 110 | 112 | 117 | 118 | 119 | 121 | 126 | 127 | 128 | 130 | 133 | 140 | 141 | 142 | 143 | 145 | 148 | 149 | 151 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.winterwell.markdown 7 | markdown.editor.parent 8 | 1.2.0-SNAPSHOT 9 | 10 | 11 | winterwell.markdown 12 | eclipse-plugin 13 | 14 | Markdown Editor (plugin) 15 | Markdown Editor Plugin for Eclipse 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/Activator.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown; 2 | 3 | import org.eclipse.ui.plugin.AbstractUIPlugin; 4 | import org.osgi.framework.BundleContext; 5 | 6 | import winterwell.markdown.preferences.MarkdownPreferencePage; 7 | 8 | /** 9 | * The activator class controls the plug-in life cycle 10 | */ 11 | public class Activator extends AbstractUIPlugin { 12 | 13 | // The plug-in ID 14 | public static final String PLUGIN_ID = "winterwell.markdown"; 15 | 16 | // The shared instance 17 | private static Activator plugin; 18 | 19 | /** 20 | * The constructor 21 | */ 22 | public Activator() { 23 | } 24 | 25 | /* 26 | * (non-Javadoc) 27 | * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) 28 | */ 29 | public void start(BundleContext context) throws Exception { 30 | super.start(context); 31 | plugin = this; 32 | doInstall(); 33 | MarkdownPreferencePage.setDefaultPreferences(getPreferenceStore()); 34 | } 35 | 36 | // ?? Have this method called by start(), saving a reminder so it doesn't repeat itself?? 37 | private void doInstall() { 38 | // ??Try to make this the default for file types -- but is this possible?? 39 | // c.f. http://stackoverflow.com/questions/15877123/eclipse-rcp-programmatically-associate-file-type-with-editor 40 | } 41 | 42 | /* 43 | * (non-Javadoc) 44 | * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) 45 | */ 46 | public void stop(BundleContext context) throws Exception { 47 | plugin = null; 48 | super.stop(context); 49 | } 50 | 51 | /** 52 | * Returns the shared instance 53 | * 54 | * @return the shared instance 55 | */ 56 | public static Activator getDefault() { 57 | return plugin; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/LogUtil.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown; 2 | 3 | import org.eclipse.core.runtime.ILog; 4 | import org.eclipse.core.runtime.IStatus; 5 | import org.eclipse.core.runtime.Status; 6 | 7 | /** 8 | * Nodeclipse Log Util 9 | * @author Lamb Gao, Paul Verest 10 | */ 11 | public class LogUtil { 12 | 13 | public static void info(String message) { 14 | log(IStatus.INFO, IStatus.OK, message, null); 15 | } 16 | 17 | public static void error(Throwable exception) { 18 | error("Unexpected Exception", exception); 19 | } 20 | 21 | public static void error(String message) { 22 | error(message, null); 23 | } 24 | 25 | public static void error(String message, Throwable exception) { 26 | log(IStatus.ERROR, IStatus.ERROR, message, exception); 27 | } 28 | 29 | public static void log(int severity, int code, String message, Throwable exception) { 30 | log(createStatus(severity, code, message, exception)); 31 | } 32 | 33 | public static IStatus createStatus(int severity, int code, String message, Throwable exception) { 34 | return new Status(severity, Activator.PLUGIN_ID, code, message, exception); 35 | } 36 | 37 | public static void log(IStatus status) { 38 | ILog log = Activator.getDefault().getLog(); 39 | log.log(status); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/StringMethods.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic String manipulation utilities. 3 | * (c) Winterwell 2010 and ThinkTank Mathematics 2007 4 | */ 5 | package winterwell.markdown; 6 | 7 | import java.math.BigInteger; 8 | import java.security.MessageDigest; 9 | import java.security.NoSuchAlgorithmException; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.regex.Pattern; 13 | 14 | import winterwell.utils.Mutable; 15 | import winterwell.utils.containers.Pair; 16 | 17 | /** 18 | * A collection of general-purpose String handling methods. 19 | * 20 | * @author daniel.winterstein 21 | */ 22 | public final class StringMethods { 23 | 24 | /** 25 | * Removes xml tags, comment blocks and script blocks. 26 | * 27 | * @param page 28 | * @return the page with all xml tags removed. 29 | */ 30 | public static String stripTags(String page) { 31 | // This code is rather ugly, but it does the job 32 | StringBuilder stripped = new StringBuilder(page.length()); 33 | boolean inTag = false; 34 | // Comment blocks and script blocks are given special treatment 35 | boolean inComment = false; 36 | boolean inScript = false; 37 | // Go through the text 38 | for (int i = 0; i < page.length(); i++) { 39 | char c = page.charAt(i); 40 | // First check whether we are ignoring text 41 | if (inTag) { 42 | if (c == '>') 43 | inTag = false; 44 | } else if (inComment) { 45 | if (c == '>' && page.charAt(i - 1) == '-' 46 | && page.charAt(i - 1) == '-') { 47 | inComment = false; 48 | } 49 | } else if (inScript) { 50 | if (c == '>' && page.substring(i - 7, i).equals("/script")) { 51 | inScript = false; 52 | } 53 | } else { 54 | // Check for the start of a tag - looks for '<' followed by any 55 | // non-whitespace character 56 | if (c == '<' && !Character.isWhitespace(page.charAt(i + 1))) { 57 | // Comment, script-block or tag? 58 | if (page.charAt(i + 1) == '!' && page.charAt(i + 2) == '-' 59 | && page.charAt(i + 3) == '-') { 60 | inComment = true; 61 | } else if (i + 8 < page.length() 62 | && page.substring(i + 1, i + 7).equals("script")) { 63 | inScript = true; 64 | i += 7; 65 | } else 66 | inTag = true; // Normal tag by default 67 | } else { 68 | // Append all non-tag chars 69 | stripped.append(c); 70 | } 71 | } // end if... 72 | } 73 | return stripped.toString(); 74 | } 75 | 76 | /** 77 | * The local line-end string. \n on unix, \r\n on windows, \r on mac. 78 | */ 79 | public static final String LINEEND = System.getProperty("line.separator"); 80 | 81 | /** 82 | * @param s 83 | * @return A version of s where the first letter is uppercase and all others 84 | * are lowercase 85 | */ 86 | public static final String capitalise(final String s) { 87 | return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); 88 | } 89 | 90 | /** 91 | * Convert all line breaks into the system line break. 92 | */ 93 | public static final String convertLineBreaks(String text) { 94 | return convertLineBreaks(text, LINEEND); 95 | } 96 | 97 | /** 98 | * Convert all line breaks into the specified line break. 99 | */ 100 | public static final String convertLineBreaks(String text, String br) { 101 | text = text.replaceAll("\r\n", br); 102 | text = text.replaceAll("\r", br); 103 | text = text.replaceAll("\n", br); 104 | return text; 105 | } 106 | 107 | /** 108 | * @param string 109 | * @param character 110 | * @return the number of times character appears in the string 111 | * @author Sam Halliday 112 | */ 113 | static public int countCharsInString(String string, char character) { 114 | int count = 0; 115 | for (char c : string.toCharArray()) { 116 | if (c == character) { 117 | count++; 118 | } 119 | } 120 | return count; 121 | } 122 | 123 | /** 124 | * 125 | * E.g. 126 | * findEnclosingRegion("text with a [region] inside", 15, '[', ']') 127 | * is (??,??) 128 | * 129 | * @param text 130 | * @param offset 131 | * @param start 132 | * @param end 133 | * @return the smallest enclosed region (including start and end chars, the 134 | * 1st number is inclusive, the 2nd exclusive), or null if none. So 135 | * text.subString(start,end) is the specified region 136 | */ 137 | public static Pair findEnclosingRegion(String text, int offset, 138 | char startMarker, char endMarker) { 139 | // Forward 140 | int end = findEnclosingRegion2(text, offset, endMarker, 1); 141 | if (end == -1) 142 | return null; 143 | end++; // end is exclusive 144 | // Backward 145 | int start = findEnclosingRegion2(text, offset, startMarker, -1); 146 | if (start == -1) 147 | return null; 148 | // Sanity 149 | assert text.substring(start, end).charAt(0) == startMarker; 150 | assert text.substring(start, end).endsWith("" + endMarker); 151 | // Done 152 | return new Pair(start, end); 153 | } 154 | 155 | private static int findEnclosingRegion2(String text, int offset, 156 | char endMarker, int direction) { 157 | while (offset > -1 && offset < text.length()) { 158 | char c = text.charAt(offset); 159 | if (c == endMarker) 160 | return offset; 161 | offset += direction; 162 | } 163 | return -1; 164 | } 165 | 166 | /** 167 | * A convenience wrapper for 168 | * {@link #findEnclosingRegion(String, int, char, char)} E.g. 169 | findEnclosingRegion("text with a [region] inside", 15, '[', ']') .equals("[region]"); 170 | 171 | * 172 | * @param text 173 | * @param offset 174 | * @param start 175 | * @param end 176 | * @return the smallest enclosed region (including start and end chars), or 177 | * null if none. 178 | */ 179 | public static String findEnclosingText(String text, int offset, 180 | char startMarker, char endMarker) { 181 | Pair region = findEnclosingRegion(text, offset, startMarker, 182 | endMarker); 183 | if (region == null) 184 | return null; 185 | String s = text.substring(region.first, region.second); 186 | return s; 187 | } 188 | 189 | /** 190 | * Format a block of text to use the given line-width. I.e. adjust the line 191 | * breaks. Also known as hard line-wrapping. Paragraphs are 192 | * recognised by a line of blank space between them (e.g. two returns). 193 | *

194 | * Note: a side-effect of this method is that it converts all line-breaks 195 | * into the local system's line-breaks. E.g. on Windows, \n will become \r\n 196 | * 197 | * @param text 198 | * The text to format 199 | * @param lineWidth 200 | * The number of columns in a line. Typically 78 or 80. 201 | * @param respectLeadingCharacters 202 | * Can be null. If set, the specified leading characters will be 203 | * copied if the line is split. Use with " \t" to keep indented 204 | * paragraphs properly indented. Use with "> \t" to also handle 205 | * email-style quoting. Note that respected leading characters 206 | * receive no special treatment when they are used inside a 207 | * paragraph. 208 | * @return A copy of text, formatted to the given line-width. 209 | *

210 | * TODO: recognise paragraphs by changes in the respected leading 211 | * characters 212 | */ 213 | public static String format(String text, int lineWidth, int tabWidth, 214 | String respectLeadingCharacters) { 215 | // Switch to Linux line breaks for easier internal workings 216 | text = convertLineBreaks(text, "\n"); 217 | // Find paragraphs 218 | List paras = format2_splitParagraphs(text, 219 | respectLeadingCharacters); 220 | // Rebuild text 221 | StringBuilder sb = new StringBuilder(text.length() + 10); 222 | for (String p : paras) { 223 | String fp = format3_oneParagraph(p, lineWidth, tabWidth, 224 | respectLeadingCharacters); 225 | sb.append(fp); 226 | // Paragraphs end with a double line break 227 | sb.append("\n\n"); 228 | } 229 | // Pop the last line breaks 230 | sb.delete(sb.length() - 2, sb.length()); 231 | // Convert line breaks to system ones 232 | text = convertLineBreaks(sb.toString()); 233 | // Done 234 | return text; 235 | } 236 | 237 | private static List format2_splitParagraphs(String text, 238 | String respectLeadingCharacters) { 239 | List paras = new ArrayList(); 240 | Mutable.Int index = new Mutable.Int(0); 241 | // TODO The characters prefacing this paragraph 242 | String leadingChars = ""; 243 | while (index.value < text.length()) { 244 | // One paragraph 245 | boolean inSpace = false; 246 | int start = index.value; 247 | while (index.value < text.length()) { 248 | char c = text.charAt(index.value); 249 | index.value++; 250 | if (!Character.isWhitespace(c)) { 251 | inSpace = false; 252 | continue; 253 | } 254 | // Line end? 255 | if (c == '\r' || c == '\n') { 256 | // // Handle MS Windows 2 character \r\n line breaks 257 | // if (index.value < text.length()) { 258 | // char c2 = text.charAt(index.value); 259 | // if (c=='\r' && c2=='\n') index.value++; // Push on past 260 | // the 2nd line break char 261 | // } 262 | // Double line end - indicating a paragraph break 263 | if (inSpace) 264 | break; 265 | inSpace = true; 266 | } 267 | // TODO Other paragraph markers, spotted by a change in 268 | // leadingChars 269 | } 270 | String p = text.substring(start, index.value); 271 | paras.add(p); 272 | } 273 | // Done 274 | return paras; 275 | } 276 | 277 | /** 278 | * Format a block of text to fit the given line width 279 | * 280 | * @param p 281 | * @param lineWidth 282 | * @param tabWidth 283 | * @param respectLeadingCharacters 284 | * @return 285 | */ 286 | private static String format3_oneParagraph(String p, int lineWidth, 287 | int tabWidth, String respectLeadingCharacters) { 288 | // Collect the reformatted paragraph 289 | StringBuilder sb = new StringBuilder(p.length() + 10); // Allow for 290 | // some extra 291 | // line-breaks 292 | // Get respected leading chars 293 | String leadingChars = format4_getLeadingChars(p, 294 | respectLeadingCharacters); 295 | // First Line 296 | sb.append(leadingChars); 297 | int lineLength = leadingChars.length(); 298 | int index = leadingChars.length(); 299 | // Loop 300 | while (index < p.length()) { 301 | // Get the next word 302 | StringBuilder word = new StringBuilder(); 303 | char c = p.charAt(index); 304 | index++; 305 | while (!Character.isWhitespace(c)) { 306 | word.append(c); 307 | if (index == p.length()) 308 | break; 309 | c = p.charAt(index); 310 | index++; 311 | } 312 | // Break the line if the word will not fit 313 | if (lineLength + word.length() > lineWidth && lineLength != 0) { 314 | trimEnd(sb); 315 | sb.append('\n'); // lineEnd(sb); 316 | // New line 317 | sb.append(leadingChars); 318 | lineLength = leadingChars.length(); 319 | } 320 | // Add word 321 | sb.append(word); 322 | lineLength += word.length(); 323 | // Add the whitespace 324 | if (index != p.length() && lineLength < lineWidth) { 325 | if (c == '\n') { 326 | c = ' '; 327 | } 328 | sb.append(c); 329 | lineLength += (c == '\t') ? tabWidth : 1; 330 | } 331 | } 332 | // A final trim 333 | trimEnd(sb); 334 | // Done 335 | return sb.toString(); 336 | } 337 | 338 | /** 339 | * 340 | * @param text 341 | * @param respectLeadingCharacters 342 | * Can be null 343 | * @return The characters at the beginning of text which are respected. E.g. 344 | * ("> Hello", " \t>") --> "> " 345 | */ 346 | private static String format4_getLeadingChars(String text, 347 | String respectLeadingCharacters) { 348 | if (respectLeadingCharacters == null) 349 | return ""; 350 | // Line-breaks cannot be respected 351 | assert respectLeadingCharacters.indexOf('\n') == -1; 352 | // Look for the first non-respected char 353 | for (int i = 0; i < text.length(); i++) { 354 | char c = text.charAt(i); 355 | if (respectLeadingCharacters.indexOf(c) == -1) { 356 | // Return the previous chars 357 | return text.substring(0, i); 358 | } 359 | } 360 | // All chars are respected 361 | return text; 362 | } 363 | 364 | /** 365 | * Ensure that line ends with the right line-end character(s) 366 | */ 367 | public static final String lineEnd(String line) { 368 | // strip possibly inappropriate line-endings 369 | if (line.endsWith("\n")) { 370 | line = line.substring(0, line.length() - 1); 371 | } 372 | if (line.endsWith("\r\n")) { 373 | line = line.substring(0, line.length() - 2); 374 | } 375 | if (line.endsWith("\r")) { 376 | line = line.substring(0, line.length() - 1); 377 | } 378 | // add in proper line end 379 | if (!line.endsWith(LINEEND)) { 380 | line += LINEEND; 381 | } 382 | return line; 383 | } 384 | 385 | /** 386 | * Ensure that line ends with the right line-end character(s). This is more 387 | * efficient than the version for Strings. 388 | * 389 | * @param line 390 | */ 391 | public static final void lineEnd(final StringBuilder line) { 392 | if (line.length() == 0) { 393 | line.append(LINEEND); 394 | return; 395 | } 396 | // strip possibly inappropriate line-endings 397 | final char last = line.charAt(line.length() - 1); 398 | if (last == '\n') { 399 | if ((line.length() > 1) && (line.charAt(line.length() - 2) == '\r')) { 400 | // \r\n 401 | line.replace(line.length() - 2, line.length(), LINEEND); 402 | return; 403 | } 404 | line.replace(line.length() - 1, line.length(), LINEEND); 405 | return; 406 | } 407 | if (last == '\r') { 408 | line.replace(line.length() - 1, line.length(), LINEEND); 409 | return; 410 | } 411 | line.append(LINEEND); 412 | return; 413 | } 414 | 415 | 416 | 417 | /** 418 | * @param string 419 | * @return the MD5 sum of the string using the default charset. Null if 420 | * there was an error in calculating the hash. 421 | * @author Sam Halliday 422 | */ 423 | public static String md5Hash(String string) { 424 | MessageDigest md5 = null; 425 | try { 426 | md5 = MessageDigest.getInstance("MD5"); 427 | } catch (NoSuchAlgorithmException e) { 428 | // ignore this exception, we know MD5 exists 429 | } 430 | md5.update(string.getBytes()); 431 | BigInteger hash = new BigInteger(1, md5.digest()); 432 | return hash.toString(16); 433 | } 434 | 435 | /** 436 | * Removes HTML-style tags from a string. 437 | * 438 | * @param s 439 | * a String from which to remove tags 440 | * @return a string with all instances of <.*> removed. 441 | */ 442 | public static String removeTags(String s) { 443 | StringBuffer sb = new StringBuffer(); 444 | boolean inTag = false; 445 | for (int i = 0; i < s.length(); i++) { 446 | char c = s.charAt(i); 447 | if (c == '<') 448 | inTag = true; 449 | if (!inTag) 450 | sb.append(c); 451 | if (c == '>') 452 | inTag = false; 453 | } 454 | return sb.toString(); 455 | } 456 | 457 | /** 458 | * Repeat a character. 459 | * 460 | * @param c 461 | * @param i 462 | * @return A String consisting of i x c. 463 | * @example assert repeat('-', 5).equals("-----"); 464 | */ 465 | public static String repeat(Character c, int i) { 466 | StringBuilder dashes = new StringBuilder(i); 467 | for (int j = 0; j < i; j++) 468 | dashes.append(c); 469 | return dashes.toString(); 470 | } 471 | 472 | /** 473 | * Split a piece of text into separate lines. The line breaks are left at 474 | * the end of each line. 475 | * 476 | * @param text 477 | * @return The individual lines in the text. 478 | */ 479 | public static List splitLines(String text) { 480 | List lines = new ArrayList(); 481 | // Search for lines 482 | int start = 0; 483 | for (int i = 0; i < text.length(); i++) { 484 | char c = text.charAt(i); 485 | if (c == '\r' || c == '\n') { 486 | // Handle MS Windows 2 character \r\n line breaks 487 | if (i + 1 < text.length()) { 488 | char c2 = text.charAt(i + 1); 489 | if (c == '\r' && c2 == '\n') 490 | i++; 491 | } 492 | // Get the line, with the line break 493 | String line = text.substring(start, i + 1); 494 | lines.add(line); 495 | start = i + 1; 496 | } 497 | } 498 | // Last one 499 | if (start != text.length()) { 500 | String line = text.substring(start); 501 | lines.add(line); 502 | } 503 | return lines; 504 | } 505 | 506 | /** 507 | * Remove trailing whitespace. c.f. String#trim() which removes 508 | * leading and trailing whitespace. 509 | * 510 | * @param sb 511 | */ 512 | private static void trimEnd(StringBuilder sb) { 513 | while (true) { 514 | // Get the last character 515 | int i = sb.length() - 1; 516 | if (i == -1) 517 | return; // Quit if sb is empty 518 | char c = sb.charAt(i); 519 | if (!Character.isWhitespace(c)) 520 | return; // Finish? 521 | sb.deleteCharAt(i); // Remove and continue 522 | } 523 | } 524 | 525 | /** 526 | * Returns true if the string is just whitespace, or empty, or null. 527 | * 528 | * @param s 529 | */ 530 | public static final boolean whitespace(final String s) { 531 | if (s == null) { 532 | return true; 533 | } 534 | for (int i = 0; i < s.length(); i++) { 535 | final char c = s.charAt(i); 536 | if (!Character.isWhitespace(c)) { 537 | return false; 538 | } 539 | } 540 | return true; 541 | } 542 | 543 | /** 544 | * @param text 545 | * @return the number of words in text. Uses a crude whitespace 546 | * measure. 547 | */ 548 | public static int wordCount(String text) { 549 | String[] bits = text.split("\\W+"); 550 | int wc = 0; 551 | for (String string : bits) { 552 | if (!whitespace(string)) wc++; 553 | } 554 | return wc; 555 | } 556 | 557 | } 558 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/commands/OpenGfmView.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.commands; 2 | 3 | import org.eclipse.core.commands.AbstractHandler; 4 | import org.eclipse.core.commands.ExecutionEvent; 5 | import org.eclipse.core.commands.ExecutionException; 6 | import org.eclipse.core.runtime.Platform; 7 | import org.eclipse.jface.dialogs.MessageDialog; 8 | import org.eclipse.swt.widgets.Display; 9 | import org.eclipse.ui.IViewPart; 10 | import org.eclipse.ui.IWorkbenchPage; 11 | import org.eclipse.ui.PartInitException; 12 | import org.eclipse.ui.PlatformUI; 13 | import org.osgi.framework.Bundle; 14 | 15 | import winterwell.markdown.LogUtil; 16 | 17 | public class OpenGfmView extends AbstractHandler { 18 | 19 | private static final String GFM_VIEW_ID = "code.satyagraha.gfm.viewer.views.GfmView"; 20 | private static final String GFM_SYMBOLIC_NAME = "code.satyagraha.gfm.viewer.plugin"; 21 | 22 | @Override 23 | public Object execute(final ExecutionEvent event) throws ExecutionException { 24 | try { 25 | Bundle gfmBundle = Platform.getBundle(GFM_SYMBOLIC_NAME); 26 | if (gfmBundle == null) { 27 | showInstallWarning(); 28 | } else { 29 | IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); 30 | IViewPart gfmView = activePage.showView(GFM_VIEW_ID); 31 | activePage.activate(gfmView); 32 | } 33 | } catch (PartInitException e) { 34 | showError(e); 35 | } catch (Exception e) { 36 | showError(e); 37 | } 38 | return null; 39 | } 40 | 41 | private void showInstallWarning() { 42 | String title = "GFM viewer was not found"; 43 | String message = title + " (" + GFM_SYMBOLIC_NAME + ")" 44 | + "\n\nPlease ensure that you have correctly installed the plugin, see https://github.com/satyagraha/gfm_viewer"; 45 | MessageDialog.openWarning(Display.getDefault().getActiveShell(), title, message); 46 | } 47 | 48 | private void showError(Exception e) { 49 | String title = "Exception while opening GitHub Flavored Markdown View"; 50 | String message = title + " (" + GFM_VIEW_ID + ")" 51 | + "\nCheck Error Log View and continue at https://github.com/winterstein/Eclipse-Markdown-Editor-Plugin/issues/42" 52 | + "\n\nYou can also right-click file in Project Explorer" 53 | + "\n and select \"Show in GFM view\"."; 54 | LogUtil.error(message, e); 55 | MessageDialog.openError(Display.getDefault().getActiveShell(), title, message); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/commands/OpenMdView.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.commands; 2 | 3 | import org.eclipse.core.commands.AbstractHandler; 4 | import org.eclipse.core.commands.ExecutionEvent; 5 | import org.eclipse.core.commands.ExecutionException; 6 | import org.eclipse.jface.dialogs.MessageDialog; 7 | import org.eclipse.swt.widgets.Display; 8 | import org.eclipse.ui.IViewPart; 9 | import org.eclipse.ui.IWorkbenchPage; 10 | import org.eclipse.ui.PartInitException; 11 | import org.eclipse.ui.handlers.HandlerUtil; 12 | 13 | import winterwell.markdown.LogUtil; 14 | 15 | public class OpenMdView extends AbstractHandler { 16 | 17 | @Override 18 | public Object execute(final ExecutionEvent event) throws ExecutionException { 19 | try { 20 | IWorkbenchPage activePage = HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); 21 | String mdViewId = "winterwell.markdown.views.MarkdownPreview"; 22 | IViewPart mdView = activePage.showView(mdViewId); 23 | activePage.activate(mdView); 24 | } catch (PartInitException e) { 25 | showError(e); 26 | } catch (Exception e) { 27 | showError(e); 28 | } 29 | return null; 30 | } 31 | 32 | private void showError(Exception e) { 33 | String title = "Exception while opening Markdown View"; 34 | String message = title+" (winterwell.markdown.views.MarkdownPreview)" 35 | +"\nCheck Error Log View"; 36 | LogUtil.error(message, e); 37 | MessageDialog.openError(Display.getDefault().getActiveShell(), title , message); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/commands/Preferences.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.commands; 2 | 3 | import org.eclipse.core.commands.AbstractHandler; 4 | import org.eclipse.core.commands.ExecutionEvent; 5 | import org.eclipse.core.commands.ExecutionException; 6 | import org.eclipse.jface.preference.PreferenceDialog; 7 | import org.eclipse.ui.PlatformUI; 8 | import org.eclipse.ui.dialogs.PreferencesUtil; 9 | 10 | public class Preferences extends AbstractHandler { 11 | 12 | @Override 13 | public Object execute(final ExecutionEvent event) throws ExecutionException { 14 | PreferenceDialog pref = PreferencesUtil.createPreferenceDialogOn( 15 | PlatformUI.getWorkbench().getDisplay().getActiveShell(), 16 | "winterwell.markdown.preferences.MarkdownPreferencePage", null, null); 17 | pref.open(); 18 | return null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/ActionBarContributor.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.editors; 2 | 3 | import org.eclipse.jface.action.IMenuManager; 4 | import org.eclipse.ui.IActionBars; 5 | import org.eclipse.ui.IEditorPart; 6 | import org.eclipse.ui.editors.text.TextEditorActionContributor; 7 | 8 | import winterwell.markdown.views.MarkdownPreview; 9 | 10 | public class ActionBarContributor extends TextEditorActionContributor { 11 | 12 | private static IEditorPart activeEditor = null; 13 | 14 | // IAction print = new PrintAction(); 15 | 16 | public void setActiveEditor(IEditorPart targetEditor) { 17 | super.setActiveEditor(targetEditor); 18 | activeEditor = targetEditor; 19 | // add print action 20 | IActionBars bars= getActionBars(); 21 | if (bars != null) { 22 | // todo bars.setGlobalActionHandler(ActionFactory.PRINT.getId(), print); 23 | // bars.updateActionBars(); 24 | } 25 | // Update preview? 26 | if (MarkdownPreview.preview != null) { 27 | MarkdownPreview.preview.update(); 28 | } 29 | } 30 | public static IEditorPart getActiveEditor() { 31 | return activeEditor; 32 | } 33 | 34 | @Override 35 | public void contributeToMenu(IMenuManager menu) { 36 | super.contributeToMenu(menu); 37 | // Add format action 38 | IMenuManager edit = menu.findMenuUsingPath("edit"); 39 | if (edit != null) { 40 | edit.add(new FormatAction()); 41 | } 42 | // Add Export action 43 | IMenuManager file = menu.findMenuUsingPath("file"); 44 | if (file != null) { 45 | file.appendToGroup("import.ext", new ExportHTMLAction()); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/ColorManager.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.editors; 2 | 3 | import java.util.HashMap; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | 7 | import org.eclipse.swt.graphics.Color; 8 | import org.eclipse.swt.graphics.RGB; 9 | import org.eclipse.swt.widgets.Display; 10 | 11 | public class ColorManager { 12 | 13 | protected Map fColorTable = new HashMap(10); 14 | 15 | public void dispose() { 16 | Iterator e = fColorTable.values().iterator(); 17 | while (e.hasNext()) 18 | ((Color) e.next()).dispose(); 19 | } 20 | public Color getColor(RGB rgb) { 21 | Color color = (Color) fColorTable.get(rgb); 22 | if (color == null) { 23 | color = new Color(Display.getCurrent(), rgb); 24 | fColorTable.put(rgb, color); 25 | } 26 | return color; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/EmphasisRule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright winterwell Mathematics Ltd. 3 | * @author Daniel Winterstein 4 | * 11 Jan 2007 5 | */ 6 | package winterwell.markdown.editors; 7 | 8 | import org.eclipse.core.runtime.Assert; 9 | import org.eclipse.jface.text.rules.ICharacterScanner; 10 | import org.eclipse.jface.text.rules.IRule; 11 | import org.eclipse.jface.text.rules.IToken; 12 | import org.eclipse.jface.text.rules.MultiLineRule; 13 | import org.eclipse.jface.text.rules.Token; 14 | 15 | /** 16 | * 17 | * 18 | * @author Daniel Winterstein 19 | */ 20 | public class EmphasisRule implements IRule { 21 | private static char[][] fDelimiters = null; 22 | private char[] fSequence; 23 | protected IToken fToken; 24 | 25 | 26 | public EmphasisRule(String marker, IToken token) { 27 | assert marker.equals("*") || marker.equals("_") || marker.equals("**") 28 | || marker.equals("***") || marker.equals("`") || marker.equals("``"); 29 | Assert.isNotNull(token); 30 | fSequence = marker.toCharArray(); 31 | fToken = token; 32 | } 33 | 34 | // Copied from org.eclipse.jface.text.rules.PatternRule 35 | protected boolean sequenceDetected(ICharacterScanner scanner, char[] sequence, boolean eofAllowed) { 36 | for (int i = 1; i < sequence.length; i++) { 37 | int c = scanner.read(); 38 | if (c == ICharacterScanner.EOF && eofAllowed) { 39 | return true; 40 | } else if (c != sequence[i]) { 41 | // Non-matching character detected, rewind the scanner back to 42 | // the start. 43 | // Do not unread the first character. 44 | for (int j = i; j > 0; j--) 45 | scanner.unread(); 46 | return false; 47 | } 48 | } 49 | return true; 50 | } 51 | 52 | /* 53 | * @see IRule#evaluate(ICharacterScanner) 54 | * 55 | * @since 2.0 56 | */ 57 | public IToken evaluate(ICharacterScanner scanner) { 58 | // Should be connected only on the right side 59 | scanner.unread(); 60 | boolean sawSpaceBefore = Character.isWhitespace(scanner.read()); 61 | if (!sawSpaceBefore && scanner.getColumn() != 0) { 62 | return Token.UNDEFINED; 63 | } 64 | 65 | int c = scanner.read(); 66 | // Should be connected only on right side 67 | if (c != fSequence[0] || !sequenceDetected(scanner, fSequence, false)) { 68 | scanner.unread(); 69 | return Token.UNDEFINED; 70 | } 71 | int readCount = fSequence.length; 72 | if (fDelimiters == null) { 73 | fDelimiters = scanner.getLegalLineDelimiters(); 74 | } 75 | // Start sequence detected 76 | int delimiterFound = 0; 77 | // Is it a list item marker, or just a floating *? 78 | if (sawSpaceBefore) { 79 | boolean after = Character.isWhitespace(scanner.read()); 80 | scanner.unread(); 81 | if (after) 82 | delimiterFound = 2; 83 | } 84 | 85 | while (delimiterFound < 2 86 | && (c = scanner.read()) != ICharacterScanner.EOF) { 87 | readCount++; 88 | 89 | if (!sawSpaceBefore && c == fSequence[0] 90 | && sequenceDetected(scanner, fSequence, false)) { 91 | return fToken; 92 | } 93 | 94 | int i; 95 | for (i = 0; i < fDelimiters.length; i++) { 96 | if (c == fDelimiters[i][0] 97 | && sequenceDetected(scanner, fDelimiters[i], true)) { 98 | delimiterFound++; 99 | break; 100 | } 101 | } 102 | if (i == fDelimiters.length) 103 | delimiterFound = 0; 104 | sawSpaceBefore = Character.isWhitespace(c); 105 | } 106 | // Reached ICharacterScanner.EOF 107 | for (; readCount > 0; readCount--) 108 | scanner.unread(); 109 | return Token.UNDEFINED; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/ExportHTMLAction.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.editors; 2 | 3 | import java.io.File; 4 | 5 | import org.eclipse.core.runtime.IPath; 6 | import org.eclipse.jface.action.Action; 7 | import org.eclipse.ui.IEditorInput; 8 | import org.eclipse.ui.IEditorPart; 9 | import org.eclipse.ui.IPathEditorInput; 10 | 11 | import winterwell.utils.io.FileUtils; 12 | 13 | 14 | public class ExportHTMLAction extends Action { 15 | public ExportHTMLAction() { 16 | super("Export to HTML"); 17 | } 18 | @Override 19 | public void run() { 20 | IEditorPart ed = ActionBarContributor.getActiveEditor(); 21 | if (!(ed instanceof MarkdownEditor)) { 22 | return; 23 | } 24 | MarkdownEditor editor = (MarkdownEditor) ed; 25 | IEditorInput i = editor.getEditorInput(); 26 | if (i instanceof IPathEditorInput) { 27 | IPathEditorInput input = (IPathEditorInput) i; 28 | IPath path = input.getPath(); 29 | path = path.removeFileExtension(); 30 | path = path.addFileExtension("html"); 31 | File file = path.toFile(); 32 | String html = editor.getMarkdownPage().html(); 33 | FileUtils.write(file, html); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/FormatAction.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.editors; 2 | 3 | import java.util.List; 4 | 5 | import org.eclipse.core.commands.ExecutionEvent; 6 | import org.eclipse.core.commands.ExecutionException; 7 | import org.eclipse.core.commands.IHandler; 8 | import org.eclipse.core.commands.IHandlerListener; 9 | import org.eclipse.jface.action.Action; 10 | import org.eclipse.jface.text.BadLocationException; 11 | import org.eclipse.jface.text.IDocument; 12 | import org.eclipse.jface.text.IRegion; 13 | import org.eclipse.jface.text.ITextSelection; 14 | import org.eclipse.jface.text.Region; 15 | import org.eclipse.jface.text.source.ISourceViewer; 16 | 17 | import winterwell.markdown.pagemodel.MarkdownFormatter; 18 | import winterwell.markdown.pagemodel.MarkdownPage; 19 | import winterwell.markdown.pagemodel.MarkdownPage.KLineType; 20 | import winterwell.utils.containers.IntRange; 21 | 22 | /** 23 | * TODO An action for formatting text (via hard wrapping, i.e. inserting returns). 24 | * 25 | * 26 | * @author daniel 27 | */ 28 | public class FormatAction extends Action implements IHandler { 29 | 30 | public FormatAction() { 31 | super("&Format paragraph"); 32 | setActionDefinitionId("winterwell.markdown.formatParagraphCommand"); 33 | setToolTipText("Format the paragraph under the caret by inserting/removing line-breaks"); 34 | } 35 | 36 | @Override 37 | public void run() { 38 | try { 39 | MarkdownEditor ed = (MarkdownEditor) ActionBarContributor.getActiveEditor(); 40 | if (ed == null) return; // The active editor is not a markdown editor. 41 | int cols = ed.getPrintColumns(); 42 | // Do we have a selection? 43 | ITextSelection s = (ITextSelection) ed.getSelectionProvider().getSelection(); 44 | if (s != null && s.getLength() > 0) { 45 | formatSelectedRegion(ed, s, cols); 46 | return; 47 | } 48 | // Where is the caret? 49 | ISourceViewer viewer = ed.getViewer(); 50 | int caretOffset = viewer.getTextWidget().getCaretOffset(); 51 | int lineNum = ed.getDocument().getLineOfOffset(caretOffset); 52 | // Get a paragraph region 53 | MarkdownPage page = ed.getMarkdownPage(); 54 | IRegion pRegion = getParagraph(page, lineNum, ed.getDocument()); 55 | if (pRegion==null) { 56 | // Not in a paragraph - so give up 57 | // TODO tell the user why we've given up 58 | return; 59 | } 60 | String paragraph = ed.getDocument().get(pRegion.getOffset(), pRegion.getLength()); 61 | // Format 62 | String formatted = MarkdownFormatter.format(paragraph, cols); 63 | if (formatted.equals(paragraph)) return; // No change! 64 | // Replace the unformatted region with the new formatted one 65 | ed.getDocument().replace(pRegion.getOffset(), pRegion.getLength(), formatted); 66 | // Done 67 | } catch (Exception ex) { 68 | System.out.println(ex); 69 | } 70 | } 71 | 72 | private void formatSelectedRegion(MarkdownEditor ed, ITextSelection s, int cols) 73 | throws BadLocationException { 74 | int start = s.getStartLine(); 75 | int end = s.getEndLine(); 76 | IDocument doc = ed.getDocument(); 77 | int soff = doc.getLineOffset(start); 78 | int eoff = lineEndOffset(end, doc); 79 | IntRange editedRegion = new IntRange(soff, eoff); 80 | MarkdownPage page = ed.getMarkdownPage(); 81 | StringBuilder sb = new StringBuilder(s.getLength()); 82 | for(int i=start; i<=end; i++) { 83 | IRegion para = getParagraph(page, i, ed.getDocument()); 84 | if (para==null) { 85 | sb.append(page.getText().get(i)); 86 | continue; 87 | } 88 | String paragraph = ed.getDocument().get(para.getOffset(), para.getLength()); 89 | // int lines = StrUtils.splitLines(paragraph).length; 90 | String formatted = MarkdownFormatter.format(paragraph, cols); 91 | // append formatted and move forward 92 | sb.append(formatted); 93 | CharSequence le = lineEnd(i, doc); 94 | sb.append(le); 95 | int pEnd = doc.getLineOfOffset(para.getOffset()+para.getLength()); 96 | i = pEnd; 97 | // Adjust edited region? 98 | IntRange pr = new IntRange(para.getOffset(), 99 | para.getOffset()+para.getLength()+le.length()); 100 | editedRegion = new IntRange(Math.min(pr.low, editedRegion.low), 101 | Math.max(pr.high, editedRegion.high)); 102 | } 103 | // Replace the unformatted region with the new formatted one 104 | String old = doc.get(editedRegion.low, editedRegion.size()); 105 | String newText = sb.toString(); 106 | if (old.equals(newText)) return; 107 | ed.getDocument().replace(editedRegion.low, editedRegion.size(), newText); 108 | } 109 | 110 | private CharSequence lineEnd(int line, IDocument doc) throws BadLocationException { 111 | int eoff = doc.getLineOffset(line) + doc.getLineInformation(line).getLength(); 112 | char c = doc.getChar(eoff); 113 | if (c=='\r' && doc.getLength() > eoff+1 114 | && doc.getChar(eoff+1) =='\n') return "\r\n"; 115 | return ""+c; 116 | } 117 | 118 | private int lineEndOffset(int end, IDocument doc) 119 | throws BadLocationException { 120 | int eoff = doc.getLineOffset(end) + doc.getLineInformation(end).getLength(); 121 | // Include line end 122 | char c = doc.getChar(eoff); 123 | if (c=='\r' && doc.getLength() > eoff+1 124 | && doc.getChar(eoff+1) =='\n') eoff += 2; 125 | else eoff += 1; 126 | return eoff; 127 | } 128 | 129 | /** 130 | * 131 | * @param page 132 | * @param lineNum 133 | * @param doc 134 | * @return region of paragraph containing this line, or null 135 | * @throws BadLocationException 136 | */ 137 | private IRegion getParagraph(MarkdownPage page, int lineNum, IDocument doc) 138 | throws BadLocationException { 139 | // Get doc info 140 | List lines = page.getText(); 141 | List lineInfo = page.getLineTypes(); 142 | // Check we are in a paragraph or list 143 | KLineType pType = lineInfo.get(lineNum); 144 | switch(pType) { 145 | case NORMAL: break; 146 | default: // Not in a paragraph, so we cannot format. 147 | return null; 148 | } 149 | // Work out the paragraph 150 | // Beginning 151 | int start; 152 | for(start=lineNum; start>-1; start--) { 153 | if (lineInfo.get(start) != pType) { 154 | start++; 155 | break; 156 | } 157 | } 158 | // End 159 | int end; 160 | for(end=lineNum; end 0; j--) 48 | scanner.unread(); 49 | return false; 50 | } 51 | } 52 | return true; 53 | } 54 | 55 | /* 56 | * @see IRule#evaluate(ICharacterScanner) 57 | * @since 2.0 58 | */ 59 | public IToken evaluate(ICharacterScanner scanner) { 60 | int c; 61 | if ((c = scanner.read()) != '[') { 62 | if ((c != 'h' || ( !sequenceDetected(scanner, "http://".toCharArray(), false) && !sequenceDetected(scanner, "https://".toCharArray(), false) )) 63 | && (c != 'f' || !sequenceDetected(scanner, "ftp://".toCharArray(), false)) ) { 64 | // Not even a non-standard link 65 | scanner.unread(); 66 | return Token.UNDEFINED; 67 | } 68 | 69 | //+ preventing NPE (Non-standard link should not be below as comment above suggests) by Paul Verest 70 | if (fDelimiters == null) { 71 | scanner.unread(); 72 | return Token.UNDEFINED; 73 | } 74 | 75 | // Non-standard link 76 | while ((c = scanner.read()) != ICharacterScanner.EOF && !Character.isWhitespace(c)) { 77 | for (int i = 0; i < fDelimiters.length; i++) { 78 | if (c == fDelimiters[i][0] && sequenceDetected(scanner, fDelimiters[i], true)) { 79 | return fToken; 80 | } 81 | } 82 | } 83 | return fToken; 84 | } 85 | if (fDelimiters == null) { 86 | fDelimiters = scanner.getLegalLineDelimiters(); 87 | } 88 | int readCount = 1; 89 | 90 | // Find '](' and then find ')' 91 | boolean sequenceFound = false; 92 | int delimiterFound = 0; 93 | while ((c = scanner.read()) != ICharacterScanner.EOF && delimiterFound < 2) { 94 | readCount++; 95 | if ( !sequenceFound && c == ']') { 96 | c = scanner.read(); 97 | if (c == '(') { 98 | readCount++; 99 | sequenceFound = true; 100 | } else { 101 | scanner.unread(); 102 | } 103 | } else if (c == ')') { // '](' is already found 104 | return fToken; 105 | } 106 | 107 | int i; 108 | for (i = 0; i < fDelimiters.length; i++) { 109 | if (c == fDelimiters[i][0] && sequenceDetected(scanner, fDelimiters[i], true)) { 110 | delimiterFound ++; 111 | break; 112 | } 113 | } 114 | if (i == fDelimiters.length) 115 | delimiterFound = 0; 116 | } 117 | 118 | for (; readCount > 0; readCount--) 119 | scanner.unread(); 120 | return Token.UNDEFINED; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/ListRule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright winterwell Mathematics Ltd. 3 | * @author Daniel Winterstein 4 | * 11 Jan 2007 5 | */ 6 | package winterwell.markdown.editors; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Arrays; 11 | 12 | import org.eclipse.core.runtime.Assert; 13 | import org.eclipse.jface.text.rules.ICharacterScanner; 14 | import org.eclipse.jface.text.rules.IRule; 15 | import org.eclipse.jface.text.rules.IToken; 16 | import org.eclipse.jface.text.rules.Token; 17 | 18 | /** 19 | * 20 | * 21 | * @author Daniel Winterstein 22 | */ 23 | public class ListRule implements IRule { 24 | private ArrayList markerList; 25 | protected IToken fToken; 26 | 27 | public ListRule(IToken token) { 28 | Assert.isNotNull(token); 29 | fToken= token; 30 | } 31 | 32 | 33 | /* 34 | * @see IRule#evaluate(ICharacterScanner) 35 | * @since 2.0 36 | */ 37 | public IToken evaluate(ICharacterScanner scanner) { 38 | if (scanner.getColumn() != 0) { 39 | return Token.UNDEFINED; 40 | } 41 | // // Fast mode 42 | // if (scanner.read() != '-') { 43 | // scanner.unread(); 44 | // return Token.UNDEFINED; 45 | // } 46 | // if (Character.isWhitespace(scanner.read())) { 47 | // return fToken; 48 | // } 49 | // scanner.unread(); 50 | // scanner.unread(); 51 | // return Token.UNDEFINED; 52 | // // Fast mode 53 | int readCount = 0; 54 | int c; 55 | while ((c = scanner.read()) != ICharacterScanner.EOF) { 56 | readCount++; 57 | if( !Character.isWhitespace( c ) ) { 58 | int after = scanner.read(); 59 | // readCount++; 60 | scanner.unread(); 61 | // if ( markerList.contains(c) && Character.isWhitespace( after ) ) { 62 | if ( (c == '-' || c == '+' || c == '*') 63 | && Character.isWhitespace( after ) ) { 64 | return fToken; 65 | } else { 66 | for (; readCount > 0; readCount--) 67 | scanner.unread(); 68 | return Token.UNDEFINED; 69 | } 70 | } 71 | } 72 | // Reached ICharacterScanner.EOF 73 | for (; readCount > 0; readCount--) 74 | scanner.unread(); 75 | return Token.UNDEFINED; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/MDColorConstants.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.editors; 2 | 3 | import org.eclipse.swt.graphics.RGB; 4 | 5 | public interface MDColorConstants { 6 | RGB COMMENT = new RGB(128, 0, 0); 7 | RGB HEADER = new RGB(0, 0, 128); 8 | RGB DEFAULT = new RGB(0, 0, 0); 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/MDConfiguration.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.editors; 2 | 3 | import org.eclipse.core.runtime.NullProgressMonitor; 4 | import org.eclipse.jface.preference.IPreferenceStore; 5 | import org.eclipse.jface.text.IDocument; 6 | import org.eclipse.jface.text.IRegion; 7 | import org.eclipse.jface.text.ITextHover; 8 | import org.eclipse.jface.text.presentation.IPresentationReconciler; 9 | import org.eclipse.jface.text.presentation.PresentationReconciler; 10 | import org.eclipse.jface.text.reconciler.DirtyRegion; 11 | import org.eclipse.jface.text.reconciler.IReconciler; 12 | import org.eclipse.jface.text.reconciler.IReconcilingStrategy; 13 | import org.eclipse.jface.text.reconciler.MonoReconciler; 14 | import org.eclipse.jface.text.rules.DefaultDamagerRepairer; 15 | import org.eclipse.jface.text.source.ISourceViewer; 16 | import org.eclipse.ui.editors.text.TextSourceViewerConfiguration; 17 | 18 | public class MDConfiguration extends TextSourceViewerConfiguration { 19 | private ColorManager colorManager; 20 | 21 | public MDConfiguration(ColorManager colorManager, IPreferenceStore prefStore) { 22 | super(prefStore); 23 | this.colorManager = colorManager; 24 | } 25 | 26 | @Override 27 | public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { 28 | MDScanner scanner = new MDScanner(colorManager); 29 | PresentationReconciler pr = (PresentationReconciler) super.getPresentationReconciler(sourceViewer); // FIXME 30 | DefaultDamagerRepairer ddr = new DefaultDamagerRepairer(scanner); 31 | pr.setRepairer(ddr, IDocument.DEFAULT_CONTENT_TYPE); 32 | pr.setDamager(ddr, IDocument.DEFAULT_CONTENT_TYPE); 33 | return pr; 34 | } 35 | 36 | 37 | @Override 38 | public IReconciler getReconciler(ISourceViewer sourceViewer) { 39 | // This awful mess adds in update support 40 | // Get super strategy 41 | IReconciler rs = super.getReconciler(sourceViewer); 42 | if (true) return rs; // Seems to work fine?! 43 | final IReconcilingStrategy fsuperStrategy = rs==null? null : rs.getReconcilingStrategy("text"); 44 | // Add our own 45 | IReconcilingStrategy strategy = new IReconcilingStrategy() { 46 | private IDocument doc; 47 | public void reconcile(IRegion partition) { 48 | MarkdownEditor ed = MarkdownEditor.getEditor(doc); 49 | if (ed != null) ed.updatePage(partition); 50 | if (fsuperStrategy!=null) fsuperStrategy.reconcile(partition); 51 | } 52 | public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { 53 | MarkdownEditor ed = MarkdownEditor.getEditor(doc); 54 | if (ed != null) ed.updatePage(subRegion); 55 | if (fsuperStrategy!=null) fsuperStrategy.reconcile(dirtyRegion, subRegion); 56 | } 57 | public void setDocument(IDocument document) { 58 | this.doc = document; 59 | if (fsuperStrategy!=null) fsuperStrategy.setDocument(document); 60 | } 61 | }; 62 | // Make a reconciler 63 | MonoReconciler m2 = new MonoReconciler(strategy, true); 64 | m2.setIsIncrementalReconciler(true); 65 | m2.setProgressMonitor(new NullProgressMonitor()); 66 | m2.setDelay(500); 67 | // Done 68 | return m2; 69 | } 70 | 71 | @SuppressWarnings("unused") 72 | @Override 73 | public ITextHover getTextHover(ISourceViewer sourceViewer, 74 | String contentType) { 75 | if (true) return super.getTextHover(sourceViewer, contentType); 76 | // Add hover support for images 77 | return new MDTextHover(); 78 | } 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/MDScanner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright winterwell Mathematics Ltd. 3 | * @author Daniel Winterstein 4 | * 13 Jan 2007 5 | */ 6 | package winterwell.markdown.editors; 7 | 8 | import org.eclipse.jface.preference.IPreferenceStore; 9 | import org.eclipse.jface.preference.PreferenceConverter; 10 | import org.eclipse.jface.text.TextAttribute; 11 | import org.eclipse.jface.text.rules.IRule; 12 | import org.eclipse.jface.text.rules.IWhitespaceDetector; 13 | import org.eclipse.jface.text.rules.MultiLineRule; 14 | import org.eclipse.jface.text.rules.RuleBasedScanner; 15 | import org.eclipse.jface.text.rules.Token; 16 | import org.eclipse.jface.text.rules.WhitespaceRule; 17 | import org.eclipse.swt.SWT; 18 | 19 | import winterwell.markdown.Activator; 20 | import winterwell.markdown.preferences.MarkdownPreferencePage; 21 | 22 | /** 23 | * 24 | * 25 | * @author Daniel Winterstein 26 | */ 27 | public class MDScanner extends RuleBasedScanner { 28 | ColorManager cm; 29 | public MDScanner(ColorManager cm) { 30 | this.cm = cm; 31 | IPreferenceStore pStore = Activator.getDefault().getPreferenceStore(); 32 | Token heading = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_HEADER)), null, SWT.BOLD)); 33 | Token comment = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_COMMENT)))); 34 | Token emphasis = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_DEFUALT)), null, SWT.ITALIC)); 35 | Token list = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_HEADER)), null, SWT.BOLD)); 36 | Token link = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_LINK)), null, TextAttribute.UNDERLINE)); 37 | Token code = new Token(new TextAttribute( 38 | cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_CODE)), 39 | cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_CODE_BG)), 40 | SWT.NORMAL)); 41 | setRules(new IRule[] { 42 | new LinkRule(link), 43 | new HeaderRule(heading), 44 | new HeaderWithUnderlineRule(heading), 45 | new ListRule(list), 46 | new EmphasisRule("_", emphasis), 47 | new EmphasisRule("***", emphasis), 48 | new EmphasisRule("**", emphasis), 49 | new EmphasisRule("*", emphasis), 50 | new EmphasisRule("``", code), 51 | new EmphasisRule("`", code), 52 | new MultiLineRule("", comment), 53 | // WhitespaceRule messes up with the rest of rules 54 | // new WhitespaceRule(new IWhitespaceDetector() { 55 | // public boolean isWhitespace(char c) { 56 | // return Character.isWhitespace(c); 57 | // } 58 | // }), 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/MDTextHover.java: -------------------------------------------------------------------------------- 1 | /** 2 | * (c) Winterwell 2010 and ThinkTank Mathematics 2007 3 | */ 4 | package winterwell.markdown.editors; 5 | 6 | import org.eclipse.jface.text.IDocument; 7 | import org.eclipse.jface.text.IRegion; 8 | import org.eclipse.jface.text.ITextHover; 9 | import org.eclipse.jface.text.ITextViewer; 10 | import org.eclipse.jface.text.Region; 11 | 12 | import winterwell.markdown.StringMethods; 13 | import winterwell.utils.containers.Pair; 14 | 15 | /** 16 | * 17 | * 18 | * @author daniel 19 | */ 20 | public class MDTextHover implements ITextHover //, ITextHoverExtension 21 | { 22 | 23 | /* (non-Javadoc) 24 | * @see org.eclipse.jface.text.ITextHover#getHoverInfo(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion) 25 | */ 26 | public String getHoverInfo(ITextViewer textViewer, IRegion region) { 27 | try { 28 | IDocument doc = textViewer.getDocument(); 29 | String text = doc.get(region.getOffset(), region.getLength()); 30 | return ""+text+""; 31 | } catch (Exception e) { 32 | return null; 33 | } 34 | } 35 | 36 | /* (non-Javadoc) 37 | * @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int) 38 | */ 39 | public IRegion getHoverRegion(ITextViewer textViewer, int offset) { 40 | try { 41 | IDocument doc = textViewer.getDocument(); 42 | int line = doc.getLineOfOffset(offset); 43 | int lineOffset = doc.getLineOffset(line); 44 | int lineLength = doc.getLineLength(line); 45 | String text = doc.get(lineOffset, lineLength); 46 | // Look for image tags 47 | Pair altRegion; 48 | Pair urlRegion = 49 | StringMethods.findEnclosingRegion(text, offset-lineOffset, '(', ')'); 50 | if (urlRegion==null) { 51 | altRegion = StringMethods.findEnclosingRegion(text, offset-lineOffset, '[', ']'); 52 | if (altRegion == null) return null; 53 | urlRegion = StringMethods.findEnclosingRegion(text, altRegion.second, '(', ')'); 54 | } else { 55 | altRegion = StringMethods.findEnclosingRegion(text, urlRegion.first-1, '[', ']'); 56 | } 57 | if (urlRegion==null || altRegion==null) return null; 58 | // Is it an image link? 59 | if (text.charAt(altRegion.first-1) != '!') return null; 60 | Region r = new Region(urlRegion.first+1+lineOffset, urlRegion.second-urlRegion.first-2); 61 | return r; 62 | } catch (Exception ex) { 63 | return null; 64 | } 65 | } 66 | 67 | // public IInformationControlCreator getHoverControlCreator() { 68 | // return new IInformationControlCreator() { 69 | // public IInformationControl createInformationControl(Shell parent) { 70 | // int style= fIsFocusable ? SWT.V_SCROLL | SWT.H_SCROLL : SWT.NONE; 71 | // 72 | // if (BrowserInformationControl.isAvailable(parent)) { 73 | // final int shellStyle= SWT.TOOL | (fIsFocusable ? SWT.RESIZE : SWT.NO_TRIM); 74 | // return new BrowserInformationControl(parent, shellStyle, style, null); 75 | // } 76 | // return new DefaultInformationControl(parent, style, new HTMLTextPresenter()); 77 | // } 78 | // }; 79 | // } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright winterwell Mathematics Ltd. 3 | * @author Daniel Winterstein 4 | * 11 Jan 2007 5 | */ 6 | package winterwell.markdown.editors; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.HashSet; 11 | import java.util.List; 12 | 13 | import org.eclipse.jface.action.Action; 14 | import org.eclipse.jface.action.IAction; 15 | import org.eclipse.jface.action.IToolBarManager; 16 | import org.eclipse.jface.resource.ImageDescriptor; 17 | import org.eclipse.jface.text.BadLocationException; 18 | import org.eclipse.jface.text.DocumentEvent; 19 | import org.eclipse.jface.text.IDocument; 20 | import org.eclipse.jface.text.IDocumentListener; 21 | import org.eclipse.jface.text.IRegion; 22 | import org.eclipse.jface.text.Region; 23 | import org.eclipse.jface.viewers.ISelection; 24 | import org.eclipse.jface.viewers.IStructuredSelection; 25 | import org.eclipse.jface.viewers.ITreeContentProvider; 26 | import org.eclipse.jface.viewers.LabelProvider; 27 | import org.eclipse.jface.viewers.SelectionChangedEvent; 28 | import org.eclipse.jface.viewers.StructuredSelection; 29 | import org.eclipse.jface.viewers.TreeViewer; 30 | import org.eclipse.jface.viewers.Viewer; 31 | import org.eclipse.swt.SWT; 32 | import org.eclipse.swt.events.KeyEvent; 33 | import org.eclipse.swt.events.KeyListener; 34 | import org.eclipse.swt.widgets.Composite; 35 | import org.eclipse.swt.widgets.Control; 36 | import org.eclipse.ui.IActionBars; 37 | import org.eclipse.ui.part.IPageSite; 38 | import org.eclipse.ui.texteditor.IDocumentProvider; 39 | import org.eclipse.ui.views.contentoutline.ContentOutlinePage; 40 | 41 | import winterwell.markdown.pagemodel.MarkdownPage; 42 | import winterwell.markdown.pagemodel.MarkdownPage.Header; 43 | import winterwell.markdown.pagemodel.MarkdownPage.KLineType; 44 | import winterwell.utils.StrUtils; 45 | import winterwell.utils.Utils; 46 | import winterwell.utils.web.WebUtils; 47 | 48 | /** 49 | * 50 | * 51 | * @author Daniel Winterstein 52 | */ 53 | public final class MarkdownContentOutlinePage extends ContentOutlinePage { 54 | 55 | /** 56 | * 57 | * 58 | * @author Daniel Winterstein 59 | */ 60 | public final class ContentProvider implements ITreeContentProvider, 61 | IDocumentListener { 62 | 63 | // protected final static String SEGMENTS= "__md_segments"; 64 | // //$NON-NLS-1$ 65 | // protected IPositionUpdater fPositionUpdater= new 66 | // DefaultPositionUpdater(SEGMENTS); 67 | private MarkdownPage fContent; 68 | // protected List fContent= new ArrayList(10); 69 | private MarkdownEditor fTextEditor; 70 | 71 | private void parse() { 72 | fContent = fTextEditor.getMarkdownPage(); 73 | } 74 | 75 | /* 76 | * @see IContentProvider#inputChanged(Viewer, Object, Object) 77 | */ 78 | public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 79 | // Detach from old 80 | if (oldInput != null) { 81 | IDocument document = fDocumentProvider.getDocument(oldInput); 82 | if (document != null) { 83 | document.removeDocumentListener(this); 84 | } 85 | } 86 | fContent = null; 87 | // Attach to new 88 | if (newInput == null) 89 | return; 90 | IDocument document = fDocumentProvider.getDocument(newInput); 91 | if (document == null) 92 | return; 93 | fTextEditor = MarkdownEditor.getEditor(document); 94 | document.addDocumentListener(this); 95 | parse(); 96 | } 97 | 98 | /* 99 | * @see IContentProvider#dispose 100 | */ 101 | public void dispose() { 102 | fContent = null; 103 | } 104 | 105 | /* 106 | * @see IContentProvider#isDeleted(Object) 107 | */ 108 | public boolean isDeleted(Object element) { 109 | return false; 110 | } 111 | 112 | /* 113 | * @see IStructuredContentProvider#getElements(Object) 114 | */ 115 | public Object[] getElements(Object element) { 116 | return fContent.getHeadings(null).toArray(); 117 | } 118 | 119 | /* 120 | * @see ITreeContentProvider#hasChildren(Object) 121 | */ 122 | public boolean hasChildren(Object element) { 123 | if (element == fInput) { 124 | return true; 125 | } 126 | if (element instanceof MarkdownPage.Header) { 127 | MarkdownPage.Header header = (MarkdownPage.Header) element; 128 | return header.getSubHeaders().size() > 0; 129 | } 130 | ; 131 | return false; 132 | } 133 | 134 | /* 135 | * @see ITreeContentProvider#getParent(Object) 136 | */ 137 | public Object getParent(Object element) { 138 | if (!(element instanceof MarkdownPage.Header)) 139 | return null; 140 | return ((MarkdownPage.Header) element).getParent(); 141 | } 142 | 143 | /* 144 | * @see ITreeContentProvider#getChildren(Object) 145 | */ 146 | public Object[] getChildren(Object element) { 147 | if (element == fInput) { 148 | return fContent.getHeadings(null).toArray(); 149 | } 150 | if (!(element instanceof MarkdownPage.Header)) 151 | return null; 152 | return ((MarkdownPage.Header) element).getSubHeaders().toArray(); 153 | } 154 | 155 | public void documentAboutToBeChanged(DocumentEvent event) { 156 | // nothing 157 | } 158 | 159 | public void documentChanged(DocumentEvent event) { 160 | parse(); 161 | update(); 162 | } 163 | } 164 | 165 | private Object fInput = null; 166 | private final IDocumentProvider fDocumentProvider; 167 | private final MarkdownEditor fTextEditor; 168 | protected boolean showWordCounts; 169 | private List

selectedHeaders; 170 | 171 | /** 172 | * @param documentProvider 173 | * @param mdEditor 174 | */ 175 | public MarkdownContentOutlinePage(IDocumentProvider documentProvider, 176 | MarkdownEditor mdEditor) { 177 | fDocumentProvider = documentProvider; 178 | fTextEditor = mdEditor; 179 | } 180 | 181 | /* 182 | * (non-Javadoc) Method declared on ContentOutlinePage 183 | */ 184 | @Override 185 | public void createControl(Composite parent) { 186 | super.createControl(parent); 187 | TreeViewer viewer = getTreeViewer(); 188 | viewer.setContentProvider(new ContentProvider()); 189 | // Add word count annotations 190 | viewer.setLabelProvider(new LabelProvider() { 191 | @Override 192 | public String getText(Object element) { 193 | if (!(element instanceof MarkdownPage.Header)) 194 | return super.getText(element); 195 | Header header = ((MarkdownPage.Header) element); 196 | String hText = header.toString(); 197 | if (!showWordCounts) 198 | return hText; 199 | IRegion region = getRegion(header); 200 | String text; 201 | try { 202 | text = fTextEditor.getDocument().get(region.getOffset(), 203 | region.getLength()); 204 | text = WebUtils.stripTags(text); 205 | text = text.replaceAll("#", "").trim(); 206 | assert text.startsWith(hText); 207 | text = text.substring(hText.length()); 208 | int wc = StrUtils.wordCount(text); 209 | return hText + " (" + wc + ":" + text.length() + ")"; 210 | } catch (BadLocationException e) { 211 | return hText; 212 | } 213 | } 214 | }); 215 | viewer.addSelectionChangedListener(this); 216 | 217 | if (fInput != null) 218 | viewer.setInput(fInput); 219 | 220 | // Buttons 221 | IPageSite site = getSite(); 222 | IActionBars bars = site.getActionBars(); 223 | IToolBarManager toolbar = bars.getToolBarManager(); 224 | // Word count action 225 | Action action = new Action("123", IAction.AS_CHECK_BOX) { 226 | @Override 227 | public void run() { 228 | showWordCounts = isChecked(); 229 | update(); 230 | } 231 | }; 232 | action.setToolTipText("Show/hide section word:character counts"); 233 | toolbar.add(action); 234 | // +/- actions 235 | action = new Action("<") { 236 | @Override 237 | public void run() { 238 | doPromoteDemote(-1); 239 | } 240 | }; 241 | action.setToolTipText("Promote the selected section\n -- move it up a level."); 242 | toolbar.add(action); 243 | // 244 | action = new Action(">") { 245 | @Override 246 | public void run() { 247 | doPromoteDemote(1); 248 | } 249 | }; 250 | action.setToolTipText("Demote the selected section\n -- move it down a level."); 251 | toolbar.add(action); 252 | // up/down actions 253 | action = new Action("/\\") { 254 | @Override 255 | public void run() { 256 | try { 257 | doMove(-1); 258 | } catch (BadLocationException e) { 259 | throw Utils.runtime(e); 260 | } 261 | } 262 | }; 263 | action.setToolTipText("Move the selected section earlier"); 264 | toolbar.add(action); 265 | // 266 | action = new Action("\\/") { 267 | @Override 268 | public void run() { 269 | try { 270 | doMove(1); 271 | } catch (BadLocationException e) { 272 | throw Utils.runtime(e); 273 | } 274 | } 275 | }; 276 | action.setToolTipText("Move the selected section later"); 277 | toolbar.add(action); 278 | // Collapse 279 | ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif"); 280 | action = new Action("collapse", id) { 281 | @Override 282 | public void run() { 283 | doCollapseAll(); 284 | } 285 | }; 286 | action.setImageDescriptor(id); 287 | action.setToolTipText("Collapse outline tree"); 288 | toolbar.add(action); 289 | // Sync 290 | id = ImageDescriptor.createFromFile(getClass(), "synced.gif"); 291 | action = new Action("sync") { 292 | @Override 293 | public void run() { 294 | try { 295 | doSyncToEditor(); 296 | } catch (BadLocationException e) { 297 | throw Utils.runtime(e); 298 | } 299 | } 300 | }; 301 | action.setImageDescriptor(id); 302 | action.setToolTipText("Link with editor"); 303 | toolbar.add(action); 304 | // Add edit ability 305 | viewer.getControl().addKeyListener(new KeyListener() { 306 | public void keyPressed(KeyEvent e) { 307 | if (e.keyCode==SWT.F2) { 308 | doEditHeader(); 309 | } 310 | } 311 | public void keyReleased(KeyEvent e) { 312 | // 313 | } 314 | }); 315 | } 316 | 317 | /** 318 | * @throws BadLocationException 319 | * 320 | */ 321 | protected void doSyncToEditor() throws BadLocationException { 322 | TreeViewer viewer = getTreeViewer(); 323 | if (viewer == null) return; 324 | // Get header 325 | MarkdownPage page = fTextEditor.getMarkdownPage(); 326 | int caretOffset = fTextEditor.getViewer().getTextWidget().getCaretOffset(); 327 | IDocument doc = fTextEditor.getDocument(); 328 | int line = doc.getLineOfOffset(caretOffset); 329 | List lineTypes = page.getLineTypes(); 330 | for(; line>-1; line--) { 331 | KLineType lt = lineTypes.get(line); 332 | if (lt.toString().startsWith("H")) break; 333 | } 334 | if (line<0) return; 335 | Header header = (Header) page.getPageObject(line); 336 | // Set 337 | IStructuredSelection selection = new StructuredSelection(header); 338 | viewer.setSelection(selection , true); 339 | } 340 | 341 | void doEditHeader() { 342 | TreeViewer viewer = getTreeViewer(); 343 | viewer.editElement(selectedHeaders.get(0), 0); 344 | } 345 | 346 | protected void doCollapseAll() { 347 | TreeViewer viewer = getTreeViewer(); 348 | if (viewer == null) return; 349 | // Control control = viewer.getControl(); 350 | // if (control != null && !control.isDisposed()) { 351 | // control.setRedraw(false); 352 | viewer.collapseAll(); 353 | // control.setRedraw(true); 354 | // } 355 | } 356 | 357 | /** 358 | * Move the selected sections up/down 359 | * @param i 1 or -1. 1==move later, -1=earlier 360 | * @throws BadLocationException 361 | */ 362 | protected void doMove(int i) throws BadLocationException { 363 | assert i==1 || i==-1; 364 | if (selectedHeaders == null || selectedHeaders.size() == 0) 365 | return; 366 | // Get text region to move 367 | MarkdownPage.Header first = selectedHeaders.get(0); 368 | MarkdownPage.Header last = selectedHeaders.get(selectedHeaders.size()-1); 369 | int start = fTextEditor.getDocument().getLineOffset( 370 | first.getLineNumber()); 371 | IRegion r = getRegion(last); 372 | int end = r.getOffset() + r.getLength(); 373 | int length = end - start; 374 | // Get new insertion point 375 | int insert; 376 | if (i==1) { 377 | Header nextSection = last.getNext(); 378 | if (nextSection==null) return; 379 | IRegion nr = getRegion(nextSection); 380 | insert = nr.getOffset()+nr.getLength(); 381 | } else { 382 | Header prevSection = first.getPrevious(); 383 | if (prevSection==null) return; 384 | IRegion nr = getRegion(prevSection); 385 | insert = nr.getOffset(); 386 | } 387 | // Get text 388 | String text = fTextEditor.getDocument().get(); 389 | // Move text 390 | String section = text.substring(start, end); 391 | String pre, post; 392 | if (i==1) { 393 | pre = text.substring(0, start) + text.substring(end, insert); 394 | post = text.substring(insert); 395 | } else { 396 | pre = text.substring(0, insert); 397 | post = text.substring(insert,start)+text.substring(end); 398 | } 399 | text = pre + section + post; 400 | assert text.length() == fTextEditor.getDocument().get().length() : 401 | text.length()-fTextEditor.getDocument().get().length()+" chars gained/lost"; 402 | // Update doc 403 | fTextEditor.getDocument().set(text); 404 | } 405 | 406 | /** 407 | * Does not support -------- / ========= underlining, only # headers 408 | * @param upDown 1 for demote (e.g. h2 -> h3), -1 for promote (e.g. h2 -> h1) 409 | */ 410 | protected void doPromoteDemote(int upDown) { 411 | assert upDown==1 || upDown==-1; 412 | if (selectedHeaders == null || selectedHeaders.size() == 0) 413 | return; 414 | HashSet
toAdjust = new HashSet
(selectedHeaders); 415 | HashSet
adjusted = new HashSet
(); 416 | // Adjust 417 | MarkdownPage mdPage = fTextEditor.getMarkdownPage(); 418 | List lines = new ArrayList(mdPage.getText()); 419 | while(toAdjust.size() != 0) { 420 | Header h = toAdjust.iterator().next(); 421 | toAdjust.remove(h); 422 | adjusted.add(h); 423 | String line = lines.get(h.getLineNumber()); 424 | if (upDown==-1) { 425 | if (h.getLevel() == 1) return; // Level 1; can't promote 426 | if (line.startsWith("##")) line = line.substring(1); 427 | else { 428 | return; // TODO support for ------ / ======== 429 | } 430 | } else line = "#" + line; 431 | int ln = h.getLineNumber(); 432 | lines.set(ln, line); 433 | // kids 434 | ArrayList
kids = new ArrayList
(h.getSubHeaders()); 435 | for (Header header : kids) { 436 | if ( ! adjusted.contains(header)) toAdjust.add(header); 437 | } 438 | } 439 | // Set 440 | StringBuilder sb = new StringBuilder(); 441 | for (String line : lines) { 442 | sb.append(line); 443 | } 444 | fTextEditor.getDocument().set(sb.toString()); 445 | } 446 | 447 | /** 448 | * The region of text for this header. This includes the header itself. 449 | * @param header 450 | * @return 451 | * @throws BadLocationException 452 | */ 453 | protected IRegion getRegion(Header header) { 454 | try { 455 | IDocument doc = fTextEditor.getDocument(); 456 | // Line numbers 457 | int start = header.getLineNumber(); 458 | Header next = header.getNext(); 459 | int end; 460 | if (next != null) { 461 | end = next.getLineNumber() - 1; 462 | } else { 463 | end = doc.getNumberOfLines() - 1; 464 | } 465 | int offset = doc.getLineOffset(start); 466 | IRegion ei = doc.getLineInformation(end); 467 | int length = ei.getOffset() + ei.getLength() - offset; 468 | return new Region(offset, length); 469 | } catch (BadLocationException ex) { 470 | throw Utils.runtime(ex); 471 | } 472 | } 473 | 474 | /* 475 | * (non-Javadoc) Method declared on ContentOutlinePage 476 | */ 477 | @Override 478 | public void selectionChanged(SelectionChangedEvent event) { 479 | super.selectionChanged(event); 480 | selectedHeaders = null; 481 | ISelection selection = event.getSelection(); 482 | if (selection.isEmpty()) 483 | return; 484 | if (!(selection instanceof IStructuredSelection)) 485 | return; 486 | try { 487 | IStructuredSelection strucSel = (IStructuredSelection) selection; 488 | Object[] sections = strucSel.toArray(); 489 | selectedHeaders = (List) Arrays.asList(sections); 490 | MarkdownPage.Header first = (Header) sections[0]; 491 | MarkdownPage.Header last = (Header) sections[sections.length - 1]; 492 | int start = fTextEditor.getDocument().getLineOffset( 493 | first.getLineNumber()); 494 | int length; 495 | if (first == last) { 496 | length = fTextEditor.getDocument().getLineLength( 497 | first.getLineNumber()); 498 | } else { 499 | IRegion r = getRegion(last); 500 | int end = r.getOffset() + r.getLength(); 501 | length = end - start; 502 | } 503 | fTextEditor.setHighlightRange(start, length, true); 504 | } catch (Exception x) { 505 | System.out.println(x.getStackTrace()); 506 | fTextEditor.resetHighlightRange(); 507 | } 508 | } 509 | 510 | /** 511 | * Sets the input of the outline page 512 | * 513 | * @param input 514 | * the input of this outline page 515 | */ 516 | public void setInput(Object input) { 517 | fInput = input; 518 | update(); 519 | } 520 | 521 | /** 522 | * Updates the outline page. 523 | */ 524 | public void update() { 525 | TreeViewer viewer = getTreeViewer(); 526 | 527 | if (viewer != null) { 528 | Control control = viewer.getControl(); 529 | if (control != null && !control.isDisposed()) { 530 | control.setRedraw(false); 531 | viewer.setInput(fInput); 532 | viewer.expandAll(); 533 | control.setRedraw(true); 534 | } 535 | } 536 | } 537 | 538 | } 539 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/MarkdownEditor.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.editors; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | 10 | import org.eclipse.core.resources.IFile; 11 | import org.eclipse.core.resources.IMarker; 12 | import org.eclipse.core.resources.IResource; 13 | import org.eclipse.core.resources.IWorkspace; 14 | import org.eclipse.core.resources.IWorkspaceRoot; 15 | import org.eclipse.core.resources.ResourcesPlugin; 16 | import org.eclipse.core.runtime.CoreException; 17 | import org.eclipse.core.runtime.IPath; 18 | import org.eclipse.jface.preference.IPreferenceStore; 19 | import org.eclipse.jface.text.DocumentEvent; 20 | import org.eclipse.jface.text.IDocument; 21 | import org.eclipse.jface.text.IDocumentListener; 22 | import org.eclipse.jface.text.IRegion; 23 | import org.eclipse.jface.text.Position; 24 | import org.eclipse.jface.text.source.Annotation; 25 | import org.eclipse.jface.text.source.ISourceViewer; 26 | import org.eclipse.jface.text.source.IVerticalRuler; 27 | import org.eclipse.jface.text.source.projection.ProjectionAnnotation; 28 | import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; 29 | import org.eclipse.jface.text.source.projection.ProjectionSupport; 30 | import org.eclipse.jface.text.source.projection.ProjectionViewer; 31 | import org.eclipse.jface.util.IPropertyChangeListener; 32 | import org.eclipse.jface.util.PropertyChangeEvent; 33 | import org.eclipse.swt.custom.StyledText; 34 | import org.eclipse.swt.widgets.Composite; 35 | import org.eclipse.ui.IEditorInput; 36 | import org.eclipse.ui.IPathEditorInput; 37 | import org.eclipse.ui.editors.text.TextEditor; 38 | import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; 39 | import org.eclipse.ui.texteditor.IDocumentProvider; 40 | import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; 41 | import org.eclipse.ui.views.contentoutline.IContentOutlinePage; 42 | 43 | import winterwell.markdown.Activator; 44 | import winterwell.markdown.pagemodel.MarkdownPage; 45 | import winterwell.markdown.pagemodel.MarkdownPage.Header; 46 | import winterwell.markdown.preferences.MarkdownPreferencePage; 47 | import winterwell.markdown.views.MarkdownPreview; 48 | 49 | 50 | /** 51 | * Text editor with markdown support. 52 | * @author Daniel Winterstein 53 | */ 54 | public class MarkdownEditor extends TextEditor implements IDocumentListener 55 | { 56 | 57 | /** 58 | * Maximum length for a task tag message 59 | */ 60 | private static final int MAX_TASK_MSG_LENGTH = 80; 61 | private ColorManager colorManager; 62 | private MarkdownContentOutlinePage fOutlinePage = null; 63 | 64 | IDocument oldDoc = null; 65 | 66 | private MarkdownPage page; 67 | 68 | 69 | private boolean pageDirty = true; 70 | 71 | private ProjectionSupport projectionSupport; 72 | private final IPreferenceStore pStore; 73 | private IPropertyChangeListener prefChangeListener; 74 | 75 | 76 | public MarkdownEditor() { 77 | super(); 78 | pStore = Activator.getDefault().getPreferenceStore(); 79 | colorManager = new ColorManager(); 80 | setSourceViewerConfiguration(new MDConfiguration(colorManager, getPreferenceStore())); 81 | } 82 | 83 | 84 | @Override 85 | public void createPartControl(Composite parent) { 86 | // Over-ride to add code-folding support 87 | super.createPartControl(parent); 88 | if (getSourceViewer() instanceof ProjectionViewer) { 89 | ProjectionViewer viewer =(ProjectionViewer)getSourceViewer(); 90 | projectionSupport = new ProjectionSupport(viewer,getAnnotationAccess(),getSharedColors()); 91 | projectionSupport.install(); 92 | //turn projection mode on 93 | viewer.doOperation(ProjectionViewer.TOGGLE); 94 | } 95 | } 96 | 97 | /** 98 | * Returns the editor's source viewer. May return null before the editor's part has been created and after disposal. 99 | */ 100 | public ISourceViewer getViewer() { 101 | return getSourceViewer(); 102 | } 103 | 104 | @Override 105 | protected ISourceViewer createSourceViewer(Composite parent, 106 | IVerticalRuler ruler, int styles) { 107 | // if (true) return super.createSourceViewer(parent, ruler, styles); 108 | // Create with code-folding 109 | ISourceViewer viewer = new ProjectionViewer(parent, ruler, 110 | getOverviewRuler(), isOverviewRulerVisible(), styles); 111 | // ensure decoration support has been created and configured. 112 | SourceViewerDecorationSupport decSupport = getSourceViewerDecorationSupport(viewer); 113 | // SourceViewer viewer = (SourceViewer) super.createSourceViewer(parent, ruler, styles); 114 | // Setup word-wrapping 115 | final StyledText widget = viewer.getTextWidget(); 116 | // Listen to pref changes 117 | prefChangeListener = new IPropertyChangeListener() { 118 | public void propertyChange(PropertyChangeEvent event) { 119 | if (event.getProperty().equals(MarkdownPreferencePage.PREF_WORD_WRAP)) { 120 | widget.setWordWrap(MarkdownPreferencePage.wordWrap()); 121 | } 122 | } 123 | }; 124 | pStore.addPropertyChangeListener(prefChangeListener); 125 | // Switch on word-wrapping 126 | if (MarkdownPreferencePage.wordWrap()) { 127 | widget.setWordWrap(true); 128 | } 129 | return viewer; 130 | } 131 | 132 | public void dispose() { 133 | if (pStore != null) { 134 | pStore.removePropertyChangeListener(prefChangeListener); 135 | } 136 | colorManager.dispose(); 137 | super.dispose(); 138 | } 139 | public void documentAboutToBeChanged(DocumentEvent event) { 140 | } 141 | 142 | public void documentChanged(DocumentEvent event) { 143 | pageDirty = true; 144 | } 145 | 146 | @Override 147 | protected void doSetInput(IEditorInput input) throws CoreException { 148 | // Detach from old 149 | if (oldDoc!= null) { 150 | oldDoc.removeDocumentListener(this); 151 | if (doc2editor.get(oldDoc) == this) doc2editor.remove(oldDoc); 152 | } 153 | // Set 154 | super.doSetInput(input); 155 | // Attach as a listener to new doc 156 | IDocument doc = getDocument(); 157 | oldDoc = doc; 158 | if (doc==null) return; 159 | doc.addDocumentListener(this); 160 | doc2editor.put(doc, this); 161 | // Initialise code folding 162 | haveRunFolding = false; 163 | updateSectionFoldingAnnotations(null); 164 | } 165 | 166 | @Override 167 | protected void editorSaved() { 168 | if (MarkdownPreview.preview != null) { 169 | // Update the preview when the file is saved 170 | MarkdownPreview.preview.update(); 171 | } 172 | } 173 | 174 | public Object getAdapter(Class required) { 175 | if (IContentOutlinePage.class.equals(required)) { 176 | if (fOutlinePage == null) { 177 | fOutlinePage= new MarkdownContentOutlinePage(getDocumentProvider(), this); 178 | if (getEditorInput() != null) 179 | fOutlinePage.setInput(getEditorInput()); 180 | } 181 | return fOutlinePage; 182 | } 183 | return super.getAdapter(required); 184 | } 185 | public IDocument getDocument() { 186 | IEditorInput input = getEditorInput(); 187 | IDocumentProvider docProvider = getDocumentProvider(); 188 | return docProvider==null? null : docProvider.getDocument(input); 189 | } 190 | /** 191 | * 192 | * @return The {@link MarkdownPage} for the document being edited, or null 193 | * if unavailable. 194 | */ 195 | public MarkdownPage getMarkdownPage() { 196 | if (pageDirty) updateMarkdownPage(); 197 | return page; 198 | } 199 | 200 | public int getPrintColumns() { 201 | return getPreferenceStore().getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN); 202 | } 203 | 204 | /** 205 | * @return The text of the editor's document, or null if unavailable. 206 | */ 207 | public String getText() { 208 | IDocument doc = getDocument(); 209 | return doc==null? null : doc.get(); 210 | } 211 | 212 | private void updateMarkdownPage() { 213 | String text = getText(); 214 | if (text==null) text=""; 215 | page = new MarkdownPage(text); 216 | pageDirty = false; 217 | } 218 | 219 | void updateTaskTags(IRegion region) { 220 | try { 221 | boolean useTags = pStore.getBoolean(MarkdownPreferencePage.PREF_TASK_TAGS); 222 | if (!useTags) return; 223 | // Get task tags 224 | // IPreferenceStore peuistore = EditorsUI.getPreferenceStore(); 225 | //// IPreferenceStore pStore_jdt = org.eclipse.jdt.core.compiler.getDefault().getPreferenceStore(); 226 | // String tagString = peuistore.getString("org.eclipse.jdt.core.compiler.taskTags"); 227 | String tagString = pStore.getString(MarkdownPreferencePage.PREF_TASK_TAGS_DEFINED); 228 | List tags = Arrays.asList(tagString.split(",")); 229 | // Get resource for editor 230 | IFile docFile = getResource(this); 231 | // Get existing tasks 232 | IMarker[] taskMarkers = docFile.findMarkers(IMarker.TASK, true, IResource.DEPTH_INFINITE); 233 | List markers = new ArrayList(Arrays.asList(taskMarkers)); 234 | // Collections.sort(markers, c) sort for efficiency 235 | // Find tags in doc 236 | List text = getMarkdownPage().getText(); 237 | for(int i=1; i<=text.size(); i++) { 238 | String line = text.get(i-1); // wierd off-by-one bug 239 | for (String tag : tags) { 240 | tag = tag.trim(); 241 | int tagIndex = line.indexOf(tag); 242 | if (tagIndex == -1) continue; 243 | IMarker exists = updateTaskTags2_checkExisting(i, tagIndex, line, markers); 244 | if (exists!=null) { 245 | markers.remove(exists); 246 | continue; 247 | } 248 | IMarker marker = docFile.createMarker(IMarker.TASK); 249 | //Once we have a marker object, we can set its attributes 250 | marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_NORMAL); 251 | String msg = line.substring(line.indexOf(tag), Math.min(tagIndex+MAX_TASK_MSG_LENGTH, line.length()-1)); 252 | marker.setAttribute(IMarker.MESSAGE, msg); 253 | marker.setAttribute(IMarker.LINE_NUMBER, i); 254 | } 255 | } 256 | // Remove old markers 257 | for (IMarker m : markers) { 258 | try { 259 | m.delete(); 260 | } catch (Exception ex) { 261 | // 262 | } 263 | } 264 | } catch (Exception ex) { 265 | // 266 | } 267 | } 268 | 269 | /** 270 | * Find an existing marker, if there is one. 271 | * @param i 272 | * @param tagIndex 273 | * @param line 274 | * @param markers 275 | * @return 276 | */ 277 | private IMarker updateTaskTags2_checkExisting(int i, int tagIndex, 278 | String line, List markers) { 279 | String tagMessage = line.substring(tagIndex).trim(); 280 | for (IMarker marker : markers) { 281 | try { 282 | Integer lineNum = (Integer) marker.getAttribute(IMarker.LINE_NUMBER); 283 | if (i != lineNum) continue; 284 | String txt = ((String) marker.getAttribute(IMarker.MESSAGE)).trim(); 285 | if (tagMessage.equals(txt)) return marker; 286 | } catch (Exception ex) { 287 | // Ignore 288 | } 289 | } 290 | return null; 291 | } 292 | 293 | 294 | private IFile getResource(MarkdownEditor markdownEditor) { 295 | IPathEditorInput input = (IPathEditorInput) getEditorInput(); 296 | IPath path = input.getPath(); 297 | IWorkspace workspace = ResourcesPlugin.getWorkspace(); 298 | IWorkspaceRoot root = workspace.getRoot(); 299 | IFile[] files = root.findFilesForLocation(path); 300 | if (files.length != 1) return null; 301 | IFile docFile = files[0]; 302 | return docFile; 303 | } 304 | 305 | 306 | /** 307 | * @param doc 308 | * @return 309 | */ 310 | public static MarkdownEditor getEditor(IDocument doc) { 311 | return doc2editor.get(doc); 312 | } 313 | 314 | private static final Map doc2editor = new HashMap(); 315 | 316 | 317 | /** 318 | * @param region 319 | * 320 | */ 321 | public void updatePage(IRegion region) { 322 | // if (!pageDirty) return; 323 | updateTaskTags(region); 324 | updateSectionFoldingAnnotations(region); 325 | } 326 | 327 | 328 | private static final Annotation[] ANNOTATION_ARRAY = new Annotation[0]; 329 | 330 | private static final Position[] POSITION_ARRAY = new Position[0]; 331 | 332 | private boolean haveRunFolding = false; 333 | private Map oldAnnotations = new HashMap(0); 334 | 335 | /** 336 | * @param region can be null 337 | */ 338 | private void updateSectionFoldingAnnotations(IRegion region) { 339 | if (!haveRunFolding) region = null; // Do the whole doc 340 | if ( ! (getSourceViewer() instanceof ProjectionViewer)) return; 341 | ProjectionViewer viewer = ((ProjectionViewer)getSourceViewer()); 342 | MarkdownPage mPage = getMarkdownPage(); 343 | List
headers = mPage.getHeadings(null); 344 | // this will hold the new annotations along 345 | // with their corresponding positions 346 | Map annotations = new HashMap(); 347 | IDocument doc = getDocument(); 348 | updateSectionFoldingAnnotations2(doc, headers, annotations, doc.getLength()); 349 | // Filter existing ones 350 | Position[] newValues = annotations.values().toArray(POSITION_ARRAY); 351 | List deletedAnnotations = new ArrayList(); 352 | for(Entry ae : oldAnnotations.entrySet()) { 353 | Position oldp = ae.getValue(); 354 | boolean stillExists = false; 355 | for (Position newp : newValues) { 356 | if (oldp.equals(newp)) { 357 | annotations.remove(newp); 358 | stillExists = true; 359 | break; 360 | } 361 | } 362 | if (!stillExists && intersectsRegion(oldp, region)) { 363 | deletedAnnotations.add(ae.getKey()); 364 | } 365 | } 366 | // Filter out-of-region ones 367 | for(Annotation a : annotations.keySet().toArray(ANNOTATION_ARRAY)) { 368 | Position p = annotations.get(a); 369 | if (!intersectsRegion(p , region)) annotations.remove(a); 370 | } 371 | // Adjust the page 372 | ProjectionAnnotationModel annotationModel = viewer.getProjectionAnnotationModel(); 373 | if (annotationModel==null) return; 374 | annotationModel.modifyAnnotations(deletedAnnotations.toArray(ANNOTATION_ARRAY), annotations, null); 375 | // Remember old values 376 | oldAnnotations.putAll(annotations); 377 | for (Annotation a : deletedAnnotations) { 378 | oldAnnotations.remove(a); 379 | } 380 | haveRunFolding = true; 381 | } 382 | 383 | 384 | /** 385 | * @param p 386 | * @param region 387 | * @return true if p overlaps with region, or if region is null 388 | */ 389 | private boolean intersectsRegion(Position p, IRegion region) { 390 | if (region==null) return true; 391 | if (p.offset > region.getOffset()+region.getLength()) return false; 392 | if (p.offset+p.length < region.getOffset()) return false; 393 | return true; 394 | } 395 | 396 | 397 | /** 398 | * Calculate where to fold, sticking the info into newAnnotations 399 | * @param doc 400 | * @param headers 401 | * @param newAnnotations 402 | * @param endParent 403 | */ 404 | private void updateSectionFoldingAnnotations2(IDocument doc, List
headers, 405 | Map newAnnotations, int endParent) { 406 | for (int i=0; i subHeaders = header.getSubHeaders(); 418 | if (subHeaders.size() > 0) { 419 | updateSectionFoldingAnnotations2(doc, subHeaders, newAnnotations, end); 420 | } 421 | } catch (Exception ex) { 422 | System.out.println(ex); 423 | } 424 | } 425 | } 426 | 427 | 428 | } 429 | 430 | 431 | 432 | /* 433 | 434 | 435 | - 436 | 437 | 438 | 439 | IEditorPart editor = null; 440 | IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); 441 | if (window != null) { 442 | IWorkbenchPage activePage = window.getActivePage(); 443 | if (activePage != null) editor = activePage.getActiveEditor(); 444 | } 445 | if (editor != null) { 446 | // todo: Add operations for active editor 447 | } 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | */ -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/PrintAction.java: -------------------------------------------------------------------------------- 1 | //package winterwell.markdown.editors; 2 | // 3 | //import java.util.List; 4 | // 5 | //import net.sf.paperclips.PaperClips; 6 | //import net.sf.paperclips.Print; 7 | //import net.sf.paperclips.PrintJob; 8 | //import net.sf.paperclips.TextPrint; 9 | // 10 | //import org.eclipse.core.commands.ExecutionEvent; 11 | //import org.eclipse.core.commands.ExecutionException; 12 | //import org.eclipse.core.commands.IHandler; 13 | //import org.eclipse.core.commands.IHandlerListener; 14 | //import org.eclipse.jface.action.Action; 15 | //import org.eclipse.jface.text.BadLocationException; 16 | //import org.eclipse.jface.text.DocumentEvent; 17 | //import org.eclipse.jface.text.IDocument; 18 | //import org.eclipse.jface.text.IDocumentListener; 19 | //import org.eclipse.jface.text.IRegion; 20 | //import org.eclipse.jface.text.ITextSelection; 21 | //import org.eclipse.jface.text.Region; 22 | //import org.eclipse.jface.text.source.ISourceViewer; 23 | //import org.eclipse.jface.viewers.ISelection; 24 | //import org.eclipse.swt.SWT; 25 | //import org.eclipse.swt.printing.PrintDialog; 26 | //import org.eclipse.swt.printing.PrinterData; 27 | //import org.eclipse.swt.widgets.Display; 28 | //import org.eclipse.ui.IEditorPart; 29 | //import org.eclipse.ui.IPropertyListener; 30 | //import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; 31 | // 32 | //import winterwell.markdown.pagemodel.MarkdownFormatter; 33 | //import winterwell.markdown.pagemodel.MarkdownPage; 34 | //import winterwell.markdown.pagemodel.MarkdownPage.KLineType; 35 | //import winterwell.utils.containers.Pair; 36 | //import winterwell.utils.containers.Range; 37 | // 38 | ///** 39 | // * Print the file 40 | // * 41 | // * 42 | // * @author daniel 43 | // */ 44 | //public class PrintAction extends Action { 45 | // 46 | // public PrintAction() { 47 | // super("Print..."); 48 | // } 49 | // 50 | // @Override 51 | // public void run() { 52 | // try { 53 | // MarkdownEditor ed = (MarkdownEditor) ActionBarContributor.getActiveEditor(); 54 | // if (ed == null) return; // The active editor is not a markdown editor. 55 | // PrintDialog dialog = new PrintDialog(Display.getDefault().getActiveShell(), SWT.NONE); 56 | // PrinterData printerData = dialog.open (); 57 | // if (printerData == null) return; 58 | // Print doc = new TextPrint(ed.getText()); 59 | // PrintJob job = new PrintJob(ed.getTitle(), doc ); 60 | // PaperClips.print(job, printerData); 61 | // // Done 62 | // } catch (Exception ex) { 63 | // System.out.println(ex); 64 | // } 65 | // } 66 | // 67 | // 68 | // 69 | // public void dispose() { 70 | // // Ignore 71 | // } 72 | // 73 | // 74 | //} 75 | // -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/collapseall.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/plugin/src/winterwell/markdown/editors/collapseall.gif -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/editors/synced.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winterstein/Eclipse-Markdown-Editor-Plugin/7436b1a62068db5a9e460ef51089af648e9a08cd/plugin/src/winterwell/markdown/editors/synced.gif -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/pagemodel/MarkdownFormatter.java: -------------------------------------------------------------------------------- 1 | 2 | package winterwell.markdown.pagemodel; 3 | 4 | import java.util.List; 5 | 6 | import winterwell.utils.StrUtils; 7 | 8 | /** 9 | * Formats a string that is compatible with the Markdown syntax. 10 | * Strings must not include headers. 11 | * 12 | * @author Howard Abrams 13 | */ 14 | public class MarkdownFormatter 15 | { 16 | // Expect everyone to simply use the public static methods... 17 | private MarkdownFormatter () 18 | { 19 | } 20 | 21 | /** 22 | * Formats a collection of lines to a particular width and honors typical 23 | * Markdown syntax and formatting. 24 | * 25 | * The method assumes that if the first line ends with a line 26 | * termination character, all the other lines will as well. 27 | * 28 | * @param lines A list of strings that should be formatted and wrapped. 29 | * @param lineWidth The width of the page 30 | * @return A string containing each 31 | */ 32 | public static String format (List lines, int lineWidth) 33 | { 34 | if (lines == null) 35 | return null; // Should we return an empty string? 36 | 37 | final String lineEndings; 38 | if ( lines.get(0).endsWith ("\r\n") ) 39 | lineEndings = "\r\n"; 40 | else if ( lines.get(0).endsWith ("\r") ) 41 | lineEndings = "\r"; 42 | else 43 | lineEndings = StrUtils.LINEEND; 44 | 45 | final StringBuilder buf = new StringBuilder(); 46 | for (String line : lines) { 47 | buf.append (line); 48 | buf.append (' '); // We can add extra spaces with impunity, and this 49 | // makes sure our lines don't run together. 50 | } 51 | return format ( buf.toString(), lineWidth, lineEndings ); 52 | } 53 | 54 | 55 | /** 56 | * Formats a string of text. The formatting does line wrapping at the 57 | * lineWidth boundary, but it also honors the formatting 58 | * of initial paragraph lines, allowing indentation of the entire 59 | * paragraph. 60 | * 61 | * @param text The line of text to format 62 | * @param lineWidth The width of the lines 63 | * @return A string containing the formatted text. 64 | */ 65 | public static String format ( final String text, final int lineWidth) 66 | { 67 | return format(text, lineWidth, StrUtils.LINEEND); 68 | } 69 | 70 | /** 71 | * Formats a string of text. The formatting does line wrapping at the 72 | * lineWidth boundary, but it also honors the formatting 73 | * of initial paragraph lines, allowing indentation of the entire 74 | * paragraph. 75 | * 76 | * @param text The line of text to format 77 | * @param lineWidth The width of the lines 78 | * @param lineEnding The line ending that overrides the default System value 79 | * @return A string containing the formatted text. 80 | */ 81 | public static String format (final String text, final int lineWidth, final String lineEnding) 82 | { 83 | return new String( format(text.toCharArray (), lineWidth, lineEnding)); 84 | } 85 | 86 | /** 87 | * The available cursor position states as it sits in the buffer. 88 | */ 89 | private enum StatePosition { 90 | /** The beginning of a paragraph ... the start of the buffer */ 91 | BEGIN_FIRST_LINE, 92 | 93 | /** The beginning of the next line, which may be completely ignored. */ 94 | BEGIN_OTHER_LINE, 95 | 96 | /** The beginning of a new line that will not be ignored, but appended. */ 97 | BEGIN_NEW_LINE, 98 | 99 | /** The middle of a line. */ 100 | MIDDLE_OF_LINE 101 | } 102 | 103 | /** 104 | * The method that does the work of formatting a string of text. The text, 105 | * however, is a character array, which is more efficient to work with. 106 | * 107 | * TODO: Should we make the format(char[]) method public? 108 | * 109 | * @param text The line of text to format 110 | * @param lineWidth The width of the lines 111 | * @param lineEnding The line ending that overrides the default System value 112 | * @return A string containing the formatted text. 113 | */ 114 | static char[] format ( final char[] text, final int lineWidth, final String lineEnding ) 115 | { 116 | final StringBuilder word = new StringBuilder(); 117 | final StringBuilder indent = new StringBuilder(); 118 | final StringBuilder buffer = new StringBuilder(text.length + 10); 119 | 120 | StatePosition state = StatePosition.BEGIN_FIRST_LINE; 121 | int lineLength = 0; 122 | 123 | // There are times when we will run across a character(s) that will 124 | // cause us to stop doing word wrap until we get to the 125 | // "end of non-wordwrap" character(s). 126 | // 127 | // If this string is set to null, it tells us to "do" word-wrapping. 128 | char endWordwrap1 = 0; 129 | char endWordwrap2 = 0; 130 | 131 | // We loop one character past the end of the loop, and when we get to 132 | // this position, we assign 'c' to be 0 ... as a marker for the end of 133 | // the string... 134 | 135 | for (int i = 0; i <= text.length; i++) 136 | { 137 | final char c; 138 | if (i < text.length) 139 | c = text[i]; 140 | else 141 | c = 0; 142 | 143 | final char nextChar; 144 | if (i+1 < text.length) 145 | nextChar = text[i+1]; 146 | else 147 | nextChar = 0; 148 | 149 | // Are we actually word-wrapping? 150 | if (endWordwrap1 != 0) { 151 | // Did we get the ending sequence of the non-word-wrap? 152 | if ( ( endWordwrap2 == 0 && c == endWordwrap1 ) || 153 | ( c == endWordwrap1 && nextChar == endWordwrap2 ) ) 154 | endWordwrap1 = 0; 155 | buffer.append (c); 156 | lineLength++; 157 | 158 | if (endWordwrap1 == 0 && endWordwrap2 != 0) { 159 | buffer.append (nextChar); 160 | lineLength++; 161 | i++; 162 | } 163 | continue; 164 | } 165 | 166 | // Check to see if we got one of our special non-word-wrapping 167 | // character sequences ... 168 | 169 | if ( c == '[' ) { // [Hyperlink] 170 | endWordwrap1 = ']'; 171 | } 172 | else if ( c == '*' && nextChar == '*' ) { // **Bold** 173 | endWordwrap1 = '*'; 174 | endWordwrap2 = '*'; 175 | } // *Italics* 176 | else if ( c == '*' && state == StatePosition.MIDDLE_OF_LINE ) { 177 | endWordwrap1 = '*'; 178 | } 179 | else if ( c == '`' ) { // `code` 180 | endWordwrap1 = '`'; 181 | } 182 | else if ( c == '(' && nextChar == '(' ) { // ((Footnote)) 183 | endWordwrap1 = ')'; 184 | endWordwrap2 = ')'; 185 | } 186 | else if ( c == '!' && nextChar == '[' ) { // ![Image] 187 | endWordwrap1 = ')'; 188 | } 189 | 190 | // We are no longer doing word-wrapping, so tidy the situation up... 191 | if (endWordwrap1 != 0) { 192 | if (word.length() > 0) 193 | lineLength = addWordToBuffer (lineWidth, lineEnding, word, indent, buffer, lineLength); 194 | else if (buffer.length() > 0 && buffer.charAt (buffer.length()-1) != ']' ) 195 | buffer.append(' '); 196 | // We are adding an extra space for most situations, unless we get a 197 | // [link][ref] where we want them to be together without a space. 198 | 199 | buffer.append (c); 200 | lineLength++; 201 | continue; 202 | } 203 | 204 | // Normal word-wrapping processing continues ... 205 | 206 | if (state == StatePosition.BEGIN_FIRST_LINE) 207 | { 208 | if ( c == '\n' || c == '\r' ) { // Keep, but ignore initial line feeds 209 | buffer.append (c); 210 | lineLength = 0; 211 | continue; 212 | } 213 | 214 | if (Character.isWhitespace (c)) 215 | indent.append (c); 216 | else if ( (c == '*' || c == '-' || c == '.' ) && 217 | Character.isWhitespace (nextChar) ) 218 | indent.append (' '); 219 | else if ( Character.isDigit (c) && nextChar == '.' && 220 | Character.isWhitespace (text[i+2])) 221 | indent.append (' '); 222 | else if ( c == '>' ) 223 | indent.append ('>'); 224 | else 225 | state = StatePosition.MIDDLE_OF_LINE; 226 | 227 | // If we are still in the initial state, then put 'er in... 228 | if (state == StatePosition.BEGIN_FIRST_LINE) { 229 | buffer.append (c); 230 | lineLength++; 231 | } 232 | } 233 | 234 | // While it would be more accurate to explicitely state the range of 235 | // possibilities, with something like: 236 | // EnumSet.range (StatePosition.BEGIN_OTHER_LINE, StatePosition.MIDDLE_OF_LINE ).contains (state) 237 | // We know that what is left is just the BEGIN_FIRST_LINE ... 238 | 239 | if ( state != StatePosition.BEGIN_FIRST_LINE ) 240 | { 241 | // If not the middle of the line, then it must be at the first of a line 242 | // Either BEGIN_OTHER_LINE or BEGIN_NEW_LINE 243 | if (state != StatePosition.MIDDLE_OF_LINE) 244 | { 245 | if ( Character.isWhitespace(c) || c == '>' || c == '.' ) 246 | word.append (c); 247 | else if ( ( ( c == '*' || c == '-' ) && Character.isWhitespace (nextChar) ) || 248 | ( Character.isDigit(c) && nextChar == '.' && Character.isWhitespace( text[i+2] ) ) ) { 249 | word.append (c); 250 | state = StatePosition.BEGIN_NEW_LINE; 251 | } 252 | else { 253 | if (state == StatePosition.BEGIN_NEW_LINE) { 254 | buffer.append (word); 255 | lineLength = word.substring ( word.indexOf("\n")+1 ).length(); 256 | } 257 | word.setLength (0); 258 | state = StatePosition.MIDDLE_OF_LINE; 259 | } 260 | } 261 | 262 | if (state == StatePosition.MIDDLE_OF_LINE) 263 | { 264 | // Are we at the end of a word? Then we need to calculate whether 265 | // to wrap the line or not. 266 | // 267 | // This condition does double duty, in that is also serves to 268 | // ignore multiple spaces and special characters that may be at 269 | // the beginning of the line. 270 | if ( Character.isWhitespace(c) || c == 0 ) 271 | { 272 | if ( word.length() > 0) { 273 | lineLength = addWordToBuffer (lineWidth, lineEnding, word, indent, buffer, lineLength); 274 | } 275 | // Do we we two spaces at the end of the line? Honor this... 276 | else if ( c == ' ' && ( nextChar == '\r' || nextChar == '\n' ) && 277 | state != StatePosition.BEGIN_OTHER_LINE ) { 278 | buffer.append (" "); 279 | buffer.append (lineEnding); 280 | lineLength = 0; 281 | } 282 | 283 | if ( c == '\r' || c == '\n' ) { 284 | state = StatePosition.BEGIN_OTHER_LINE; 285 | word.append(c); 286 | } 287 | 288 | // Linefeeds are completely ignored and just treated as whitespace, 289 | // unless, of course, there are two of 'em... and of course, end of 290 | // lines are simply evil on Windows machines. 291 | 292 | if ( (c == '\n' && nextChar == '\n') || // Unix-style line-ends 293 | ( c == '\r' && nextChar == '\n' && // Windows-style line-ends 294 | text[i+2] == '\r' && text[i+3] == '\n' ) ) 295 | { 296 | state = StatePosition.BEGIN_FIRST_LINE; 297 | word.setLength(0); 298 | indent.setLength (0); 299 | lineLength = 0; 300 | 301 | if (c == '\r') { // If we are dealing with Windows-style line-ends, 302 | i++; // we need to skip past the next character... 303 | buffer.append("\r\n"); 304 | } else 305 | buffer.append(c); 306 | } 307 | 308 | } else { 309 | word.append (c); 310 | state = StatePosition.MIDDLE_OF_LINE; 311 | } 312 | } 313 | } 314 | } 315 | 316 | return buffer.toString().toCharArray(); 317 | } 318 | 319 | /** 320 | * Adds a word to the buffer, performing word wrap if necessary. 321 | * @param lineWidth The current width of the line 322 | * @param lineEnding The line ending to append, if necessary 323 | * @param word The word to append 324 | * @param indent The indentation string to insert, if necesary 325 | * @param buffer The buffer to perform all this stuff to 326 | * @param lineLength The current length of the current line 327 | * @return The new length of the current line 328 | */ 329 | private static int addWordToBuffer (final int lineWidth, final String lineEnding, 330 | final StringBuilder word, 331 | final StringBuilder indent, 332 | final StringBuilder buffer, int lineLength) 333 | { 334 | if ( word.length() + lineLength + 1 > lineWidth ) 335 | { 336 | buffer.append (lineEnding); 337 | buffer.append (indent); 338 | buffer.append (word); 339 | 340 | lineLength = indent.length() + word.length(); 341 | } 342 | else { 343 | if ( lineLength > indent.length() ) 344 | buffer.append (' '); 345 | buffer.append (word); 346 | lineLength += word.length() + 1; 347 | } 348 | word.setLength (0); 349 | return lineLength; 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/pagemodel/MarkdownFormatterTest.java: -------------------------------------------------------------------------------- 1 | //package winterwell.markdown.pagemodel; 2 | // 3 | //import java.util.Arrays; 4 | //import java.util.List; 5 | // 6 | //import junit.framework.TestCase; 7 | //// import winterwell.utils.MarkdownFormatter; 8 | // 9 | ///** 10 | // * Test methods in the StringMethods utility class. 11 | // */ 12 | //public class MarkdownFormatterTest extends TestCase 13 | //{ 14 | // /** 15 | // * The local line-end string. \n on unix, \r\n on windows. 16 | // * I really want to run through all of these tests with both styles. 17 | // * We'll come back to that sort of a trick. 18 | // */ 19 | // // public String LINEEND = System.getProperty("line.separator"); 20 | // public static final String LINEEND = "\r\n"; 21 | // 22 | // /** 23 | // * Test default word wrapping of a long line of normal text. 24 | // */ 25 | // public void testFormatStringInt () 26 | // { 27 | // final String LONG_LINE = 28 | // "Now is the time for all good " + 29 | // "chickens to come to the aid of " + 30 | // "their coopertino lattes, and " + 31 | // "begin the process of singing."; 32 | // final String EXPECTED = 33 | // "Now is the time for all good" + LINEEND + 34 | // "chickens to come to the aid of" + LINEEND + // This line is 30 characters 35 | // "their coopertino lattes, and" + LINEEND + 36 | // "begin the process of singing."; 37 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 30, LINEEND)); 38 | // } 39 | // 40 | // /** 41 | // * If the initial part of the line contains some spaces, we use that as 42 | // * the "indentation" for every other line. 43 | // * @throws Exception 44 | // */ 45 | // public void testIndentOfSpaces () throws Exception 46 | // { 47 | // final String LONG_LINE = 48 | // " Now is the time for all good " + 49 | // "chickens to come to the aid of " + 50 | // "their coopertino lattes, and " + 51 | // "begin the process of singing."; 52 | // final String EXPECTED = 53 | // " Now is the time for all good" + LINEEND + 54 | // " chickens to come to the aid of" + LINEEND + 55 | // " their coopertino lattes, and" + LINEEND + 56 | // " begin the process of singing."; 57 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 58 | // } 59 | // 60 | // /** 61 | // * Can we maintain the format of text that is already formatted? 62 | // * @throws Exception 63 | // */ 64 | // public void testAlreadyFormatted () throws Exception 65 | // { 66 | // final String LONG_LINE = 67 | // " Now is the time for all good" + LINEEND + 68 | // " chickens to come to the aid of" + LINEEND + 69 | // " their coopertino lattes, and" + LINEEND + 70 | // " begin the process of singing."; 71 | // assertEquals (LONG_LINE, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 72 | // } 73 | // 74 | // /** 75 | // * Formatting a single line is all fine and dandy, but what about 76 | // * formatting multiple paragraphs, that is, blank lines. 77 | // * @throws Exception 78 | // */ 79 | // public void testMultipleParagraphs () throws Exception 80 | // { 81 | // final String LONG_LINE = 82 | // " Now is the time for all good " + 83 | // "chickens to come to their aid." + LINEEND + LINEEND + 84 | // " And drink coopertino lattes, and " + 85 | // "begin the process of singing."; 86 | // final String EXPECTED = 87 | // " Now is the time for all good" + LINEEND + 88 | // " chickens to come to their aid." + LINEEND + LINEEND + 89 | // " And drink coopertino lattes," + LINEEND + 90 | // " and begin the process of" + LINEEND + " singing."; 91 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 92 | // } 93 | // 94 | // /** 95 | // * What if the section we are formatting, begins with line feeds? 96 | // * Do we keep 'em? Might as well. :-) 97 | // * @throws Exception 98 | // */ 99 | // public void testInitialLineFeeds () throws Exception 100 | // { 101 | // final String LONG_LINE = LINEEND + LINEEND + LINEEND + 102 | // " Now is the time for all good" + LINEEND + 103 | // " chickens to come to the aid of" + LINEEND + 104 | // " their coopertino lattes, and" + LINEEND + 105 | // " begin the process of singing."; 106 | // assertEquals (LONG_LINE, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 107 | // } 108 | // 109 | // /** 110 | // * We need to be able to format bulleted lists appropriately. 111 | // * @throws Exception 112 | // */ 113 | // public void testSingleBulletedList () throws Exception 114 | // { 115 | // final String LONG_LINE = 116 | // " * Now is the time for all good " + 117 | // "chickens to come to the aid of " + LINEEND + 118 | // "their coopertino lattes, and " + 119 | // "begin the process of singing."; 120 | // final String EXPECTED = 121 | // " * Now is the time for all good" + LINEEND + 122 | // " chickens to come to the aid of" + LINEEND + 123 | // " their coopertino lattes, and" + LINEEND + 124 | // " begin the process of singing."; 125 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 126 | // } 127 | // 128 | // /** 129 | // * What about dealing with multiple bulleted lists. 130 | // * @throws Exception 131 | // */ 132 | // public void testMultipleBulletedList () throws Exception 133 | // { 134 | // final String LONG_LINE = 135 | // "Now is the time for all good " + 136 | // "chickens to:" + LINEEND + LINEEND + 137 | // " * Cluck" + LINEEND + 138 | // " * Sing" + LINEEND + 139 | // " * Drink coopertino lattes."; 140 | // final String EXPECTED = 141 | // "Now is the time for all good" + LINEEND + 142 | // "chickens to:" + LINEEND + LINEEND + 143 | // " * Cluck" + LINEEND + 144 | // " * Sing" + LINEEND + 145 | // " * Drink coopertino lattes."; 146 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 147 | // } 148 | // 149 | // /** 150 | // * What about dealing with multiple bulleted lists. 151 | // * @throws Exception 152 | // */ 153 | // public void testMultipleDashedBulletedList () throws Exception 154 | // { 155 | // final String LONG_LINE = 156 | // "Now is the time for all good " + 157 | // "chickens to:" + LINEEND + LINEEND + 158 | // " - Cluck" + LINEEND + 159 | // " - Sing" + LINEEND + 160 | // " - Drink coopertino lattes."; 161 | // final String EXPECTED = 162 | // "Now is the time for all good" + LINEEND + 163 | // "chickens to:" + LINEEND + LINEEND + 164 | // " - Cluck" + LINEEND + 165 | // " - Sing" + LINEEND + 166 | // " - Drink coopertino lattes."; 167 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 168 | // } 169 | // 170 | // /** 171 | // * Tests whether we can have nested bulleted lists. 172 | // * @throws Exception 173 | // */ 174 | // public void testSubindentedBulletedLists () throws Exception 175 | // { 176 | // final String LONG_LINE = 177 | // "Now is the time for all good " + 178 | // "chickens to:" + LINEEND + LINEEND + 179 | // " * Cluck, cluck, cluck till their little feets hurt:" + LINEEND + 180 | // " * Do it again and again and again and again." + LINEEND + 181 | // " * And maybe again and again if their mommy's say so." + LINEEND + 182 | // " * We can indent really, really, deep with three levels of subitems." + LINEEND + 183 | // " * But we aren't sure if this is getting ridiculous or just plain expected." + LINEEND + 184 | // " * Sing, sing, sing till their little voices break:" + LINEEND + 185 | // " * Do it again and again and again and again." + LINEEND + 186 | // " * And maybe again and again if their mommy's say so." + LINEEND + 187 | // " * Drink coopertino lattes."; 188 | // final String EXPECTED = 189 | // "Now is the time for all good" + LINEEND + 190 | // "chickens to:" + LINEEND + LINEEND + 191 | // " * Cluck, cluck, cluck till their" + LINEEND + 192 | // " little feets hurt:" + LINEEND + 193 | // " * Do it again and again and" + LINEEND + 194 | // " again and again." + LINEEND + 195 | // " * And maybe again and again if" + LINEEND + 196 | // " their mommy's say so." + LINEEND + 197 | // " * We can indent really," + LINEEND + 198 | // " really, deep with three" + LINEEND + 199 | // " levels of subitems." + LINEEND + 200 | // " * But we aren't sure if this" + LINEEND + 201 | // " is getting ridiculous or " + LINEEND + 202 | // " just plain expected." + LINEEND + 203 | // " * Sing, sing, sing till their" + LINEEND + 204 | // " little voices break:" + LINEEND + 205 | // " * Do it again and again and" + LINEEND + 206 | // " again and again." + LINEEND + 207 | // " * And maybe again and again if" + LINEEND + 208 | // " their mommy's say so." + LINEEND + 209 | // " * Drink coopertino lattes."; 210 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 211 | // } 212 | // 213 | // /** 214 | // * Tests whether we can have nested bulleted lists. 215 | // * @throws Exception 216 | // */ 217 | // public void testSubindentedBulletedLists2 () throws Exception 218 | // { 219 | // final String LONG_LINE = 220 | // "Now is the time for all good " + 221 | // "chickens to:" + LINEEND + LINEEND + 222 | // " * Cluck, cluck, cluck till their little feets hurt:" + LINEEND + LINEEND + 223 | // " * Do it again and again and again and again." + LINEEND + LINEEND + 224 | // " * And maybe again and again if their mommy's say so." + LINEEND + LINEEND + 225 | // " * We can indent really, really, deep with three levels of subitems." + LINEEND + LINEEND + 226 | // " * But we aren't sure if this is getting ridiculous or just plain expected." + LINEEND + LINEEND + 227 | // " * Sing, sing, sing till their little voices break:" + LINEEND + LINEEND + 228 | // " * Do it again and again and again and again." + LINEEND + LINEEND + 229 | // " * And maybe again and again if their mommy's say so." + LINEEND + LINEEND + 230 | // " * Drink coopertino lattes."; 231 | // final String EXPECTED = 232 | // "Now is the time for all good" + LINEEND + 233 | // "chickens to:" + LINEEND + LINEEND + 234 | // " * Cluck, cluck, cluck till their" + LINEEND + 235 | // " little feets hurt:" + LINEEND + LINEEND + 236 | // " * Do it again and again and" + LINEEND + 237 | // " again and again." + LINEEND + LINEEND + 238 | // " * And maybe again and again if" + LINEEND + 239 | // " their mommy's say so." + LINEEND + LINEEND + 240 | // " * We can indent really," + LINEEND + 241 | // " really, deep with three" + LINEEND + 242 | // " levels of subitems." + LINEEND + LINEEND + 243 | // " * But we aren't sure if this" + LINEEND + 244 | // " is getting ridiculous or" + LINEEND + 245 | // " just plain expected." + LINEEND + LINEEND + 246 | // " * Sing, sing, sing till their" + LINEEND + 247 | // " little voices break:" + LINEEND + LINEEND + 248 | // " * Do it again and again and" + LINEEND + 249 | // " again and again." + LINEEND + LINEEND + 250 | // " * And maybe again and again if" + LINEEND + 251 | // " their mommy's say so." + LINEEND + LINEEND + 252 | // " * Drink coopertino lattes."; 253 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 254 | // } 255 | // 256 | // /** 257 | // * What about dealing with a numeric list? 258 | // * @throws Exception 259 | // */ 260 | // public void testSingleNumericList () throws Exception 261 | // { 262 | // final String LONG_LINE = 263 | // " 2. Now is the time for all good " + 264 | // "chickens to come to the aid of " + 265 | // "their coopertino lattes, and " + 266 | // "begin the process of singing."; 267 | // final String EXPECTED = 268 | // " 2. Now is the time for all good" + LINEEND + 269 | // " chickens to come to the aid of" + LINEEND + 270 | // " their coopertino lattes, and" + LINEEND + 271 | // " begin the process of singing."; 272 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 273 | // } 274 | // 275 | // /** 276 | // * What about dealing with multiple bulleted lists. 277 | // * @throws Exception 278 | // */ 279 | // public void testMultipleNumericList () throws Exception 280 | // { 281 | // final String LONG_LINE = 282 | // "Now is the time for all good " + 283 | // "chickens to:" + LINEEND + LINEEND + 284 | // " 1. Cluck" + LINEEND + 285 | // " 2. Sing" + LINEEND + 286 | // " 3. Drink coopertino lattes."; 287 | // final String EXPECTED = 288 | // "Now is the time for all good" + LINEEND + 289 | // "chickens to:" + LINEEND + LINEEND + 290 | // " 1. Cluck" + LINEEND + 291 | // " 2. Sing" + LINEEND + 292 | // " 3. Drink coopertino lattes."; 293 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 294 | // } 295 | // 296 | // /** 297 | // * What about dealing with sections that should not be word wrapped, like 298 | // * the text between brackets (since they are hyperlinks). 299 | // * @throws Exception 300 | // */ 301 | // public void testNoWordWrapBracket() throws Exception 302 | // { 303 | // final String LONG_LINE = 304 | // "Now is the time for all good " + 305 | // "chickens to come to [the spurious and costly][3] " + 306 | // "aid of their coopertino cups, " + 307 | // "and begin sing."; 308 | // final String EXPECTED = 309 | // "Now is the time for all good" + LINEEND + 310 | // "chickens to come to [the spurious and costly][3]" + LINEEND + 311 | // "aid of their coopertino cups, and" + LINEEND + 312 | // "begin sing."; 313 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 314 | // } 315 | // /** 316 | // * What about dealing with bracketed sections with no extra white space 317 | // * @throws Exception 318 | // */ 319 | // public void testNoWordWrapBracket2() throws Exception 320 | // { 321 | // final String LONG_LINE = 322 | // "Now is the time for all good " + 323 | // "chickens to come to[the spurious and costly][3] " + 324 | // "aid of their coopertino cups, " + 325 | // "and begin sing."; 326 | // final String EXPECTED = 327 | // "Now is the time for all good" + LINEEND + 328 | // "chickens to come to[the spurious and costly][3]" + LINEEND + 329 | // "aid of their coopertino cups, and" + LINEEND + 330 | // "begin sing."; 331 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 332 | // } 333 | // 334 | // /** 335 | // * What about dealing with bold sections that should not be word wrapped. 336 | // * @throws Exception 337 | // */ 338 | // public void testNoWordWrapDoubleAsterix() throws Exception 339 | // { 340 | // final String LONG_LINE = 341 | // "Now is the time for all good " + 342 | // "chickens to come to **the spurious and costly** " + 343 | // "aid of their coopertino cups, " + 344 | // "and begin sing."; 345 | // final String EXPECTED = 346 | // "Now is the time for all good" + LINEEND + 347 | // "chickens to come to **the spurious and costly**" + LINEEND + 348 | // "aid of their coopertino cups, and" + LINEEND + 349 | // "begin sing."; 350 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 351 | // } 352 | // 353 | // /** 354 | // * What about dealing with italic sections that should not be word wrapped 355 | // * @throws Exception 356 | // */ 357 | // public void testNoWordWrapSingleAsterix() throws Exception 358 | // { 359 | // final String LONG_LINE = 360 | // "Now is the time for all good " + 361 | // "chickens to come to *the spurious and costly* " + 362 | // "aid of their coopertino cups, " + 363 | // "and begin sing."; 364 | // final String EXPECTED = 365 | // "Now is the time for all good" + LINEEND + 366 | // "chickens to come to *the spurious and costly*" + LINEEND + 367 | // "aid of their coopertino cups, and" + LINEEND + 368 | // "begin sing."; 369 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 370 | // } 371 | // 372 | // /** 373 | // * What about dealing with sections that are code should not be broken. 374 | // * @throws Exception 375 | // */ 376 | // public void testNoWordWrapCode() throws Exception 377 | // { 378 | // final String LONG_LINE = 379 | // "Now is the time for all good " + 380 | // "chickens to come to `the spurious and costly` " + 381 | // "aid of their coopertino cups, " + 382 | // "and begin sing."; 383 | // final String EXPECTED = 384 | // "Now is the time for all good" + LINEEND + 385 | // "chickens to come to `the spurious and costly`" + LINEEND + 386 | // "aid of their coopertino cups, and" + LINEEND + 387 | // "begin sing."; 388 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 389 | // } 390 | // 391 | // /** 392 | // * What about dealing with double parenthesis sections ... these shouldn't 393 | // * be broken up. 394 | // * @throws Exception 395 | // */ 396 | // public void testNoWordWrapDoubleParens() throws Exception 397 | // { 398 | // final String LONG_LINE = 399 | // "Now is the time for all good " + 400 | // "chickens to come to ((the spurious and costly)) " + 401 | // "aid of their coopertino cups, " + 402 | // "and begin sing."; 403 | // final String EXPECTED = 404 | // "Now is the time for all good" + LINEEND + 405 | // "chickens to come to ((the spurious and costly))" + LINEEND + 406 | // "aid of their coopertino cups, and" + LINEEND + 407 | // "begin sing."; 408 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND)); 409 | // } 410 | // 411 | // 412 | // /** 413 | // * If a line, embedded in a paragraph has two spaces at the end of the line, 414 | // * these need to be honored and maintained. 415 | // * @throws Exception 416 | // */ 417 | // public void testLineBreaksHonored () throws Exception 418 | // { 419 | // final String LONG_LINE = 420 | // "Now is the time for all good " + 421 | // "chickens to come " + LINEEND + 422 | // "to the aid of their coopertino lattes, and " + 423 | // "begin the process of singing."; 424 | // final String EXPECTED = 425 | // "Now is the time for all good" + LINEEND + 426 | // "chickens to come " + LINEEND + 427 | // "to the aid of their coopertino" + LINEEND + 428 | // "lattes, and begin the process of" + LINEEND + 429 | // "singing."; 430 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 33, LINEEND)); 431 | // } 432 | // 433 | // /** 434 | // * A "blockquote" in Markdown can accept > characters at the beginning 435 | // * of all of the lines. 436 | // * @throws Exception 437 | // */ 438 | // public void testBlockQuoteSimple () throws Exception 439 | // { 440 | // final String LONG_LINE = 441 | // " > Now is the time for all good " + 442 | // "chickens to come to the aid of " + 443 | // "their coopertino , and " + 444 | // "begin the process of singing."; 445 | // final String EXPECTED = 446 | // " > Now is the time for all good" + LINEEND + 447 | // " > chickens to come to the aid of" + LINEEND + 448 | // " > their coopertino , and" + LINEEND + 449 | // " > begin the process of singing."; 450 | // assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 33, LINEEND)); 451 | // } 452 | // 453 | // /** 454 | // * A "blockquote" in Markdown can accept > characters at the beginning 455 | // * of all of the lines. Can we accept a version that is already formatted? 456 | // * @throws Exception 457 | // */ 458 | // public void testBlockQuoteAlreadyFormatted () throws Exception 459 | // { 460 | // final String EXPECTED = 461 | // " > Now is the time for all good" + LINEEND + 462 | // " > chickens to come to the aid of" + LINEEND + 463 | // " > their coopertino , and" + LINEEND + 464 | // " > begin the process of singing."; 465 | // assertEquals (EXPECTED, MarkdownFormatter.format (EXPECTED, 33, LINEEND)); 466 | // } 467 | // 468 | // /** 469 | // * Tests that the "list" interface works if each string does not have 470 | // * carriage returns. 471 | // * @throws Exception 472 | // */ 473 | // public void testListWithoutLinefeeds () throws Exception 474 | // { 475 | // final String lineend = System.getProperty("line.separator"); 476 | // 477 | // final List lines = Arrays.asList ( new String[] { 478 | // "Now is the time for all good", 479 | // "chickens to come to the aid of", 480 | // "their coopertino lattes, and", 481 | // "begin the process of singing." 482 | // } ); 483 | // final String EXPECTED = 484 | // "Now is the time for all good" + lineend + 485 | // "chickens to come to the aid of" + lineend + // This line is 30 characters 486 | // "their coopertino lattes, and" + lineend + 487 | // "begin the process of singing."; 488 | // 489 | // final String RESULTS = MarkdownFormatter.format (lines, 30); 490 | // assertEquals (EXPECTED, RESULTS); 491 | // } 492 | // 493 | // /** 494 | // * Tests that the "list" interface works if each string has carriage returns. 495 | // * @throws Exception 496 | // */ 497 | // public void testListWithLinefeeds () throws Exception 498 | // { 499 | // final List lines = Arrays.asList ( new String[] { 500 | // "Now is the time for all good chickens to come" + LINEEND, 501 | // "to the aid of" + LINEEND, 502 | // "their coopertino lattes, and" + LINEEND, 503 | // "begin the process of singing." 504 | // } ); 505 | // final String EXPECTED = 506 | // "Now is the time for all good" + LINEEND + 507 | // "chickens to come to the aid of" + LINEEND + // This line is 30 characters 508 | // "their coopertino lattes, and" + LINEEND + 509 | // "begin the process of singing."; 510 | // 511 | // final String RESULTS = MarkdownFormatter.format (lines, 30); 512 | // assertEquals (EXPECTED, RESULTS); 513 | // } 514 | // 515 | // /** 516 | // * Tests that we don't break up image tags. 517 | // * @throws Exception 518 | // */ 519 | // public void testImageTags () throws Exception 520 | // { 521 | // final List lines = Arrays.asList ( new String[] { 522 | // "Now is the time for all good chickens to come " + 523 | // "to the aid ![Some text description](http://www.google.com/images/logo.gif)" + LINEEND, 524 | // "their coopertino lattes, and" + LINEEND, 525 | // "begin the process of singing." 526 | // } ); 527 | // final String EXPECTED = 528 | // "Now is the time for all good" + LINEEND + 529 | // "chickens to come to the aid " + // This line is 30 characters 530 | // "![Some text description](http://www.google.com/images/logo.gif)" + LINEEND + 531 | // "their coopertino lattes, and" + LINEEND + 532 | // "begin the process of singing."; 533 | // 534 | // final String RESULTS = MarkdownFormatter.format (lines, 30); 535 | // assertEquals (EXPECTED, RESULTS); 536 | // } 537 | //} 538 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/pagemodel/MarkdownPage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright winterwell Mathematics Ltd. 3 | * @author Daniel Winterstein 4 | * 11 Jan 2007 5 | */ 6 | package winterwell.markdown.pagemodel; 7 | 8 | import java.io.File; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.regex.Matcher; 15 | import java.util.regex.Pattern; 16 | 17 | import org.eclipse.jface.preference.IPreferenceStore; 18 | 19 | import winterwell.markdown.Activator; 20 | import winterwell.markdown.StringMethods; 21 | import winterwell.markdown.preferences.MarkdownPreferencePage; 22 | import winterwell.utils.FailureException; 23 | import winterwell.utils.Process; 24 | import winterwell.utils.StrUtils; 25 | import winterwell.utils.Utils; 26 | import winterwell.utils.io.FileUtils; 27 | 28 | import com.petebevin.markdown.MarkdownProcessor; 29 | 30 | /** 31 | * Understands Markdown syntax. 32 | * 33 | * @author Daniel Winterstein 34 | */ 35 | public class MarkdownPage { 36 | 37 | /** 38 | * Strip leading and trailing #s and whitespace 39 | * 40 | * @param line 41 | * @return cleaned up line 42 | */ 43 | private String cleanHeader(String line) { 44 | for (int j = 0; j < line.length(); j++) { 45 | char c = line.charAt(j); 46 | if (c != '#' && !Character.isWhitespace(c)) { 47 | line = line.substring(j); 48 | break; 49 | } 50 | } 51 | for (int j = line.length() - 1; j > 0; j--) { 52 | char c = line.charAt(j); 53 | if (c != '#' && !Character.isWhitespace(c)) { 54 | line = line.substring(0, j + 1); 55 | break; 56 | } 57 | } 58 | return line; 59 | } 60 | 61 | /** 62 | * Represents information about a section header. E.g. ## Misc Warblings 63 | * 64 | * @author daniel 65 | */ 66 | public class Header { 67 | /** 68 | * 1 = top-level (i.e. #), 2= 2nd-level (i.e. ##), etc. 69 | */ 70 | final int level; 71 | /** 72 | * The text of the Header 73 | */ 74 | final String heading; 75 | /** 76 | * Sub-sections, if any 77 | */ 78 | final List
subHeaders = new ArrayList
(); 79 | /** 80 | * The line on which this header occurs. 81 | */ 82 | final int lineNumber; 83 | 84 | public int getLineNumber() { 85 | return lineNumber; 86 | } 87 | 88 | /** 89 | * 90 | * @return the next section (at this depth if possible), null if none 91 | */ 92 | public Header getNext() { 93 | if (parent == null) { 94 | int ti = level1Headers.indexOf(this); 95 | if (ti == -1 || ti == level1Headers.size() - 1) 96 | return null; 97 | return level1Headers.get(ti + 1); 98 | } 99 | int i = parent.subHeaders.indexOf(this); 100 | assert i != -1 : this; 101 | if (i == parent.subHeaders.size() - 1) 102 | return parent.getNext(); 103 | return parent.subHeaders.get(i + 1); 104 | } 105 | /** 106 | * 107 | * @return the next section (at this depth if possible), null if none 108 | */ 109 | public Header getPrevious() { 110 | if (parent == null) { 111 | int ti = level1Headers.indexOf(this); 112 | if (ti == -1 || ti == 0) 113 | return null; 114 | return level1Headers.get(ti - 1); 115 | } 116 | int i = parent.subHeaders.indexOf(this); 117 | assert i != -1 : this; 118 | if (i == 0) 119 | return parent.getPrevious(); 120 | return parent.subHeaders.get(i - 1); 121 | } 122 | 123 | 124 | /** 125 | * The parent section. Can be null. 126 | */ 127 | private Header parent; 128 | 129 | /** 130 | * Create a marker for a section Header 131 | * 132 | * @param level 133 | * 1 = top-level (i.e. #), 2= 2nd-level (i.e. ##), etc. 134 | * @param lineNumber 135 | * The line on which this header occurs 136 | * @param heading 137 | * The text of the Header, trimmed of #s 138 | * @param currentHeader 139 | * The previous Header. This is used to find the parent 140 | * section if there is one. Can be null. 141 | */ 142 | Header(int level, int lineNumber, String heading, Header currentHeader) { 143 | this.lineNumber = lineNumber; 144 | this.level = level; 145 | this.heading = cleanHeader(heading); 146 | // Heading Tree 147 | setParent(currentHeader); 148 | } 149 | 150 | private void setParent(Header currentHeader) { 151 | if (currentHeader == null) { 152 | parent = null; 153 | return; 154 | } 155 | if (currentHeader.level < level) { 156 | parent = currentHeader; 157 | parent.subHeaders.add(this); 158 | return; 159 | } 160 | setParent(currentHeader.parent); 161 | } 162 | 163 | public Header getParent() { 164 | return parent; 165 | } 166 | 167 | /** 168 | * Sub-sections. May be zero-length, never null. 169 | */ 170 | public List
getSubHeaders() { 171 | return subHeaders; 172 | } 173 | 174 | @Override 175 | public String toString() { 176 | return heading; 177 | } 178 | 179 | public int getLevel() { 180 | return level; 181 | } 182 | } 183 | 184 | /** 185 | * The raw text, broken up into individual lines. 186 | */ 187 | private List lines; 188 | 189 | /** 190 | * The raw text, broken up into individual lines. 191 | */ 192 | public List getText() { 193 | return Collections.unmodifiableList(lines); 194 | } 195 | 196 | public enum KLineType { 197 | NORMAL, H1, H2, H3, H4, H5, H6, BLANK, 198 | // TODO LIST, BLOCKQUOTE, 199 | /** A line marking Markdown info about the preceding line, e.g. ====== */ 200 | MARKER, 201 | /** A line containing meta-data, e.g. title: My Page */ 202 | META 203 | } 204 | 205 | /** 206 | * Information about each line. 207 | */ 208 | private List lineTypes; 209 | private Map pageObjects = new HashMap(); 210 | 211 | // TODO meta-data, footnotes, tables, link & image attributes 212 | private static Pattern multiMarkdownTag = Pattern.compile("^([\\w].*):(.*)"); 213 | private Map multiMarkdownTags = new HashMap(); 214 | 215 | // Regular expression for Github support 216 | private static Pattern githubURLDetection = Pattern.compile("((https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|])"); 217 | 218 | /** 219 | * The top-level headers. FIXME handle documents which have a 2nd level 220 | * header before any 1st level ones 221 | */ 222 | private final List
level1Headers = new ArrayList
(); 223 | private final IPreferenceStore pStore; 224 | 225 | /** 226 | * Create a page. 227 | * 228 | * @param text 229 | */ 230 | public MarkdownPage(String text) { 231 | pStore = Activator.getDefault().getPreferenceStore(); 232 | setText(text); 233 | } 234 | 235 | /** 236 | * Reset the text for this page. 237 | * 238 | * @param text 239 | */ 240 | private void setText(String text) { 241 | // Get lines 242 | lines = StringMethods.splitLines(text); 243 | // Clean out old 244 | level1Headers.clear(); 245 | lineTypes = new ArrayList(lines.size()); 246 | pageObjects.clear(); 247 | // Dummy level-1 header in case there are none 248 | Header dummyTopHeader = new Header(1, 0, "", null); 249 | level1Headers.add(dummyTopHeader); 250 | Header currentHeader = dummyTopHeader; 251 | // Identify line types 252 | int lineNum = 0; 253 | 254 | // Check if we should support the Multi-Markdown Metadata 255 | boolean multiMarkdownMetadataSupport = 256 | pStore.getBoolean(MarkdownPreferencePage.PREF_MULTIMARKDOWN_METADATA); 257 | 258 | // Multi-markdown header 259 | if (multiMarkdownMetadataSupport) { 260 | // The key is the text before the colon, and the data is the text 261 | // after the 262 | // colon. In the above example, notice that there are two lines of 263 | // information 264 | // for the Author key. If you end a line with “space-space-newline”, 265 | // the newline 266 | // will be included when converted to other formats. 267 | // 268 | // There must not be any whitespace above the metadata, and the 269 | // metadata block 270 | // ends with the first whitespace only line. The metadata is 271 | // stripped from the 272 | // document before it is passed on to the syntax parser. 273 | 274 | // 275 | // Check if the Metdatas are valid 276 | // 277 | boolean validMetadata = true; 278 | for (lineNum = 0; lineNum < lines.size(); lineNum++) { 279 | String line = lines.get(lineNum); 280 | if (Utils.isBlank(line)) { 281 | break; 282 | } 283 | Matcher m = multiMarkdownTag.matcher(line); 284 | if (!m.find()) { 285 | if (lineNum == 0) { 286 | // No MultiMarkdown metadata 287 | validMetadata = false; 288 | break; 289 | } else if (!line.matches("^\\s.*\n")) { 290 | // The next line was not intended (ie. it does not start 291 | // with a whitespace) 292 | validMetadata = false; 293 | break; 294 | } 295 | } 296 | } 297 | 298 | // Valid Metadatas have been found. We need to retrieve these keys/values. 299 | if (validMetadata) { 300 | String data = ""; 301 | String tag = ""; 302 | for (lineNum = 0; lineNum < lines.size(); lineNum++) { 303 | String line = lines.get(lineNum); 304 | if (Utils.isBlank(line)) { 305 | break; 306 | } 307 | Matcher m = multiMarkdownTag.matcher(line); 308 | if (!m.find()) { 309 | if (lineNum == 0) { 310 | break; 311 | } 312 | // Multi-line tag 313 | lineTypes.add(KLineType.META); 314 | data += StrUtils.LINEEND + line.trim(); 315 | multiMarkdownTags.put(tag, data); 316 | } else { 317 | lineTypes.add(KLineType.META); 318 | tag = m.group(0); 319 | data = m.group(1).trim(); 320 | if (m.group(1).endsWith(line)) 321 | multiMarkdownTags.put(tag, data); 322 | } 323 | } 324 | } else { 325 | lineNum = 0; 326 | } 327 | } 328 | 329 | boolean githubSyntaxSupport = 330 | pStore.getBoolean(MarkdownPreferencePage.PREF_GITHUB_SYNTAX); 331 | 332 | boolean inCodeBlock = false; 333 | 334 | for (; lineNum < lines.size(); lineNum++) { 335 | String line = lines.get(lineNum); 336 | // Code blocks 337 | if (githubSyntaxSupport && line.startsWith("```")) { 338 | inCodeBlock = !inCodeBlock; 339 | } 340 | if (!inCodeBlock) { 341 | // Headings 342 | int h = numHash(line); 343 | String hLine = line; 344 | int hLineNum = lineNum; 345 | int underline = -1; 346 | if (lineNum != 0) { 347 | underline = just(line, '=') ? 1 : just(line, '-') ? 2 : -1; 348 | } 349 | if (underline != -1) { 350 | h = underline; 351 | hLineNum = lineNum - 1; 352 | hLine = lines.get(lineNum - 1); 353 | lineTypes.set(hLineNum, KLineType.values()[h]); 354 | lineTypes.add(KLineType.MARKER); 355 | } 356 | // Create a Header object 357 | if (h > 0) { 358 | if (underline == -1) 359 | lineTypes.add(KLineType.values()[h]); 360 | Header header = new Header(h, hLineNum, hLine, currentHeader); 361 | if (h == 1) { 362 | level1Headers.add(header); 363 | } 364 | pageObjects.put(hLineNum, header); 365 | currentHeader = header; 366 | continue; 367 | } 368 | } 369 | // TODO List 370 | // TODO Block quote 371 | // Blank line 372 | if (Utils.isBlank(line)) { 373 | lineTypes.add(KLineType.BLANK); 374 | continue; 375 | } 376 | // Normal 377 | lineTypes.add(KLineType.NORMAL); 378 | } // end line-loop 379 | // Remove dummy header? 380 | if (dummyTopHeader.getSubHeaders().size() == 0) { 381 | level1Headers.remove(dummyTopHeader); 382 | } 383 | if (githubSyntaxSupport) { 384 | /* 385 | * Support Code block 386 | */ 387 | inCodeBlock = false; 388 | for (lineNum = 0; lineNum < lines.size(); lineNum++) { 389 | String line = lines.get(lineNum); 390 | // Found the start or end of a code block 391 | if (line.matches("^```.*\n")) { 392 | // We reverse the boolean value 393 | inCodeBlock = !inCodeBlock; 394 | 395 | // We force the line to be blank. But we mark it as normal 396 | // to prevent to be stripped 397 | lines.set(lineNum, "\n"); 398 | lineTypes.set(lineNum, KLineType.NORMAL); 399 | continue; 400 | } 401 | if (inCodeBlock) { 402 | lines.set(lineNum, " " + line); 403 | } 404 | } 405 | 406 | /* 407 | * Support for URL Detection 408 | * We search for links that are not captured by Markdown syntax 409 | */ 410 | for (lineNum = 0; lineNum < lines.size(); lineNum++) { 411 | String line = lines.get(lineNum); 412 | // When a link has been replaced we need to scan again the string 413 | // as the offsets have changed (we add '<' and '>' to the link to 414 | // be interpreted by the markdown library) 415 | boolean urlReplaced; 416 | 417 | do { 418 | urlReplaced = false; 419 | Matcher m = githubURLDetection.matcher(line); 420 | while (m.find()) { 421 | // Ignore the URL following the format 422 | if ((m.start() - 1 >= 0) && (m.end() < line.length()) && 423 | (line.charAt(m.start() - 1) == '<') && 424 | (line.charAt(m.end()) == '>')) 425 | { 426 | continue; 427 | } 428 | 429 | // Ignore the URL following the format [description](link) 430 | if ((m.start() - 2 >= 0) && (m.end() < line.length()) && 431 | (line.charAt(m.start() - 2) == ']') && 432 | (line.charAt(m.start() - 1) == '(') && 433 | (line.charAt(m.end()) == ')')) 434 | { 435 | continue; 436 | } 437 | 438 | // Ignore the URL following the format [description](link "title") 439 | if ((m.start() - 2 >= 0) && (m.end() + 1 < line.length()) && 440 | (line.charAt(m.start() - 2) == ']') && 441 | (line.charAt(m.start() - 1) == '(') && 442 | (line.charAt(m.end()) == ' ') && 443 | (line.charAt(m.end() + 1) == '"')) 444 | { 445 | continue; 446 | } 447 | 448 | if (m.start() - 1 >= 0) { 449 | // Case when the link is at the beginning of the string 450 | line = line.substring(0, m.start()) + "<" + m.group(0) + ">" + line.substring(m.end()); 451 | } else { 452 | line = "<" + m.group(0) + ">" + line.substring(m.end()); 453 | } 454 | 455 | // We replaced the string in the array 456 | lines.set(lineNum, line); 457 | urlReplaced = true; 458 | break; 459 | } 460 | } while (urlReplaced); 461 | } 462 | } 463 | } 464 | 465 | /** 466 | * @param line 467 | * @param c 468 | * @return true if line is just cs (and whitespace at the start/end) 469 | */ 470 | boolean just(String line, char c) { 471 | return line.matches("\\s*"+c+"+\\s*"); 472 | } 473 | 474 | /** 475 | * @param line 476 | * @return The number of # symbols prepending the line. 477 | */ 478 | private int numHash(String line) { 479 | for (int i = 0; i < line.length(); i++) { 480 | if (line.charAt(i) != '#') 481 | return i; 482 | } 483 | return line.length(); 484 | } 485 | 486 | /** 487 | * 488 | * @param parent 489 | * Can be null for top-level 490 | * @return List of sub-headers. Never null. FIXME handle documents which 491 | * have a 2nd level header before any 1st level ones 492 | */ 493 | public List
getHeadings(Header parent) { 494 | if (parent == null) { 495 | return Collections.unmodifiableList(level1Headers); 496 | } 497 | return Collections.unmodifiableList(parent.subHeaders); 498 | } 499 | 500 | // public WebPage getWebPage() { 501 | // WebPage page = new WebPage(); 502 | // // Add the lines, one by one 503 | // boolean inParagraph = false; 504 | // for (int i=0; i"); 512 | // line = cleanHeader(line); 513 | // page.addText("<"+type+">"+line+""); 514 | // continue; 515 | // case MARKER: // Ignore 516 | // continue; 517 | // // TODO List? 518 | // // TODO Block quote? 519 | // } 520 | // // Paragraph end? 521 | // if (Utils.isBlank(line)) { 522 | // if (inParagraph) page.addText("

"); 523 | // continue; 524 | // } 525 | // // Paragraph start? 526 | // if (!inParagraph) { 527 | // page.addText("

"); 528 | // inParagraph = true; 529 | // } 530 | // // Plain text 531 | // page.addText(line); 532 | // } 533 | // return page; 534 | // } 535 | 536 | /** 537 | * Get the HTML for this page. Uses the MarkdownJ project. 538 | */ 539 | public String html() { 540 | // Section numbers?? 541 | boolean sectionNumbers = pStore 542 | .getBoolean(MarkdownPreferencePage.PREF_SECTION_NUMBERS); 543 | // Chop out multi-markdown header 544 | StringBuilder sb = new StringBuilder(); 545 | assert lines.size() == lineTypes.size(); 546 | for (int i = 0, n = lines.size(); i < n; i++) { 547 | KLineType type = lineTypes.get(i); 548 | if (type == KLineType.META) 549 | continue; 550 | String line = lines.get(i); 551 | if (sectionNumbers && isHeader(type) && line.contains("$section")) { 552 | // TODO Header section = headers.get(i); 553 | // String secNum = section.getSectionNumber(); 554 | // line.replace("$section", secNum); 555 | } 556 | sb.append(line); 557 | } 558 | String text = sb.toString(); 559 | // Use external converter? 560 | final String cmd = pStore 561 | .getString(MarkdownPreferencePage.PREF_MARKDOWN_COMMAND); 562 | if (Utils.isBlank(cmd) 563 | || (cmd.startsWith("(") && cmd.contains("MarkdownJ"))) { 564 | // Use MarkdownJ 565 | MarkdownProcessor markdown = new MarkdownProcessor(); 566 | // MarkdownJ doesn't convert £s for some reason 567 | text = text.replace("£", "£"); 568 | String html = markdown.markdown(text); 569 | return html; 570 | } 571 | // Attempt to run external command 572 | try { 573 | final File md = File.createTempFile("tmp", ".md"); 574 | FileUtils.write(md, text); 575 | Process process = new Process(cmd+" "+md.getAbsolutePath()); 576 | process.run(); 577 | int ok = process.waitFor(10000); 578 | if (ok != 0) throw new FailureException(cmd+" failed:\n"+process.getError()); 579 | String html = process.getOutput(); 580 | FileUtils.delete(md); 581 | return html; 582 | } catch (Exception e) { 583 | throw Utils.runtime(e); 584 | } 585 | } 586 | 587 | /** 588 | * @param type 589 | * @return 590 | */ 591 | private boolean isHeader(KLineType type) { 592 | return type == KLineType.H1 || type == KLineType.H2 593 | || type == KLineType.H3 || type == KLineType.H4 594 | || type == KLineType.H5 || type == KLineType.H6; 595 | } 596 | 597 | /** 598 | * Return the raw text of this page. 599 | */ 600 | @Override 601 | public String toString() { 602 | StringBuilder sb = new StringBuilder(); 603 | for (String line : lines) { 604 | sb.append(line); 605 | } 606 | return sb.toString(); 607 | } 608 | 609 | /** 610 | * Line type information for the raw text. 611 | * 612 | * @return 613 | */ 614 | public List getLineTypes() { 615 | return Collections.unmodifiableList(lineTypes); 616 | } 617 | 618 | /** 619 | * @param line 620 | * @return 621 | */ 622 | public Object getPageObject(int line) { 623 | return pageObjects.get(line); 624 | } 625 | 626 | } 627 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/pagemodel/MarkdownPageTest.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.pagemodel; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | 6 | import winterwell.markdown.pagemodel.MarkdownPage.Header; 7 | import winterwell.utils.io.FileUtils; 8 | 9 | 10 | 11 | public class MarkdownPageTest //extends TestCase 12 | { 13 | 14 | public static void main(String[] args) { 15 | MarkdownPageTest mpt = new MarkdownPageTest(); 16 | mpt.testGetHeadings(); 17 | } 18 | 19 | public void testGetHeadings() { 20 | // problem caused by a line beginning --, now fixed 21 | String txt = FileUtils.read(new File( 22 | "/home/daniel/winterwell/companies/DTC/projects/DTC-bayes/report1.txt")); 23 | MarkdownPage p = new MarkdownPage(txt); 24 | List

h1s = p.getHeadings(null); 25 | Header h1 = h1s.get(0); 26 | List
h2s = h1.getSubHeaders(); 27 | assert h2s.size() > 2; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/preferences/MarkdownPreferencePage.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.preferences; 2 | 3 | import org.eclipse.jface.preference.BooleanFieldEditor; 4 | import org.eclipse.jface.preference.ColorFieldEditor; 5 | import org.eclipse.jface.preference.FieldEditor; 6 | import org.eclipse.jface.preference.FieldEditorPreferencePage; 7 | import org.eclipse.jface.preference.IPreferenceStore; 8 | import org.eclipse.jface.preference.PreferenceConverter; 9 | import org.eclipse.jface.preference.StringFieldEditor; 10 | import org.eclipse.swt.graphics.RGB; 11 | import org.eclipse.swt.widgets.Composite; 12 | import org.eclipse.ui.IWorkbench; 13 | import org.eclipse.ui.IWorkbenchPreferencePage; 14 | 15 | import winterwell.markdown.Activator; 16 | 17 | /** 18 | * This class represents a preference page that 19 | * is contributed to the Preferences dialog. By 20 | * subclassing FieldEditorPreferencePage, we 21 | * can use the field support built into JFace that allows 22 | * us to create a page that is small and knows how to 23 | * save, restore and apply itself. 24 | *

25 | * This page is used to modify preferences only. They 26 | * are stored in the preference store that belongs to 27 | * the main plug-in class. That way, preferences can 28 | * be accessed directly via the preference store. 29 | */ 30 | 31 | public class MarkdownPreferencePage 32 | extends FieldEditorPreferencePage 33 | implements IWorkbenchPreferencePage { 34 | 35 | public static final String PREF_FOLDING = "Pref_Folding"; 36 | public static final String PREF_WORD_WRAP = "Pref_WordWrap"; 37 | public static final String PREF_TASK_TAGS = "Pref_TaskTagsOn"; 38 | public static final String PREF_TASK_TAGS_DEFINED = "Pref_TaskTags"; 39 | public static final String PREF_SECTION_NUMBERS = "Pref_SectionNumbers"; 40 | 41 | public static final String PREF_MARKDOWN_COMMAND = "Pref_Markdown_Command"; 42 | private static final String MARKDOWNJ = "(use built-in MarkdownJ converter)"; 43 | 44 | 45 | public static final String PREF_DEFUALT = "Pref_Default"; 46 | public static final String PREF_COMMENT = "Pref_Comment"; 47 | public static final String PREF_HEADER = "Pref_Header"; 48 | public static final String PREF_LINK = "Pref_Link"; 49 | public static final String PREF_CODE = "Pref_Code"; 50 | public static final String PREF_CODE_BG = "Pref_Code_Background"; 51 | 52 | public static final String PREF_GITHUB_SYNTAX = "Pref_Github_Syntax"; 53 | public static final String PREF_MULTIMARKDOWN_METADATA = "Pref_MultiMarkdown_Metadata"; 54 | 55 | private static final RGB DEF_DEFAULT = new RGB(0, 0, 0); 56 | private static final RGB DEF_COMMENT = new RGB(128, 0, 0); 57 | private static final RGB DEF_HEADER = new RGB(0, 128, 0); 58 | private static final RGB DEF_LINK = new RGB(106, 131, 199); 59 | private static final RGB DEF_CODE = new RGB(0, 0, 0); 60 | private static final RGB DEF_CODE_BG = new RGB(244,244,244); 61 | 62 | public MarkdownPreferencePage() { 63 | super(GRID); 64 | IPreferenceStore pStore = Activator.getDefault().getPreferenceStore(); 65 | setDefaultPreferences(pStore); 66 | setPreferenceStore(pStore); 67 | setDescription("Settings for the Markdown text editor. See also the general text editor preferences."); 68 | } 69 | 70 | public static void setDefaultPreferences(IPreferenceStore pStore) { 71 | pStore.setDefault(PREF_WORD_WRAP, false); 72 | pStore.setDefault(PREF_FOLDING, true); 73 | pStore.setDefault(PREF_TASK_TAGS, true); 74 | pStore.setDefault(PREF_TASK_TAGS_DEFINED, "TODO,FIXME,??"); 75 | pStore.setDefault(PREF_MARKDOWN_COMMAND, MARKDOWNJ); 76 | pStore.setDefault(PREF_SECTION_NUMBERS, true); 77 | pStore.setDefault(PREF_GITHUB_SYNTAX, true); 78 | pStore.setDefault(PREF_MULTIMARKDOWN_METADATA, false); 79 | 80 | PreferenceConverter.setDefault(pStore, PREF_DEFUALT, DEF_DEFAULT); 81 | PreferenceConverter.setDefault(pStore, PREF_COMMENT, DEF_COMMENT); 82 | PreferenceConverter.setDefault(pStore, PREF_HEADER, DEF_HEADER); 83 | PreferenceConverter.setDefault(pStore, PREF_LINK, DEF_LINK); 84 | PreferenceConverter.setDefault(pStore, PREF_CODE, DEF_CODE); 85 | PreferenceConverter.setDefault(pStore, PREF_CODE_BG, DEF_CODE_BG); 86 | } 87 | 88 | /** 89 | * Creates the field editors. Field editors are abstractions of 90 | * the common GUI blocks needed to manipulate various types 91 | * of preferences. Each field editor knows how to save and 92 | * restore itself. 93 | */ 94 | @Override 95 | public void createFieldEditors() { 96 | // Word wrap 97 | BooleanFieldEditor fd = new BooleanFieldEditor(PREF_WORD_WRAP, 98 | "Soft word wrapping \r\n" 99 | +"Note: may cause line numbers and related \r\n" + 100 | "functionality to act a bit strangely", 101 | getFieldEditorParent()); 102 | addField(fd); 103 | // Task tags 104 | fd = new BooleanFieldEditor(PREF_TASK_TAGS, 105 | "Manage tasks using task tags \r\n" + 106 | "If true, this will add and delete tags in sync with edits.", 107 | getFieldEditorParent()); 108 | addField(fd); 109 | StringFieldEditor tags = new StringFieldEditor(PREF_TASK_TAGS_DEFINED, 110 | "Task tags\nComma separated list of recognised task tags.", getFieldEditorParent()); 111 | addField(tags); 112 | // Code folding 113 | fd = new BooleanFieldEditor(PREF_FOLDING, 114 | "Document folding, a.k.a. outline support", 115 | getFieldEditorParent()); 116 | addField(fd); 117 | // Command line 118 | // addField(new DummyField() { 119 | // protected void makeComponent(Composite parent) { 120 | // Label label = new Label(parent, 0); 121 | // label.setText("Hello!"); 122 | // GridData gd = new GridData(100, 20); 123 | // label.setLayoutData(gd); 124 | // } 125 | // }); 126 | StringFieldEditor cmd = new StringFieldEditor(PREF_MARKDOWN_COMMAND, 127 | "UNSTABLE: Command-line to run Markdown.\r\n" + 128 | "This should take in a file and output to std-out.\n" + 129 | "Leave blank to use the built-in Java converter.", getFieldEditorParent()); 130 | addField(cmd); 131 | 132 | ColorFieldEditor def = new ColorFieldEditor(PREF_DEFUALT, "Default text", getFieldEditorParent()); 133 | addField(def); 134 | 135 | ColorFieldEditor com = new ColorFieldEditor(PREF_COMMENT, "Comment", getFieldEditorParent()); 136 | addField(com); 137 | 138 | ColorFieldEditor link = new ColorFieldEditor(PREF_LINK, "Link", getFieldEditorParent()); 139 | addField(link); 140 | 141 | ColorFieldEditor head = new ColorFieldEditor(PREF_HEADER, "Header and List indicator", getFieldEditorParent()); 142 | addField(head); 143 | 144 | ColorFieldEditor code = new ColorFieldEditor(PREF_CODE, "Code", getFieldEditorParent()); 145 | addField(code); 146 | 147 | ColorFieldEditor codeBg = new ColorFieldEditor(PREF_CODE_BG, "Code Background", getFieldEditorParent()); 148 | addField(codeBg); 149 | 150 | /* 151 | * Fields for the preview window 152 | */ 153 | 154 | // Github Syntax support 155 | fd = new BooleanFieldEditor(PREF_GITHUB_SYNTAX, 156 | "Support Github Syntax", 157 | getFieldEditorParent()); 158 | addField(fd); 159 | 160 | // Multi-Markdown support 161 | fd = new BooleanFieldEditor(PREF_MULTIMARKDOWN_METADATA, 162 | "Support Multi-Markdown Metadata", 163 | getFieldEditorParent()); 164 | addField(fd); 165 | } 166 | 167 | /* (non-Javadoc) 168 | * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) 169 | */ 170 | public void init(IWorkbench workbench) { 171 | 172 | } 173 | 174 | public static boolean wordWrap() { 175 | IPreferenceStore pStore = Activator.getDefault().getPreferenceStore(); 176 | if (! pStore.contains(MarkdownPreferencePage.PREF_WORD_WRAP)) { 177 | return false; 178 | } 179 | return pStore.getBoolean(MarkdownPreferencePage.PREF_WORD_WRAP); 180 | } 181 | 182 | } 183 | 184 | abstract class DummyField extends FieldEditor { 185 | @Override 186 | protected void adjustForNumColumns(int numColumns) { 187 | // do nothing 188 | } 189 | @Override 190 | protected void doFillIntoGrid(Composite parent, int numColumns) { 191 | makeComponent(parent); 192 | } 193 | abstract protected void makeComponent(Composite parent); 194 | 195 | @Override 196 | protected void doLoad() { 197 | // 198 | } 199 | @Override 200 | protected void doLoadDefault() { 201 | // 202 | } 203 | 204 | @Override 205 | protected void doStore() { 206 | // 207 | } 208 | 209 | @Override 210 | public int getNumberOfControls() { 211 | return 1; 212 | } 213 | 214 | } -------------------------------------------------------------------------------- /plugin/src/winterwell/markdown/views/MarkdownPreview.java: -------------------------------------------------------------------------------- 1 | package winterwell.markdown.views; 2 | 3 | 4 | import java.io.File; 5 | import java.net.URI; 6 | 7 | import org.eclipse.core.runtime.IPath; 8 | import org.eclipse.swt.SWT; 9 | import org.eclipse.swt.browser.Browser; 10 | import org.eclipse.swt.widgets.Composite; 11 | import org.eclipse.ui.IEditorPart; 12 | import org.eclipse.ui.IPathEditorInput; 13 | import org.eclipse.ui.part.ViewPart; 14 | 15 | import winterwell.markdown.editors.ActionBarContributor; 16 | import winterwell.markdown.editors.MarkdownEditor; 17 | import winterwell.markdown.pagemodel.MarkdownPage; 18 | 19 | 20 | 21 | 22 | public class MarkdownPreview extends ViewPart { 23 | 24 | public static MarkdownPreview preview = null; 25 | 26 | private Browser viewer = null; 27 | 28 | /** 29 | * The constructor. 30 | */ 31 | public MarkdownPreview() { 32 | preview = this; 33 | } 34 | 35 | /** 36 | * This is a callback that will allow us 37 | * to create the viewer and initialize it. 38 | */ 39 | @Override 40 | public void createPartControl(Composite parent) { 41 | viewer = new Browser(parent, SWT.MULTI); // | SWT.H_SCROLL | SWT.V_SCROLL 42 | } 43 | 44 | 45 | 46 | 47 | /** 48 | * Passing the focus request to the viewer's control. 49 | */ 50 | @Override 51 | public void setFocus() { 52 | if (viewer==null) return; 53 | viewer.setFocus(); 54 | update(); 55 | } 56 | 57 | public void update() { 58 | if (viewer==null) return; 59 | try { 60 | IEditorPart editor = ActionBarContributor.getActiveEditor(); 61 | if (!(editor instanceof MarkdownEditor)) { 62 | viewer.setText(""); 63 | return; 64 | } 65 | MarkdownEditor ed = (MarkdownEditor) editor; 66 | MarkdownPage page = ed.getMarkdownPage(); 67 | String html = page.html(); 68 | html = addBaseURL(editor, html); 69 | if (page != null) viewer.setText(html); 70 | else viewer.setText(""); 71 | } catch (Exception ex) { 72 | // Smother 73 | System.out.println(ex); 74 | 75 | if (viewer != null && !viewer.isDisposed()) 76 | viewer.setText(ex.getMessage()); 77 | } 78 | } 79 | 80 | /** 81 | * Adjust the URL base to be the file's directory. 82 | * @param editor 83 | * @param html 84 | * @return 85 | */ 86 | private String addBaseURL(IEditorPart editor, String html) { 87 | try { 88 | IPathEditorInput input = (IPathEditorInput) editor.getEditorInput(); 89 | IPath path = input.getPath(); 90 | path = path.removeLastSegments(1); 91 | File f = path.toFile(); 92 | URI fileURI = f.toURI(); 93 | String html2 = "\r\n"+html 94 | +"\r\n"; 95 | return html2; 96 | } catch (Exception ex) { 97 | return html; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.winterwell.markdown 8 | markdown.editor.parent 9 | 1.2.0-SNAPSHOT 10 | pom 11 | Markdown Editor (parent) 12 | 13 | 14 | Winterwell Associates Ltd 15 | http://www.winterwell.com 16 | 17 | 18 | 19 | 20 | daniel.winterstein 21 | Daniel Winterstein 22 | daniel@winterwell.com 23 | Winterwell Associates Ltd 24 | 25 | Lead Developer 26 | 27 | 0 28 | 29 | 30 | 31 | 32 | Paul Verest 33 | Nodeclipse organization 34 | http://www.nodeclipse.org/ 35 | +8 36 | 37 | 38 | 39 | 40 | 3.0 41 | 42 | 43 | 44 | 0.18.1 45 | 46 | 47 | UTF-8 48 | UTF-8 49 | 50 | 55 | 56 | 57 | 58 | 59 | plugin 60 | feature 61 | site 62 | 63 | 64 | 65 | 72 | 73 | 74 | kepler 75 | p2 76 | http://download.eclipse.org/releases/kepler 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | org.eclipse.tycho 85 | tycho-maven-plugin 86 | ${tycho-version} 87 | true 88 | 89 | 90 | org.eclipse.tycho 91 | tycho-compiler-plugin 92 | ${tycho-version} 93 | 94 | 1.6 95 | 1.6 96 | 97 | 98 | 99 | 127 | 128 | 129 | org.eclipse.tycho 130 | target-platform-configuration 131 | ${tycho-version} 132 | 133 | 134 | 135 | linux 136 | gtk 137 | x86 138 | 139 | 140 | linux 141 | gtk 142 | x86_64 143 | 144 | 145 | win32 146 | win32 147 | x86 148 | 149 | 150 | win32 151 | win32 152 | x86_64 153 | 154 | 155 | macosx 156 | cocoa 157 | x86_64 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /site/category.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Extend the text editor to provide good Markdown support. 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /site/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.winterwell.markdown 9 | markdown.editor.parent 10 | 1.2.0-SNAPSHOT 11 | 12 | 13 | markdown.editor.site 14 | eclipse-repository 15 | 16 | Markdown Editor (site) 17 | Markdown Editor (site) 18 | 19 | 20 | -------------------------------------------------------------------------------- /site/src/main/resources/index.html: -------------------------------------------------------------------------------- 1 | This is Eclipse update site. URL should be entered in Help -> Install New Software... -------------------------------------------------------------------------------- /updatesite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | markdown.editor.updatesite 4 | 5 | 6 | 55 | 56 | 57 | 58 |

59 | 60 | 61 | -------------------------------------------------------------------------------- /updatesite/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Extend the text editor to provide good Markdown support. 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /updatesite/web/site.css: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /updatesite/web/site.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | markdown.editor.updatesite 9 | 10 | 11 | 12 |

markdown.editor.updatesite

13 |

14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | dark-row 34 | 35 | 36 | light-row 37 | 38 | 39 | 54 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | dark-row 106 | 107 | 108 | light-row 109 | 110 | 111 | 126 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | dark-row 161 | 162 | 163 | light-row 164 | 165 | 166 | 181 | 205 | 206 | 207 | 208 | 209 |
21 | 22 | 24 | 25 |
40 | 41 | 42 | 43 |
44 |
45 | ( - ) 46 |
47 |
48 | 49 | - 50 | 51 |
52 |
53 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
Operating Systems:
Windows Systems:
Languages:
Architecture:
77 |


86 | Uncategorized 87 |
112 | 113 | 114 | 115 |
116 |
117 | ( - ) 118 |
119 |
120 | 121 | - 122 | 123 |
124 |

125 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |
Operating Systems:
Windows Systems:
Languages:
Architecture:
149 |
167 | 168 | 169 | 170 |
171 |
172 | ( - ) 173 |
174 |
175 | 176 | - 177 | 178 |
179 |

180 |
182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |
Operating Systems:
Windows Systems:
Languages:
Architecture:
204 |
210 | 211 | 212 |
213 |
214 |
215 | --------------------------------------------------------------------------------