Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a code action to create nonexistent linked files #239

Merged
merged 3 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 48 additions & 6 deletions LanguageServerProtocol/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,45 @@ type TextDocumentEdit =
/// The edits to be applied.
Edits: TextEdit [] }

/// Create file operation
type CreateFile =
{ /// The kind of resource operation. This should always be `"create"`.
jparoz marked this conversation as resolved.
Show resolved Hide resolved
Kind: string

/// The resource to create.
Uri: DocumentUri
}

/// Rename file operation
type RenameFile =
{ /// The kind of resource operation. This should always be `"rename"`.
Kind: string

/// The old (existing) location.
oldUri: DocumentUri

/// The new location.
newUri: DocumentUri
}

/// Delete file operation
type DeleteFile =
{ /// The kind of resource operation. This should always be `"delete"`.
Kind: string

/// The file to delete.
Uri: DocumentUri
}


/// Represents the possible values in the `WorkspaceEdit`'s `DocumentChanges` field.
[<ErasedUnion>]
type DocumentChange =
| TextDocumentEdit of TextDocumentEdit
| CreateFile of CreateFile
| RenameFile of RenameFile
| DeleteFile of DeleteFile

type TraceSetting =
| Off = 0
| Messages = 1
Expand Down Expand Up @@ -1147,22 +1186,25 @@ type WorkspaceEdit =
/// where each text document edit addresses a specific version of a text document.
/// Whether a client supports versioned document edits is expressed via
/// `WorkspaceClientCapabilities.workspaceEdit.documentChanges`.
DocumentChanges: TextDocumentEdit [] option }
static member DocumentChangesToChanges(edits: TextDocumentEdit []) =
DocumentChanges: DocumentChange [] option }
static member DocumentChangesToChanges(edits: DocumentChange []) =
edits
|> Array.map (fun edit -> edit.TextDocument.Uri.ToString(), edit.Edits)
|> Array.collect (fun docChange ->
match docChange with
| TextDocumentEdit edit -> [|edit.TextDocument.Uri.ToString(), edit.Edits|]
| _ -> [||])
|> Map.ofArray

static member CanUseDocumentChanges(capabilities: ClientCapabilities) =
(capabilities.Workspace
|> Option.bind (fun x -> x.WorkspaceEdit)
|> Option.bind (fun x -> x.DocumentChanges)) = Some true

