Skip to content

Commit

Permalink
code-action: TOC improvements (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
keynmol authored Aug 25, 2022
1 parent 757bc4b commit b441131
Show file tree
Hide file tree
Showing 11 changed files with 458 additions and 70 deletions.
81 changes: 81 additions & 0 deletions Marksman/CodeActions.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module Marksman.CodeActions

open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.Logging

open FSharpPlus.GenericBuilders
open Marksman.Toc
open Marksman.Workspace
open Marksman.Text
open Marksman.Misc

open type System.Environment

let private logger = LogProvider.getLoggerByName "CodeActions"

type DocumentAction = { name: string; newText: string; edit: Range }

let documentEdit range text documentUri : WorkspaceEdit =
let textEdit = { NewText = text; Range = range }

let workspaceChanges = Map.ofList [ documentUri, [| textEdit |] ]

{ Changes = Some workspaceChanges; DocumentChanges = None }

let tableOfContents (document: Doc) : DocumentAction option =
match TableOfContents.mk document.index with
| Some (toc) ->
let rendered = TableOfContents.render toc
let existing = TableOfContents.detect document.text

let name =
match existing with
| None -> "Create a Table of Contents"
| _ -> "Update the Table of Contents"

let insertionPoint =
match existing with
| Some (range) -> Replacing range
| None -> TableOfContents.insertionPoint document

logger.trace (
Log.setMessage ("Determining table of contents insertion point")
>> Log.addContext "insertionPoint" insertionPoint
>> Log.addContext "existing" existing
>> Log.addContext "text" rendered
)

let isEmpty lineNumber = document.text.LineContent(lineNumber).Trim().Length.Equals(0)

let emptyLine = NewLine + NewLine
let lineBreak = NewLine

let (editRange, newLinesBefore, newLinesAfter) =
match insertionPoint with
| DocumentBeginning ->
let after =
if isEmpty Text.documentBeginning.Start.Line then "" else emptyLine

Text.documentBeginning, "", after

| Replacing range ->
let before = if isEmpty (range.Start.Line - 1) then "" else emptyLine
let after = if isEmpty (range.End.Line + 1) then "" else emptyLine

range, before, after

| After range ->
let lineAfterLast = range.End.Line + 1
let newRange = Range.Mk(lineAfterLast, 0, lineAfterLast, 0)

let before = if isEmpty range.End.Line then "" else lineBreak
let after = if isEmpty (lineAfterLast) then lineBreak else emptyLine

newRange, before, after


let text = $"{newLinesBefore}{rendered}{newLinesAfter}"

Some { name = name; newText = text; edit = editRange }

| _ -> None
9 changes: 8 additions & 1 deletion Marksman/Cst.fs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ type Element =
| WL of Node<WikiLink>
| ML of Node<MdLink>
| MLD of Node<MdLinkDef>
| YML of TextNode

and Heading =
{ level: int
Expand All @@ -163,6 +164,7 @@ let rec private fmtElement =
| WL x -> fmtWikiLink x
| ML l -> fmtMdLink l
| MLD r -> fmtMdLinkDef r
| YML y -> Node.fmtText y

and private fmtHeading node =
let inner = node.data
Expand Down Expand Up @@ -216,13 +218,15 @@ module Element =
| WL n -> n.range
| ML n -> n.range
| MLD n -> n.range
| YML n -> n.range

let text =
function
| H n -> n.text
| WL n -> n.text
| ML n -> n.text
| MLD n -> n.text
| YML n -> n.text

let asHeading =
function
Expand All @@ -246,6 +250,7 @@ module Element =
function
| WL _
| ML _ -> false
| YML _
| H _
| MLD _ -> true

Expand All @@ -254,7 +259,8 @@ module Element =
| WL _
| ML _ -> true
| H _
| MLD _ -> false
| MLD _
| YML _ -> false

let isTitle el =
asHeading el |>> Node.data |>> Heading.isTitle
Expand All @@ -271,6 +277,7 @@ module Cst =

match el with
| H h -> yield! collect h.data.children
| YML _
| WL _
| ML _
| MLD _ -> ()
Expand Down
8 changes: 6 additions & 2 deletions Marksman/Index.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ type Index =
headingsBySlug: Map<Slug, list<Node<Heading>>>
wikiLinks: array<Node<WikiLink>>
mdLinks: array<Node<MdLink>>
linkDefs: array<Node<MdLinkDef>> }
linkDefs: array<Node<MdLinkDef>>
yamlFrontMatter: option<TextNode> }

module Index =
let ofCst (cst: Cst) : Index =
Expand All @@ -22,6 +23,7 @@ module Index =
let headings = ResizeArray()
let mdLinks = ResizeArray()
let linkDefs = ResizeArray()
let mutable yaml = None

for el in Cst.elementsAll cst do
match el with
Expand All @@ -40,6 +42,7 @@ module Index =
| WL wl -> wikiLinks.Add(wl)
| ML ml -> mdLinks.Add(ml)
| MLD linkDef -> linkDefs.Add(linkDef)
| YML yml -> yaml <- Some yml

let headingsBySlug =
seq {
Expand All @@ -59,7 +62,8 @@ module Index =
headingsBySlug = headingsBySlug
wikiLinks = wikiLinks
mdLinks = mdLinks
linkDefs = linkDefs }
linkDefs = linkDefs
yamlFrontMatter = yaml }

let titles index = index.titles

