19 |
20 | The main Rewrap command is: **Rewrap Comment / Text**, by default bound to
21 | `Alt+Q`. With the cursor in a comment block, hit this to re-wrap the contents to the
22 | [specified wrapping column](https://stkb.github.io/Rewrap/configuration/#wrapping-column).
23 |
24 |
25 | ## Features
26 |
27 | * Re-wrap comment blocks in many languages, with per-language settings.
28 | * Smart handling of contents, including Java-/JS-/XMLDoc tags and code examples.
29 | * Can select lines to wrap or multiple comments/paragraphs at once (even the whole
30 | document).
31 | * Also works with Markdown documents, LaTeX or any kind of plain text file.
32 |
33 | The contents of comments are usually parsed as markdown, so you can use lists, code
34 | samples (which are untouched) etc:
35 |
36 |
37 |
38 |
40 |
--------------------------------------------------------------------------------
/Rewrap.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.0.31912.275
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VS", "vs\VS.csproj", "{267305CC-3DBC-49F9-83ED-45F4F10E304D}"
6 | EndProject
7 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Core", "core\Core.fsproj", "{2AAAD6E7-324C-465F-83C3-A6B45C469C28}"
8 | EndProject
9 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Core.Test", "core\Core.Test.fsproj", "{24398B66-2AA9-4587-821E-0515E30B79B7}"
10 | EndProject
11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6898F3AE-ED21-4B44-A75F-80588ACF451D}"
12 | ProjectSection(SolutionItems) = preProject
13 | .editorconfig = .editorconfig
14 | EndProjectSection
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {267305CC-3DBC-49F9-83ED-45F4F10E304D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {267305CC-3DBC-49F9-83ED-45F4F10E304D}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {267305CC-3DBC-49F9-83ED-45F4F10E304D}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {267305CC-3DBC-49F9-83ED-45F4F10E304D}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {2AAAD6E7-324C-465F-83C3-A6B45C469C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {2AAAD6E7-324C-465F-83C3-A6B45C469C28}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {2AAAD6E7-324C-465F-83C3-A6B45C469C28}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {2AAAD6E7-324C-465F-83C3-A6B45C469C28}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {24398B66-2AA9-4587-821E-0515E30B79B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {24398B66-2AA9-4587-821E-0515E30B79B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {24398B66-2AA9-4587-821E-0515E30B79B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {24398B66-2AA9-4587-821E-0515E30B79B7}.Release|Any CPU.Build.0 = Release|Any CPU
34 | EndGlobalSection
35 | GlobalSection(SolutionProperties) = preSolution
36 | HideSolutionNode = FALSE
37 | EndGlobalSection
38 | GlobalSection(ExtensibilityGlobals) = postSolution
39 | SolutionGuid = {EC87FBD9-F636-4979-B1AF-566318E24C92}
40 | EndGlobalSection
41 | EndGlobal
42 |
--------------------------------------------------------------------------------
/core/Block.fs:
--------------------------------------------------------------------------------
1 | module Block
2 |
3 | open Prelude
4 | open Parsing_
5 |
6 |
7 | module Wrappable =
8 | let inline mapPrefixes f x = Tuple.mapFirst f x
9 | let inline mapLines f x = Tuple.mapSecond f x
10 | let inline fromLines prefixes lines = (prefixes, lines)
11 | let toLines ((pHead: string, pTail: string), lines) =
12 | lines |> Nonempty.mapHead ((+) pHead) |> Nonempty.mapTail ((+) pTail)
13 |
14 | /// Takes a function (string -> string), parser (Lines -> Blocks) and Prefixes &
15 | /// Lines tuple. Uses the parser on the lines to make blocks. For each of these
16 | /// blocks, prepends the prefix from Prefixes for the corresponding line. Where
17 | /// wrapping produces new lines in a block, uses the function to transform the
18 | /// prefix for the first line of that block to something for new lines.
19 | let splitUp : (string -> string) -> (Nonempty -> Blocks) -> (Nonempty * Nonempty) -> Blocks =
20 | let concatPrefixes (h1, t1) (h2, t2) = h1 + h2, t1 + t2
21 |
22 | let prependPrefixTrimEndOfBlankLine (p: string) (s: string) : string =
23 | if Line.isBlank s then p.TrimEnd() else p + s
24 |
25 | fun makeDefPrefix ->
26 | /// Takes the remaining list of prefixes and the block that needs prefixes
27 | /// prepending. Removes prefixes from the list equal to the current size of
28 | /// the block. Returns that list, plus the prefixes to prepend to the head &
29 | /// tail lines of the block.
30 | let takePrefixes : Nonempty -> Block -> (string * string * Nonempty) =
31 | fun prefixes block ->
32 | let (Nonempty(p1, pBlockRest)), maybePRest = Nonempty.splitAt (size block) prefixes
33 | let pRest = maybePRest |? (singleton (List.tryLast pBlockRest |? p1))
34 | p1, List.tryHead pBlockRest |? makeDefPrefix p1, pRest
35 |
36 | let prependPrefixes (prefixes, Nonempty(block, nextBlocks)) =
37 | let pre1, pre2, preNext = takePrefixes prefixes block
38 | let block' =
39 | match block with
40 | | Block.Comment _ -> // A comment in a comment (probably) won't happen :)
41 | block
42 | | Block.Wrap wrappable ->
43 | Block.Wrap (Wrappable.mapPrefixes (concatPrefixes (pre1, pre2)) wrappable)
44 | | Block.NoWrap ls ->
45 | ls
46 | |> Nonempty.mapHead (prependPrefixTrimEndOfBlankLine pre1)
47 | |> Nonempty.mapTail (prependPrefixTrimEndOfBlankLine pre2)
48 | |> Block.NoWrap
49 | | NBlock _ -> raise (System.Exception("splitUp on new block"))
50 | block', tuple preNext <<|> Nonempty.fromList nextBlocks
51 |
52 | fun parser (prefixes, lines) ->
53 | Nonempty.unfold prependPrefixes (prefixes, parser lines)
54 |
55 | /// Old version, for compatibility with old code
56 | let oldSplitUp : (Nonempty -> Blocks) -> Wrappable -> Blocks =
57 | fun parser ((pre1, pre2), lines) ->
58 | splitUp (always pre2) parser (Nonempty(pre1, [pre2]), lines)
59 |
60 |
61 | // Constructors
62 |
63 | let commentBlock : (Nonempty -> Blocks) -> Wrappable -> Block =
64 | fun parser wrappable -> Comment (oldSplitUp parser wrappable)
65 | let textBlock : Wrappable -> Block = Block.Wrap
66 | let ignoreBlock : Nonempty -> Block = Block.NoWrap
67 |
--------------------------------------------------------------------------------
/core/Columns.fs:
--------------------------------------------------------------------------------
1 | module internal Columns
2 |
3 | // Deals the [wrapping to rulers
4 | // feature](https://stkb.github.io/Rewrap/#/settings-vscode#wrapping-to-rulers).
5 | // The user can have multiple "rulers" (potential wrapping columns) set, either
6 | // with the `editor.rulers` setting in VSCode or the Guidelines extension in VS.
7 | //
8 | // When a user opens a document the wrapping column is set at the first ruler in
9 | // their settings. To change it to the next ruler, they have to do a wrap
10 | // (alt+q) twice in a row without doing anything inbetween.
11 | //
12 | // If the user doesn't have any rulers set, we always treat it as if they have
13 | // a list of one ruler (their set wrapping column).
14 |
15 | open Rewrap
16 | open Prelude
17 |
18 | // We store one DocState record for the state of the editor after every wrap
19 | // operation, and then look at it again before the next. If it hasn't changed,
20 | // it's counted as doing a wrap twice in a row, and we move the wrapping column
21 | // to the next ruler (if it exists).
22 | //
23 | // The DocState stores the current filepath, document version number and
24 | // selection position, to make sure the user hasn't switched file, made an
25 | // edit, or even moved the cursor, respectively.
26 | let mutable private lastDocState : DocState =
27 | { filePath = ""; version = 0; selections = [||] }
28 |
29 | /// We remember the last wrapping column used for each document.
30 | let private docWrappingColumns =
31 | new System.Collections.Generic.Dictionary()
32 |
33 |
34 | //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv PUBLIC MEMBERS vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv//
35 |
36 |
37 | // Gets the current wrapping column for the given document, given the supplied
38 | // list of valid wrapping columns (rulers). The current wrapping column for the
39 | // document is stored, but this is only returned if it's contained in the given
40 | // list of rulers.
41 | //
42 | // If it isn't, then the user has changed these since the last wrap, so we just
43 | // pick the first one in the list. The most likely scenario here is simply that
44 | // the user changed the wrapping column in the settings, so we definitely want
45 | // to respect the new setting.
46 | //
47 | // The list of rulers must not be empty.
48 | let getWrappingColumn filePath rulers =
49 | let setAndReturn column = docWrappingColumns.[filePath] <- column; column
50 | let firstRuler = Array.head rulers
51 | if not (docWrappingColumns.ContainsKey(filePath)) then
52 | setAndReturn firstRuler
53 | else
54 | Array.tryFind ((=) docWrappingColumns.[filePath]) rulers
55 | |> Option.defaultValue firstRuler
56 | |> setAndReturn
57 |
58 | // Takes a set of rulers and check if we already have a wrapping column for the
59 | // given document.
60 | // 1) If we don't yet have one, the first ruler is used and that value is saved.
61 | // 2) If we do already have a wrapping column and it exists in the given rulers,
62 | // that value is returned.
63 | // 3) If the value we have isn't found in the given rulers, then the rulers must
64 | // have changed since we last wrapped. Like 1) we save and return the first
65 | // ruler
66 | // The list of rulers must not be empty
67 | let maybeChangeWrappingColumn (docState: DocState) (rulers: int[]) : int =
68 | let filePath = docState.filePath
69 | if not (docWrappingColumns.ContainsKey(filePath)) then
70 | getWrappingColumn filePath rulers
71 | else
72 | let shiftRulerIfDocStateUnchanged i =
73 | if docState = lastDocState then (i + 1) % rulers.Length else i
74 | let rulerIndex =
75 | Array.tryFindIndex ((=) docWrappingColumns.[filePath]) rulers
76 | |> maybe 0 shiftRulerIfDocStateUnchanged
77 |
78 | docWrappingColumns.[filePath] <- rulers.[rulerIndex]
79 | docWrappingColumns.[filePath]
80 |
81 | /// Saves the DocState, to compare against next time.
82 | let saveDocState docState =
83 | lastDocState <- docState
84 |
--------------------------------------------------------------------------------
/core/Core.Test.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | ..\.obj\.net\test
7 | ..\.obj\.net\test\bin
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/core/Core.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | Rewrap.Core
6 | ..\.obj\.net\core
7 | ..\.obj\.net\core\bin
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/core/Main.fs:
--------------------------------------------------------------------------------
1 | module Rewrap.Core
2 |
3 | open System
4 | open Prelude
5 | open Line
6 | open Parsing_
7 | open Parsing.Language
8 | open Parsing.Documents
9 | open Columns
10 |
11 | // Re-exports from Columns
12 | let getWrappingColumn filePath rulers = getWrappingColumn filePath rulers
13 | let maybeChangeWrappingColumn docState rulers = maybeChangeWrappingColumn docState rulers
14 | let saveDocState docState = saveDocState docState
15 |
16 | /// Empty CustomMarkers object, for when no custom markers are supplied.
17 | let noCustomMarkers : CustomMarkers = {line = ""; block = ("","")}
18 |
19 | let languageNameForFile (file: File) : string =
20 | maybe null Language.name (languageForFile file)
21 |
22 | let languages : string[] =
23 | Seq.map Language.name Parsing.Documents.languages |> Seq.toArray
24 |
25 | /// The main rewrap function, to be called by clients
26 | let rewrap file settings selections (getLine: Func) =
27 | let processor = Parsing.Documents.select file
28 | let mkLine i =
29 | getLine.Invoke(i) |> Option.ofObj |> Option.map (fun l -> (l,i+1))
30 | let strLines = Seq.unfold mkLine 0 |> Nonempty.fromSeqUnsafe // Can we get rid of this?
31 | let lines = strLines |> Seq.map (fun s -> Line("", s))
32 | let ctx = Context(settings)
33 | processor ctx lines
34 | Selections.wrapSelected strLines selections ctx
35 |
36 |
37 | let strWidth tabSize str = Line.strWidth tabSize str
38 |
39 | /// The autowrap function, to be called by clients. Checks conditions and does
40 | /// an autowrap if all pass.
41 | ///
42 | /// The client must supply the new text that was inserted in the edit, as well
43 | /// as the position where it was inserted
44 | let maybeAutoWrap file settings newText (pos: Position) (getLine: Func) =
45 | let noEdit = Edit.empty
46 |
47 | if String.IsNullOrEmpty(newText) then noEdit
48 | // If column < 1 we never wrap
49 | elif settings.column < 1 then noEdit
50 | elif not (String.IsNullOrWhiteSpace(newText)) then noEdit else
51 |
52 | let enterPressed, indent =
53 | match newText.[0] with
54 | | '\r' -> true, newText.Substring(2)
55 | | '\n' -> true, newText.Substring(1)
56 | | _ -> false, ""
57 | if not enterPressed && newText.Length > 1 then noEdit else
58 |
59 | let line, char =
60 | pos.line, pos.character + (if enterPressed then 0 else newText.Length)
61 | let lineText = getLine.Invoke(line)
62 | let visualWidth = strWidth settings.tabWidth (String.takeStart char lineText)
63 | if visualWidth <= settings.column then noEdit else
64 |
65 | let fakeSelection = {
66 | anchor = { line = line; character = 0 }
67 | active = { line = line; character = lineText.Length }
68 | }
69 | // A getLine function that only gets lines up to & including where the
70 | // cursor is
71 | let wrappedGetLine =
72 | Func(fun i -> if i > line then null else getLine.Invoke(i))
73 | rewrap file settings ([|fakeSelection|]) wrappedGetLine
74 | |> fun edit ->
75 | let afterPos =
76 | if enterPressed then { line = line + 1; character = indent.Length }
77 | else { line = line; character = char }
78 | edit.withSelections [| { anchor=afterPos; active=afterPos} |]
79 |
--------------------------------------------------------------------------------
/core/Native.js:
--------------------------------------------------------------------------------
1 | import * as FS from 'fs'
2 |
3 | // Reads the contents of a file as an array of strings
4 | export const readFile = path => FS.readFileSync(path, {encoding: 'utf8'}).split(/\r?\n/)
5 |
6 | export const files = readSpecs(".")
7 |
8 | function readSpecs(dir) {
9 | if(FS.existsSync(dir + "/docs")) return readDir(dir + "/docs/specs")
10 | else return readSpecs(dir + "/..")
11 | }
12 |
13 | // Gets as an array the full paths of all *.md files in a dir and all subdirs
14 | function readDir (path) {
15 | const step = (acc, x) => {
16 | const fullName = path + "/" + x.name
17 | return x.isDirectory() ? [...acc, ...(readDir (fullName))]
18 | : fullName.endsWith(".md") ? [...acc, fullName]
19 | : acc
20 | }
21 | return FS.readdirSync(path, {withFileTypes: true}).reduce(step, [])
22 | }
23 |
--------------------------------------------------------------------------------
/core/Nonempty.fs:
--------------------------------------------------------------------------------
1 | module Nonempty
2 |
3 | open Prelude
4 |
5 | let toList : 'a Nonempty -> 'a List = fun (Nonempty (h, t)) -> h :: t
6 |
7 | // ================ Creating ================ //
8 |
9 | // Duplicate for now so I don't have to modify old code
10 | let inline singleton x = x .@ []
11 |
12 | let fromList : 'a List -> 'a Nonempty Option =
13 | function | [] -> None | x :: xs -> Some (x .@ xs)
14 |
15 | let fromSeqUnsafe : 'a seq -> 'a Nonempty =
16 | fun xs -> Seq.head xs .@ List.ofSeq (Seq.tail xs)
17 |
18 | let cons : 'a -> 'a Nonempty -> 'a Nonempty =
19 | fun h neList -> h .@ toList neList
20 |
21 | /// Appends an element to the end of the Nonempty list
22 | let snoc : 'a -> 'a Nonempty -> 'a Nonempty =
23 | fun last (Nonempty(h, t)) -> h .@ t @ [last]
24 |
25 | let append : 'a Nonempty -> 'a Nonempty -> 'a Nonempty =
26 | fun (Nonempty(h, t)) b -> h .@ t @ toList b
27 |
28 | let appendToList listA neListB =
29 | match fromList listA with
30 | | Some neListA -> append neListA neListB
31 | | None -> neListB
32 |
33 | // ================ Getting elements or other ================ //
34 |
35 | let head : 'a Nonempty -> 'a = fun (Nonempty (h, _)) -> h
36 | let tail : 'a Nonempty -> 'a List = fun (Nonempty (_, t)) -> t
37 | let last : 'a Nonempty -> 'a =
38 | fun (Nonempty (h, t)) -> fromMaybe h (List.tryLast t)
39 |
40 | let tryFind : ('a -> bool) -> 'a Nonempty -> 'a Option =
41 | fun predicate -> toList >> List.tryFind predicate
42 |
43 |
44 | // ================ Transforming ================ //
45 |
46 | let rev : 'a Nonempty -> 'a Nonempty =
47 | fun list -> list |> toList |> List.rev |> fromSeqUnsafe
48 |
49 | let mapHead : ('a -> 'a) -> 'a Nonempty -> 'a Nonempty =
50 | fun fn (Nonempty (h, t)) -> fn h .@ t
51 |
52 | let mapTail : ('a -> 'a) -> 'a Nonempty -> 'a Nonempty =
53 | fun fn (Nonempty (h, t)) -> h .@ List.map fn t
54 |
55 | let mapInit : ('a -> 'a) -> 'a Nonempty -> 'a Nonempty =
56 | fun fn -> rev >> mapTail fn >> rev
57 |
58 | let mapLast : ('a -> 'a) -> 'a Nonempty -> 'a Nonempty =
59 | fun fn -> rev >> mapHead fn >> rev
60 |
61 | let mapFold : ('s -> 'a -> 'b * 's) -> 's -> 'a Nonempty -> 'b Nonempty * 's =
62 | fun fn s (Nonempty (h,t)) ->
63 | let h', s' = fn s h in List.mapFold fn s' t |> Tuple.mapFirst ((.@) h')
64 |
65 | let replaceHead : 'a -> 'a Nonempty -> 'a Nonempty =
66 | fun h -> mapHead (fun _ -> h)
67 |
68 | let concatMap : ('a -> 'b Nonempty) -> 'a Nonempty -> 'b Nonempty =
69 | fun fn neList ->
70 | let rec loop output = function
71 | | [] -> output | x :: xs -> loop (append (fn x) output) xs
72 | rev neList |> (fun (Nonempty(head, tail)) -> loop (fn head) tail)
73 |
74 | /// Splits the list at the given position. If n is less than 1 then n = 1
75 | let splitAt : int -> 'a Nonempty -> ('a Nonempty * 'a Nonempty Option) =
76 | fun n (Nonempty(head, tail)) ->
77 | let rec loop count leftAcc maybeRightAcc =
78 | match maybeRightAcc with
79 | | None -> leftAcc, None
80 | | Some (Nonempty(x, xs)) ->
81 | if count < 1 then leftAcc, maybeRightAcc
82 | else loop (count - 1) (cons x leftAcc) (fromList xs)
83 | loop (n - 1) (singleton head) (fromList tail) |> Tuple.mapFirst rev
84 |
85 | /// Takes a predicate and a list, and Optionally returns a Tuple, of the longest
86 | /// prefix of the list for which the predicate holds, and the rest of the list.
87 | /// If that prefix is empty, returns None.
88 | let span : ('a -> bool) -> 'a Nonempty -> ('a Nonempty * 'a Nonempty Option) Option =
89 | fun predicate ->
90 | let rec loop output maybeRemaining =
91 | match maybeRemaining with
92 | | Some (Nonempty(h, t)) when predicate h -> loop (h :: output) (fromList t)
93 | | _ -> fromList (List.rev output) |> map (fun o -> o, maybeRemaining)
94 | Some >> loop []
95 |
96 | /// Like span, but instead of a function that returns a bool, uses one that
97 | /// returns an Option.
98 | let spanMaybes : ('a -> 'b Option) -> 'a Nonempty -> ('b Nonempty * 'a Nonempty Option) Option =
99 | fun fn ->
100 | let rec loop output maybeRemaining =
101 | let inline finish () = fromList (List.rev output) |> map (fun o -> o, maybeRemaining)
102 | match maybeRemaining with
103 | | Some (Nonempty(h, t)) ->
104 | match fn h with Some x -> loop (x :: output) (fromList t) | None -> finish ()
105 | | _ -> finish ()
106 | Some >> loop []
107 |
108 | /// Splits after the first element where the predicate evaluates true
109 | let splitAfter : ('a -> bool) -> 'a Nonempty -> 'a Nonempty * 'a Nonempty Option =
110 | fun predicate ->
111 | let rec loop output (Nonempty(h, t)) =
112 | match fromList t with
113 | | Some nextList when not (predicate h) -> loop (h :: output) nextList
114 | | x -> h .@ output, x
115 | loop [] >> Tuple.mapFirst rev
116 |
117 | let unfold : ('b -> 'a * 'b Option) -> 'b -> 'a Nonempty =
118 | fun fn ->
119 | let rec loop output input =
120 | match fn input with
121 | | (res, Some nextInput) -> loop (res :: output) nextInput
122 | | (res, None) -> Nonempty(res, output)
123 | loop [] >> rev
124 |
--------------------------------------------------------------------------------
/core/Parsers.fs:
--------------------------------------------------------------------------------
1 | module internal Parsers
2 |
3 | open Prelude
4 | open Parsing_
5 | open Parsing_Internal
6 |
7 | let ignoreAll : ContentParser =
8 | let rec parseLine line = pending line noWrapBlock (ThisLine << parseLine)
9 | fun _ctx -> parseLine
10 |
11 | let markdown ctx = Parsers_Markdown.markdown ctx
12 |
13 | let rst ctx = Parsers_RST.rst ctx
14 |
--------------------------------------------------------------------------------
/core/Parsing.Comments.fs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stkb/Rewrap/6a27098e20baf95759c621a5cc1cd9b3fedbd9ed/core/Parsing.Comments.fs
--------------------------------------------------------------------------------
/core/Parsing.DocComments.fs:
--------------------------------------------------------------------------------
1 | module internal Parsing.DocComments
2 | // This module deals with special formatting inside comments that are used for
3 | // documentation, eg javadoc, xmldoc, RDoc
4 |
5 | open Prelude
6 | open Rewrap
7 | open Block
8 | open Parsers
9 | open Parsing_
10 | open Parsing.Core
11 | open Sgml
12 | open System.Text.RegularExpressions
13 |
14 | type private Lines = Nonempty
15 |
16 | let private markdown = Parsing.Markdown.markdown
17 |
18 | /// Splits lines into sections which start with lines matching the given regex.
19 | /// For each of those sections, the matchParser is applied to turn the lines
20 | /// into blocks. If the first section doesn't start with a matching line, it is
21 | /// processed with the noMatchParser.
22 | let private splitBeforeTags
23 | : Regex -> (Match -> Settings -> string TotalParser) -> (Settings -> string TotalParser)
24 | -> Settings -> Lines -> Blocks =
25 | fun regex matchParser noMatchParser settings (Nonempty(outerHead, outerTail)) ->
26 |
27 | let rec prependRev (Nonempty(head, tail)) maybeRest =
28 | let nextRest = maybeRest |> maybe (singleton head) (Nonempty.cons head)
29 | Nonempty.fromList tail |> maybe nextRest (fun next -> prependRev next (Some nextRest))
30 |
31 | let rec loop (tagMatch: Match) buffer maybeOutput lines =
32 | let parser = if tagMatch.Success then matchParser tagMatch else noMatchParser
33 | let addBufferToOutput () = prependRev (parser settings (Nonempty.rev buffer)) maybeOutput
34 | match lines with
35 | | [] -> (addBufferToOutput ()) |> Nonempty.rev
36 | | headLine :: tailLines ->
37 | let m = regex.Match(headLine)
38 | let nextTagMatch, nextBuffer, nextOutput =
39 | if m.Success then m, singleton headLine, Some (addBufferToOutput ())
40 | else tagMatch, Nonempty.cons headLine buffer, maybeOutput
41 | loop nextTagMatch nextBuffer nextOutput tailLines
42 |
43 | loop (regex.Match(outerHead)) (Nonempty.singleton outerHead) None outerTail
44 |
45 | let javadoc =
46 | let tagRegex = Regex(@"^\s*@(\w+)(.*)$")
47 |
48 | /// "Freezes" inline tags ({@tag }) so that they don't get broken up
49 | let inlineTagRegex = Regex(@"{@[a-z]+.*?[^\\]}", RegexOptions.IgnoreCase)
50 | let markdownWithInlineTags settings =
51 | let replaceSpace (m: Match) = m.Value.Replace(' ', '\000')
52 | map (fun s -> inlineTagRegex.Replace(s, replaceSpace)) >> markdown settings
53 |
54 | let matchParser (m: Match) =
55 | if Line.isBlank (m.Groups.Item(2).Value) then
56 | if m.Groups.Item(1).Value.ToLower() = "example" then
57 | (fun _ -> ignoreBlock >> singleton)
58 | else ignoreFirstLine markdownWithInlineTags
59 | else markdownWithInlineTags
60 |
61 | splitBeforeTags tagRegex matchParser markdownWithInlineTags
62 |
63 |
64 | /// DartDoc has just a few special tags. We keep lines beginning with these
65 | /// unwrapped.
66 | let dartdoc =
67 | let tagRegex = Regex(@"^\s*(@nodoc|{@template|{@endtemplate|{@macro)")
68 | splitBeforeTags tagRegex (fun _ -> ignoreFirstLine markdown) markdown
69 |
70 |
71 | let psdoc =
72 | let tagRegex = Regex(@"^\s*\.([A-Z]+)")
73 | let codeLineRegex = Regex(@"^\s*PS C:\\>")
74 |
75 | let exampleSection settings lines =
76 | let trimmedExampleSection =
77 | ignoreFirstLine (splitBeforeTags codeLineRegex (fun _ -> ignoreFirstLine markdown) markdown)
78 | match Nonempty.span Line.isBlank lines with
79 | | Some (blankLines, None) -> Nonempty.singleton (ignoreBlock blankLines)
80 | | Some (blankLines, Some remaining) ->
81 | Nonempty.cons (ignoreBlock blankLines) (trimmedExampleSection settings remaining)
82 | | None -> trimmedExampleSection settings lines
83 |
84 | splitBeforeTags tagRegex
85 | (fun m ->
86 | if m.Groups.Item(1).Value = "EXAMPLE" then ignoreFirstLine exampleSection else
87 | ignoreFirstLine
88 | (fun settings ->
89 | Comments.extractWrappable "" false (fun _ -> " ") settings
90 | >> Block.oldSplitUp (markdown settings)
91 | )
92 | )
93 | markdown
94 |
95 |
96 | /// DDoc for D. Stub until it's implemented. https://dlang.org/spec/ddoc.html
97 | let ddoc =
98 | markdown
99 |
100 |
101 | /// Godoc comments use a godoc compatible parser instead of markdown.
102 | /// Here, any lines that are indented more than the "standard" aren't wrapped.
103 | ///
104 | /// https://blog.golang.org/godoc-documenting-go-code
105 | let godoc settings =
106 | let indentedLines =
107 | ignoreParser (Nonempty.span (fun line -> line.[0] = ' ' || line.[0] = '\t'))
108 | let textLines =
109 | splitIntoChunks (afterRegex (Regex(" $"))) >> map (Wrap << Wrappable.fromLines ("", ""))
110 |
111 | textLines
112 | |> takeUntil (tryMany [blankLines; indentedLines])
113 | |> repeatToEnd
114 |
115 | let xmldoc =
116 | let blank = docOf ignoreAll
117 | let blockTags =
118 | [| "code"; "description"; "example"; "exception"; "include"
119 | "inheritdoc"; "list"; "listheader"; "item"; "para"; "param"
120 | "permission"; "remarks"; "seealso"; "summary"; "term"; "typeparam"
121 | "typeparamref"; "returns"; "value"
122 | |]
123 | sgml blank blank blockTags
124 |
--------------------------------------------------------------------------------
/core/Parsing.Language.fs:
--------------------------------------------------------------------------------
1 | module internal Parsing.Language
2 |
3 | open System
4 | open Parsing_
5 |
6 | type Language =
7 | private Language of string * array * array * DocumentProcessor
8 |
9 | module Language =
10 |
11 | // Takes 4 args to create a Language:
12 | // 1. display name (used only in VS)
13 | // 2. string of aliases (language IDs used by the client. Not needed if
14 | // they only differ from display name by casing)
15 | // 3. string of file extensions (including `.`). Used to give support to
16 | // files that are not known by the client.
17 | // 4. document processor
18 | //
19 | // Aliases and extensions are separated by `|`
20 | let create (name: string) (aliases: string) (exts: string) parser : Language =
21 | let split (s: string) = s.ToLower().Split([|'|'|], StringSplitOptions.RemoveEmptyEntries)
22 | Language(name, Array.append [|name.ToLower()|] (split aliases), split exts, parser)
23 |
24 | let name (Language(n,_,_,_)) = n
25 |
26 | let parser (Language(_,_,_,p)) = p
27 |
28 | let matchesFileLanguage (fileLang: string) (Language(_,ids,_,_)) =
29 | Seq.contains (fileLang.ToLower()) ids
30 |
31 | let matchesFilePath (path: string) (Language(_,_,exts,_)) =
32 | let fileName = path.ToLower().Split('\\', '/') |> Array.last
33 | let tryMatch : string -> bool = function
34 | | ext when ext.StartsWith(".") -> fileName.EndsWith(ext)
35 | | fullname -> fileName.Equals(fullname)
36 |
37 | Seq.exists tryMatch exts
38 |
--------------------------------------------------------------------------------
/core/Parsing.Sgml.fs:
--------------------------------------------------------------------------------
1 | module internal Parsing.Sgml
2 |
3 | open Prelude
4 | open Line
5 | open Parsing_
6 | open Block
7 | open Rewrap
8 | open Parsing.Core
9 | open System.Text.RegularExpressions
10 |
11 | let private markdown = Parsing.Markdown.markdown
12 |
13 | let inline private regex str =
14 | Regex(str, RegexOptions.IgnoreCase)
15 |
16 | let private scriptMarkers =
17 | (regex " ¦ ¦
49 |
50 | ¦ ¦
58 |
59 | ¦ -> ¦
60 | // one two three // one two ¦
61 | // four ¦ three // four¦
62 | ¦ ¦
63 |
--------------------------------------------------------------------------------
/docs/specs/content-types/julia.md:
--------------------------------------------------------------------------------
1 | > language: "julia"
2 |
3 | Julia is like Python with slight differences. There are only triple double-quote
4 | strings (no triple single-quote). These can effectively be preceeded by any
5 | user-defined string
6 |
7 | r"""¦ -> r"""¦
8 | a b c a b ¦
9 | d ¦ c d ¦
10 | """ ¦ """ ¦
11 |
12 | doc"""¦ -> doc"""
13 | a b c d a b c ¦
14 | e ¦ d e ¦
15 | """ ¦ """ ¦
16 |
17 | This can also be preceeded by the `@doc` macro
18 |
19 | @doc raw"""¦ -> @doc raw"""
20 | a b c d e f g a b c d e f
21 | h i ¦ g h i ¦
22 | """ ¦ """ ¦
23 |
24 |
25 | It also has `#` line comments and `#= =#` block comments.
26 |
27 | # a b c -> # a b ¦
28 | # d ¦ # c d ¦
29 |
30 | #= ¦ #= ¦
31 | a b c -> a b ¦
32 | d ¦ c d ¦
33 | =# ¦ =# ¦
34 |
--------------------------------------------------------------------------------
/docs/specs/content-types/lean.md:
--------------------------------------------------------------------------------
1 | > language: "lean"
2 |
3 | -- a b c -> -- a b ¦
4 | -- d ¦ -- c d ¦
5 | x x x x x x x x x x
6 | /- a b c /- a b ¦
7 | d ¦ c d ¦
8 | -/ ¦ -/ ¦
9 | x x x x x x x x x x
10 |
--------------------------------------------------------------------------------
/docs/specs/content-types/lua.md:
--------------------------------------------------------------------------------
1 | > language: "lua"
2 |
3 | Line comment
4 |
5 | --a b c -> --a b ¦
6 | --d ¦ --c d ¦
7 | z y x w z y x w
8 | v u ¦ v u ¦
9 |
10 | Block comment
11 |
12 | --[[ --[[
13 | a b c -> a b ¦
14 | d ¦ c d ¦
15 | ]]-- ¦ ]]-- ¦
16 | z y x w z y x w
17 | v u ¦ v u ¦
18 |
19 |
20 | Block comment markers can have any number or '='s in them as long as they match
21 |
22 | --[=[ --[=[
23 | a b c -> a b ¦
24 | d ¦ c d ¦
25 | ]=]-- ¦ ]=]-- ¦
26 | z y x w z y x w
27 | v u ¦ v u ¦
28 |
29 | --[=====[ --[=====[
30 | a b c -> a b ¦
31 | d ¦ c d ¦
32 | ]=====]-- ]=====]--
33 | z y x w z y x w
34 | v u ¦ v u ¦
35 |
--------------------------------------------------------------------------------
/docs/specs/content-types/markdown/blockquotes.md:
--------------------------------------------------------------------------------
1 | > language: "markdown"
2 |
3 | Issue 204
4 |
5 | > a ¦ > a ¦
6 | > ¦ -> > ¦
7 | > a b c > a b¦
8 | > d ¦ > c d¦
9 |
10 | Content can have varying prefixes. With reformat off this needs to be preserved.
11 |
12 | > a ¦ > a ¦
13 | > ¦ -> > ¦
14 | >a b c >a b ¦
15 | >d ¦ >c d ¦
16 |
17 | > a ¦ > a ¦
18 | > ¦ -> > ¦
19 | >a b c >a b ¦
20 | >d ¦ >c d ¦
21 | > ¦ > ¦
22 | > a ¦ > a ¦
23 |
24 | > a ¦ > a ¦
25 | > ¦ -> > ¦
26 | >>a b c >>a b¦
27 | d ¦ c d ¦
28 | > ¦ > ¦
29 | >>> a¦ >>> a¦
30 |
31 | > a ¦ > a ¦
32 | >>> a b -> >>> a¦
33 | >>> ¦ >>> b¦
34 | >>> ¦
35 |
36 | If the input is only 1 line, then created lines are given the same prefix as the
37 | first
38 |
39 | > a b c d e f -> > a b c ¦
40 | ¦ > d e f ¦
41 |
42 | >a b c d e f -> >a b c ¦
43 | ¦ >d e f ¦
44 |
45 | > a b c d e f -> > a b c ¦
46 | ¦ > d e f ¦
47 |
48 | If the a line doesn't start with the `>` marker, then the blockquote section has
49 | terminated, unless the line is a paragraph continuation line.
50 |
51 | >··``` ¦ >··``` ¦
52 | ···foo ¦ -> ···foo ¦
53 | ··bar ¦ ··bar baz¦
54 | ··baz ¦ ¦
55 |
56 | The first (optional) space after the `>` marker is treated as part of the
57 | blockquote marker, so an indented code block has to be indented 5 spaces.
58 |
59 | >·····code block >·····code block
60 | >····text ¦ -> >····text text¦
61 | >····text ¦ ¦
62 |
63 | > one·· ¦ > one·· ¦
64 | two ¦ -> two three¦
65 | > three four > four
66 |
--------------------------------------------------------------------------------
/docs/specs/content-types/markdown/comments.md:
--------------------------------------------------------------------------------
1 | # Markdown: Comments
2 |
3 | > language: markdown
4 |
5 | Comments are detected but wrapping their contents is not yet supported.
6 |
7 | ¦ ¦
8 | ¦ -> --> ¦
12 | ¦ ¦
13 | new ¦ new paragraph ¦
14 | paragraph ¦ ¦
15 |
--------------------------------------------------------------------------------
/docs/specs/content-types/markdown/fenced-code-blocks.md:
--------------------------------------------------------------------------------
1 | > language: "markdown"
2 |
3 | A fenced code block begins with at least 3 backticks or at least 3 tildes.
4 |
5 | ``` ¦ -> ``` ¦
6 | code ¦ code ¦
7 | block ¦ block ¦
8 |
9 | ~~~ ¦ -> ~~~ ¦
10 | code ¦ code ¦
11 | block ¦ block ¦
12 |
13 | Fewer than 3 doesn't start a code block.
14 |
15 | `` ¦ -> `` no code¦
16 | no ¦ block ¦
17 | code block¦ ¦
18 |
19 | ~~ ¦ -> ~~ no code¦
20 | no ¦ block ¦
21 | code block¦ ¦
22 |
23 | As with other markdown they may be indented up to inc 3 spaces.
24 |
25 | ···~~~ ¦ -> ···~~~ ¦
26 | code code ¦
27 | block block ¦
28 |
29 | 4 or more and it doesn't count.
30 |
31 | ····~~~ ¦ -> ····~~~ ¦
32 | Not in a code block Not in a ¦
33 | code block¦
34 |
35 | If the opening code fence is tildes, any characters may follow it.
36 |
37 | ~~~a bc`~`~ ~~~a bc`~`~
38 | code ¦ code ¦
39 | block ¦ -> block ¦
40 | ~~~ ¦ ~~~ ¦
41 | one ¦ one two ¦
42 | two ¦ ¦
43 |
44 | If the opening code fence is backticks, any characters except backticks may
45 | follow it.
46 |
47 | ```a bc~~~~ ```a bc~~~~
48 | code ¦ code ¦
49 | block ¦ -> block ¦
50 | ``` ¦ ``` ¦
51 | text ¦ text text ¦
52 | text ¦ ¦
53 |
54 | If there are backticks following, then the text between is treated as inline code
55 | instead.
56 |
57 | ```a bc``¦ ```a bc``
58 | not in ¦ -> not in a ¦
59 | a code ¦ code ¦
60 | block ¦ block ¦
61 |
62 | The closing fence must use the same character as the opening fence.
63 |
64 | ``` ¦ ``` ¦
65 | code ¦ code ¦
66 | block ¦ block ¦
67 | ~~~ ¦ ~~~ ¦
68 | still ¦ -> still ¦
69 | in code block in code block
70 | ``` ¦ ``` ¦
71 | outside code outside ¦
72 | block ¦ code block¦
73 |
74 | ~~~ ¦ ~~~ ¦
75 | code ¦ code ¦
76 | block ¦ block ¦
77 | ``` ¦ ``` ¦
78 | still ¦ -> still ¦
79 | in code block in code block
80 | ~~~ ¦ ~~~ ¦
81 | outside code outside ¦
82 | block ¦ code block¦
83 |
84 | If the closing fence is indented more than 3 spaces (relative to the container
85 | not the opening fence), it doesn't count.
86 |
87 | ··``` ¦ ··``` ¦
88 | code ¦ code ¦
89 | block ¦ block ¦
90 | ····``` ¦ ····``` ¦
91 | still ¦ -> still ¦
92 | in code block in code block
93 | ``` ¦ ``` ¦
94 | outside code outside ¦
95 | block ¦ code block¦
96 |
97 | If the opening fence is 4 or more characters, the closing fence must be as least
98 | as long.
99 |
100 | ~~~~ ¦ ~~~~ ¦
101 | code ¦ code ¦
102 | block ¦ block ¦
103 | ~~~ ¦ ~~~ ¦
104 | still ¦ -> still ¦
105 | in code block in code block
106 | ~~~~~ ¦ ~~~~~ ¦
107 | outside code outside ¦
108 | block ¦ code block¦
109 |
110 | If there is anything other than whitespace on the line after the closing fence,
111 | it doesn't count.
112 |
113 | ``` ¦ ``` ¦
114 | code ¦ code ¦
115 | block ¦ block ¦
116 | ``` abc ¦ ``` abc ¦
117 | still ¦ -> still ¦
118 | in code block in code block
119 | ``` ¦ ``` ¦
120 | outside code outside ¦
121 | block ¦ code block¦
122 |
--------------------------------------------------------------------------------
/docs/specs/content-types/markdown/footnotes.md:
--------------------------------------------------------------------------------
1 | > language: "markdown"
2 |
3 | Footnotes are not to be confused with [link reference definitions](linkrefdefs.md).
4 |
5 | Footnotes are not in the CommonMark spec, but are supported by a variety of other flavors
6 | of markdown, including GFM, (PHP) Markdown Extra, MultiMarkdown and Pandoc. These all have
7 | slight differences, so until Rewrap offers support for multiple markdown specs, it tries
8 | to cover everything as best it can.
9 |
10 | A footnote is a section beginning with `[^