From 1ce1fb4c4d256299d6ec8d52f2710db765270000 Mon Sep 17 00:00:00 2001 From: Julian Valentin Date: Sun, 18 Apr 2021 11:02:06 +0200 Subject: [PATCH] Add support for Org Fixes valentjn/vscode-ltex#277. --- CHANGELOG.md | 1 + README.md | 2 +- .../parsing/CodeAnnotatedTextBuilder.java | 3 + .../ltexls/parsing/CodeFragmentizer.java | 3 + .../parsing/org/OrgAnnotatedTextBuilder.java | 651 ++++++++++++++++++ .../ltexls/parsing/org/OrgFragmentizer.java | 20 + .../ltexls/server/LtexWorkspaceService.java | 2 + .../bsplines/ltexls/settings/Settings.java | 4 +- .../org/OrgAnnotatedTextBuilderTest.java | 429 ++++++++++++ .../parsing/org/OrgFragmentizerTest.java | 56 ++ .../server/LtexWorkspaceServiceTest.java | 2 +- 11 files changed, 1169 insertions(+), 4 deletions(-) create mode 100644 ltexls/src/main/java/org/bsplines/ltexls/parsing/org/OrgAnnotatedTextBuilder.java create mode 100644 ltexls/src/main/java/org/bsplines/ltexls/parsing/org/OrgFragmentizer.java create mode 100644 ltexls/src/test/java/org/bsplines/ltexls/parsing/org/OrgAnnotatedTextBuilderTest.java create mode 100644 ltexls/src/test/java/org/bsplines/ltexls/parsing/org/OrgFragmentizerTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b42746..b093b39e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - `ltex.hideFalsePositives` → `_ltex.hideFalsePositives` - `ltex.checkDocument` → `_ltex.checkDocument` - `ltex.getServerStatus` → `_ltex.getServerStatus` +- Add support for Org; use the code language ID `org` (fixes [vscode-ltex#277](https://github.com/valentjn/vscode-ltex/issues/277)) - Add basic support for reStructuredText; use the code language ID `restructuredtext` (fixes [vscode-ltex#32](https://github.com/valentjn/vscode-ltex/issues/32)) - Add `--server-type=tcpSocket` option to communicate over a TCP socket - Add `--host` and `--port` options to control host and port of the TCP socket diff --git a/README.md b/README.md index 65c1953e..5edee4d1 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Find more information about LTEX at the [website of vscode-ltex](http ## Features -- **Supported markup languages:** LATEX, Markdown, reStructuredText, R Sweave +- **Supported markup languages:** LATEX, Markdown, Org, reStructuredText, R Sweave - Comes with **everything included,** no need to install Java or LanguageTool - **Offline checking:** Does not upload anything to the internet - Supports **over 20 languages:** English, French, German, Dutch, Chinese, Russian, etc. diff --git a/ltexls/src/main/java/org/bsplines/ltexls/parsing/CodeAnnotatedTextBuilder.java b/ltexls/src/main/java/org/bsplines/ltexls/parsing/CodeAnnotatedTextBuilder.java index 179ac7e2..3212506c 100644 --- a/ltexls/src/main/java/org/bsplines/ltexls/parsing/CodeAnnotatedTextBuilder.java +++ b/ltexls/src/main/java/org/bsplines/ltexls/parsing/CodeAnnotatedTextBuilder.java @@ -12,6 +12,7 @@ import java.util.function.Function; import org.bsplines.ltexls.parsing.latex.LatexAnnotatedTextBuilder; import org.bsplines.ltexls.parsing.markdown.MarkdownAnnotatedTextBuilder; +import org.bsplines.ltexls.parsing.org.OrgAnnotatedTextBuilder; import org.bsplines.ltexls.parsing.plaintext.PlaintextAnnotatedTextBuilder; import org.bsplines.ltexls.parsing.restructuredtext.RestructuredtextAnnotatedTextBuilder; import org.bsplines.ltexls.settings.Settings; @@ -30,6 +31,8 @@ public abstract class CodeAnnotatedTextBuilder extends AnnotatedTextBuilder { new LatexAnnotatedTextBuilder(codeLanguageId)); constructorMap.put("markdown", (String codeLanguageId) -> new MarkdownAnnotatedTextBuilder(codeLanguageId)); + constructorMap.put("org", (String codeLanguageId) -> + new OrgAnnotatedTextBuilder(codeLanguageId)); constructorMap.put("plaintext", (String codeLanguageId) -> new PlaintextAnnotatedTextBuilder(codeLanguageId)); constructorMap.put("restructuredtext", (String codeLanguageId) -> diff --git a/ltexls/src/main/java/org/bsplines/ltexls/parsing/CodeFragmentizer.java b/ltexls/src/main/java/org/bsplines/ltexls/parsing/CodeFragmentizer.java index 91c94279..7a3054f1 100644 --- a/ltexls/src/main/java/org/bsplines/ltexls/parsing/CodeFragmentizer.java +++ b/ltexls/src/main/java/org/bsplines/ltexls/parsing/CodeFragmentizer.java @@ -15,6 +15,7 @@ import org.bsplines.ltexls.parsing.bibtex.BibtexFragmentizer; import org.bsplines.ltexls.parsing.latex.LatexFragmentizer; import org.bsplines.ltexls.parsing.markdown.MarkdownFragmentizer; +import org.bsplines.ltexls.parsing.org.OrgFragmentizer; import org.bsplines.ltexls.parsing.plaintext.PlaintextFragmentizer; import org.bsplines.ltexls.parsing.restructuredtext.RestructuredtextFragmentizer; import org.bsplines.ltexls.settings.Settings; @@ -32,6 +33,8 @@ public abstract class CodeFragmentizer { new LatexFragmentizer(codeLanguageId)); constructorMap.put("markdown", (String codeLanguageId) -> new MarkdownFragmentizer(codeLanguageId)); + constructorMap.put("org", (String codeLanguageId) -> + new OrgFragmentizer(codeLanguageId)); constructorMap.put("plaintext", (String codeLanguageId) -> new PlaintextFragmentizer(codeLanguageId)); constructorMap.put("restructuredtext", (String codeLanguageId) -> diff --git a/ltexls/src/main/java/org/bsplines/ltexls/parsing/org/OrgAnnotatedTextBuilder.java b/ltexls/src/main/java/org/bsplines/ltexls/parsing/org/OrgAnnotatedTextBuilder.java new file mode 100644 index 00000000..c7b9cbf3 --- /dev/null +++ b/ltexls/src/main/java/org/bsplines/ltexls/parsing/org/OrgAnnotatedTextBuilder.java @@ -0,0 +1,651 @@ +/* Copyright (C) 2020 Julian Valentin, LTeX Development Community + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package org.bsplines.ltexls.parsing.org; + +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.bsplines.ltexls.parsing.CodeAnnotatedTextBuilder; +import org.bsplines.ltexls.parsing.DummyGenerator; +import org.bsplines.ltexls.settings.Settings; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class OrgAnnotatedTextBuilder extends CodeAnnotatedTextBuilder { + private enum ElementType { + HEADLINE, + GREATER_CENTER_BLOCK, + GREATER_QUOTE_BLOCK, + GREATER_SPECIAL_BLOCK, + COMMENT_BLOCK, + EXAMPLE_BLOCK, + EXPORT_BLOCK, + SOURCE_BLOCK, + VERSE_BLOCK, + DRAWER, + PROPERTY_DRAWER, + DYNAMIC_BLOCK, + LATEX_ENVIRONMENT, + TABLE, + PARAGRAPH, + } + + private enum ObjectType { + REGULAR_LINK_DESCRIPTION, + BOLD, + STRIKETHROUGH, + ITALIC, + VERBATIM, + UNDERLINE, + CODE, + } + + private static final String regularLinkPathPatternString = + "[ \\-/0-9A-Z\\\\a-z]+" + + "|[A-Za-z]+:(//)?[^\r\n\\[\\]]+" + + "|id:[-0-9A-Fa-f]+" + + "|#[^\r\n\\[\\]]+" + + "|\\([^\r\n\\[\\]]+\\)" + + "|[^\r\n\\[\\]]+"; + + private static final String timestampPatternString = + "[0-9]{4}-[0-9]{2}-[0-9]{2}[ \t]+[^ \t\r\n+\\-0-9>\\]]+" + + "([ \t]+[0-9]{1,2}:[0-9]{2})?" + + "([ \t]+(\\+|\\+\\+|\\.\\+|-|--)[0-9]+[dhmwy]){0,2}"; + private static final String timestampRangePatternString = + "[0-9]{4}-[0-9]{2}-[0-9]{2}[ \t]+[^ \t\r\n+\\-0-9>\\]]+" + + "[ \t]+[0-9]{1,2}:[0-9]{2}-[0-9]{1,2}:[0-9]{2}" + + "([ \t]+(\\+|\\+\\+|\\.\\+|-|--)[0-9]+[dhmwy]){0,2}"; + + private static final Pattern whitespacePattern = Pattern.compile( + "^[ \t]*", Pattern.CASE_INSENSITIVE); + + private static final Pattern headlinePattern = Pattern.compile( + "^(\\*+(?= ))([ \t]+(?-i:TODO|DONE))?([ \t]+\\[#[A-Za-z]\\])?[ \t]*", + Pattern.CASE_INSENSITIVE); + private static final Pattern headlineCommentPattern = Pattern.compile( + "^(\\*+(?= ))([ \t]+(?-i:TODO|DONE))?([ \t]+\\[#[A-Za-z]\\])?" + + "[ \t]+COMMENT(?=[ \t]|\r?\n|$)[^\r\n]*(?=\r?\n|$)", + Pattern.CASE_INSENSITIVE); + private static final Pattern headlineTagsPattern = Pattern.compile( + "^[ \t]*((:[#%0-9@A-Z_a-z]+)+:)?[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + + private static final Pattern affiliatedKeywordsPattern = Pattern.compile( + "^#\\+((CAPTION|HEADER|NAME|PLOT|RESULTS)|" + + "((CAPTION|RESULTS)\\[[^\r\n]*?\\])|ATTR_[-0-9A-Z_a-z]+): [^\r\n]*(?=\r?\n|$)", + Pattern.CASE_INSENSITIVE); + + private static final Pattern blockBeginPattern = Pattern.compile( + "^#\\+BEGIN_([^ \t\r\n]+)([ \t]+[^\r\n]*?)?[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + private static final Pattern blockEndPattern = Pattern.compile( + "^#\\+END_([^ \t\r\n]+)[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + + private static final Pattern drawerBeginPattern = Pattern.compile( + "^:([-A-Z_a-z]+):[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + private static final Pattern drawerEndPattern = Pattern.compile( + "^:END:[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + + private static final Pattern dynamicBlockBeginPattern = Pattern.compile( + "^#\\+BEGIN: ([^ \t\r\n]+)([ \t]+[^\r\n]*?)[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + private static final Pattern dynamicBlockEndPattern = Pattern.compile( + "^#\\+END:[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + + private static final Pattern footnoteDefinitionPattern = Pattern.compile( + "^\\[fn:([0-9]+|[-A-Z_a-z]+)\\][ \t]*", Pattern.CASE_INSENSITIVE); + + private static final Pattern itemPattern = Pattern.compile( + "^(\\*|-|\\+|([0-9]+|[A-Za-z])[.)])(?=[ \t]|$)([ \t]+\\[@([0-9]+|[A-Za-z])\\])?" + + "([ \t]+\\[[- \tX]\\])?([ \t]+[^\r\n]*?[ \t]+::)?[ \t]*", + Pattern.CASE_INSENSITIVE); + + private static final Pattern tableRowPattern = Pattern.compile( + "^\\|[ \t]*", Pattern.CASE_INSENSITIVE); + private static final Pattern ruleTableRowPattern = Pattern.compile( + "^\\|-[^\r\n]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + private static final Pattern tableCellSeparatorPattern = Pattern.compile( + "^[ \t]*\\|[ \t]*", Pattern.CASE_INSENSITIVE); + + private static final Pattern babelCallPattern = Pattern.compile( + "^#\\+CALL:[ \t]*([^\r\n]+?)[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + + private static final Pattern clockPattern = Pattern.compile( + "^CLOCK:[ \t]*([^\r\n]+?)[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + private static final Pattern diarySexpPattern = Pattern.compile( + "^%%\\(([^\r\n]*)", Pattern.CASE_INSENSITIVE); + private static final Pattern planningPattern = Pattern.compile( + "^(DEADLINE|SCHEDULED|CLOSED):[ \t]*(" + + "<%%\\([^\r\n>]+\\)>" + + "|<" + timestampPatternString + ">" + + "|\\[" + timestampPatternString + "\\]" + + "|<" + timestampPatternString + ">--<" + timestampPatternString + ">" + + "|<" + timestampRangePatternString + ">" + + "|\\[" + timestampPatternString + "\\]--\\[" + timestampPatternString + "\\]" + + "|\\[" + timestampRangePatternString + "\\]" + + ")[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + + private static final Pattern commentPattern = Pattern.compile( + "^#([ \t]+[^\r\n]*?)?(\r?\n|$)", Pattern.CASE_INSENSITIVE); + + private static final Pattern fixedWidthLinePattern = Pattern.compile( + "^:([ \t]+|(?=\r?\n|$))", Pattern.CASE_INSENSITIVE); + + private static final Pattern horizontalRulePattern = Pattern.compile( + "^-{5,}[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + + private static final Pattern keywordPattern = Pattern.compile( + "^#\\+([^ \t\r\n]+?):[ \t]*([^\r\n]+?)[ \t]*(?=\r?\n|$)", Pattern.CASE_INSENSITIVE); + + private static final Pattern latexEnvironmentBeginPattern = Pattern.compile( + "^\\\\begin\\{([*0-9A-Za-z]+)\\}[ \t]*", Pattern.CASE_INSENSITIVE); + private static final Pattern latexEnvironmentEndPattern = Pattern.compile( + "^\\\\end\\{([*0-9A-Za-z]+)\\}[ \t]*", Pattern.CASE_INSENSITIVE); + + private static final Pattern latexFragmentPattern1 = Pattern.compile( + "^\\\\[A-Za-z]+(\\[[^\r\n{}\\[\\]]*\\]|\\{[^\r\n{}]*\\})*", Pattern.CASE_INSENSITIVE); + private static final Pattern latexFragmentPattern2 = Pattern.compile( + "^\\\\\\((.|\r?\n)*?\\\\\\)", Pattern.CASE_INSENSITIVE); + private static final Pattern latexFragmentPattern3 = Pattern.compile( + "^\\\\\\[(.|\r?\n)*?\\\\\\]", Pattern.CASE_INSENSITIVE); + private static final Pattern latexFragmentPattern4 = Pattern.compile( + "^\\$\\$(.|\r?\n)*?\\$\\$", Pattern.CASE_INSENSITIVE); + private static final Pattern latexFragmentPattern5 = Pattern.compile( + "^\\$[^ \t\r\n\"',.;?]\\$(?=[ \t\"'(),.;<>?\\[\\]]|\r?\n|$)", Pattern.CASE_INSENSITIVE); + private static final Pattern latexFragmentPattern6 = Pattern.compile( + "^\\$[^ \t\r\n$,.;]([^\r\n$]|\r?\n)*[^ \t\r\n$,.]\\$(?=[ \t!\"'(),.;<>?\\[\\]]|\r?\n|$)", + Pattern.CASE_INSENSITIVE); + + private static final Pattern exportSnippetPattern = Pattern.compile( + "^@@[-0-9A-Za-z]+:[^\r\n]*?@@", Pattern.CASE_INSENSITIVE); + + private static final Pattern footnoteReferencePattern1 = Pattern.compile( + "^\\[fn:[-0-9A-Z_a-z]*\\]", Pattern.CASE_INSENSITIVE); + private static final Pattern footnoteReferencePattern2 = Pattern.compile( + "^\\[fn:([-0-9A-Z_a-z]*)?:[^\r\n]*?\\]", Pattern.CASE_INSENSITIVE); + + private static final Pattern inlineBabelCallPattern = Pattern.compile( + "^call_[^ \t\r\n()]+(\\[[^\r\n]*?\\])?\\([^\r\n]*?\\)(\\[[^\r\n]*?\\])?", + Pattern.CASE_INSENSITIVE); + private static final Pattern inlineSourceBlockPattern = Pattern.compile( + "^src_[^ \t\r\n]+(\\[[^\r\n]*?\\])?\\{[^\r\n]*?\\}", Pattern.CASE_INSENSITIVE); + + private static final Pattern macroPattern = Pattern.compile( + "^\\{\\{\\{[A-Za-z][-0-9A-Z_a-z]*(\\([^\r\n]*?\\))?\\}\\}\\}", Pattern.CASE_INSENSITIVE); + + private static final Pattern statisticsCookiePattern = Pattern.compile( + "^\\[[0-9]*(%|/[0-9]*)\\]", Pattern.CASE_INSENSITIVE); + + private static final Pattern diaryTimestampPattern = Pattern.compile( + "^<%%\\([^\r\n>]+\\)>", Pattern.CASE_INSENSITIVE); + private static final Pattern activeTimestampRangePattern1 = Pattern.compile( + "^<" + timestampPatternString + ">--<" + timestampPatternString + ">", + Pattern.CASE_INSENSITIVE); + private static final Pattern activeTimestampRangePattern2 = Pattern.compile( + "^<" + timestampRangePatternString + ">", Pattern.CASE_INSENSITIVE); + private static final Pattern inactiveTimestampRangePattern1 = Pattern.compile( + "^\\[" + timestampPatternString + "\\]--\\[" + timestampPatternString + "\\]", + Pattern.CASE_INSENSITIVE); + private static final Pattern inactiveTimestampRangePattern2 = Pattern.compile( + "^\\[" + timestampRangePatternString + "\\]", Pattern.CASE_INSENSITIVE); + private static final Pattern activeTimestampPattern = Pattern.compile( + "^<" + timestampPatternString + ">", Pattern.CASE_INSENSITIVE); + private static final Pattern inactiveTimestampPattern = Pattern.compile( + "^\\[" + timestampPatternString + "\\]", Pattern.CASE_INSENSITIVE); + + private static final Pattern angleLinkPattern = Pattern.compile( + "^<[A-Za-z]+:[^\r\n<>\\]]+>", Pattern.CASE_INSENSITIVE); + private static final Pattern plainLinkPattern = Pattern.compile( + "^[A-Za-z]+:[^ \t\r\n()<>]+(?<=[A-Za-z]|[^ \t\r\n!,.;?]/)(?=[^\r\n0-9A-Za-z]|\r?\n|$)", + Pattern.CASE_INSENSITIVE); + + private static final Pattern linkPrecedingPattern = Pattern.compile( + "^[^\r\n0-9A-Za-z]", Pattern.CASE_INSENSITIVE); + private static final Pattern radioTargetPattern = Pattern.compile( + "^<<<(?![ \t])[^\r\n<>]+(?>>", Pattern.CASE_INSENSITIVE); + private static final Pattern targetPattern = Pattern.compile( + "^<<(?![ \t])[^\r\n<>]+(?>", Pattern.CASE_INSENSITIVE); + + private static final Pattern regularLinkPatternWithoutDescription = Pattern.compile( + "^\\[\\[(" + regularLinkPathPatternString + ")\\]\\]", + Pattern.CASE_INSENSITIVE); + private static final Pattern regularLinkPatternWithDescription = Pattern.compile( + "^\\[\\[(" + regularLinkPathPatternString + ")\\]\\[(?=[^\r\n\\[\\]]+\\]\\])", + Pattern.CASE_INSENSITIVE); + private static final Pattern regularLinkDescriptionEndPattern = Pattern.compile( + "^\\]\\]", Pattern.CASE_INSENSITIVE); + + private static final Pattern textMarkupStartPrecedingPattern = Pattern.compile( + "^[ \t\r\n\"'(\\-{]", Pattern.CASE_INSENSITIVE); + private static final Pattern textMarkupStartFollowingPattern = Pattern.compile( + "^[^ \t\r\n]", Pattern.CASE_INSENSITIVE); + private static final Pattern textMarkupEndPrecedingPattern = Pattern.compile( + "^[^ \t\r\n]", Pattern.CASE_INSENSITIVE); + private static final Pattern textMarkupEndFollowingPattern = Pattern.compile( + "^([ \t\r\n!\"'),\\-.:;?\\[}]|$)", Pattern.CASE_INSENSITIVE); + private static final Pattern textMarkupMarkerPattern = Pattern.compile( + "^[*+/=_~]", Pattern.CASE_INSENSITIVE); + private static final Pattern textMarkupVerbatimEndPattern = Pattern.compile( + "^=", Pattern.CASE_INSENSITIVE); + private static final Pattern textMarkupCodeEndPattern = Pattern.compile( + "^~", Pattern.CASE_INSENSITIVE); + + private String code; + private int pos; + private char curChar; + private String curString; + + private DummyGenerator dummyGenerator; + private int dummyCounter; + private int indentation; + private String appendAtEndOfLine; + private Stack elementTypeStack; + private @Nullable String latexEnvironmentName; + private Stack objectTypeStack; + + private String language; + + public OrgAnnotatedTextBuilder(String codeLanguageId) { + super(codeLanguageId); + this.language = "en-US"; + reinitialize(); + } + + @EnsuresNonNull({"code", "curString", "curElementType", "dummyGenerator", "appendAtEndOfLine", + "elementTypeStack", "objectTypeStack"}) + public void reinitialize(@UnknownInitialization OrgAnnotatedTextBuilder this) { + this.code = ""; + this.pos = 0; + this.curString = ""; + + this.dummyGenerator = new DummyGenerator(); + this.dummyCounter = 0; + this.indentation = -1; + this.appendAtEndOfLine = ""; + this.elementTypeStack = new Stack<>(); + this.elementTypeStack.push(ElementType.PARAGRAPH); + this.latexEnvironmentName = null; + this.objectTypeStack = new Stack<>(); + } + + @Override + public void setSettings(Settings settings) { + super.setSettings(settings); + this.language = settings.getLanguageShortCode(); + } + + public OrgAnnotatedTextBuilder addText(@Nullable String text) { + if ((text == null) || text.isEmpty()) return this; + super.addText(text); + this.pos += text.length(); + return this; + } + + public OrgAnnotatedTextBuilder addMarkup(@Nullable String markup) { + if ((markup == null) || markup.isEmpty()) return this; + super.addMarkup(markup); + this.pos += markup.length(); + return this; + } + + public OrgAnnotatedTextBuilder addMarkup(@Nullable String markup, @Nullable String interpretAs) { + if ((interpretAs == null) || interpretAs.isEmpty()) { + return addMarkup(markup); + } else { + if (markup == null) markup = ""; + super.addMarkup(markup, interpretAs); + this.pos += markup.length(); + return this; + } + } + + @Override + public CodeAnnotatedTextBuilder addCode(String code) { + reinitialize(); + this.code = code; + + while (this.pos < this.code.length()) { + this.curChar = this.code.charAt(this.pos); + this.curString = String.valueOf(this.curChar); + boolean isStartOfLine = ((this.pos == 0) || this.code.charAt(this.pos - 1) == '\n'); + + if (isStartOfLine) { + @Nullable Matcher whitespaceMatcher = matchFromPosition(whitespacePattern); + String whitespace = ((whitespaceMatcher != null) ? whitespaceMatcher.group() : ""); + this.indentation = whitespace.length(); + addMarkup(whitespace); + this.curChar = this.code.charAt(this.pos); + this.curString = String.valueOf(this.curChar); + } + + @Nullable Matcher matcher; + + if (this.objectTypeStack.contains(ObjectType.VERBATIM)) { + if ((matcher = matchInlineEndFromPosition(textMarkupVerbatimEndPattern)) != null) { + popObjectType(); + addMarkup(matcher.group(), generateDummy()); + } else { + addMarkup(this.curString); + } + + continue; + } else if (this.objectTypeStack.contains(ObjectType.CODE)) { + if ((matcher = matchInlineEndFromPosition(textMarkupCodeEndPattern)) != null) { + popObjectType(); + addMarkup(matcher.group(), generateDummy()); + } else { + addMarkup(this.curString); + } + + continue; + } + + if (isStartOfLine) { + boolean elementFound = true; + + if (this.elementTypeStack.contains(ElementType.TABLE) + && (matchFromPosition(tableRowPattern) == null)) { + popElementType(); + } + + if (this.elementTypeStack.contains(ElementType.COMMENT_BLOCK) + || this.elementTypeStack.contains(ElementType.EXAMPLE_BLOCK) + || this.elementTypeStack.contains(ElementType.EXPORT_BLOCK) + || this.elementTypeStack.contains(ElementType.SOURCE_BLOCK)) { + if ((matcher = matchFromPosition(blockEndPattern)) != null) { + popElementType(); + addMarkup(matcher.group()); + } else { + addMarkup(this.curString); + } + } else if (this.elementTypeStack.contains(ElementType.PROPERTY_DRAWER)) { + if ((matcher = matchFromPosition(drawerEndPattern)) != null) { + popElementType(); + addMarkup(matcher.group()); + } else { + addMarkup(this.curString); + } + } else if (this.elementTypeStack.contains(ElementType.LATEX_ENVIRONMENT)) { + if ((this.latexEnvironmentName != null) + && ((matcher = matchFromPosition(latexEnvironmentEndPattern)) != null) + && this.latexEnvironmentName.equals(matcher.group(1))) { + popElementType(); + this.latexEnvironmentName = null; + addMarkup(matcher.group()); + } else { + addMarkup(this.curString); + } + } else if ((this.indentation == 0) + && ((matcher = matchFromPosition(headlineCommentPattern)) != null)) { + addMarkup(matcher.group(), "\n"); + } else if ((this.indentation == 0) + && ((matcher = matchFromPosition(headlinePattern)) != null)) { + this.elementTypeStack.push(ElementType.HEADLINE); + this.appendAtEndOfLine = "\n"; + addMarkup(matcher.group(), "\n"); + } else if ((matcher = matchFromPosition(affiliatedKeywordsPattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(blockBeginPattern)) != null) { + @Nullable String blockType = matcher.group(1); + ElementType elementType = ElementType.GREATER_SPECIAL_BLOCK; + + if (blockType == null) { + } else if (blockType.equalsIgnoreCase("CENTER")) { + elementType = ElementType.GREATER_CENTER_BLOCK; + } else if (blockType.equalsIgnoreCase("QUOTE")) { + elementType = ElementType.GREATER_QUOTE_BLOCK; + } else if (blockType.equalsIgnoreCase("COMMENT")) { + elementType = ElementType.COMMENT_BLOCK; + } else if (blockType.equalsIgnoreCase("EXAMPLE")) { + elementType = ElementType.EXAMPLE_BLOCK; + } else if (blockType.equalsIgnoreCase("EXPORT")) { + elementType = ElementType.EXPORT_BLOCK; + } else if (blockType.equalsIgnoreCase("SRC")) { + elementType = ElementType.SOURCE_BLOCK; + } else if (blockType.equalsIgnoreCase("VERSE")) { + elementType = ElementType.VERSE_BLOCK; + } + + this.elementTypeStack.push(elementType); + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(blockEndPattern)) != null) { + popElementType(); + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(drawerBeginPattern)) != null) { + @Nullable String drawerName = matcher.group(1); + + if ((drawerName != null) && drawerName.equalsIgnoreCase("PROPERTIES")) { + this.elementTypeStack.push(ElementType.PROPERTY_DRAWER); + } else { + this.elementTypeStack.push(ElementType.DRAWER); + } + + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(drawerEndPattern)) != null) { + popElementType(); + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(dynamicBlockBeginPattern)) != null) { + this.elementTypeStack.push(ElementType.DYNAMIC_BLOCK); + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(dynamicBlockEndPattern)) != null) { + popElementType(); + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(footnoteDefinitionPattern)) != null) { + addMarkup(matcher.group()); + } else if (((matcher = matchFromPosition(ruleTableRowPattern)) != null) + || ((matcher = matchFromPosition(tableRowPattern)) != null)) { + if (!this.elementTypeStack.contains(ElementType.TABLE)) { + this.elementTypeStack.push(ElementType.TABLE); + } + + this.appendAtEndOfLine = "\n"; + addMarkup(matcher.group(), "\n"); + } else if ((matcher = matchFromPosition(itemPattern)) != null) { + this.appendAtEndOfLine = "\n"; + addMarkup(matcher.group(), "\n"); + } else if ((matcher = matchFromPosition(babelCallPattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(clockPattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(diarySexpPattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(planningPattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(commentPattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(fixedWidthLinePattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(horizontalRulePattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(keywordPattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(latexEnvironmentBeginPattern)) != null) { + this.elementTypeStack.push(ElementType.LATEX_ENVIRONMENT); + this.latexEnvironmentName = matcher.group(1); + addMarkup(matcher.group()); + } else { + elementFound = false; + } + + if (elementFound) continue; + } + + if (this.elementTypeStack.contains(ElementType.COMMENT_BLOCK) + || this.elementTypeStack.contains(ElementType.EXAMPLE_BLOCK) + || this.elementTypeStack.contains(ElementType.EXPORT_BLOCK) + || this.elementTypeStack.contains(ElementType.SOURCE_BLOCK) + || this.elementTypeStack.contains(ElementType.PROPERTY_DRAWER) + || this.elementTypeStack.contains(ElementType.LATEX_ENVIRONMENT)) { + addMarkup(this.curString); + continue; + } + + if (this.elementTypeStack.contains(ElementType.HEADLINE) + && ((matcher = matchFromPosition(headlineTagsPattern)) != null)) { + addMarkup(matcher.group()); + } else if (this.elementTypeStack.contains(ElementType.TABLE) + && ((matcher = matchFromPosition(tableCellSeparatorPattern)) != null)) { + addMarkup(matcher.group(), "\n\n"); + } else if (this.objectTypeStack.contains(ObjectType.REGULAR_LINK_DESCRIPTION) + && ((matcher = matchFromPosition(regularLinkDescriptionEndPattern)) != null)) { + popObjectType(); + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(latexFragmentPattern1)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(latexFragmentPattern2)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(latexFragmentPattern3)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(latexFragmentPattern4)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((isStartOfLine || this.code.charAt(this.pos - 1) != '$') + && ((matcher = matchFromPosition(latexFragmentPattern5)) != null)) { + addMarkup(matcher.group(), generateDummy()); + } else if ((isStartOfLine || this.code.charAt(this.pos - 1) != '$') + && ((matcher = matchFromPosition(latexFragmentPattern6)) != null)) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(exportSnippetPattern)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(footnoteReferencePattern1)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(footnoteReferencePattern2)) != null) { + addMarkup(matcher.group()); + } else if ((matcher = matchFromPosition(inlineBabelCallPattern)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(inlineSourceBlockPattern)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(macroPattern)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(statisticsCookiePattern)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(diaryTimestampPattern)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(activeTimestampRangePattern1)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(activeTimestampRangePattern2)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(inactiveTimestampRangePattern1)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(inactiveTimestampRangePattern2)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(activeTimestampPattern)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(inactiveTimestampPattern)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if (((matcher = matchFromPosition(radioTargetPattern)) != null) + && (isStartOfLine || linkPrecedingPattern.matcher( + String.valueOf(this.code.charAt(this.pos - 1))).find())) { + addMarkup(matcher.group(), generateDummy()); + } else if (((matcher = matchFromPosition(targetPattern)) != null) + && (isStartOfLine || linkPrecedingPattern.matcher( + String.valueOf(this.code.charAt(this.pos - 1))).find())) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(angleLinkPattern)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if (((matcher = matchFromPosition(plainLinkPattern)) != null) + && (isStartOfLine || linkPrecedingPattern.matcher( + String.valueOf(this.code.charAt(this.pos - 1))).find())) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(regularLinkPatternWithoutDescription)) != null) { + addMarkup(matcher.group(), generateDummy()); + } else if ((matcher = matchFromPosition(regularLinkPatternWithDescription)) != null) { + this.objectTypeStack.add(ObjectType.REGULAR_LINK_DESCRIPTION); + addMarkup(matcher.group()); + } else if (((matcher = matchInlineStartFromPosition(textMarkupMarkerPattern)) != null) + || ((matcher = matchInlineEndFromPosition(textMarkupMarkerPattern)) != null)) { + String textMarkupMarker = matcher.group(); + + if (textMarkupMarker.equals("*")) { + toggleObjectType(ObjectType.BOLD); + addMarkup(textMarkupMarker); + } else if (textMarkupMarker.equals("+")) { + toggleObjectType(ObjectType.STRIKETHROUGH); + addMarkup(textMarkupMarker); + } else if (textMarkupMarker.equals("/")) { + toggleObjectType(ObjectType.ITALIC); + addMarkup(textMarkupMarker); + } else if (textMarkupMarker.equals("=")) { + toggleObjectType(ObjectType.VERBATIM); + addMarkup(textMarkupMarker); + } else if (textMarkupMarker.equals("_")) { + toggleObjectType(ObjectType.UNDERLINE); + addMarkup(textMarkupMarker); + } else if (textMarkupMarker.equals("~")) { + toggleObjectType(ObjectType.CODE); + addMarkup(textMarkupMarker); + } else { + addText(textMarkupMarker); + } + } else if (this.curChar == '\n') { + addMarkup("\n", "\n" + this.appendAtEndOfLine); + this.appendAtEndOfLine = ""; + if (this.elementTypeStack.contains(ElementType.HEADLINE)) popElementType(); + } else { + addText(this.curString); + } + } + + addMarkup("", this.appendAtEndOfLine); + + return this; + } + + private @Nullable Matcher matchFromPosition(Pattern pattern) { + return matchFromPosition(pattern, this.pos); + } + + private @Nullable Matcher matchFromPosition(Pattern pattern, int pos) { + Matcher matcher = pattern.matcher(this.code.substring(pos)); + return ((matcher.find() && !matcher.group().isEmpty()) ? matcher : null); + } + + private @Nullable Matcher matchInlineStartFromPosition(Pattern pattern) { + if ((this.pos > 0) && (matchFromPosition(textMarkupStartPrecedingPattern, this.pos - 1) == null)) { + return null; + } + + @Nullable Matcher matcher = matchFromPosition(pattern); + if (matcher == null) return null; + if ((this.pos == 0) || (this.pos >= this.code.length() - 1)) return matcher; + + return ((matchFromPosition(textMarkupStartFollowingPattern, + this.pos + matcher.group().length()) == null) ? null : matcher); + } + + private @Nullable Matcher matchInlineEndFromPosition(Pattern pattern) { + if ((this.pos == 0) || (matchFromPosition(textMarkupEndPrecedingPattern, this.pos - 1) == null)) { + return null; + } + + @Nullable Matcher matcher = matchFromPosition(pattern); + if (matcher == null) return null; + + return ((matchFromPosition(textMarkupEndFollowingPattern, + this.pos + matcher.group().length()) != null) ? matcher : null); + } + + private String generateDummy() { + return this.dummyGenerator.generate(this.language, this.dummyCounter++); + } + + private void popElementType() { + if (!this.elementTypeStack.isEmpty()) this.elementTypeStack.pop(); + if (this.elementTypeStack.isEmpty()) this.elementTypeStack.push(ElementType.PARAGRAPH); + } + + private void popObjectType() { + if (!this.objectTypeStack.isEmpty()) this.objectTypeStack.pop(); + } + + private void toggleObjectType(ObjectType objectType) { + if (!this.objectTypeStack.isEmpty() && (this.objectTypeStack.peek() == objectType)) { + popObjectType(); + } else { + this.objectTypeStack.push(objectType); + } + } +} diff --git a/ltexls/src/main/java/org/bsplines/ltexls/parsing/org/OrgFragmentizer.java b/ltexls/src/main/java/org/bsplines/ltexls/parsing/org/OrgFragmentizer.java new file mode 100644 index 00000000..566eac21 --- /dev/null +++ b/ltexls/src/main/java/org/bsplines/ltexls/parsing/org/OrgFragmentizer.java @@ -0,0 +1,20 @@ +/* Copyright (C) 2020 Julian Valentin, LTeX Development Community + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package org.bsplines.ltexls.parsing.org; + +import java.util.regex.Pattern; +import org.bsplines.ltexls.parsing.RegexCodeFragmentizer; + +public class OrgFragmentizer extends RegexCodeFragmentizer { + private static final Pattern pattern = Pattern.compile( + "^[ \t]*#[ \t]+(?i)ltex(?-i):(.*?)[ \t]*$", Pattern.MULTILINE); + + public OrgFragmentizer(String codeLanguageId) { + super(codeLanguageId, pattern); + } +} diff --git a/ltexls/src/main/java/org/bsplines/ltexls/server/LtexWorkspaceService.java b/ltexls/src/main/java/org/bsplines/ltexls/server/LtexWorkspaceService.java index 992bbdbd..adc447ec 100644 --- a/ltexls/src/main/java/org/bsplines/ltexls/server/LtexWorkspaceService.java +++ b/ltexls/src/main/java/org/bsplines/ltexls/server/LtexWorkspaceService.java @@ -100,6 +100,8 @@ public CompletableFuture executeCheckDocumentCommand(JsonObject argument codeLanguageId = "bibtex"; } else if (fileNameStr.endsWith(".md")) { codeLanguageId = "markdown"; + } else if (fileNameStr.endsWith(".org")) { + codeLanguageId = "org"; } else if (fileNameStr.endsWith(".Rnw") || fileNameStr.endsWith(".rnw")) { codeLanguageId = "rsweave"; } else if (fileNameStr.endsWith(".rst")) { diff --git a/ltexls/src/main/java/org/bsplines/ltexls/settings/Settings.java b/ltexls/src/main/java/org/bsplines/ltexls/settings/Settings.java index df47a5f0..237d133a 100644 --- a/ltexls/src/main/java/org/bsplines/ltexls/settings/Settings.java +++ b/ltexls/src/main/java/org/bsplines/ltexls/settings/Settings.java @@ -25,8 +25,8 @@ import org.eclipse.lsp4j.DiagnosticSeverity; public class Settings { - private static final Set defaultEnabled = - new HashSet<>(Arrays.asList("bibtex", "latex", "markdown", "restructuredtext", "rsweave")); + private static final Set defaultEnabled = new HashSet<>(Arrays.asList( + "bibtex", "latex", "markdown", "org", "restructuredtext", "rsweave")); private @Nullable Set enabled; private @Nullable String languageShortCode; diff --git a/ltexls/src/test/java/org/bsplines/ltexls/parsing/org/OrgAnnotatedTextBuilderTest.java b/ltexls/src/test/java/org/bsplines/ltexls/parsing/org/OrgAnnotatedTextBuilderTest.java new file mode 100644 index 00000000..1c98adf6 --- /dev/null +++ b/ltexls/src/test/java/org/bsplines/ltexls/parsing/org/OrgAnnotatedTextBuilderTest.java @@ -0,0 +1,429 @@ +/* Copyright (C) 2020 Julian Valentin, LTeX Development Community + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package org.bsplines.ltexls.parsing.org; + +import org.bsplines.ltexls.parsing.CodeAnnotatedTextBuilder; +import org.bsplines.ltexls.settings.Settings; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.languagetool.markup.AnnotatedText; + +public class OrgAnnotatedTextBuilderTest { + private static void assertPlainText(String code, String expectedPlainText) { + AnnotatedText annotatedText = buildAnnotatedText(code); + Assertions.assertEquals(expectedPlainText, annotatedText.getPlainText()); + } + + private static AnnotatedText buildAnnotatedText(String code) { + CodeAnnotatedTextBuilder builder = CodeAnnotatedTextBuilder.create("org"); + Settings settings = new Settings(); + builder.setSettings(settings); + return builder.addCode(code).build(); + } + + @Test + public void testHeadlinesAndSections() { + assertPlainText( + "* \n" + + "\n" + + "** DONE\n" + + "\n" + + "*** This is a test\n" + + "\n" + + "**** TODO [#A] COMMENT Another test :tag:a2%:\n" + + "\n" + + "**** TODO [#A] Final test :tag:a2%:\n", + "\n\n\n\n\n\n\n\n\nThis is a test\n\n\n\n\n\n\nFinal test\n\n"); + } + + @Test + public void testAffiliatedKeywords() { + assertPlainText( + "This is a test.\n" + + "\n" + + "#+HEADER: test\n" + + "#+CAPTION[abc]: def\n" + + "Second sentence.\n" + + "\n" + + " #+ATTR_foo01_bar: BOOM\n" + + "Final sentence.\n", + "This is a test.\n\n\n\nSecond sentence.\n\n\nFinal sentence.\n"); + } + + @Test + public void testBlocks() { + assertPlainText( + "This is a test.\n" + + "#+BEGIN_CENTER\n" + + "Contents.\n" + + "#+END_CENTER\n" + + "This is another test.\n", + "This is a test.\n\nContents.\n\nThis is another test.\n"); + assertPlainText( + "This is a test.\n" + + "#+BEGIN_QUOTE\n" + + "Contents.\n" + + "#+END_QUOTE\n" + + "This is another test.\n", + "This is a test.\n\nContents.\n\nThis is another test.\n"); + assertPlainText( + "This is a test.\n" + + "#+BEGIN_FOOBAR\n" + + "Contents.\n" + + "#+END_FOOBAR\n" + + "This is another test.\n", + "This is a test.\n\nContents.\n\nThis is another test.\n"); + assertPlainText( + "This is a test.\n" + + "#+BEGIN_COMMENT\n" + + "Contents.\n" + + "#+END_COMMENT\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + assertPlainText( + "This is a test.\n" + + "#+BEGIN_EXAMPLE\n" + + "Contents.\n" + + "#+END_EXAMPLE\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + assertPlainText( + "This is a test.\n" + + "#+BEGIN_EXPORT\n" + + "Contents.\n" + + "#+END_EXPORT\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + assertPlainText( + "This is a test.\n" + + "#+BEGIN_SRC\n" + + "Contents.\n" + + "#+END_SRC\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + assertPlainText( + "This is a test.\n" + + "#+BEGIN_VERSE\n" + + "Contents.\n" + + "#+END_VERSE\n" + + "This is another test.\n", + "This is a test.\n\nContents.\n\nThis is another test.\n"); + } + + @Test + public void testDrawers() { + assertPlainText( + "This is a test.\n" + + ":TEST_DRAWER:\n" + + "Contents.\n" + + ":END:\n" + + "This is another test.\n", + "This is a test.\n\nContents.\n\nThis is another test.\n"); + assertPlainText( + "This is a test.\n" + + ":PROPERTIES:\n" + + "Contents.\n" + + ":END:\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + } + + @Test + public void testDynamicBlocks() { + assertPlainText( + "This is a test.\n" + + "#+BEGIN: test-block :abc\n" + + "Contents.\n" + + ":END:\n" + + "This is another test.\n", + "This is a test.\n\nContents.\n\nThis is another test.\n"); + } + + @Test + public void testFootnotes() { + assertPlainText( + "This is a test.\n" + + "[fn:1] Contents.\n" + + "This is another test.\n", + "This is a test.\nContents.\nThis is another test.\n"); + } + + @Test + public void testLists() { + assertPlainText( + "1. Test 1.\n" + + "2. [X] Test 2.\n" + + " - Test tag :: Test 3.\n", + "\nTest 1.\n\n\nTest 2.\n\n\nTest 3.\n\n"); + } + + @Test + public void testTables() { + assertPlainText( + "This is a test.\n" + + "| Test1 | Test2 | Test3 |\n" + + "|-------+-------+-------|\n" + + "| Test4 | Test5 | Test6 |\n" + + "| Test7 | Test8 | Test9 |\n" + + "This is another test.\n", + "This is a test.\n\nTest1\n\nTest2\n\nTest3\n\n\n\n\n\n\n\nTest4\n\nTest5\n\n" + + "Test6\n\n\n\n\nTest7\n\nTest8\n\nTest9\n\n\n\nThis is another test.\n"); + } + + @Test + public void testBabelCalls() { + assertPlainText( + "This is a test.\n" + + "#+CALL: Contents.\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + } + + @Test + public void testClocks() { + assertPlainText( + "This is a test.\n" + + "CLOCK: [1234-05-06 07:08]\n" + + "CLOCK: [1234-05-06 07:08-09:10] => 1:23\n" + + "CLOCK: [1234-05-06 07:08]--[1234-05-06 07:08] => 1:23\n" + + "This is another test.\n", + "This is a test.\n\n\n\nThis is another test.\n"); + } + + @Test + public void testDiarySexps() { + assertPlainText( + "This is a test.\n" + + "%%(Contents.\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + } + + @Test + public void testPlannings() { + assertPlainText( + "* Test\n" + + "DEADLINE: [1234-05-06 Sat 07:08]\n" + + "This is a test.\n", + "\nTest\n\n\nThis is a test.\n"); + } + + @Test + public void testComments() { + assertPlainText( + "This is a test.\n" + + "\t#\tComment\n" + + "# Another comment\n" + + "This is another test.\n", + "This is a test.\nThis is another test.\n"); + } + + @Test + public void testFixedWidthLines() { + assertPlainText( + "This is a test.\n" + + "\t:\n" + + ": Contents.\n" + + "This is another test.\n", + "This is a test.\n\nContents.\nThis is another test.\n"); + } + + @Test + public void testHorizontalRules() { + assertPlainText( + "This is a test.\n" + + "-----\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + } + + @Test + public void testKeywords() { + assertPlainText( + "This is a test.\n" + + "#+TAGS: test1 test2\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + } + + @Test + public void testLatexEnvironments() { + assertPlainText( + "This is a test.\n" + + "\\begin{test}\n" + + " Contents 1.\n" + + " \\begin{equation}\n" + + " Contents 2.\n" + + " \\end{equation}\n" + + " Contents 3.\n" + + "\\end{test}\n" + + "This is another test.\n", + "This is a test.\n\nThis is another test.\n"); + } + + @Test + public void testEntities() { + assertPlainText( + "This is a test: \\entityone, \\entitytwo{}.\n", + "This is a test: Dummy0, Dummy1.\n"); + } + + @Test + public void testLatexFragments() { + assertPlainText( + "This is a test: \\test[abc][def]{ghi}.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: \\(E = mc^2\\).\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: \\[E = mc^2\\].\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: $$E = mc^2$$.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: $E$.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: $E = mc^2$.\n", + "This is a test: Dummy0.\n"); + } + + @Test + public void testExportSnippets() { + assertPlainText( + "This is a test: @@html:@@Test@@html:@@.\n", + "This is a test: Test.\n"); + } + + @Test + public void testFootnoteReferences() { + assertPlainText( + "This is a test[fn:1].\n", + "This is a test.\n"); + assertPlainText( + "This is a test[fn:1:contents].\n", + "This is a test.\n"); + assertPlainText( + "This is a test[fn::contents].\n", + "This is a test.\n"); + } + + @Test + public void testInlineBabelCalls() { + assertPlainText( + "This is a test: call_test(abc, def).\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: call_test[foo](abc, def)[bar].\n", + "This is a test: Dummy0.\n"); + } + + @Test + public void testInlineSourceBlocks() { + assertPlainText( + "This is a test: src_abc{def}.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: src_abc[foo]{def}.\n", + "This is a test: Dummy0.\n"); + } + + @Test + public void testLinks() { + assertPlainText( + "This is a test: <<>>.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: <>.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: .\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: https://bsplines.org/.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: [[https://bsplines.org/]].\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: [[https://bsplines.org/][a *good* test]].\n", + "This is a test: a good test.\n"); + } + + @Test + public void testMacros() { + assertPlainText( + "This is a test: {{{test(abc, def)}}}.\n", + "This is a test: Dummy0.\n"); + } + + @Test + public void testStatisticsCookies() { + assertPlainText( + "This is a test: [50%], [1/3].\n", + "This is a test: Dummy0, Dummy1.\n"); + } + + @Test + public void testTimestamps() { + assertPlainText( + "This is a test: <%%(abv)>.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: <1234-05-06 Sat 07:08>.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: <1234-05-06 Sat 07:08 +1w>.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: <1234-05-06 Sat 07:08 -2d>.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: <1234-05-06 Sat 07:08 +1w -2d>.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: [1234-05-06 Sat 07:08].\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: <1234-05-06 Sat 07:08>--<1234-05-06 Sat 07:08>.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: <1234-05-06 Sat 07:08-09:10>.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: [1234-05-06 Sat 07:08]--[1234-05-06 Sat 07:08].\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: [1234-05-06 Sat 07:08-09:10].\n", + "This is a test: Dummy0.\n"); + } + + @Test + public void testTextMarkup() { + assertPlainText( + "This is a test: *Test*.\n", + "This is a test: Test.\n"); + assertPlainText( + "This is a test: +Test+.\n", + "This is a test: Test.\n"); + assertPlainText( + "This is a test: /Test/.\n", + "This is a test: Test.\n"); + assertPlainText( + "This is a test: =Test=.\n", + "This is a test: Dummy0.\n"); + assertPlainText( + "This is a test: _Test_.\n", + "This is a test: Test.\n"); + assertPlainText( + "This is a test: ~Test~.\n", + "This is a test: Dummy0.\n"); + } +} diff --git a/ltexls/src/test/java/org/bsplines/ltexls/parsing/org/OrgFragmentizerTest.java b/ltexls/src/test/java/org/bsplines/ltexls/parsing/org/OrgFragmentizerTest.java new file mode 100644 index 00000000..3f1e1a05 --- /dev/null +++ b/ltexls/src/test/java/org/bsplines/ltexls/parsing/org/OrgFragmentizerTest.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2020 Julian Valentin, LTeX Development Community + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package org.bsplines.ltexls.parsing.org; + +import java.util.List; +import org.bsplines.ltexls.parsing.CodeFragment; +import org.bsplines.ltexls.parsing.CodeFragmentizer; +import org.bsplines.ltexls.settings.Settings; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class OrgFragmentizerTest { + private static void testFragmentizer(CodeFragmentizer fragmentizer, String code) { + List codeFragments = fragmentizer.fragmentize(code, new Settings()); + Assertions.assertEquals(3, codeFragments.size()); + + Assertions.assertEquals("org", codeFragments.get(0).getCodeLanguageId()); + Assertions.assertEquals(0, codeFragments.get(0).getFromPos()); + Assertions.assertEquals(12, codeFragments.get(0).getCode().length()); + Assertions.assertEquals("en-US", codeFragments.get(0).getSettings().getLanguageShortCode()); + + Assertions.assertEquals("org", codeFragments.get(1).getCodeLanguageId()); + Assertions.assertEquals(12, codeFragments.get(1).getFromPos()); + Assertions.assertEquals(37, codeFragments.get(1).getCode().length()); + Assertions.assertEquals("de-DE", codeFragments.get(1).getSettings().getLanguageShortCode()); + + Assertions.assertEquals("org", codeFragments.get(2).getCodeLanguageId()); + Assertions.assertEquals(49, codeFragments.get(2).getFromPos()); + Assertions.assertEquals(36, codeFragments.get(2).getCode().length()); + Assertions.assertEquals("en-US", codeFragments.get(2).getSettings().getLanguageShortCode()); + } + + @Test + public void test() { + CodeFragmentizer fragmentizer = CodeFragmentizer.create("org"); + + testFragmentizer(fragmentizer, + "Sentence 1\n" + + "\n # ltex: language=de-DE\n\nSentence 2\n" + + "\n\t#\tltex:\tlanguage=en-US\n\nSentence 3\n"); + } + + @Test + public void testWrongSettings() { + OrgFragmentizer fragmentizer = new OrgFragmentizer("org"); + Assertions.assertDoesNotThrow(() -> fragmentizer.fragmentize( + "Sentence 1\n\n# ltex: languagede-DE\n\nSentence 2\n", new Settings())); + Assertions.assertDoesNotThrow(() -> fragmentizer.fragmentize( + "Sentence 1\n\n# ltex: unknownKey=abc\n\nSentence 2\n", new Settings())); + } +} diff --git a/ltexls/src/test/java/org/bsplines/ltexls/server/LtexWorkspaceServiceTest.java b/ltexls/src/test/java/org/bsplines/ltexls/server/LtexWorkspaceServiceTest.java index bde8d655..da6d5c24 100644 --- a/ltexls/src/test/java/org/bsplines/ltexls/server/LtexWorkspaceServiceTest.java +++ b/ltexls/src/test/java/org/bsplines/ltexls/server/LtexWorkspaceServiceTest.java @@ -77,7 +77,7 @@ public void testCheckDocument() throws IOException, InterruptedException, Execut assertCheckDocumentResult("invalid_uri", false); assertCheckDocumentResult("file:///non_existent_path", false); - for (String extension : Arrays.asList(".bib", ".md", ".Rnw", ".rst", ".tex")) { + for (String extension : Arrays.asList(".bib", ".md", ".org", ".Rnw", ".rst", ".tex")) { File tmpFile = File.createTempFile("ltex-", extension); try {