Expand Down
1 change: 1 addition & 0 deletions Marksman/Marksman.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<Compile Include="Diag.fs"/>
<Compile Include="State.fs"/>
<Compile Include="Toc.fs"/>
<Compile Include="CodeActions.fs"/>
<Compile Include="Refs.fs"/>
<Compile Include="Compl.fs"/>
<Compile Include="Refactor.fs"/>
Expand Down
15 changes: 14 additions & 1 deletion Marksman/Parser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module Markdown =
open Markdig.Syntax.Inlines
open Markdig.Parsers
open Markdig.Helpers
open Markdig.Extensions.Yaml

type WikiLinkInline
(
Expand Down Expand Up @@ -128,7 +129,10 @@ module Markdown =
false

let markdigPipeline =
let pipelineBuilder = MarkdownPipelineBuilder().UsePreciseSourceLocation()
let pipelineBuilder =
MarkdownPipelineBuilder()
.UsePreciseSourceLocation()
.UseYamlFrontMatter()

pipelineBuilder.InlineParsers.Insert(0, MarkdigPatches.PatchedLinkInlineParser())
pipelineBuilder.InlineParsers.Insert(0, WikiLinkParser())
Expand All @@ -154,6 +158,14 @@ module Markdown =

for b in parsed.Descendants() do
match b with
| :? YamlFrontMatterBlock as y ->
let fullText = text.content.Substring(y.Span.Start, y.Span.Length)
let range = sourceSpanToRange text y.Span

let node: TextNode = Node.mkText fullText range

elements.Add(YML node)

| :? HeadingBlock as h ->
let level = h.Level

Expand Down Expand Up @@ -333,6 +345,7 @@ let rec private reconstructHierarchy (text: Text) (flat: seq<Element>) : seq<Ele

for el in flat do
match el with
| YML _
| WL _
| ML _
| MLD _ ->
Expand Down
1 change: 1 addition & 0 deletions Marksman/Refs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ module Uref =
| MdLink.RC label
| MdLink.RF (_, label) -> Some(Uref.LinkDef label)
| H _
| YML _
| MLD _ -> None

let hasExplicitDoc =
Expand Down
47 changes: 12 additions & 35 deletions Marksman/Server.fs
Original file line number Diff line number Diff line change
Expand Up @@ -802,28 +802,6 @@ type MarksmanServer(client: MarksmanClient) =

let documentBeginning = Range.Mk(0, 0, 0, 0)

let editAt rangeOpt text =
let textEdit =
{ NewText = text
Range = Option.defaultValue documentBeginning rangeOpt }

let mp = Map.ofList [ opts.TextDocument.Uri, [| textEdit |] ]

{ Changes = Some mp; DocumentChanges = None }

let toc =
monad' {
let! document = State.tryFindDocument docPath state
let! toc = TableOfContents.mk document.index

let rendered = TableOfContents.render toc
let detected = TableOfContents.detect document.text

let result = ($"{rendered}", detected)

result
}

let codeAction title edit =
{ Title = title
Kind = Some CodeActionKind.Source
Expand All @@ -834,19 +812,18 @@ type MarksmanServer(client: MarksmanClient) =
Disabled = None
Edit = Some edit }

let codeActions =
match toc with
| None -> Array.empty
| Some (render, existing) ->
match existing with
| None ->
[| U2.Second(codeAction "Create a Table of Contents" (editAt None render)) |]
| Some oldTocRange ->
[| U2.Second(
codeAction
"Update the Table of Contents"
(editAt (Some oldTocRange) render)
) |]
let tocAction =
State.tryFindDocument docPath state
|> Option.bind CodeActions.tableOfContents
|> Option.toArray
|> Array.map (fun ca ->
let wsEdit =
(CodeActions.documentEdit ca.edit ca.newText opts.TextDocument.Uri)

codeAction ca.name wsEdit)

let codeActions: TextDocumentCodeActionResult =
tocAction |> Array.map U2.Second

Mutation.output (LspResult.success (Some codeActions))

Expand Down
13 changes: 12 additions & 1 deletion Marksman/Text.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ open Ionide.LanguageServerProtocol.Types
open Misc

type LineRange = int * int
type CharRange = int * int

type LineMap =
| LineMap of array<LineRange>
Expand Down Expand Up @@ -61,7 +62,7 @@ type LineMap =
let end_ = this.TryFindOffset range.End
start, end_

member this.FindRange(range: Range) : LineRange =
member this.FindRange(range: Range) : CharRange =
match this.TryFindRange range with
| Some s, Some e -> s, e
| None, _ -> failwith $"Range start outside of line map: {range}"
Expand All @@ -75,6 +76,14 @@ type Text =
let s, e = this.lineMap.FindRange range
this.content.Substring(s, e - s)

member this.Cutout(range: Range) : string * string =
let s, e = this.lineMap.FindRange range

let before = this.content.Substring(0, s)
let after = this.content.Substring(e, this.content.Length - e)

(before, after)

member this.CharAt(p: Position) : char =
let off = this.lineMap.FindOffset(p)
this.content[off]
Expand Down Expand Up @@ -158,6 +167,8 @@ let mkLineMap (str: string) : LineMap =

LineMap(lineMap.ToArray())

let documentBeginning = Range.Mk(0, 0, 0, 0)

let mkText (content: string) : Text =
let lineMap = mkLineMap content
{ content = content; lineMap = lineMap }
Expand Down
Loading

0 comments on commit b441131

Please sign in to comment.