static member Create(edits: TextDocumentEdit [], capabilities: ClientCapabilities) =
static member Create(documentChanges: DocumentChange [], capabilities: ClientCapabilities) =
if WorkspaceEdit.CanUseDocumentChanges(capabilities) then
{ Changes = None; DocumentChanges = Some edits }
{ Changes = None; DocumentChanges = Some documentChanges }
else
{ Changes = Some(WorkspaceEdit.DocumentChangesToChanges edits)
{ Changes = Some(WorkspaceEdit.DocumentChangesToChanges documentChanges)
DocumentChanges = None }

type MessageType =
Expand Down
49 changes: 49 additions & 0 deletions Marksman/CodeActions.fs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
module Marksman.CodeActions

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

open Marksman.Toc
open Marksman.Workspace
open Marksman.Misc
open Marksman.Refs
open Marksman.Index
open Marksman.Names
open Marksman.Paths

open type System.Environment

Expand All @@ -20,6 +25,14 @@ let documentEdit range text documentUri : WorkspaceEdit =

{ Changes = Some workspaceChanges; DocumentChanges = None }

type CreateFileAction = { name: string; newFileUri: DocumentUri }

let createFile newFileUri : WorkspaceEdit =
let documentChanges =
[| DocumentChange.CreateFile { Kind = "create"; Uri = newFileUri } |]

{ Changes = None; DocumentChanges = Some documentChanges }

let tableOfContentsInner (doc: Doc) : DocumentAction option =
match TableOfContents.mk (Doc.index doc) with
| Some toc ->
Expand Down Expand Up @@ -99,3 +112,39 @@ let tableOfContents
(doc: Doc)
: DocumentAction option =
tableOfContentsInner doc

let createNonexistentLink
(range: Range)
(_context: CodeActionContext)
(doc: Doc)
(folder: Folder)
: CreateFileAction option =
let configuredExts = (Folder.configOrDefault folder).CoreMarkdownFileExtensions()
let pos = range.Start // XXX: Should this just use the Start??
jparoz marked this conversation as resolved.
Show resolved Hide resolved
monad' {
let! atPos = Doc.index doc |> Index.linkAtPos pos
let! uref = Uref.ofElement configuredExts (Doc.id doc) atPos
let refs = Dest.tryResolveUref uref doc folder

// Early return if the file exists
do! guard (Seq.isEmpty refs)
jparoz marked this conversation as resolved.
Show resolved Hide resolved

let! name =
match uref with
| Uref.Doc name -> Some name.data
| Uref.Heading (Some name, _) -> Some name.data
| _ -> None
let! internPath = InternName.tryAsPath name
let relPath =
InternPath.toRel internPath
|> RelPath.toSystem
|> ensureMarkdownExt configuredExts
|> RelPath
let rootPath = Folder.rootPath folder
jparoz marked this conversation as resolved.
Show resolved Hide resolved
let path = RootPath.append rootPath relPath
let filename = AbsPath.filename path
let uri = AbsPath.toUri path

// create the file
{ name = $"Create `{filename}`"; newFileUri = uri }
}
15 changes: 15 additions & 0 deletions Marksman/Config.fs
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,21 @@ module TextSync =
/// without lenses manageable.
type Config =
{ caTocEnable: option<bool>
caCreateNonexistentLinkEnable: option<bool>
jparoz marked this conversation as resolved.
Show resolved Hide resolved
coreMarkdownFileExtensions: option<array<string>>
coreTextSync: option<TextSync>
complWikiStyle: option<ComplWikiStyle> }

static member Default =
{ caTocEnable = Some true
caCreateNonexistentLinkEnable = Some true
coreMarkdownFileExtensions = Some [| "md"; "markdown" |]
coreTextSync = Some Full
complWikiStyle = Some TitleSlug }

static member Empty =
{ caTocEnable = None
caCreateNonexistentLinkEnable = None
coreMarkdownFileExtensions = None
coreTextSync = None
complWikiStyle = None }
Expand All @@ -145,6 +148,11 @@ type Config =
|> Option.orElse Config.Default.caTocEnable
|> Option.get

member this.CaCreateNonexistentLinkEnable() =
this.caCreateNonexistentLinkEnable
|> Option.orElse Config.Default.caCreateNonexistentLinkEnable
|> Option.get

