-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathCodeActions.fs
More file actions
164 lines (121 loc) · 5.17 KB
/
CodeActions.fs
File metadata and controls
164 lines (121 loc) · 5.17 KB
1
2
3
4
5
6
7
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
module Marksman.CodeActions
open type System.Environment
open FSharpPlus
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.Logging
open Marksman.Misc
open Marksman.Paths
open Marksman.Names
open Marksman.Doc
open Marksman.Index
open Marksman.Folder
open Marksman.Refs
open Marksman.Toc
open Marksman.Structure
open Marksman.Syms
open Marksman.Config
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 }
type CreateFileAction = { name: string; newFileUri: DocumentUri }
let createFile newFileUri : WorkspaceEdit =
let documentChanges = [| DocumentChange.createFile newFileUri |]
{ Changes = None; DocumentChanges = Some documentChanges }
let tableOfContentsInner (includeLevels: array<int>) (doc: Doc) : DocumentAction option =
match TableOfContents.mk includeLevels (Doc.index doc) with
| Some toc ->
let rendered = TableOfContents.render toc
let existingRange = TableOfContents.detect (Doc.text doc)
let isSame =
existingRange
|> Option.map (Doc.text doc).Substring
|> Option.map (TableOfContents.isSame rendered)
|> Option.defaultValue false
if isSame then
None
else
let name =
match existingRange with
| None -> "Create a Table of Contents"
| _ -> "Update the Table of Contents"
let insertionPoint =
match existingRange with
| Some range -> Replacing range
| None -> TableOfContents.insertionPoint doc
logger.trace (
Log.setMessage "Determining table of contents insertion point"
>> Log.addContext "insertionPoint" insertionPoint
>> Log.addContext "existing" existingRange
>> Log.addContext "text" rendered
)
let isEmpty lineNumber = (Doc.text doc).LineContent(lineNumber).IsWhitespace()
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 range.Start.Line <= 0 || 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
let tableOfContents
(_range: Range)
(_context: CodeActionContext)
(config: Config)
(doc: Doc)
: DocumentAction option =
let includeLevels = config.CaTocInclude()
tableOfContentsInner includeLevels doc
let createMissingFile
(range: Range)
(_context: CodeActionContext)
(doc: Doc)
(folder: Folder)
(extraFolders: seq<Folder>)
: CreateFileAction option =
let configuredExts = (Folder.configuredMarkdownExts folder)
let pos = range.Start
monad' {
let! atPos = Doc.index doc |> Index.linkAtPos pos
// Extract a potential xref to another doc. We should extract only the doc part to ensure
// that empty list of references means that the file doesn't exist
let! symAtPos = doc.Structure |> Structure.tryFindSymbolForConcrete atPos
let! docAtPos =
match symAtPos with
| Sym.Ref(CrossRef r) -> Some(r.Doc)
| _ -> None
let docRefAtPos = Sym.Ref(CrossRef(CrossDoc docAtPos))
let refs = Dest.tryResolveSym folder extraFolders doc docRefAtPos
// Early return if the file exists
do! guard (Seq.isEmpty refs)
let! internPath = InternName.tryAsPath { name = docAtPos; src = doc.Id }
let relPath =
InternPath.toRel internPath
|> RelPath.toSystem
|> ensureMarkdownExt configuredExts
|> RelPath
let path = RootPath.append (Folder.rootPath folder) relPath
let filename = AbsPath.filename path
let uri = AbsPath.toUri path
// create the file
{ name = $"Create `{filename}`"; newFileUri = uri }
}