1
0

Program.fs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. module Program
  2. open FsharpMyExtension
  3. open FsharpMyExtension.Either
  4. open LanguageServerProtocol
  5. open LanguageServerProtocol.Server
  6. open LanguageServerProtocol.Types
  7. open Qsp
  8. // берётся — `FSharp.Compiler.Range.range`
  9. type VscodeRange =
  10. { StartColumn : int
  11. StartLine : int
  12. EndColumn : int
  13. EndLine : int }
  14. let toVscodeRange ((p1, p2):Qsp.Tokens.Range) =
  15. { StartColumn = int p1.Column
  16. StartLine = int p1.Line
  17. EndColumn = int p2.Column
  18. EndLine = int p2.Line }
  19. let toVscodeRange2 (r:Qsp.Tokens.InlineRange) =
  20. { StartColumn = int r.Column1
  21. StartLine = int r.Line
  22. EndColumn = int r.Column2
  23. EndLine = int r.Line }
  24. module Position =
  25. let ofRange ((p1, p2):Qsp.Tokens.Range) =
  26. {
  27. Start = { Line = int p1.Line - 1
  28. Character = int p1.Column - 1 }
  29. End = { Line = int p2.Line - 1
  30. Character = int p2.Column - 1 }
  31. }
  32. let ofInlineRange (r:Qsp.Tokens.InlineRange) =
  33. {
  34. Start = { Line = int r.Line - 1
  35. Character = int r.Column1 - 1 }
  36. End = { Line = int r.Line - 1
  37. Character = int r.Column2 - 1 }
  38. }
  39. type UriString = string
  40. type SourceFilePath = string
  41. type HighlightingRequest = { FileName: string }
  42. type Serializer = obj -> string
  43. type PlainNotification= { Content: string }
  44. module CommandResponse =
  45. type ResponseMsg<'T> =
  46. {
  47. Kind: string
  48. Data: 'T
  49. }
  50. type HighlightingRange = { Range: VscodeRange; TokenType: string }
  51. type HighlightingResponse = { Highlights: HighlightingRange [] }
  52. open Qsp.Tokens
  53. let highlighting ranges =
  54. let map (t: TokenType): string =
  55. match t with
  56. | ConstantNumericInteger -> "constantNumericInteger"
  57. | NameLabel -> "string"
  58. | LabelColon -> "punctuationSeparatorColon"
  59. | InterpolationEnd -> "interpolationEnd"
  60. | InterpolationBegin -> "interpolationBegin"
  61. | Procedure -> "procedure"
  62. | Type -> "storage"
  63. | Variable -> "variable"
  64. | Keyword -> "keyword"
  65. | Comment -> "comment"
  66. | Function -> "function"
  67. | If
  68. | ElseIf
  69. | Else
  70. | Act
  71. | Colon
  72. | End
  73. | Underscore
  74. | Exit
  75. | For
  76. | To
  77. | Step
  78. | SharpBeginLoc
  79. | MinusEndLoc -> "keywordControl"
  80. | BinaryOperator op ->
  81. match op with
  82. | Ast.Plus
  83. | Ast.Minus
  84. | Ast.Times
  85. | Ast.Divide
  86. | Ast.Mod ->
  87. "operatorArithmetic"
  88. | Ast.Eq ->
  89. "operatorComparison"
  90. | Ast.Gt
  91. | Ast.Ge
  92. | Ast.Lt
  93. | Ast.Le
  94. | Ast.Bang
  95. | Ast.Ne
  96. | Ast.El
  97. | Ast.Eg ->
  98. "operatorRelational"
  99. | Ast.Or
  100. | Ast.And ->
  101. "operatorLogical"
  102. | UnaryOperator(unaryOperator) ->
  103. match unaryOperator with
  104. | Ast.UnarOp.Obj -> "operator"
  105. | Ast.UnarOp.Loc -> "operator"
  106. | Ast.UnarOp.No -> "operatorLogical"
  107. | Ast.UnarOp.Neg -> "operatorArithmetic"
  108. | OperatorAssignment -> "operatorAssignment"
  109. | PunctuationTerminatorStatement -> "punctuationTerminatorStatement"
  110. | StringQuotedSingle
  111. | StringQuotedDouble
  112. | StringBraced -> "string"
  113. {
  114. Kind = "highlighting"
  115. Data =
  116. { Highlights =
  117. ranges
  118. |> Array.map (fun (struct ((pos:Tokens.InlineRange), tk)) ->
  119. { Range = toVscodeRange2 pos; TokenType = map tk }) }
  120. }
  121. let changeExtensionToQsp path =
  122. System.IO.Path.ChangeExtension(path, ".qsp")
  123. let txt2qspPath = @"3rd\txt2gam.exe"
  124. let buildQsp src =
  125. let dst = changeExtensionToQsp src
  126. let args = sprintf "\"%s\" \"%s\"" src dst
  127. let startProcString path args =
  128. let drivenOutput = new System.Text.StringBuilder()
  129. Proc.startProc (fun e ->
  130. drivenOutput.AppendLine(e) |> ignore
  131. ) path args
  132. |> fun code -> code, drivenOutput.ToString()
  133. startProcString txt2qspPath args
  134. let buildQspTest () =
  135. let src = @"E:\Project\Qsp\QspSyntax\sample-code\Sandbox.qsps"
  136. buildQsp src
  137. type Commands() =
  138. member x.GetHighlighting documentText = // (file: SourceFilePath) =
  139. // let file = Path.GetFullPath file
  140. // async {
  141. // let! res = x.TryGetLatestTypeCheckResultsForFile file
  142. // let res =
  143. // match res with
  144. // | Some res ->
  145. // let r = res.GetCheckResults.GetSemanticClassification(None)
  146. // Some r
  147. // | None -> None
  148. // return CoreResponse.Res res
  149. // return
  150. // }
  151. Qsp.Parser.Main.start documentText
  152. let isValidDoc uri =
  153. // git-файлы лучше обходить стороной, чтобы не смущать пространство лишний раз
  154. let uri = System.Uri uri
  155. uri.Scheme <> "git"
  156. type WorkspaceLoadParms = {
  157. /// Project files to load
  158. TextDocuments: TextDocumentIdentifier []
  159. }
  160. type FsacClient(sendServerRequest: ClientNotificationSender) =
  161. inherit LspClient ()
  162. override __.WindowShowMessage(p) =
  163. sendServerRequest "window/showMessage" (box p) |> Async.Ignore
  164. override __.WindowLogMessage(p) =
  165. sendServerRequest "window/logMessage" (box p) |> Async.Ignore
  166. override __.TextDocumentPublishDiagnostics(p) =
  167. sendServerRequest "textDocument/publishDiagnostics" (box p) |> Async.Ignore
  168. override __.WorkspaceWorkspaceFolders () = // TODO
  169. failwith ""
  170. type State = unit
  171. type UpdateFileParms = {
  172. // File: BackgroundFileCheckType
  173. Content: string
  174. Version: int
  175. }
  176. type QspConfig =
  177. {
  178. FormatConfig: Qsp.Show.FormatConfig
  179. }
  180. static member Default =
  181. {
  182. FormatConfig = Qsp.Show.FormatConfig.Default
  183. }
  184. type Config =
  185. { Qsp : QspConfig option }
  186. static member Default =
  187. {
  188. Qsp = None
  189. }
  190. /// Перезапускающийся таймер: как только подаются новые данные, старый счетчик сбрасывается, и наступает вновь отсчет, но уже с новыми данными. Если счетчик дойдет до нуля, то вызовется функция.
  191. let restartableTimer interval f =
  192. let m = MailboxProcessor.Start(fun agent ->
  193. let mutable data = None
  194. let rec loop (timer:System.Timers.Timer) =
  195. async {
  196. let! msg = agent.Receive()
  197. data <- Some msg
  198. timer.Stop()
  199. timer.Start()
  200. return! loop timer
  201. }
  202. let timer = new System.Timers.Timer(interval)
  203. timer.AutoReset <- false
  204. timer.Elapsed.Add(fun x -> f x data.Value)
  205. loop timer
  206. )
  207. m.Post
  208. let test () =
  209. let interval = 1500.
  210. let time = restartableTimer interval (fun e (x:string, y:int) -> printfn "%A" (x, y))
  211. time("1", 1)
  212. time("2", 1)
  213. time("3", 1)
  214. type BackgroundServiceServer(state: State, client: FsacClient) =
  215. inherit LspServer()
  216. let mutable clientCapabilities = None
  217. let mutable currentDocument : TextDocumentItem option = None
  218. let mutable currentWorkspacePath = ""
  219. let mutable lastCharPos = None
  220. let mutable parserResult = None
  221. let mutable hovers = []
  222. let mutable highlights = Qsp.Parser.Generic.highlightsEmpty
  223. let mutable config = QspConfig.Default
  224. let getVarHighlight (pos:Position) =
  225. highlights.VarHighlights.Ranges
  226. |> List.tryFind (fun (r, _) ->
  227. if (int (r.Line - 1L) = pos.Line) then
  228. int (r.Column1 - 1L) <= pos.Character && pos.Character <= int (r.Column2 - 1L)
  229. else
  230. false
  231. )
  232. |> Option.map (fun (_, var) ->
  233. Map.find var highlights.VarHighlights.Ma) // находить должно всегда
  234. let getLocHighlight (pos:Position) =
  235. highlights.LocHighlights.Ranges
  236. |> List.tryFind (fun (r, _) ->
  237. if (int (r.Line - 1L) = pos.Line) then
  238. int (r.Column1 - 1L) <= pos.Character && pos.Character <= int (r.Column2 - 1L)
  239. else
  240. false
  241. )
  242. |> Option.map (fun (_, var) ->
  243. Map.find var highlights.LocHighlights.Ma) // находить должно всегда
  244. let commands = Commands()
  245. let publishDiagnostics uri (res:list<_>) =
  246. let diagnostics =
  247. res
  248. |> List.map (fun (range, word) ->
  249. {
  250. Range = range
  251. Severity = Some (DiagnosticSeverity.Error)
  252. Code = None
  253. Source = "qsp"
  254. Message = sprintf "unknown '%s'" word
  255. RelatedInformation = None
  256. Tags = None
  257. })
  258. |> Array.ofList
  259. client.TextDocumentPublishDiagnostics {
  260. Uri = uri
  261. Diagnostics = diagnostics
  262. }
  263. // let interval = 500.
  264. // let reactor =
  265. // restartableTimer interval
  266. // (fun e (uri, text) ->
  267. // publishDiagnostics uri text
  268. // |> Async.RunSynchronously
  269. // )
  270. let parse uri documentText =
  271. let res = commands.GetHighlighting documentText
  272. parserResult <- Some res
  273. let genericFromState (st:Qsp.Parser.Generic.State) =
  274. hovers <- st.Hovers |> List.map (mapFst Position.ofInlineRange)
  275. highlights <- st.Highlights
  276. lastCharPos <- Some st.LastSymbolPos
  277. match res with
  278. | FParsec.CharParsers.Success(_, st, _) ->
  279. genericFromState st
  280. st.SemanticErrors
  281. |> List.map (mapFst Position.ofInlineRange)
  282. |> publishDiagnostics uri
  283. |> Async.RunSynchronously
  284. st.Tokens
  285. |> List.map (fun x -> struct (x.Range, x.TokenType) )
  286. |> Array.ofList
  287. | FParsec.CharParsers.Failure(msg, err, st) ->
  288. // client.WindowLogMessage {
  289. // Type = MessageType.Error
  290. // Message = sprintf "%A" msg
  291. // }
  292. // |> Async.RunSynchronously
  293. genericFromState st
  294. let pos = err.Position
  295. let range =
  296. {
  297. Start = { Line = int pos.Line - 1
  298. Character = int pos.Column - 1 }
  299. End = { Line = int pos.Line - 1
  300. Character = int pos.Column + 1000 }
  301. }
  302. // client.WindowLogMessage {
  303. // Type = MessageType.Error
  304. // Message = sprintf "%A\n%A\n%A" pos range pos.Index
  305. // }
  306. // |> Async.RunSynchronously
  307. // TODO: придется лезть в документацию, чтобы постичь всю боль
  308. // FParsec.ErrorMessageList.ToSortedArray err.Messages // TODO
  309. // |> Array.map (function
  310. // | FParsec.Error.CompoundError(msg, pos, someMagicObj, errs) ->
  311. // failwith ""
  312. // // | FParsec.Error.Unexpected
  313. // // x.Type
  314. // )
  315. let xs =
  316. st.SemanticErrors
  317. |> List.map (mapFst Position.ofInlineRange)
  318. (range, msg) :: xs
  319. |> publishDiagnostics uri
  320. |> Async.RunSynchronously
  321. st.Tokens
  322. |> List.map (fun x -> struct (x.Range, x.TokenType) )
  323. |> Array.ofList
  324. member __.GetHighlighting(p : HighlightingRequest) =
  325. let uri = sprintf "file:///%s" p.FileName
  326. let res =
  327. currentDocument
  328. |> Option.map (fun x ->
  329. let res = parse uri x.Text
  330. { Content =
  331. CommandResponse.highlighting res
  332. |> FSharpJsonType.SerializeOption.serNotIndent }
  333. )
  334. async {
  335. return LspResult.success res
  336. }
  337. override __.TextDocumentDidChange(p) = async {
  338. if isValidDoc p.TextDocument.Uri then
  339. currentDocument <-
  340. currentDocument
  341. |> Option.map (fun x ->
  342. { x with
  343. Uri = p.TextDocument.Uri
  344. Version = Option.defaultValue x.Version p.TextDocument.Version
  345. Text =
  346. match Array.tryExactlyOne p.ContentChanges with
  347. | Some x ->
  348. // documentRange <- x.Range // увы, но при `TextDocumentSyncKind.Full` он всегда равен `None`
  349. x.Text
  350. | None ->
  351. // do! client.WindowLogMessage {
  352. // Type = MessageType.Error
  353. // Message = sprintf "Array.tryExactlyOne p.ContentChanges error:\n%A" p.ContentChanges
  354. // }
  355. failwith "Array.tryExactlyOne p.ContentChanges = None"
  356. }
  357. )
  358. }
  359. override __.TextDocumentDidOpen(p: DidOpenTextDocumentParams) = async {
  360. // Вот что, этот негодяй одновременно открывает целую кучу всего: здесь и git, и обычный файл, и даже output. Надо бы как-то за всем этим уследить.
  361. // "git:/e%3A/Project/Qsp/QspSyntax/sample-code/Sandbox.qsps?%7B%22path%22%3A%22e%3A%5C%5CProject%5C%5CQsp%5C%5CQspSyntax%5C%5Csample-code%5C%5CSandbox.qsps%22%2C%22ref%22%3A%22~%22%7D"
  362. // p.TextDocument
  363. if p.TextDocument.LanguageId = "qsp" && isValidDoc p.TextDocument.Uri then
  364. currentDocument <- Some p.TextDocument
  365. // documentUri <- p.TextDocument.Uri
  366. // documentVersion <- Some p.TextDocument.Version
  367. // documentText <- p.TextDocument.Text
  368. do! client.WindowLogMessage {
  369. Type = MessageType.Info
  370. Message =
  371. let txt = p.TextDocument
  372. sprintf "TextDocumentDidOpen\n%A"
  373. ( txt.LanguageId, txt.Uri, txt.Version)
  374. }
  375. // if Set.contains (p.TextDocument.Uri.ToLower()) spellcheckIgnore then
  376. // do! client.SpellcheckDecorate []
  377. // else
  378. // let textDoc = p.TextDocument
  379. // documentVersion <- Some textDoc.Version
  380. // do! client.WindowLogMessage {
  381. // Type = MessageType.Info
  382. // Message = "TextDocumentDidOpen"
  383. // }
  384. // reactor (textDoc.Uri, textDoc.Text)
  385. }
  386. override __.TextDocumentDidClose p = async {
  387. do! client.WindowLogMessage {
  388. Type = MessageType.Info
  389. Message =
  390. sprintf "TextDocumentDidClose\n%A" p
  391. }
  392. }
  393. override __.WorkspaceDidChangeWatchedFiles p = async {
  394. do! client.WindowLogMessage {
  395. Type = MessageType.Info
  396. Message =
  397. sprintf "WorkspaceDidChangeWatchedFiles\n%A" p
  398. }
  399. }
  400. member private __.IfDiagnostic (str: string) handler p =
  401. let diag =
  402. p.Context.Diagnostics |> Seq.tryFind (fun n -> n.Message.Contains str)
  403. match diag with
  404. | None -> async.Return []
  405. | Some d -> handler d
  406. member private __.CreateFix uri ver title (d: Diagnostic option) range replacement =
  407. let e =
  408. {
  409. Range = range
  410. NewText = replacement
  411. }
  412. let edit =
  413. {
  414. TextDocument =
  415. {
  416. Uri = uri
  417. Version = ver
  418. }
  419. Edits = [|e|]
  420. }
  421. let we = WorkspaceEdit.Create([|edit|], clientCapabilities.Value)
  422. { CodeAction.Title = title
  423. Kind = Some "quickfix"
  424. Diagnostics = d |> Option.map Array.singleton
  425. Edit = we
  426. Command = None}
  427. // override this.TextDocumentCodeAction p = async {
  428. // if Set.contains (p.TextDocument.Uri.ToLower()) spellcheckIgnore then
  429. // // TODO: если текущий файл отсеивается, то самое время как-то избавить весь документ от ошибок, вот только как это сделать?
  430. // // return LspResult.Ok None // Пробовал — выбивает ошибку
  431. // return LspResult.success (Some (TextDocumentCodeActionResult.CodeActions [||]))
  432. // else
  433. // let! res =
  434. // p
  435. // |> this.IfDiagnostic "unknown " (fun d ->
  436. // async {
  437. // do! client.WindowLogMessage {
  438. // Type = MessageType.Info
  439. // Message = (sprintf "TextDocumentCodeAction 'unknown ...'")
  440. // }
  441. // match Map.tryFind d.Range lastWords with
  442. // | Some word ->
  443. // let words =
  444. // Suggestion.LevenshteinDistance.suggestions3 word dic
  445. // |> Suggestion.LevenshteinDistance.mapTruncate 10
  446. // let actions =
  447. // words
  448. // |> List.map (fun word ->
  449. // this.CreateFix p.TextDocument.Uri documentVersion (sprintf "replace on '%s'" word) (Some d) d.Range word)
  450. // return actions
  451. // | None ->
  452. // do! client.WindowLogMessage {
  453. // Type = MessageType.Info
  454. // Message = (sprintf "range not found:\n%A" d.Range)
  455. // }
  456. // return []
  457. // }
  458. // )
  459. // return res |> Array.ofList |> TextDocumentCodeActionResult.CodeActions |> Some |> LspResult.success
  460. // }
  461. override __.WorkspaceDidChangeConfiguration (x) = async {
  462. do! client.WindowLogMessage {
  463. Type = MessageType.Info
  464. Message = sprintf "WorkspaceDidChangeConfiguration\n%A" (x.Settings.ToString())
  465. }
  466. let configResult : Either<_, Config> =
  467. let ser = Newtonsoft.Json.JsonSerializer()
  468. ser.Converters.Add FSharpJsonType.SerializeOption.converter
  469. try
  470. x.Settings.ToObject(ser)
  471. |> Right
  472. with
  473. e -> Left e.Message
  474. match configResult with
  475. | Right x ->
  476. x.Qsp
  477. |> Option.iter (fun x ->
  478. config <- x
  479. )
  480. | Left msg ->
  481. do! client.WindowLogMessage {
  482. Type = MessageType.Error
  483. Message = sprintf "%s\n%s" (x.Settings.ToString()) msg
  484. }
  485. }
  486. override x.TextDocumentFormatting p = async {
  487. // p.Options.AdditionalData // версия 1.46.1, и их всё еще не завезли https://code.visualstudio.com/api/references/vscode-api#FormattingOptions
  488. match currentDocument with
  489. | Some currentDocument ->
  490. if p.TextDocument.Uri = currentDocument.Uri then
  491. match lastCharPos with
  492. | Some lastCharPos ->
  493. match parserResult with
  494. | Some r ->
  495. match r with
  496. | FParsec.CharParsers.Success(x, _, _) ->
  497. return
  498. { TextEdit.Range =
  499. {
  500. Start = { Line = 0
  501. Character = 0 }
  502. End = { Line = int lastCharPos.Line - 1
  503. Character = int lastCharPos.Column - 1 } // а быть может, даже `- 2`
  504. }
  505. NewText =
  506. if p.Options.InsertSpaces then
  507. Qsp.Show.UsingSpaces p.Options.TabSize
  508. else
  509. Qsp.Show.UsingTabs
  510. |> fun indentsOpt -> Qsp.Show.printLocs indentsOpt config.FormatConfig x }
  511. |> Array.singleton
  512. |> Some
  513. |> LspResult.success
  514. | FParsec.CharParsers.Failure(_, _, _) ->
  515. do! client.WindowShowMessage {
  516. Type = MessageType.Error
  517. Message = sprintf "Синтаксис содержит ошибки, потому форматировать его невозможно"
  518. }
  519. return LspResult.success None
  520. | None ->
  521. do! client.WindowLogMessage {
  522. Type = MessageType.Error
  523. Message = sprintf "lastSymbolPos = None"
  524. }
  525. return LspResult.success None
  526. | None ->
  527. do! client.WindowLogMessage {
  528. Type = MessageType.Error
  529. Message = sprintf "documentRange = None"
  530. }
  531. return LspResult.success None
  532. else
  533. do! client.WindowLogMessage {
  534. Type = MessageType.Error
  535. Message = sprintf "p.TextDocument.Uri <> documentUri"
  536. }
  537. return LspResult.success None
  538. | None ->
  539. return LspResult.success None
  540. }
  541. override __.TextDocumentDocumentHighlight(x) = async {
  542. // do! client.WindowLogMessage {
  543. // Type = MessageType.Error
  544. // Message = sprintf "%A" (varHovers, x.Position)
  545. // }
  546. let f fn =
  547. match fn x.Position with
  548. | None -> None
  549. | Some xs ->
  550. xs // должно находить всегда
  551. |> List.map (fun (r, kind) ->
  552. {
  553. DocumentHighlight.Range = Position.ofInlineRange r
  554. Kind =
  555. match kind with
  556. | Qsp.Parser.Generic.WriteAccess -> DocumentHighlightKind.Write
  557. | Qsp.Parser.Generic.ReadAccess -> DocumentHighlightKind.Read
  558. |> Some
  559. }
  560. )
  561. |> Array.ofList
  562. |> Some
  563. let x =
  564. f getVarHighlight
  565. |> Option.orElseWith (fun () ->
  566. f getLocHighlight
  567. )
  568. return LspResult.success x
  569. }
  570. override __.TextDocumentRename renameParams = async {
  571. let f fn =
  572. match fn renameParams.Position with
  573. | None -> None
  574. | Some xs ->
  575. let edits =
  576. [|
  577. {
  578. Edits =
  579. xs
  580. |> List.map (fun (r, _) ->
  581. {
  582. TextEdit.Range = Position.ofInlineRange r
  583. NewText = renameParams.NewName
  584. }
  585. )
  586. |> Array.ofList
  587. TextDocument =
  588. {
  589. Version = currentDocument |> Option.map (fun x -> x.Version)
  590. Uri = renameParams.TextDocument.Uri
  591. }
  592. }
  593. |]
  594. WorkspaceEdit.Create(edits, clientCapabilities.Value) // TODO: а если `None`?
  595. |> Some
  596. let x =
  597. f getVarHighlight
  598. |> Option.orElseWith (fun () ->
  599. f getLocHighlight
  600. )
  601. return LspResult.success x
  602. }
  603. override __.TextDocumentDefinition textDocumentPositionParams = async {
  604. let f fn =
  605. match fn textDocumentPositionParams.Position with
  606. | None -> None
  607. | Some xs ->
  608. xs
  609. |> List.choose (fun (r, kind) ->
  610. match kind with
  611. | Qsp.Parser.Generic.WriteAccess ->
  612. {
  613. Location.Uri = textDocumentPositionParams.TextDocument.Uri
  614. Location.Range = Position.ofInlineRange r
  615. }
  616. |> Some
  617. | _ -> None
  618. )
  619. |> Array.ofList
  620. |> GotoResult.Multiple
  621. |> Some
  622. let x =
  623. f getVarHighlight
  624. |> Option.orElseWith (fun () ->
  625. f getLocHighlight
  626. )
  627. return LspResult.success x
  628. }
  629. override __.TextDocumentHover textDocumentPositionParams =
  630. async {
  631. let res =
  632. hovers
  633. |> List.tryFind (fun (r, _) ->
  634. if (r.Start.Line = textDocumentPositionParams.Position.Line) && (r.Start.Line = r.End.Line) then
  635. r.Start.Character <= textDocumentPositionParams.Position.Character && textDocumentPositionParams.Position.Character <= r.End.Character
  636. elif r.Start.Line <= textDocumentPositionParams.Position.Line && textDocumentPositionParams.Position.Line <= r.End.Line then
  637. false // TODO: @high ¯\_(ツ)_/¯
  638. else
  639. false // TODO: @high ¯\_(ツ)_/¯
  640. )
  641. // do! client.WindowLogMessage {
  642. // Type = MessageType.Error
  643. // Message = sprintf "%A" (hovers, x.Position)
  644. // }
  645. let x =
  646. match res with
  647. | None -> None
  648. | Some(r, msg) ->
  649. {
  650. Hover.Contents =
  651. HoverContent.MarkupContent (markdown msg)
  652. Range = Some r
  653. }
  654. |> Some
  655. return LspResult.success x
  656. }
  657. override __.TextDocumentReferences refParams = async {
  658. // refParams.Context.IncludeDeclaration // загадочный параметр
  659. let f fn =
  660. match fn refParams.Position with
  661. | None -> None
  662. | Some xs ->
  663. xs
  664. |> List.map (fun (r, _) ->
  665. {
  666. Location.Uri = refParams.TextDocument.Uri
  667. Location.Range = Position.ofInlineRange r
  668. }
  669. )
  670. |> Array.ofList
  671. |> Some
  672. let x =
  673. f getVarHighlight
  674. |> Option.orElseWith (fun () ->
  675. f getLocHighlight
  676. )
  677. return LspResult.success x
  678. }
  679. override __.TextDocumentFoldingRange foldingRangeParams =
  680. // let x =
  681. // {
  682. // FoldingRange.StartLine = 0
  683. // StartCharacter = failwith "Not Implemented"
  684. // EndLine = failwith "Not Implemented"
  685. // EndCharacter = failwith "Not Implemented"
  686. // Kind = Some FoldingRangeKind.Region
  687. // }
  688. // foldingRangeParams.TextDocument.Uri
  689. async {
  690. return LspResult.success None
  691. }
  692. override __.WorkspaceDidChangeWorkspaceFolders p = async {
  693. do! client.WindowLogMessage {
  694. Type = MessageType.Info
  695. Message = sprintf "WorkspaceDidChangeWorkspaceFolders:\n%A" p
  696. }
  697. }
  698. // override __.WorkspaceWorkspaceFolders p = async {
  699. // }
  700. member __.FSharpWorkspaceLoad (p:WorkspaceLoadParms) = async {
  701. // Возвращает что-то в духе "e:\Project\Qsp\QspSyntax\sample-code", а не Uri, как там написано
  702. // currentWorkspacePath <- p.TextDocuments.[0].Uri
  703. // let dir = @"e:\Project\Qsp\QspSyntax\sample-code"
  704. // let projFiles = System.IO.Directory.GetFiles(dir, "*.qproj", System.IO.SearchOption.AllDirectories)
  705. // match projFiles with
  706. // | [||] -> () // "`.qproj` не найден"
  707. // | [|projFile|] ->
  708. // projFile <- System.IO.Path.GetDirectoryName projFile
  709. // | projFiles ->
  710. // // TODO: ошибкой было бы, если бы в одной и той же папке (или подпапке) было бы несколько файлов .qproj. Для всех остальных случаев нужно организовать работу с несколькими проектами. А ведь есть еще WorkspaceFolders.
  711. // do! client.WindowShowMessage {
  712. // Type = MessageType.Error
  713. // Message = sprintf "`.qproj` должен быть только один на весь проект, однако:\n%A" projFiles
  714. // }
  715. // do! client.WindowLogMessage {
  716. // Type = MessageType.Info
  717. // Message = sprintf "FSharpWorkspaceLoad:\n%A" p
  718. // }
  719. return LspResult.success None
  720. }
  721. override __.TextDocumentDocumentSymbol documentSymbolParams = async {
  722. let x =
  723. match currentDocument with
  724. | Some currentDocument ->
  725. let documentUri = currentDocument.Uri
  726. if documentUri = documentSymbolParams.TextDocument.Uri then
  727. client.WindowLogMessage {
  728. Type = MessageType.Info
  729. Message = sprintf "TextDocumentDocumentSymbol"
  730. }
  731. |> Async.RunSynchronously
  732. let symbolInfo =
  733. {
  734. ContainerName = None
  735. Name = "someVar"
  736. Kind = SymbolKind.Variable
  737. Location =
  738. {
  739. Location.Uri = documentUri
  740. Range =
  741. {
  742. Range.Start =
  743. {
  744. Position.Line = 0
  745. Character = 0
  746. }
  747. Range.End =
  748. {
  749. Position.Line = 0
  750. Character = 1000
  751. }
  752. }
  753. }
  754. }
  755. let symbolInfo2 =
  756. {
  757. ContainerName = None
  758. Name = "someVar2"
  759. Kind = SymbolKind.Variable
  760. Location =
  761. {
  762. Location.Uri = documentUri
  763. Range =
  764. {
  765. Range.Start =
  766. {
  767. Position.Line = 1
  768. Character = 0
  769. }
  770. Range.End =
  771. {
  772. Position.Line = 1
  773. Character = 1000
  774. }
  775. }
  776. }
  777. }
  778. Some [|symbolInfo; symbolInfo2|]
  779. else None
  780. | None -> None
  781. return LspResult.success x
  782. }
  783. override __.CompletionItemResolve completionItem = async {
  784. do! client.WindowLogMessage {
  785. Type = MessageType.Info
  786. Message = sprintf "CompletionItemResolve:\n%A" completionItem
  787. }
  788. return LspResult.success completionItem
  789. }
  790. // override __.TextDocumentCompletion completionParams = async {
  791. // do! client.WindowLogMessage {
  792. // Type = MessageType.Info
  793. // Message = sprintf "TextDocumentCompletion:\n%A" completionParams
  794. // }
  795. // let x =
  796. // {
  797. // CompletionList.IsIncomplete = false
  798. // Items = [
  799. // CompletionItem.Label = ""
  800. // ]
  801. // }
  802. // return LspResult.success None
  803. // }
  804. member __.BuildSource (uriStr:UriString) isRun =
  805. async {
  806. if System.Environment.OSVersion.Platform = System.PlatformID.Win32NT then
  807. // let uri = "file:///e:/Project/Qsp/QSP-LSP/3rd/txt2gam.exe"
  808. let uri =
  809. try
  810. let uri = System.Uri uriStr
  811. uri.LocalPath
  812. |> Right
  813. with e ->
  814. Left e.Message
  815. let res =
  816. match uri with
  817. | Right path ->
  818. try
  819. let code, output = buildQsp path
  820. if code = 0 then
  821. if isRun then
  822. changeExtensionToQsp path
  823. |> System.Diagnostics.Process.Start
  824. |> ignore
  825. Choice2Of2 "Ok"
  826. else
  827. Choice1Of2 (sprintf "txt2gam returned:\n%s" output)
  828. with e ->
  829. Choice1Of2 e.Message
  830. | Left err ->
  831. Choice1Of2 (sprintf "'%s'\n%A" uriStr err)
  832. return LspResult.success res
  833. else
  834. let res = Choice1Of2 (sprintf "Пока что txt2gam есть только Windows")
  835. return LspResult.success res
  836. }
  837. override __.Initialize p =
  838. async {
  839. clientCapabilities <- p.Capabilities
  840. /// { "AutomaticWorkspaceInit": false }
  841. let c =
  842. p.InitializationOptions
  843. |> Option.map (fun x -> x.ToString())
  844. return
  845. { Types.InitializeResult.Default with
  846. Capabilities =
  847. { Types.ServerCapabilities.Default with
  848. HoverProvider = Some true
  849. RenameProvider = Some true
  850. DefinitionProvider = Some true
  851. TypeDefinitionProvider = Some true
  852. ImplementationProvider = Some true
  853. ReferencesProvider = Some true
  854. DocumentHighlightProvider = Some true
  855. DocumentSymbolProvider = Some false
  856. WorkspaceSymbolProvider = Some false
  857. DocumentFormattingProvider = Some true
  858. DocumentRangeFormattingProvider = Some false
  859. SignatureHelpProvider =
  860. // Some {
  861. // SignatureHelpOptions.TriggerCharacters = Some [| "("; ","|]
  862. // }
  863. None
  864. CompletionProvider =
  865. None
  866. CodeLensProvider =
  867. // Some {
  868. // CodeLensOptions.ResolveProvider = Some true
  869. // }
  870. None
  871. CodeActionProvider = Some false
  872. TextDocumentSync =
  873. Some { TextDocumentSyncOptions.Default with
  874. OpenClose = Some true
  875. Change = Some TextDocumentSyncKind.Full
  876. Save = Some { IncludeText = Some true }
  877. }
  878. FoldingRangeProvider = None
  879. }
  880. }
  881. |> LspResult.success
  882. }
  883. type LocalSetting = {
  884. DicPath : string
  885. }
  886. open FsharpMyExtension
  887. [<EntryPoint>]
  888. let main argv =
  889. use input = System.Console.OpenStandardInput()
  890. use output = System.Console.OpenStandardOutput()
  891. let requestsHandlings =
  892. defaultRequestHandlings<BackgroundServiceServer>()
  893. |> Map.add "fsharp/highlighting" (requestHandling (fun s p -> s.GetHighlighting(p) ))
  894. |> Map.add "fsharp/workspaceLoad" (requestHandling (fun s p -> s.FSharpWorkspaceLoad(p) ))
  895. |> Map.add "qsp/build" (requestHandling (fun s p -> s.BuildSource p false ))
  896. |> Map.add "qsp/buildAndRun" (requestHandling (fun s p -> s.BuildSource p true ))
  897. Server.start requestsHandlings input output FsacClient (fun lspClient -> BackgroundServiceServer((), lspClient))
  898. |> printfn "%A"
  899. 0