member this.CoreMarkdownFileExtensions() =
this.coreMarkdownFileExtensions
|> Option.orElse Config.Default.coreMarkdownFileExtensions
Expand All @@ -164,6 +172,9 @@ let private configOfTable (table: TomlTable) : LookupResult<Config> =
monad {
let! caTocEnable = getFromTableOpt<bool> table [] [ "code_action"; "toc"; "enable" ]

let! caCreateNonexistentLinkEnable =
getFromTableOpt<bool> table [] [ "code_action"; "create_nonexistent_link"; "enable" ]

let! coreMarkdownFileExtensions =
getFromTableOpt<array<string>> table [] [ "core"; "markdown"; "file_extensions" ]

Expand All @@ -176,6 +187,7 @@ let private configOfTable (table: TomlTable) : LookupResult<Config> =
complWikiStyle |> Option.bind ComplWikiStyle.ofStringOpt

{ caTocEnable = caTocEnable
caCreateNonexistentLinkEnable = caCreateNonexistentLinkEnable
coreMarkdownFileExtensions = coreMarkdownFileExtensions
coreTextSync = coreTextSync
complWikiStyle = complWikiStyle }
Expand All @@ -186,6 +198,9 @@ module Config =

let merge hi low =
{ caTocEnable = hi.caTocEnable |> Option.orElse low.caTocEnable
caCreateNonexistentLinkEnable =
hi.caCreateNonexistentLinkEnable
|> Option.orElse low.caCreateNonexistentLinkEnable
coreMarkdownFileExtensions =
hi.coreMarkdownFileExtensions
|> Option.orElse low.coreMarkdownFileExtensions
Expand Down
7 changes: 7 additions & 0 deletions Marksman/Misc.fs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ let chopMarkdownExt (configuredExts: seq<string>) (path: string) : string =
else
path

let ensureMarkdownExt (configuredExts: seq<string>) (path: string) : string =
if isMarkdownFile configuredExts path then
path
else
let ext = Seq.head configuredExts
$"{path}.{ext}"

let isPotentiallyMarkdownFile (configuredExts: seq<string>) (path: string) : bool =
let ext = Path.GetExtension path

Expand Down
8 changes: 6 additions & 2 deletions Marksman/Refactor.fs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ let mkWorkspaceEdit
for docEdit in docEdits do
docEdit.Edits |> Array.sortInPlaceWith (fun x y -> -(compare x y))

let docChanges =
docEdits
|> Array.map (DocumentChange.TextDocumentEdit)

if supportsDocumentEdit then
{ Changes = None; DocumentChanges = Some docEdits }
{ Changes = None; DocumentChanges = Some docChanges }
else
{ Changes = Some(WorkspaceEdit.DocumentChangesToChanges docEdits)
{ Changes = Some(WorkspaceEdit.DocumentChangesToChanges docChanges)
DocumentChanges = None }

let renameMarkdownLabel (newLabel: string) (element: Element) : option<TextEdit> =
Expand Down
15 changes: 14 additions & 1 deletion Marksman/Server.fs
Original file line number Diff line number Diff line change
Expand Up @@ -949,8 +949,21 @@ type MarksmanServer(client: MarksmanClient) =
else
[||]

let createLinkAction =
if config.CaCreateNonexistentLinkEnable() then
CodeActions.createNonexistentLink opts.Range opts.Context doc folder
|> Option.toArray
|> Array.map (fun ca ->
let wsEdit = CodeActions.createFile ca.newFileUri
codeAction ca.name wsEdit)
jparoz marked this conversation as resolved.
Show resolved Hide resolved
else
[||]

let codeActions: TextDocumentCodeActionResult =
tocAction |> Array.map U2.Second
seq { tocAction
jparoz marked this conversation as resolved.
Show resolved Hide resolved
createLinkAction }
|> Array.concat
|> Array.map U2.Second

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

Expand Down
10 changes: 7 additions & 3 deletions Tests/RefactorTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ let editRanges =
function
| Refactor.Edit wsEdit ->
match wsEdit.DocumentChanges with
| Some docEdits ->
docEdits
|> Array.map (fun docEdit ->
| Some docChanges ->
docChanges
|> Array.map (fun docChange ->
let docEdit =
match docChange with
| TextDocumentEdit docEdit -> docEdit
| _ -> failwith $"Refactoring should always produce TextDocumentEdits"
let doc = Path.GetFileName(docEdit.TextDocument.Uri)
let ranges = docEdit.Edits |> Array.map (fun x -> x.Range)
doc, ranges)
Expand Down
6 changes: 5 additions & 1 deletion Tests/default.marksman.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ markdown.file_extensions = ["md", "markdown"]
text_sync = "full"

[code_action]
toc.enable = true # Enable/disable "Table of Contents" code action
# Enable/disable "Table of Contents" code action
toc.enable = true

# Enable/disable "Create nonexistent linked file" code action
create_nonexistent_link.enable = true

[completion]
# The style of wiki links completion.
Expand Down