Program.fs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941
  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. | KeywordSymbol -> "keywordSymbol"
  66. | Comment -> "comment"
  67. | Function -> "function"
  68. | If
  69. | ElseIf
  70. | Else
  71. | Act
  72. | Colon
  73. | End
  74. | Underscore
  75. | Exit
  76. | SharpBeginLoc
  77. | MinusEndLoc -> "keywordControl"
  78. | BinaryOperator op ->
  79. match op with
  80. | Ast.Plus
  81. | Ast.Minus
  82. | Ast.Times
  83. | Ast.Divide
  84. | Ast.Mod ->
  85. "operatorArithmetic"
  86. | Ast.Eq ->
  87. "operatorComparison"
  88. | Ast.Gt
  89. | Ast.Ge
  90. | Ast.Lt
  91. | Ast.Le
  92. | Ast.Bang
  93. | Ast.Ne
  94. | Ast.El
  95. | Ast.Eg ->
  96. "operatorRelational"
  97. | Ast.Or
  98. | Ast.And ->
  99. "operatorLogical"
  100. | UnaryOperator(unaryOperator) ->
  101. match unaryOperator with
  102. | Ast.UnarOp.Obj -> "operator"
  103. | Ast.UnarOp.Loc -> "operator"
  104. | Ast.UnarOp.No -> "operatorLogical"
  105. | Ast.UnarOp.Neg -> "operatorArithmetic"
  106. | OperatorAssignment -> "operatorAssignment"
  107. | PunctuationTerminatorStatement -> "punctuationTerminatorStatement"
  108. | StringQuotedSingle
  109. | StringQuotedDouble
  110. | StringBraced -> "string"
  111. | BraceSquareOpened
  112. | BraceSquareClosed -> "metaBraceSquare"
  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 (_, varId) ->
  233. Map.find varId highlights.VarHighlights.VarScopeSystem.Result // находить должно всегда
  234. |> snd
  235. )
  236. let getLocHighlight (pos:Position) =
  237. highlights.LocHighlights.Ranges
  238. |> List.tryFind (fun (r, _) ->
  239. if (int (r.Line - 1L) = pos.Line) then
  240. int (r.Column1 - 1L) <= pos.Character && pos.Character <= int (r.Column2 - 1L)
  241. else
  242. false
  243. )
  244. |> Option.map (fun (_, var) ->
  245. Map.find var highlights.LocHighlights.Ma) // находить должно всегда
  246. let commands = Commands()
  247. let publishDiagnostics uri (res:list<_>) =
  248. let diagnostics =
  249. res
  250. |> List.map (fun (range, msg) ->
  251. {
  252. Range = range
  253. Severity = Some (DiagnosticSeverity.Error)
  254. Code = None
  255. Source = "qsp"
  256. Message = msg
  257. RelatedInformation = None
  258. Tags = None
  259. })
  260. |> Array.ofList
  261. client.TextDocumentPublishDiagnostics {
  262. Uri = uri
  263. Diagnostics = diagnostics
  264. }
  265. // let interval = 500.
  266. // let reactor =
  267. // restartableTimer interval
  268. // (fun e (uri, text) ->
  269. // publishDiagnostics uri text
  270. // |> Async.RunSynchronously
  271. // )
  272. let parse uri documentText =
  273. let res = commands.GetHighlighting documentText
  274. parserResult <- Some res
  275. let genericFromState (st:Qsp.Parser.Generic.State) =
  276. hovers <- st.Hovers |> List.map (mapFst Position.ofInlineRange)
  277. highlights <- st.Highlights
  278. lastCharPos <- Some st.LastSymbolPos
  279. match res with
  280. | FParsec.CharParsers.Success(_, st, _) ->
  281. genericFromState st
  282. st.SemanticErrors
  283. |> List.map (mapFst Position.ofInlineRange)
  284. |> publishDiagnostics uri
  285. |> Async.RunSynchronously
  286. st.Tokens
  287. |> List.map (fun x -> struct (x.Range, x.TokenType) )
  288. |> Array.ofList
  289. | FParsec.CharParsers.Failure(msg, err, st) ->
  290. // client.WindowLogMessage {
  291. // Type = MessageType.Error
  292. // Message = sprintf "%A" msg
  293. // }
  294. // |> Async.RunSynchronously
  295. genericFromState st
  296. let pos = err.Position
  297. let range =
  298. {
  299. Start = { Line = int pos.Line - 1
  300. Character = int pos.Column - 1 }
  301. End = { Line = int pos.Line - 1
  302. Character = int pos.Column + 1000 }
  303. }
  304. // client.WindowLogMessage {
  305. // Type = MessageType.Error
  306. // Message = sprintf "%A\n%A\n%A" pos range pos.Index
  307. // }
  308. // |> Async.RunSynchronously
  309. // TODO: придется лезть в документацию, чтобы постичь всю боль
  310. // FParsec.ErrorMessageList.ToSortedArray err.Messages // TODO
  311. // |> Array.map (function
  312. // | FParsec.Error.CompoundError(msg, pos, someMagicObj, errs) ->
  313. // failwith ""
  314. // // | FParsec.Error.Unexpected
  315. // // x.Type
  316. // )
  317. let xs =
  318. st.SemanticErrors
  319. |> List.map (mapFst Position.ofInlineRange)
  320. (range, msg) :: xs
  321. |> publishDiagnostics uri
  322. |> Async.RunSynchronously
  323. st.Tokens
  324. |> List.map (fun x -> struct (x.Range, x.TokenType) )
  325. |> Array.ofList
  326. member __.GetHighlighting(p : HighlightingRequest) =
  327. let uri = System.Uri p.FileName
  328. let res =
  329. currentDocument
  330. |> Option.map (fun x ->
  331. let res = parse uri.AbsoluteUri x.Text
  332. { Content =
  333. CommandResponse.highlighting res
  334. |> FSharpJsonType.SerializeOption.serNotIndent }
  335. )
  336. async {
  337. return LspResult.success res
  338. }
  339. override __.TextDocumentDidChange(p) = async {
  340. if isValidDoc p.TextDocument.Uri then
  341. currentDocument <-
  342. currentDocument
  343. |> Option.map (fun x ->
  344. { x with
  345. Uri = p.TextDocument.Uri
  346. Version = Option.defaultValue x.Version p.TextDocument.Version
  347. Text =
  348. match Array.tryExactlyOne p.ContentChanges with
  349. | Some x ->
  350. // documentRange <- x.Range // увы, но при `TextDocumentSyncKind.Full` он всегда равен `None`
  351. x.Text
  352. | None ->
  353. // do! client.WindowLogMessage {
  354. // Type = MessageType.Error
  355. // Message = sprintf "Array.tryExactlyOne p.ContentChanges error:\n%A" p.ContentChanges
  356. // }
  357. failwith "Array.tryExactlyOne p.ContentChanges = None"
  358. }
  359. )
  360. }
  361. override __.TextDocumentDidOpen(p: DidOpenTextDocumentParams) = async {
  362. // Вот что, этот негодяй одновременно открывает целую кучу всего: здесь и git, и обычный файл, и даже output. Надо бы как-то за всем этим уследить.
  363. // "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"
  364. // p.TextDocument
  365. if p.TextDocument.LanguageId = "qsp" && isValidDoc p.TextDocument.Uri then
  366. currentDocument <- Some p.TextDocument
  367. // documentUri <- p.TextDocument.Uri
  368. // documentVersion <- Some p.TextDocument.Version
  369. // documentText <- p.TextDocument.Text
  370. do! client.WindowLogMessage {
  371. Type = MessageType.Info
  372. Message =
  373. let txt = p.TextDocument
  374. sprintf "TextDocumentDidOpen\n%A"
  375. ( txt.LanguageId, txt.Uri, txt.Version)
  376. }
  377. // if Set.contains (p.TextDocument.Uri.ToLower()) spellcheckIgnore then
  378. // do! client.SpellcheckDecorate []
  379. // else
  380. // let textDoc = p.TextDocument
  381. // documentVersion <- Some textDoc.Version
  382. // do! client.WindowLogMessage {
  383. // Type = MessageType.Info
  384. // Message = "TextDocumentDidOpen"
  385. // }
  386. // reactor (textDoc.Uri, textDoc.Text)
  387. }
  388. override __.TextDocumentDidClose p = async {
  389. do! client.WindowLogMessage {
  390. Type = MessageType.Info
  391. Message =
  392. sprintf "TextDocumentDidClose\n%A" p
  393. }
  394. }
  395. override __.WorkspaceDidChangeWatchedFiles p = async {
  396. do! client.WindowLogMessage {
  397. Type = MessageType.Info
  398. Message =
  399. sprintf "WorkspaceDidChangeWatchedFiles\n%A" p
  400. }
  401. }
  402. member private __.IfDiagnostic (str: string) handler p =
  403. let diag =
  404. p.Context.Diagnostics |> Seq.tryFind (fun n -> n.Message.Contains str)
  405. match diag with
  406. | None -> async.Return []
  407. | Some d -> handler d
  408. member private __.CreateFix uri ver title (d: Diagnostic option) range replacement =
  409. let e =
  410. {
  411. Range = range
  412. NewText = replacement
  413. }
  414. let edit =
  415. {
  416. TextDocument =
  417. {
  418. Uri = uri
  419. Version = ver
  420. }
  421. Edits = [|e|]
  422. }
  423. let we = WorkspaceEdit.Create([|edit|], clientCapabilities.Value)
  424. { CodeAction.Title = title
  425. Kind = Some "quickfix"
  426. Diagnostics = d |> Option.map Array.singleton
  427. Edit = we
  428. Command = None}
  429. // override this.TextDocumentCodeAction p = async {
  430. // if Set.contains (p.TextDocument.Uri.ToLower()) spellcheckIgnore then
  431. // // TODO: если текущий файл отсеивается, то самое время как-то избавить весь документ от ошибок, вот только как это сделать?
  432. // // return LspResult.Ok None // Пробовал — выбивает ошибку
  433. // return LspResult.success (Some (TextDocumentCodeActionResult.CodeActions [||]))
  434. // else
  435. // let! res =
  436. // p
  437. // |> this.IfDiagnostic "unknown " (fun d ->
  438. // async {
  439. // do! client.WindowLogMessage {
  440. // Type = MessageType.Info
  441. // Message = (sprintf "TextDocumentCodeAction 'unknown ...'")
  442. // }
  443. // match Map.tryFind d.Range lastWords with
  444. // | Some word ->
  445. // let words =
  446. // Suggestion.LevenshteinDistance.suggestions3 word dic
  447. // |> Suggestion.LevenshteinDistance.mapTruncate 10
  448. // let actions =
  449. // words
  450. // |> List.map (fun word ->
  451. // this.CreateFix p.TextDocument.Uri documentVersion (sprintf "replace on '%s'" word) (Some d) d.Range word)
  452. // return actions
  453. // | None ->
  454. // do! client.WindowLogMessage {
  455. // Type = MessageType.Info
  456. // Message = (sprintf "range not found:\n%A" d.Range)
  457. // }
  458. // return []
  459. // }
  460. // )
  461. // return res |> Array.ofList |> TextDocumentCodeActionResult.CodeActions |> Some |> LspResult.success
  462. // }
  463. override __.WorkspaceDidChangeConfiguration (x) = async {
  464. do! client.WindowLogMessage {
  465. Type = MessageType.Info
  466. Message = sprintf "WorkspaceDidChangeConfiguration\n%A" (x.Settings.ToString())
  467. }
  468. let configResult : Either<_, Config> =
  469. let ser = Newtonsoft.Json.JsonSerializer()
  470. ser.Converters.Add FSharpJsonType.SerializeOption.converter
  471. try
  472. x.Settings.ToObject(ser)
  473. |> Right
  474. with
  475. e -> Left e.Message
  476. match configResult with
  477. | Right x ->
  478. x.Qsp
  479. |> Option.iter (fun x ->
  480. config <- x
  481. )
  482. | Left msg ->
  483. do! client.WindowLogMessage {
  484. Type = MessageType.Error
  485. Message = sprintf "%s\n%s" (x.Settings.ToString()) msg
  486. }
  487. }
  488. override x.TextDocumentFormatting p = async {
  489. // p.Options.AdditionalData // версия 1.46.1, и их всё еще не завезли https://code.visualstudio.com/api/references/vscode-api#FormattingOptions
  490. match currentDocument with
  491. | Some currentDocument ->
  492. if p.TextDocument.Uri = currentDocument.Uri then
  493. match lastCharPos with
  494. | Some lastCharPos ->
  495. match parserResult with
  496. | Some r ->
  497. match r with
  498. | FParsec.CharParsers.Success(x, _, _) ->
  499. return
  500. { TextEdit.Range =
  501. {
  502. Start = { Line = 0
  503. Character = 0 }
  504. End = { Line = int lastCharPos.Line - 1
  505. Character = int lastCharPos.Column - 1 } // а быть может, даже `- 2`
  506. }
  507. NewText =
  508. if p.Options.InsertSpaces then
  509. Qsp.Show.UsingSpaces p.Options.TabSize
  510. else
  511. Qsp.Show.UsingTabs
  512. |> fun indentsOpt -> Qsp.Show.printLocs indentsOpt config.FormatConfig x }
  513. |> Array.singleton
  514. |> Some
  515. |> LspResult.success
  516. | FParsec.CharParsers.Failure(_, _, _) ->
  517. do! client.WindowShowMessage {
  518. Type = MessageType.Error
  519. Message = sprintf "Синтаксис содержит ошибки, потому форматировать его невозможно"
  520. }
  521. return LspResult.success None
  522. | None ->
  523. do! client.WindowLogMessage {
  524. Type = MessageType.Error
  525. Message = sprintf "lastSymbolPos = None"
  526. }
  527. return LspResult.success None
  528. | None ->
  529. do! client.WindowLogMessage {
  530. Type = MessageType.Error
  531. Message = sprintf "documentRange = None"
  532. }
  533. return LspResult.success None
  534. else
  535. do! client.WindowLogMessage {
  536. Type = MessageType.Error
  537. Message = sprintf "p.TextDocument.Uri <> documentUri"
  538. }
  539. return LspResult.success None
  540. | None ->
  541. return LspResult.success None
  542. }
  543. override __.TextDocumentDocumentHighlight(x) = async {
  544. // do! client.WindowLogMessage {
  545. // Type = MessageType.Error
  546. // Message = sprintf "%A" (varHovers, x.Position)
  547. // }
  548. let f fn =
  549. match fn x.Position with
  550. | None -> None
  551. | Some xs ->
  552. xs // должно находить всегда
  553. |> List.map (fun (r, kind) ->
  554. {
  555. DocumentHighlight.Range = Position.ofInlineRange r
  556. Kind =
  557. match kind with
  558. | Qsp.Parser.Generic.WriteAccess -> DocumentHighlightKind.Write
  559. | Qsp.Parser.Generic.ReadAccess -> DocumentHighlightKind.Read
  560. |> Some
  561. }
  562. )
  563. |> Array.ofList
  564. |> Some
  565. let x =
  566. f getVarHighlight
  567. |> Option.orElseWith (fun () ->
  568. f getLocHighlight
  569. )
  570. return LspResult.success x
  571. }
  572. override __.TextDocumentRename renameParams = async {
  573. let f fn =
  574. match fn renameParams.Position with
  575. | None -> None
  576. | Some xs ->
  577. let edits =
  578. [|
  579. {
  580. Edits =
  581. xs
  582. |> List.map (fun (r, _) ->
  583. {
  584. TextEdit.Range = Position.ofInlineRange r
  585. NewText = renameParams.NewName
  586. }
  587. )
  588. |> Array.ofList
  589. TextDocument =
  590. {
  591. Version = currentDocument |> Option.map (fun x -> x.Version)
  592. Uri = renameParams.TextDocument.Uri
  593. }
  594. }
  595. |]
  596. WorkspaceEdit.Create(edits, clientCapabilities.Value) // TODO: а если `None`?
  597. |> Some
  598. let x =
  599. f getVarHighlight
  600. |> Option.orElseWith (fun () ->
  601. f getLocHighlight
  602. )
  603. return LspResult.success x
  604. }
  605. override __.TextDocumentDefinition textDocumentPositionParams = async {
  606. let f fn =
  607. match fn textDocumentPositionParams.Position with
  608. | None -> None
  609. | Some xs ->
  610. xs
  611. |> List.choose (fun (r, kind) ->
  612. match kind with
  613. | Qsp.Parser.Generic.WriteAccess ->
  614. {
  615. Location.Uri = textDocumentPositionParams.TextDocument.Uri
  616. Location.Range = Position.ofInlineRange r
  617. }
  618. |> Some
  619. | _ -> None
  620. )
  621. |> Array.ofList
  622. |> GotoResult.Multiple
  623. |> Some
  624. let x =
  625. f getVarHighlight
  626. |> Option.orElseWith (fun () ->
  627. f getLocHighlight
  628. )
  629. return LspResult.success x
  630. }
  631. override __.TextDocumentHover textDocumentPositionParams =
  632. async {
  633. let res =
  634. hovers
  635. |> List.tryFind (fun (r, _) ->
  636. if (r.Start.Line = textDocumentPositionParams.Position.Line) && (r.Start.Line = r.End.Line) then
  637. r.Start.Character <= textDocumentPositionParams.Position.Character && textDocumentPositionParams.Position.Character <= r.End.Character
  638. elif r.Start.Line <= textDocumentPositionParams.Position.Line && textDocumentPositionParams.Position.Line <= r.End.Line then
  639. false // TODO: @high ¯\_(ツ)_/¯
  640. else
  641. false // TODO: @high ¯\_(ツ)_/¯
  642. )
  643. // do! client.WindowLogMessage {
  644. // Type = MessageType.Error
  645. // Message = sprintf "%A" (hovers, x.Position)
  646. // }
  647. let x =
  648. match res with
  649. | None -> None
  650. | Some(r, msg) ->
  651. let msg =
  652. match msg with
  653. | Parser.Generic.HoverDescription.FuncDescription predefFunc ->
  654. Map.tryFind predefFunc Defines.functionBySymbolic
  655. |> Option.map (fun x -> x.Description)
  656. |> Option.defaultValue ""
  657. | Parser.Generic.HoverDescription.RawDescription x -> x
  658. {
  659. Hover.Contents =
  660. HoverContent.MarkupContent (markdown msg)
  661. Range = Some r
  662. }
  663. |> Some
  664. return LspResult.success x
  665. }
  666. override __.TextDocumentReferences refParams = async {
  667. // refParams.Context.IncludeDeclaration // загадочный параметр
  668. let f fn =
  669. match fn refParams.Position with
  670. | None -> None
  671. | Some xs ->
  672. xs
  673. |> List.map (fun (r, _) ->
  674. {
  675. Location.Uri = refParams.TextDocument.Uri
  676. Location.Range = Position.ofInlineRange r
  677. }
  678. )
  679. |> Array.ofList
  680. |> Some
  681. let x =
  682. f getVarHighlight
  683. |> Option.orElseWith (fun () ->
  684. f getLocHighlight
  685. )
  686. return LspResult.success x
  687. }
  688. override __.TextDocumentFoldingRange foldingRangeParams =
  689. // let x =
  690. // {
  691. // FoldingRange.StartLine = 0
  692. // StartCharacter = failwith "Not Implemented"
  693. // EndLine = failwith "Not Implemented"
  694. // EndCharacter = failwith "Not Implemented"
  695. // Kind = Some FoldingRangeKind.Region
  696. // }
  697. // foldingRangeParams.TextDocument.Uri
  698. async {
  699. return LspResult.success None
  700. }
  701. override __.WorkspaceDidChangeWorkspaceFolders p = async {
  702. do! client.WindowLogMessage {
  703. Type = MessageType.Info
  704. Message = sprintf "WorkspaceDidChangeWorkspaceFolders:\n%A" p
  705. }
  706. }
  707. // override __.WorkspaceWorkspaceFolders p = async {
  708. // }
  709. member __.FSharpWorkspaceLoad (p:WorkspaceLoadParms) = async {
  710. // Возвращает что-то в духе "e:\Project\Qsp\QspSyntax\sample-code", а не Uri, как там написано
  711. // currentWorkspacePath <- p.TextDocuments.[0].Uri
  712. // let dir = @"e:\Project\Qsp\QspSyntax\sample-code"
  713. // let projFiles = System.IO.Directory.GetFiles(dir, "*.qproj", System.IO.SearchOption.AllDirectories)
  714. // match projFiles with
  715. // | [||] -> () // "`.qproj` не найден"
  716. // | [|projFile|] ->
  717. // projFile <- System.IO.Path.GetDirectoryName projFile
  718. // | projFiles ->
  719. // // TODO: ошибкой было бы, если бы в одной и той же папке (или подпапке) было бы несколько файлов .qproj. Для всех остальных случаев нужно организовать работу с несколькими проектами. А ведь есть еще WorkspaceFolders.
  720. // do! client.WindowShowMessage {
  721. // Type = MessageType.Error
  722. // Message = sprintf "`.qproj` должен быть только один на весь проект, однако:\n%A" projFiles
  723. // }
  724. // do! client.WindowLogMessage {
  725. // Type = MessageType.Info
  726. // Message = sprintf "FSharpWorkspaceLoad:\n%A" p
  727. // }
  728. return LspResult.success None
  729. }
  730. override __.TextDocumentDocumentSymbol documentSymbolParams = async {
  731. let x =
  732. match currentDocument with
  733. | Some currentDocument ->
  734. let documentUri = currentDocument.Uri
  735. if documentUri = documentSymbolParams.TextDocument.Uri then
  736. highlights.LocHighlights.Ma
  737. |> Seq.choose (fun (KeyValue(locName, v)) ->
  738. v
  739. |> List.tryPick (fun (r, typ) ->
  740. if typ = Parser.Generic.VarHighlightKind.WriteAccess then
  741. {
  742. ContainerName = None
  743. Name = locName
  744. Kind = SymbolKind.Function
  745. Location =
  746. {
  747. Location.Uri = documentUri
  748. Range = Position.ofInlineRange r
  749. }
  750. } |> Some
  751. else None
  752. )
  753. )
  754. |> Seq.sortBy (fun x -> x.Location.Range.Start.Line)
  755. |> Array.ofSeq
  756. |> Some
  757. else None
  758. | None -> None
  759. return LspResult.success x
  760. }
  761. override __.CompletionItemResolve completionItem = async {
  762. do! client.WindowLogMessage {
  763. Type = MessageType.Info
  764. Message = sprintf "CompletionItemResolve:\n%A" completionItem
  765. }
  766. return LspResult.success completionItem
  767. }
  768. // override __.TextDocumentCompletion completionParams = async {
  769. // do! client.WindowLogMessage {
  770. // Type = MessageType.Info
  771. // Message = sprintf "TextDocumentCompletion:\n%A" completionParams
  772. // }
  773. // let x =
  774. // {
  775. // CompletionList.IsIncomplete = false
  776. // Items = [
  777. // CompletionItem.Label = ""
  778. // ]
  779. // }
  780. // return LspResult.success None
  781. // }
  782. member __.BuildSource (uriStr:UriString) isRun =
  783. async {
  784. if System.Environment.OSVersion.Platform = System.PlatformID.Win32NT then
  785. // let uri = "file:///e:/Project/Qsp/QSP-LSP/3rd/txt2gam.exe"
  786. let uri =
  787. try
  788. let uri = System.Uri uriStr
  789. uri.LocalPath
  790. |> Right
  791. with e ->
  792. Left e.Message
  793. let res =
  794. match uri with
  795. | Right path ->
  796. try
  797. let code, output = buildQsp path
  798. if code = 0 then
  799. if isRun then
  800. changeExtensionToQsp path
  801. |> System.Diagnostics.Process.Start
  802. |> ignore
  803. Choice2Of2 "Ok"
  804. else
  805. Choice1Of2 (sprintf "txt2gam returned:\n%s" output)
  806. with e ->
  807. Choice1Of2 e.Message
  808. | Left err ->
  809. Choice1Of2 (sprintf "'%s'\n%A" uriStr err)
  810. return LspResult.success res
  811. else
  812. let res = Choice1Of2 (sprintf "Пока что txt2gam есть только Windows")
  813. return LspResult.success res
  814. }
  815. override __.Initialize p =
  816. async {
  817. clientCapabilities <- p.Capabilities
  818. /// { "AutomaticWorkspaceInit": false }
  819. let c =
  820. p.InitializationOptions
  821. |> Option.map (fun x -> x.ToString())
  822. return
  823. { Types.InitializeResult.Default with
  824. Capabilities =
  825. { Types.ServerCapabilities.Default with
  826. HoverProvider = Some true
  827. RenameProvider = Some true
  828. DefinitionProvider = Some true
  829. TypeDefinitionProvider = Some true
  830. ImplementationProvider = Some true
  831. ReferencesProvider = Some true
  832. DocumentHighlightProvider = Some true
  833. DocumentSymbolProvider = Some true
  834. WorkspaceSymbolProvider = Some false
  835. DocumentFormattingProvider = Some true
  836. DocumentRangeFormattingProvider = Some false
  837. SignatureHelpProvider =
  838. // Some {
  839. // SignatureHelpOptions.TriggerCharacters = Some [| "("; ","|]
  840. // }
  841. None
  842. CompletionProvider =
  843. None
  844. CodeLensProvider =
  845. // Some {
  846. // CodeLensOptions.ResolveProvider = Some true
  847. // }
  848. None
  849. CodeActionProvider = Some false
  850. TextDocumentSync =
  851. Some { TextDocumentSyncOptions.Default with
  852. OpenClose = Some true
  853. Change = Some TextDocumentSyncKind.Full
  854. Save = Some { IncludeText = Some true }
  855. }
  856. FoldingRangeProvider = None
  857. }
  858. }
  859. |> LspResult.success
  860. }
  861. type LocalSetting = {
  862. DicPath : string
  863. }
  864. open FsharpMyExtension
  865. [<EntryPoint>]
  866. let main argv =
  867. use input = System.Console.OpenStandardInput()
  868. use output = System.Console.OpenStandardOutput()
  869. let requestsHandlings =
  870. defaultRequestHandlings<BackgroundServiceServer>()
  871. |> Map.add "fsharp/highlighting" (requestHandling (fun s p -> s.GetHighlighting(p) ))
  872. |> Map.add "fsharp/workspaceLoad" (requestHandling (fun s p -> s.FSharpWorkspaceLoad(p) ))
  873. |> Map.add "qsp/build" (requestHandling (fun s p -> s.BuildSource p false ))
  874. |> Map.add "qsp/buildAndRun" (requestHandling (fun s p -> s.BuildSource p true ))
  875. Server.start requestsHandlings input output FsacClient (fun lspClient -> BackgroundServiceServer((), lspClient))
  876. |> printfn "%A"
  877. 0