├── .gitignore ├── .gitmodules ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── coffeelint.json ├── lib ├── completion-backend │ ├── buffer-info.js │ ├── index.js │ └── module-info.js ├── config.js ├── ghc-mod │ ├── build-stack.js │ ├── ghc-modi-process-real-factory.js │ ├── ghc-modi-process-real.js │ ├── index.js │ ├── interactive-process.js │ └── settings.js ├── haskell-ghc-mod.js ├── upi-consumer.js ├── util.js └── views │ └── import-list-view.js ├── package.json ├── spec ├── package.spec.ts ├── tsconfig.json └── tslint.json ├── src ├── atom-config.d.ts ├── atom.d.ts ├── completion-backend │ ├── buffer-info.ts │ ├── index.ts │ └── module-info.ts ├── config.ts ├── ghc-mod │ ├── build-stack.ts │ ├── ghc-modi-process-real-factory.ts │ ├── ghc-modi-process-real.ts │ ├── index.ts │ ├── interactive-process.ts │ └── settings.ts ├── haskell-ghc-mod.ts ├── upi-consumer.ts ├── util.ts └── views │ └── import-list-view.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "typings"] 2 | path = typings 3 | url = git@github.com:atom-haskell/typings.git 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | trailingComma: all 4 | arrowParens: always 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | ### Project specific config ### 2 | language: generic 3 | 4 | env: 5 | global: 6 | - APM_TEST_PACKAGES="" 7 | - ATOM_LINT_WITH_BUNDLED_NODE="true" 8 | 9 | matrix: 10 | - ATOM_CHANNEL=stable 11 | - ATOM_CHANNEL=beta 12 | 13 | os: 14 | - linux 15 | 16 | # Use sed to replace the SSH URL with the public URL, then initialize submodules 17 | before_install: 18 | - sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules 19 | - git submodule update --init --recursive 20 | 21 | ### Generic setup follows ### 22 | script: 23 | - curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh 24 | - chmod u+x build-package.sh 25 | - ./build-package.sh 26 | - npm run build 27 | - npm run test 28 | 29 | notifications: 30 | email: 31 | on_success: never 32 | on_failure: change 33 | 34 | branches: 35 | only: 36 | - master 37 | 38 | git: 39 | depth: 10 40 | submodules: false 41 | 42 | sudo: false 43 | 44 | dist: trusty 45 | 46 | addons: 47 | apt: 48 | packages: 49 | - build-essential 50 | - fakeroot 51 | - git 52 | - libsecret-1-dev 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.2.6 2 | 3 | - Limit pidusage version range 4 | 5 | ## 2.2.5 6 | 7 | - Update dependencies 8 | - Don't relativize and absolute paths in messages 9 | 10 | ## 2.2.4 11 | 12 | - Define asyncIterator more carefully 13 | - Use atom-ts-spec-runner 14 | - Catch tooltip errors 15 | - Fix null-strictness check 16 | - Handle some common ghc-mod related errors 17 | - Add minimal spec 18 | - Bump dependencies 19 | 20 | ## 2.2.3 21 | 22 | - Handle lint and check messages separately 23 | - Add --no-install-ghc to stack invocations 24 | - Check stack version for --copy-compiler-tool support 25 | 26 | ## 2.2.2 27 | 28 | - Tweaks and fixes to builder manager 29 | 30 | ## 2.2.1 31 | 32 | - Depend on tslib 33 | - suppressRedundantTypeInTypeAndInfoTooltips (issue \#169) 34 | 35 | ## 2.2.0 36 | 37 | - Choose cabal/stack based on ide-haskell-cabal settings and option to 38 | build ghc-mod when using stack (hidden behind `builderManagement` 39 | setting) 40 | - Make de-/re-activation more robust 41 | - Update license information 42 | - Update typings; bump dependencies; 43 | 44 | ## 2.1.3 45 | 46 | - Abort broswse when nothing to browse 47 | - Limit ghc-mod's interactive memory consumption (by killing it 48 | periodically) 49 | - Reshuffle insertTypeCommand to avoid unnecessary call to ghc-mod 50 | 51 | ## 2.1.2 52 | 53 | - Fix type/info fallbacks 54 | - Minor fixes 55 | 56 | ## 2.1.1 57 | 58 | - Update README 59 | - Add current config to spawn fail report. 60 | 61 | ## 2.1.0 62 | 63 | - Option for always interactive check, on by default 64 | - Explicit fastcheck 65 | - Optimize debuglog saving 66 | 67 | ## 2.0.10 68 | 69 | - Fix explosive log growth 70 | 71 | ## 2.0.9 72 | 73 | - Print stdout in warnings as string, not array 74 | - Enforce consistent code style 75 | 76 | ## 2.0.8 77 | 78 | - Handle some exceptions via UPI status 79 | 80 | ## 2.0.7 81 | 82 | - Remove parentheses around operators when providing completion 83 | suggestions 84 | 85 | ## 2.0.6 86 | 87 | - Define Symbol.asyncIterator 88 | 89 | ## 2.0.5 90 | 91 | - Fix type-only highlight in type-and-info tooltips 92 | 93 | ## 2.0.4 94 | 95 | - Revert "Revert to dumb browse parsing" 96 | 97 | ## 2.0.3 98 | 99 | - Use OS-dependent line-breaks for non-interactive commands too 100 | 101 | ## 2.0.2 102 | 103 | - Revert to dumb browse parsing 104 | 105 | ## 2.0.1 106 | 107 | - Use OS-dependent line breaks in InteractiveProcess 108 | 109 | ## 2.0.0 110 | 111 | - Made ghc-mod error/warning report output configurable 112 | - Whole thing rewritten in TypeScript (which hopefully makes it more 113 | robusut) 114 | - Now includes operators in completions 115 | - Migrated to UPI 0.3 116 | 117 | ## 1.19.9 118 | 119 | - Always return an array in linter code 120 | 121 | ## 1.19.8 122 | 123 | - Provide name to linter 124 | - Typo 125 | - Readme update 126 | - Refactor .json advanced settings; add project settings 127 | 128 | ## 1.19.7 129 | 130 | - Merge branch 'master' of github.com:atom-haskell/haskell-ghc-mod 131 | - Update CHANGELOG 132 | - Fix \#198 133 | - \[README\] Additional note for OS X users (\#195) (Maciej 134 | Aleksandrowicz) 135 | 136 | ## 1.19.6 137 | 138 | - Ghc-mod 5.7 is a maintenance release, no new features 139 | 140 | ## 1.19.5 141 | 142 | - Merge remote-tracking branch 'origin/atom-1.13' 143 | - s/target/currentTarget/ 144 | - Atom 1.13 update 145 | 146 | ## 1.19.4 147 | 148 | - Make interactive-process output buffering more robust 149 | 150 | ## 1.19.3 151 | 152 | - s/target/currentTarget/ 153 | 154 | ## 1.19.2 155 | 156 | - Add parts of bin-path to stack-sandbox 157 | 158 | ## 1.19.1 159 | 160 | - Add warning about GHC\_PACKAGE\_PATH;Settings tweaks 161 | - Fix LICENSE date 162 | - Update LICENSE 163 | 164 | ## 1.19.0 165 | 166 | - Support for arbitrary ghc-mod options 167 | - Global configuration; ghcOptions config option 168 | - Don't spew warnings about missing .haskell-ghc-mod.json 169 | 170 | ## 1.18.1 171 | 172 | - Show all candidates on empty prefix 173 | 174 | ## 1.18.0 175 | 176 | - Include all qualifications in suggestions, i.e. fully qualified, 177 | alias-qualified and unqualified (if applicable) 178 | - Include type constructors into suggestions (if applicable) 179 | - Show type constructors as types instead of functions 180 | - Use --numeric-version for ghc version 181 | 182 | ## 1.17.2 183 | 184 | - Fix types containing \" 185 | 186 | ## 1.17.1 187 | 188 | - Update atom-haskell-utils 189 | 190 | ## 1.17.0 191 | 192 | - More options for selection tooltips 193 | 194 | ## 1.16.1 195 | 196 | - Object.observe is no longer supported 197 | 198 | ## 1.16.0 199 | 200 | - Change onMouseHoverShow default to 'Type and Info' 201 | - More type/info tooltip options 202 | - Add sig-fill command 203 | - Fix caps display in error messages 204 | - Kill ghc-modi on InteractiveActionTimeout 205 | - Add special handler for interactive action timeout 206 | 207 | ## 1.15.3 208 | 209 | - Make interactiveActionTimeout actually work 210 | 211 | ## 1.15.2 212 | 213 | - Configurable timeouts 214 | 215 | ## 1.15.1 216 | 217 | - Fix Promise.resolve 218 | 219 | ## 1.15.0 220 | 221 | - Add .haskell-ghc-mod.json to readme 222 | - Add message on .haskell-ghc-mod.json parse error 223 | - Handle JSON.parse errors 224 | - .haskell-ghc-mod.json 225 | 226 | ## 1.14.7 227 | 228 | - Add contributors to README 229 | - Merge pull request \#162 from DeathByTape/master 230 | - Set maxBuffer to 'Infinity'. 231 | 232 | ## 1.14.6 233 | 234 | - Use absolute paths in go-to-declaration 235 | 236 | ## 1.14.5 237 | 238 | - Add option for low-memory systems 239 | 240 | ## 1.14.4 241 | 242 | - Avoid reporting Atom bugs 243 | 244 | ## 1.14.3 245 | 246 | - Dir/Path cleanup in initBackend 247 | - Use \_.extend for extending process options 248 | - Log options as object 249 | - Remove unused child\_process 250 | - Cleanup getVersion, checkComp 251 | - A bit more GhcModiProcessReal-related cleanup 252 | - Clean up GhcModiProcessReal 253 | - Log ghc-modi spawn options as object 254 | - Stringify log messages 255 | - Change debug text 256 | - Refactor execFile into execPromise 257 | - Don't use BufferedProcess because WEIRDNESS on Win 258 | - Print stderr/stdout when checking ghc version 259 | - Print stdout on stack path error 260 | - getProcessOptions once in initBackend 261 | - Always show stderr on stack path 262 | 263 | ## 1.14.2 264 | 265 | - Use 'n' as EOL in Atom 266 | - Merge branch 'upi-refactor' 267 | - UPI refactor to sep. module 268 | 269 | ## 1.14.1 270 | 271 | - Don't throw when checking for compiler 272 | - Fix message on path 273 | 274 | ## 1.14.0 275 | 276 | - Delayed process initialization; per-dir backend 277 | - AHS bump 278 | - Atom-haskell-utils bump 279 | 280 | ## 1.13.2 281 | 282 | - Use install-root for stack sandbox detection 283 | 284 | ## 1.13.1 285 | 286 | - Fixup for sporadic spaces in hlint parse error 287 | 288 | ## 1.13.0 289 | 290 | - Check compiler version 291 | - Fix pesky timeout problem 292 | - Use promise in getProcessOptions 293 | - Remove redundant condition 294 | - Removed support for older ghc-mod versions 295 | 296 | ## 1.12.6 297 | 298 | - Attempt at fixing stack sandbox resolution on Win 299 | 300 | ## 1.12.5 301 | 302 | - Oops 303 | 304 | ## 1.12.4 305 | 306 | - Fix \#143 307 | 308 | ## 1.12.3 309 | 310 | - More debug output with sandboxes 311 | - Update type signature/parent separator 312 | 313 | ## 1.12.2 314 | 315 | - Handle parse errors in parseHsModuleImports 316 | 317 | ## 1.12.1 318 | 319 | - Make imports parsing asynchronous 320 | 321 | ## 1.12.0 322 | 323 | - Option to disable ghc-mod for some projects 324 | 325 | ## 1.11.5 326 | 327 | - Add timeout to `stack path` call 328 | 329 | ## 1.11.4 330 | 331 | - Collect different capitalizations of PATH on win32 332 | 333 | ## 1.11.3 334 | 335 | - Don't run commands on buffer without URI 336 | 337 | ## 1.11.2 338 | 339 | - Fix \#125 340 | 341 | ## 1.11.1 342 | 343 | - Make case-split non-interactive 344 | - Extend 'experimental' setting description 345 | - Experimental features toggle 346 | - Ghc-mod 5.6 initial Type constraints Browse parents 347 | - Handle (..) imports 348 | - True module import parsing 349 | 350 | ## 1.11.0 351 | 352 | - Added case split 353 | - Warning should be shown as warnings 354 | 355 | ## 1.10.3 356 | 357 | - Filter out empty lines etc 358 | 359 | ## 1.10.2 360 | 361 | - Show dummy messages as notifications 362 | 363 | ## 1.10.1 364 | 365 | - Removed debug log 366 | 367 | ## 1.10.0 368 | 369 | - User-supplied hlint options 370 | - Typo 371 | 372 | ## 1.9.6 373 | 374 | - Linter support docs 375 | - Update supported versions 376 | 377 | ## 1.9.5 378 | 379 | - Handle ghc-mod 4.1.2 version 380 | 381 | ## 1.9.4 382 | 383 | - Fix an error where tabUnshift would fail 384 | 385 | ## 1.9.3 386 | 387 | - Create new instance for tab\[Un\]Shift 388 | - Improved insert-type 389 | - Insert inline type signatures 390 | - Filter empty type ranges out of ghc-mod output 391 | - Properly insert type into lhs 392 | - Fix insert-type for operators 393 | 394 | ## 1.9.2 395 | 396 | - Handling no value in onShouldShowTooltip delegated to ide-haskell 397 | 398 | ## 1.9.1 399 | 400 | - Fix atom-haskell/ide-haskell\#145 401 | 402 | ## 1.9.0 403 | 404 | - Show type on selection (disabled by default) 405 | 406 | ## 1.8.0 407 | 408 | - Some black magic to work around tabs 409 | - Somewhat better linter integration 410 | - Do away with ::shadow selector 411 | 412 | ## 1.7.0 413 | 414 | - Output message highlighter 415 | - Tooltip highlighting 416 | - Add go-to-declaration to keymap example (pull request \#116 from 417 | @PetrGlad) 418 | 419 | ## 1.6.6 420 | 421 | - Fix \#106 422 | 423 | ## 1.6.5 424 | 425 | - Partial fix for explicit type w/ctors import 426 | - Fix ide-haskell/issues/136 427 | 428 | ## 1.6.4 429 | 430 | - Fix getSymbolAtPoint handling of symbol at start of line (pull 431 | request \#97 from @jacksonja) 432 | - Use Util.isDirectory instead of FS.statSync 433 | 434 | ## 1.6.3 435 | 436 | - atom-haskell-utils 437 | 438 | ## 1.6.2 439 | 440 | - atom-haskell-utils version bump 441 | 442 | ## 1.6.1 443 | 444 | - Try to fix \#95 445 | - Remove debug print 446 | 447 | ## 1.6.0 448 | 449 | - Actually parse cabal.sanbox.config, stack bin-path 450 | - Update README.md with link to stack info 451 | 452 | ## 1.5.9 453 | 454 | - Fix \#94 455 | 456 | ## 1.5.8 457 | 458 | - Hopefully fix tokenization problems 459 | 460 | ## 1.5.7 461 | 462 | - Replace anything resembling newline in int. proc. 463 | 464 | ## 1.5.6 465 | 466 | - Remove empty arguments altogether 467 | 468 | ## 1.5.5 469 | 470 | - Don't quote empty strings 471 | 472 | ## 1.5.4 473 | 474 | - Prefer relative paths with ghc-modi\<5.5.0.0 475 | 476 | ## 1.5.3 477 | 478 | - BUGFIX: atom-haskell-utils bump 479 | 480 | ## 1.5.2 481 | 482 | - Fix isDirectory bug 483 | 484 | ## 1.5.1 485 | 486 | - Update and enable quoteArgs 487 | 488 | ## 1.5.0 489 | 490 | - Use library-supplied getRootDir/Fallback 491 | - Lint lhs files 492 | - Fix TypeError 493 | - Add package keywords 494 | - Organize source files 495 | - Activate on language-haskell:grammar-used 496 | - Async version 497 | - Defer `require`s until needed 498 | 499 | ## 1.4.1 500 | 501 | - FIX: Fat arrow 502 | - Include ghc-mod version into error report 503 | 504 | ## 1.4.0 505 | 506 | - ghc-mod error reporting 507 | - Don't enable quoteArgs yet 508 | 509 | ## 1.3.1 510 | 511 | - Better error reporting 512 | 513 | ## 1.3.0 514 | 515 | - Go-to-decl context menu item 516 | - Go-to-decl initial 517 | - Move EOT to Util 518 | - Cleaner debug vers,caps 519 | 520 | ## 1.2.8 521 | 522 | - Fix spawn leak 523 | - Fix error reporting 524 | 525 | ## 1.2.7 526 | 527 | - Better error reporting on failing to run ghc-mod 528 | 529 | ## 1.2.6 530 | 531 | - Destroy moduleInfo on process destroy (\#75) 532 | 533 | ## 1.2.5 534 | 535 | - Fix InteractiveProcess::onExit 536 | 537 | ## 1.2.4 538 | 539 | - Buffer ghc-modi warnings 540 | - Show ghc-mod warnings in console 541 | - Check ghc-mod version; capability resolver 542 | - Set encoding in getProcessOptions() 543 | - Move ghc-modi interaction to InteractiveProcess 544 | - Handle ghc-mod errors in one place 545 | - Refactor GhcModiProcess::run into base class 546 | 547 | ## 1.2.3 548 | 549 | - Use interactive mode for 'find' 550 | - Insert imports when no other imports present (\#68) 551 | - Fix insert type with operators (\#73) 552 | 553 | ## 1.2.2 554 | 555 | - Clear interactive command timeout on exit 556 | - Less verbose interactive debug messages 557 | - Fix more of ghc-mod capitalization 558 | 559 | ## 1.2.1 560 | 561 | - Interactive commands buffering and timeout 562 | - `ghc-mod` capitalization 563 | - Allow for parallel check/lint 564 | - backend-idle signal parallel to result 565 | 566 | ## 1.2.0 567 | 568 | - Fix backend-idle notification 569 | - Configurable max browse processes 570 | - Fix ghc-modi reply parsing bug 571 | 572 | ## 1.1.9 573 | 574 | - Fix ghc-modi reply parsing bug 575 | 576 | ## 1.1.8 577 | 578 | - Use promise-queues for more robust command queues 579 | - Bounded parallel auto-completion initialization 580 | - Fix backend stop command 581 | 582 | ## 1.1.7 583 | 584 | - Queue interactive commands 585 | 586 | ## 1.1.6 587 | 588 | - Merge branch 'master' of github.com:atom-haskell/haskell-ghc-mod 589 | - Work around ghc-mod stack root bug 590 | - Merge pull request \#69 from Roughsketch/master 591 | - Fixed typo 592 | 593 | ## 1.1.5 594 | 595 | - More clear description of additionalPathDirs opt. 596 | 597 | ## 1.1.4 598 | 599 | - Check for existence of sandbox bindir 600 | 601 | ## 1.1.3 602 | 603 | - Break endless cycle in getRootDir on undefined dir 604 | - Use getRootDir with fallback 605 | 606 | ## 1.1.2 607 | 608 | - More careful ghc-modi error handling 609 | 610 | ## 1.1.1 611 | 612 | - Fix \#61 613 | 614 | ## 1.1.0 615 | 616 | - Simplify check/lint subscription code 617 | - doCheckAndLint 618 | - On-change check/lint 619 | - Use promises for check/lint 620 | - Use promises for sequential ghc-modi communication 621 | 622 | ## 1.0.0 623 | 624 | - Migration to ide-haskell UPI interface 625 | 626 | ## 0.9.14 627 | 628 | - Don't check/lint empty buffers 629 | 630 | ## 0.9.13 631 | 632 | - Change end-of-input sequence from `\EOT\n` to `\n\EOT\n` 633 | 634 | ## 0.9.12 635 | 636 | - Update Linter styles 637 | 638 | ## 0.9.11 639 | 640 | - Fix occasional EACCESS 641 | 642 | ## 0.9.10 643 | 644 | - Only pause ghc-modi stdout on command, and resume after 645 | 646 | ## 0.9.9 647 | 648 | - Use platform-specific pathsep 649 | 650 | ## 0.9.8 651 | 652 | - Use platform-specific EOL whenever possible 653 | 654 | ## 0.9.7 655 | 656 | - Use `ghc-mod legacy-interactive` with ghc-mod-5.4.0.0 and up 657 | 658 | ## 0.9.6 659 | 660 | - Configurable sync launch timeout 661 | - Fixed confusing instructions about GHC Modi disabling (@wolftune) 662 | 663 | ## 0.9.5 664 | 665 | - Fix detail view on spawn error 666 | - Update supported versions 667 | 668 | ## 0.9.4 669 | 670 | - Remove redundant debug output 671 | 672 | ## 0.9.3 673 | 674 | - Use buffer for ghc-modi responses (\#51) 675 | 676 | ## 0.9.2 677 | 678 | - Fix ghc-mod revision detection 679 | - Cache buffer-rootDir mappings 680 | 681 | ## 0.9.1 682 | 683 | - Initial support for ghc-mod 5.4.0.0 (run ghc-mod in project root dir 684 | as reported by `ghc-mod root`) 685 | 686 | ## 0.9.0 687 | 688 | - Allow for hole completion refinement 689 | 690 | ## 0.8.14 691 | 692 | - Revert tab tweaks 693 | 694 | ## 0.8.13 695 | 696 | - Check point validity in pointWithTabs functions 697 | 698 | ## 0.8.12 699 | 700 | - Fix pointWithTabs 701 | 702 | ## 0.8.11 703 | 704 | - Tweak ranges for tabs 705 | - Removed redirect-map ghc-mod version notification 706 | 707 | ## 0.8.10 708 | 709 | - Fix deactivation error 710 | 711 | ## 0.8.9 712 | 713 | - Fix \#41 (undefined path in `new Directory` error on Windows) 714 | 715 | ## 0.8.8 716 | 717 | - Fix ghc-mod args in error reporting 718 | 719 | ## 0.8.7 720 | 721 | - Hotfix \#37 (res can be undefined) 722 | 723 | ## 0.8.6 724 | 725 | - Attempt at fixing \#31 (Atom returns `atom://config` as project 726 | directory) 727 | 728 | ## 0.8.5 729 | 730 | - Attempt at fixing \#36 (rely on garbage collector more in 731 | `getBufferInfo`) 732 | 733 | ## 0.8.4 734 | 735 | - atom.project.getDirectories() can return file in some cases (\#32) 736 | 737 | ## 0.8.3 738 | 739 | - Cleaner destruction of ModuleInfo/BufferInfo. Can help with 740 | heisenbugs. 741 | 742 | ## 0.8.2 743 | 744 | - More verbose debugging output 745 | 746 | ## 0.8.1 747 | 748 | - Update redirect commands to correspond with upstream updates 749 | 750 | ## 0.8.0 751 | 752 | - Optional support of AtomLinter for displaying messages (doesn't need 753 | ide-haskell installed) 754 | - Cache backends 755 | - Drop support for older provider versions 756 | - Provider versions bumped to 1.0.0 757 | 758 | ## 0.7.12 759 | 760 | - Honor additional PATH in runLang and runFlag 761 | 762 | ## 0.7.11 763 | 764 | - Add fallback child\_process.execFile for ghc-mod commands 765 | 766 | ## 0.7.10 767 | 768 | - Initial support for literate Haskell 769 | 770 | ## 0.7.9 771 | 772 | - Fix some problems with standalone files (\#29) 773 | 774 | ## 0.7.8 775 | 776 | - Wasn't possible to disable startup warning on ide-haskell not 777 | installed. 778 | 779 | ## 0.7.7 780 | 781 | - Separate completion queues for different tasks. Should result in 782 | better responsiveness on start. 783 | 784 | ## 0.7.6 785 | 786 | - Cleanup 787 | - More debugging output 788 | 789 | ## 0.7.5 790 | 791 | - Rough fix for \#24 792 | 793 | ## 0.7.4 794 | 795 | - Try to fix path capitalization issues on Windows 796 | 797 | ## 0.7.3 798 | 799 | - Relativize path on check in case ghc-mod returns full path for some 800 | reason 801 | 802 | ## 0.7.2 803 | 804 | - Avoid two consecutive separators in PATH 805 | 806 | ## 0.7.1 807 | 808 | - `additionalPathDirectories` configuration option 809 | 810 | ## 0.7.0 811 | 812 | - Fixed typo in completion-backend.coffee (@crazymykl) 813 | - Initial support for input file redirection (WIP on ghc-mod master) 814 | - Display non-fatal ghc-mod errors in outputView (was: print to 815 | console) 816 | - Don't pass buffer text as tempfile if it's saved 817 | - Always get abs. URI for check results 818 | - General cleanup, which hopefully helps with \#20, \#21 819 | - Removed frontend 820 | 821 | ## 0.6.6 822 | 823 | - Fix bug in getCompletionsForSymbolInModule 824 | 825 | ## 0.6.5 826 | 827 | - Fix typo (`rootDi` instead of `rootDir`) 828 | - After 60 minutes of inactivity, kill ghc-modi 829 | 830 | ## 0.6.4 831 | 832 | - Return at least something from getRootDir (attempt at fixing \#17, 833 | \#18) 834 | 835 | ## 0.6.3 836 | 837 | - haskell-ide-backend 0.1.2 - adds getModulesExportingSymbolAt 838 | function 839 | - Add `'` to word search regular expressions 840 | - Properly dispose of emitters 841 | - Completion-backend internal revamp 842 | 843 | ## 0.6.2 844 | 845 | - Only search for symbol in current line 846 | - Strip newlines from ghc-modi commands 847 | - Fat arrow requied in ghc-modi proc.onExit (\#14) 848 | 849 | ## 0.6.1 850 | 851 | - haskell-ide-backend 0.1.1 852 | 853 | Returns {type/info}=undefined if no type/info found 854 | 855 | ## 0.6.0 856 | 857 | - Backend services docs and fixes 858 | - Near-final version of API 859 | - Frontend removal notice 860 | 861 | ## 0.5.1 862 | 863 | - Deprecation fix 864 | 865 | ## 0.5.0 866 | 867 | - Ide-haskell compatibility (disable editor control etc) 868 | - Queue commands 869 | - Use BufferedProcess 870 | - Haskell-ghc-mod service deprecated, new services implemented 871 | 872 | ## 0.4.3 873 | 874 | - code cleanup 875 | - add filename and path to doCheck callback 876 | - Filter current file in doCheck 877 | - Add tempfile path as getInfo callback parameter 878 | - Replace tempfile path with actual path in getInfo 879 | - Fix newlines in ghc-mod check 880 | - README update 881 | 882 | ## 0.4.2 883 | 884 | - BUGFIX: Fat arrow in main module 885 | - Force ghc-mod for file check 886 | - Remove checkOnEdit option 887 | - Don't check file on open 888 | 889 | ## 0.4.1 890 | 891 | - Fix getRootPath deprecation 892 | 893 | ## 0.4.0 894 | 895 | - Migrate to new json-based service provider 896 | - Bump atom version 897 | 898 | ## 0.3.8 899 | 900 | - Check on open only if ghc-modi enabled 901 | 902 | ## 0.3.7 903 | 904 | - Fix windows path error 905 | 906 | ## 0.3.6 907 | 908 | - Fix gutter warning tooltips 909 | 910 | ## 0.3.5 911 | 912 | - Add option to disable ghc-modi (turns out there are a couple 913 | unresolved bugs in it) 914 | 915 | ## 0.3.4 916 | 917 | - Bugfixes 918 | 919 | ## 0.3.3 920 | 921 | - Preliminary support for cabal sandboxes 922 | 923 | ## 0.3.2 924 | 925 | - Better error reporting in case of ghc-modi failure 926 | 927 | ## 0.3.1 928 | 929 | - Fixed some deprecations 930 | 931 | ## 0.3.0 932 | 933 | - Service-hub API 934 | - Fix more obscure deprecations 935 | - Don't set globals 936 | - Persistent gutter tootlips 937 | 938 | ## 0.2.1 939 | 940 | - Use theme colors for decorations 941 | - Use different colors for warnings and errors 942 | 943 | ## 0.2.0 944 | 945 | - Use temp-files to feed buffer to ghc-mod directly 946 | - Add option to check file while editing (disabled by default) 947 | 948 | ## 0.1.5 949 | 950 | - Add options for check on save and ghc-mod path 951 | 952 | ## 0.1.4 953 | 954 | - BUGFIX: Sometimes, inserting type destroyed main cursor. Avoid that. 955 | 956 | ## 0.1.3 957 | 958 | - Experimental feature: insert type into editor 959 | - Highlight expression, type of which is showing 960 | 961 | ## 0.1.2 962 | 963 | - Use observeTextEditors instead of eachEditor 964 | 965 | ## 0.1.1 966 | 967 | - Stop ghc-modi if no Haskell files are open 968 | 969 | ## 0.1.0 - First Release 970 | 971 | - Basic functionality 972 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Atom-Haskell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation & archival notice 2 | 3 | GHC-Mod has been unsupported for several years now. This package still might work with older GHC releases (where GHC-Mod still works), but overall its usefulness is questionable. Hence, this repository is deprecated and archived. 4 | 5 | If you want to discuss haskell-ghc-mod, feel free to open an issue on . 6 | 7 | # haskell-ghc-mod atom package ![](https://david-dm.org/atom-haskell/haskell-ghc-mod.svg) 8 | 9 | This package is primarily intended as backend for [ide-haskell](https://atom.io/packages/ide-haskell). 10 | 11 | Haskell ghc-mod opens pipe to ghc-modi and queries types, info and checks 12 | for errors. 13 | 14 | ## Installation and configuration 15 | 16 | Please refer to the official documentation site 17 | 18 | ## Service-hub API 19 | 20 | Since 1.0.0, haskell-ghc-mod provides `haskell-completion-backend` service. 21 | 22 | **NOTE**: Prior to 1.0.0, ide-backend service was provided. It has been scrapped in favor of ide-haskell's UPI. 23 | 24 | You can find description in [src/completion-backend/index.ts][2] 25 | 26 | [2]:https://github.com/atom-haskell/haskell-ghc-mod/blob/master/src/completion-backend/index.ts 27 | 28 | # License 29 | 30 | Copyright © 2015 Atom-Haskell 31 | 32 | Contributors (by number of commits): 33 | 34 | 35 | * Nikolay Yakimov 36 | * Daniel Gröber 37 | * Petr Gladkikh 38 | * Mike MacDonald 39 | * Maiddog 40 | * Maciej Aleksandrowicz 41 | * Jason Jackson 42 | * Dennis J. McWherter Jr 43 | * Aaron Wolf 44 | 45 | 46 | 47 | See the [LICENSE.md][LICENSE] for details. 48 | 49 | [LICENSE]: https://github.com/atom-haskell/haskell-ghc-mod/blob/master/LICENSE.md 50 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrow_spacing": { 3 | "name": "arrow_spacing", 4 | "level": "error" 5 | }, 6 | "ensure_comprehensions": { 7 | "name": "ensure_comprehensions", 8 | "level": "error" 9 | }, 10 | "max_line_length": { 11 | "name": "max_line_length", 12 | "value": 120, 13 | "level": "error", 14 | "limitComments": true 15 | }, 16 | "indentation": { 17 | "name": "indentation", 18 | "value": 2, 19 | "level": "error" 20 | }, 21 | "no_empty_param_list": { 22 | "name": "no_empty_param_list", 23 | "level": "error" 24 | }, 25 | "cyclomatic_complexity": { 26 | "name": "cyclomatic_complexity", 27 | "value": 22, 28 | "level": "error" 29 | }, 30 | "no_unnecessary_fat_arrows": { 31 | "name": "no_unnecessary_fat_arrows", 32 | "level": "error" 33 | }, 34 | "space_operators": { 35 | "name": "space_operators", 36 | "level": "error" 37 | }, 38 | "spacing_after_comma": { 39 | "name": "spacing_after_comma", 40 | "level": "error" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/completion-backend/buffer-info.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const atom_1 = require("atom"); 4 | const atom_haskell_utils_1 = require("atom-haskell-utils"); 5 | class BufferInfo { 6 | constructor(buffer) { 7 | this.buffer = buffer; 8 | this.oldText = ''; 9 | this.oldImports = { name: 'Main', imports: [] }; 10 | this.destroy = () => { 11 | this.disposables.dispose(); 12 | }; 13 | this.disposables = new atom_1.CompositeDisposable(); 14 | this.disposables.add(this.buffer.onDidDestroy(this.destroy)); 15 | } 16 | async getImports() { 17 | const parsed = await this.parse(); 18 | const imports = parsed ? parsed.imports : []; 19 | if (!imports.some(({ name }) => name === 'Prelude')) { 20 | imports.push({ 21 | qualified: false, 22 | hiding: false, 23 | name: 'Prelude', 24 | importList: null, 25 | alias: null, 26 | }); 27 | } 28 | return imports; 29 | } 30 | async getModuleName() { 31 | const parsed = await this.parse(); 32 | return parsed.name; 33 | } 34 | async parse() { 35 | const newText = this.buffer.getText(); 36 | if (this.oldText === newText) { 37 | return this.oldImports; 38 | } 39 | else { 40 | this.oldText = newText; 41 | this.oldImports = await atom_haskell_utils_1.parseHsModuleImports(this.buffer.getText()); 42 | return this.oldImports; 43 | } 44 | } 45 | } 46 | exports.BufferInfo = BufferInfo; 47 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVmZmVyLWluZm8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29tcGxldGlvbi1iYWNrZW5kL2J1ZmZlci1pbmZvLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsK0JBQXNEO0FBQ3RELDJEQUkyQjtBQUkzQixNQUFhLFVBQVU7SUFLckIsWUFBNEIsTUFBa0I7UUFBbEIsV0FBTSxHQUFOLE1BQU0sQ0FBWTtRQUh0QyxZQUFPLEdBQVcsRUFBRSxDQUFBO1FBQ3BCLGVBQVUsR0FBbUIsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxFQUFFLEVBQUUsQ0FBQTtRQU8zRCxZQUFPLEdBQUcsR0FBRyxFQUFFO1lBQ3BCLElBQUksQ0FBQyxXQUFXLENBQUMsT0FBTyxFQUFFLENBQUE7UUFDNUIsQ0FBQyxDQUFBO1FBTkMsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLDBCQUFtQixFQUFFLENBQUE7UUFDNUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUE7SUFDOUQsQ0FBQztJQU1NLEtBQUssQ0FBQyxVQUFVO1FBQ3JCLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFBO1FBQ2pDLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFBO1FBRTVDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLENBQUMsSUFBSSxLQUFLLFNBQVMsQ0FBQyxFQUFFO1lBQ25ELE9BQU8sQ0FBQyxJQUFJLENBQUM7Z0JBQ1gsU0FBUyxFQUFFLEtBQUs7Z0JBQ2hCLE1BQU0sRUFBRSxLQUFLO2dCQUNiLElBQUksRUFBRSxTQUFTO2dCQUNmLFVBQVUsRUFBRSxJQUFJO2dCQUNoQixLQUFLLEVBQUUsSUFBSTthQUNaLENBQUMsQ0FBQTtTQUNIO1FBRUQsT0FBTyxPQUFPLENBQUE7SUFDaEIsQ0FBQztJQUVNLEtBQUssQ0FBQyxhQUFhO1FBQ3hCLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFBO1FBQ2pDLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQTtJQUNwQixDQUFDO0lBRU8sS0FBSyxDQUFDLEtBQUs7UUFDakIsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQTtRQUNyQyxJQUFJLElBQUksQ0FBQyxPQUFPLEtBQUssT0FBTyxFQUFFO1lBQzVCLE9BQU8sSUFBSSxDQUFDLFVBQVUsQ0FBQTtTQUN2QjthQUFNO1lBQ0wsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUE7WUFDdEIsSUFBSSxDQUFDLFVBQVUsR0FBRyxNQUFNLHlDQUFvQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQTtZQUNuRSxPQUFPLElBQUksQ0FBQyxVQUFVLENBQUE7U0FDdkI7SUFDSCxDQUFDO0NBQ0Y7QUE5Q0QsZ0NBOENDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9zaXRlRGlzcG9zYWJsZSwgVGV4dEJ1ZmZlciB9IGZyb20gJ2F0b20nXG5pbXBvcnQge1xuICBwYXJzZUhzTW9kdWxlSW1wb3J0cyxcbiAgSU1vZHVsZUltcG9ydHMsXG4gIElJbXBvcnQsXG59IGZyb20gJ2F0b20taGFza2VsbC11dGlscydcblxuZXhwb3J0IHsgSUltcG9ydCB9XG5cbmV4cG9ydCBjbGFzcyBCdWZmZXJJbmZvIHtcbiAgcHJpdmF0ZSBkaXNwb3NhYmxlczogQ29tcG9zaXRlRGlzcG9zYWJsZVxuICBwcml2YXRlIG9sZFRleHQ6IHN0cmluZyA9ICcnXG4gIHByaXZhdGUgb2xkSW1wb3J0czogSU1vZHVsZUltcG9ydHMgPSB7IG5hbWU6ICdNYWluJywgaW1wb3J0czogW10gfVxuXG4gIGNvbnN0cnVjdG9yKHB1YmxpYyByZWFkb25seSBidWZmZXI6IFRleHRCdWZmZXIpIHtcbiAgICB0aGlzLmRpc3Bvc2FibGVzID0gbmV3IENvbXBvc2l0ZURpc3Bvc2FibGUoKVxuICAgIHRoaXMuZGlzcG9zYWJsZXMuYWRkKHRoaXMuYnVmZmVyLm9uRGlkRGVzdHJveSh0aGlzLmRlc3Ryb3kpKVxuICB9XG5cbiAgcHVibGljIGRlc3Ryb3kgPSAoKSA9PiB7XG4gICAgdGhpcy5kaXNwb3NhYmxlcy5kaXNwb3NlKClcbiAgfVxuXG4gIHB1YmxpYyBhc3luYyBnZXRJbXBvcnRzKCk6IFByb21pc2U8SUltcG9ydFtdPiB7XG4gICAgY29uc3QgcGFyc2VkID0gYXdhaXQgdGhpcy5wYXJzZSgpXG4gICAgY29uc3QgaW1wb3J0cyA9IHBhcnNlZCA/IHBhcnNlZC5pbXBvcnRzIDogW11cbiAgICAvLyB0c2xpbnQ6ZGlzYWJsZTogbm8tbnVsbC1rZXl3b3JkXG4gICAgaWYgKCFpbXBvcnRzLnNvbWUoKHsgbmFtZSB9KSA9PiBuYW1lID09PSAnUHJlbHVkZScpKSB7XG4gICAgICBpbXBvcnRzLnB1c2goe1xuICAgICAgICBxdWFsaWZpZWQ6IGZhbHNlLFxuICAgICAgICBoaWRpbmc6IGZhbHNlLFxuICAgICAgICBuYW1lOiAnUHJlbHVkZScsXG4gICAgICAgIGltcG9ydExpc3Q6IG51bGwsXG4gICAgICAgIGFsaWFzOiBudWxsLFxuICAgICAgfSlcbiAgICB9XG4gICAgLy8gdHNsaW50OmVuYWJsZTogbm8tbnVsbC1rZXl3b3JkXG4gICAgcmV0dXJuIGltcG9ydHNcbiAgfVxuXG4gIHB1YmxpYyBhc3luYyBnZXRNb2R1bGVOYW1lKCk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgcGFyc2VkID0gYXdhaXQgdGhpcy5wYXJzZSgpXG4gICAgcmV0dXJuIHBhcnNlZC5uYW1lXG4gIH1cblxuICBwcml2YXRlIGFzeW5jIHBhcnNlKCk6IFByb21pc2U8SU1vZHVsZUltcG9ydHM+IHtcbiAgICBjb25zdCBuZXdUZXh0ID0gdGhpcy5idWZmZXIuZ2V0VGV4dCgpXG4gICAgaWYgKHRoaXMub2xkVGV4dCA9PT0gbmV3VGV4dCkge1xuICAgICAgcmV0dXJuIHRoaXMub2xkSW1wb3J0c1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLm9sZFRleHQgPSBuZXdUZXh0XG4gICAgICB0aGlzLm9sZEltcG9ydHMgPSBhd2FpdCBwYXJzZUhzTW9kdWxlSW1wb3J0cyh0aGlzLmJ1ZmZlci5nZXRUZXh0KCkpXG4gICAgICByZXR1cm4gdGhpcy5vbGRJbXBvcnRzXG4gICAgfVxuICB9XG59XG4iXX0= -------------------------------------------------------------------------------- /lib/completion-backend/module-info.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const atom_1 = require("atom"); 4 | const Util = require("../util"); 5 | class ModuleInfo { 6 | constructor(name, process, rootDir) { 7 | this.name = name; 8 | this.process = process; 9 | this.rootDir = rootDir; 10 | this.invalidateInterval = 30 * 60 * 1000; 11 | this.destroy = () => { 12 | Util.debug(`${this.name} destroyed`); 13 | clearTimeout(this.timeout); 14 | this.emitter.emit('did-destroy'); 15 | this.disposables.dispose(); 16 | }; 17 | Util.debug(`${this.name} created`); 18 | this.symbols = []; 19 | this.disposables = new atom_1.CompositeDisposable(); 20 | this.bufferSet = new WeakSet(); 21 | this.emitter = new atom_1.Emitter(); 22 | this.disposables.add(this.emitter); 23 | this.updatePromise = this.update(rootDir); 24 | this.timeout = setTimeout(this.destroy, this.invalidateInterval); 25 | this.disposables.add(this.process.onDidDestroy(this.destroy)); 26 | } 27 | onDidDestroy(callback) { 28 | return this.emitter.on('did-destroy', callback); 29 | } 30 | async setBuffer(bufferInfo) { 31 | const name = await bufferInfo.getModuleName(); 32 | if (name !== this.name) { 33 | return; 34 | } 35 | if (this.bufferSet.has(bufferInfo.buffer)) { 36 | return; 37 | } 38 | this.bufferSet.add(bufferInfo.buffer); 39 | Util.debug(`${this.name} buffer is set`); 40 | const disposables = new atom_1.CompositeDisposable(); 41 | disposables.add(bufferInfo.buffer.onDidSave(() => { 42 | Util.debug(`${this.name} did-save triggered`); 43 | this.updatePromise = this.update(this.rootDir); 44 | })); 45 | disposables.add(bufferInfo.buffer.onDidDestroy(() => { 46 | disposables.dispose(); 47 | this.bufferSet.delete(bufferInfo.buffer); 48 | this.disposables.remove(disposables); 49 | })); 50 | this.disposables.add(disposables); 51 | } 52 | async select(importDesc, symbolTypes, skipQualified = false) { 53 | await this.updatePromise; 54 | clearTimeout(this.timeout); 55 | this.timeout = setTimeout(this.destroy, this.invalidateInterval); 56 | let symbols = this.symbols; 57 | if (importDesc.importList) { 58 | const il = importDesc.importList; 59 | symbols = symbols.filter((s) => { 60 | const inImportList = il.includes(s.name); 61 | const parentInImportList = il.some((i) => typeof i !== 'string' && s.parent === i.parent); 62 | const shouldShow = inImportList || parentInImportList; 63 | return importDesc.hiding !== shouldShow; 64 | }); 65 | } 66 | const res = []; 67 | for (const symbol of symbols) { 68 | if (symbolTypes && !symbolTypes.includes(symbol.symbolType)) { 69 | continue; 70 | } 71 | const specific = { 72 | name: symbol.name, 73 | typeSignature: symbol.typeSignature, 74 | symbolType: symbol.symbolType, 75 | module: importDesc, 76 | }; 77 | const qn = (n) => `${importDesc.alias || importDesc.name}.${n}`; 78 | if (!skipQualified) { 79 | res.push(Object.assign({}, specific, { qparent: symbol.parent ? qn(symbol.parent) : undefined, qname: qn(symbol.name) })); 80 | } 81 | if (!importDesc.qualified) { 82 | res.push(Object.assign({}, specific, { qparent: symbol.parent, qname: symbol.name })); 83 | } 84 | } 85 | return res; 86 | } 87 | async update(rootDir) { 88 | Util.debug(`${this.name} updating`); 89 | this.symbols = await this.process.runBrowse(rootDir, [this.name]); 90 | Util.debug(`${this.name} updated`); 91 | } 92 | } 93 | exports.ModuleInfo = ModuleInfo; 94 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kdWxlLWluZm8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29tcGxldGlvbi1iYWNrZW5kL21vZHVsZS1pbmZvLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsK0JBQTBFO0FBQzFFLGdDQUErQjtBQU8vQixNQUFhLFVBQVU7SUFXckIsWUFDbUIsSUFBWSxFQUNaLE9BQXVCLEVBQ3ZCLE9BQWtCO1FBRmxCLFNBQUksR0FBSixJQUFJLENBQVE7UUFDWixZQUFPLEdBQVAsT0FBTyxDQUFnQjtRQUN2QixZQUFPLEdBQVAsT0FBTyxDQUFXO1FBVHBCLHVCQUFrQixHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFBO1FBc0I3QyxZQUFPLEdBQUcsR0FBRyxFQUFFO1lBQ3BCLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxZQUFZLENBQUMsQ0FBQTtZQUNwQyxZQUFZLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1lBQzFCLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFBO1lBQ2hDLElBQUksQ0FBQyxXQUFXLENBQUMsT0FBTyxFQUFFLENBQUE7UUFDNUIsQ0FBQyxDQUFBO1FBaEJDLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxVQUFVLENBQUMsQ0FBQTtRQUNsQyxJQUFJLENBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQTtRQUNqQixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksMEJBQW1CLEVBQUUsQ0FBQTtRQUM1QyxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksT0FBTyxFQUFFLENBQUE7UUFDOUIsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLGNBQU8sRUFBRSxDQUFBO1FBQzVCLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUNsQyxJQUFJLENBQUMsYUFBYSxH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O2FBQ0g7U0FDRjtRQUNELE9BQU8sR0FBRyxDQUFBO0lBQ1osQ0FBQztJQUVPLEtBQUssQ0FBQyxNQUFNLENBQUMsT0FBa0I7UUFDckMsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLElBQUksQ0FBQyxJQUFJLFdBQVcsQ0FBQyxDQUFBO1FBQ25DLElBQUksQ0FBQyxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQTtRQUNqRSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsSUFBSSxDQUFDLElBQUksVUFBVSxDQUFDLENBQUE7SUFDcEMsQ0FBQztDQUNGO0FBeEhELGdDQXdIQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvc2l0ZURpc3Bvc2FibGUsIEVtaXR0ZXIsIFRleHRCdWZmZXIsIERpcmVjdG9yeSB9IGZyb20gJ2F0b20nXG5pbXBvcnQgKiBhcyBVdGlsIGZyb20gJy4uL3V0aWwnXG5pbXBvcnQgeyBHaGNNb2RpUHJvY2VzcywgU3ltYm9sRGVzYyB9IGZyb20gJy4uL2doYy1tb2QnXG5pbXBvcnQgeyBCdWZmZXJJbmZvLCBJSW1wb3J0IH0gZnJvbSAnLi9idWZmZXItaW5mbydcbmltcG9ydCAqIGFzIENvbXBsZXRpb25CYWNrZW5kIGZyb20gJ2F0b20taGFza2VsbC11cGkvY29tcGxldGlvbi1iYWNrZW5kJ1xuXG5pbXBvcnQgU3ltYm9sVHlwZSA9IENvbXBsZXRpb25CYWNrZW5kLlN5bWJvbFR5cGVcblxuZXhwb3J0IGNsYXNzIE1vZHVsZUluZm8ge1xuICBwcml2YXRlIHJlYWRvbmx5IGRpc3Bvc2FibGVzOiBDb21wb3NpdGVEaXNwb3NhYmxlXG4gIHByaXZhdGUgcmVhZG9ubHkgZW1pdHRlcjogRW1pdHRlcjx7XG4gICAgJ2RpZC1kZXN0cm95JzogdW5kZWZpbmVkXG4gIH0+XG4gIHByaXZhdGUgcmVhZG9ubHkgaW52YWxpZGF0ZUludGVydmFsID0gMzAgKiA2MCAqIDEwMDAgLy8gaWYgbW9kdWxlIHVudXNlZCBmb3IgMzAgbWludXRlcywgcmVtb3ZlIGl0XG4gIHByaXZhdGUgcmVhZG9ubHkgYnVmZmVyU2V0OiBXZWFrU2V0PFRleHRCdWZmZXI+XG4gIHByaXZhdGUgdGltZW91dDogTm9kZUpTLlRpbWVyXG4gIHByaXZhdGUgdXBkYXRlUHJvbWlzZTogUHJvbWlzZTx2b2lkPlxuICBwcml2YXRlIHN5bWJvbHM6IFN5bWJvbERlc2NbXSAvLyBtb2R1bGUgc3ltYm9sc1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByaXZhdGUgcmVhZG9ubHkgbmFtZTogc3RyaW5nLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgcHJvY2VzczogR2hjTW9kaVByb2Nlc3MsXG4gICAgcHJpdmF0ZSByZWFkb25seSByb290RGlyOiBEaXJlY3RvcnksXG4gICkge1xuICAgIFV0aWwuZGVidWcoYCR7dGhpcy5uYW1lfSBjcmVhdGVkYClcbiAgICB0aGlzLnN5bWJvbHMgPSBbXVxuICAgIHRoaXMuZGlzcG9zYWJsZXMgPSBuZXcgQ29tcG9zaXRlRGlzcG9zYWJsZSgpXG4gICAgdGhpcy5idWZmZXJTZXQgPSBuZXcgV2Vha1NldCgpXG4gICAgdGhpcy5lbWl0dGVyID0gbmV3IEVtaXR0ZXIoKVxuICAgIHRoaXMuZGlzcG9zYWJsZXMuYWRkKHRoaXMuZW1pdHRlcilcbiAgICB0aGlzLnVwZGF0ZVByb21pc2UgPSB0aGlzLnVwZGF0ZShyb290RGlyKVxuICAgIHRoaXMudGltZW91dCA9IHNldFRpbWVvdXQodGhpcy5kZXN0cm95LCB0aGlzLmludmFsaWRhdGVJbnRlcnZhbClcbiAgICB0aGlzLmRpc3Bvc2FibGVzLmFkZCh0aGlzLnByb2Nlc3Mub25EaWREZXN0cm95KHRoaXMuZGVzdHJveSkpXG4gIH1cblxuICBwdWJsaWMgZGVzdHJveSA9ICgpID0+IHtcbiAgICBVdGlsLmRlYnVnKGAke3RoaXMubmFtZX0gZGVzdHJveWVkYClcbiAgICBjbGVhclRpbWVvdXQodGhpcy50aW1lb3V0KVxuICAgIHRoaXMuZW1pdHRlci5lbWl0KCdkaWQtZGVzdHJveScpXG4gICAgdGhpcy5kaXNwb3NhYmxlcy5kaXNwb3NlKClcbiAgfVxuXG4gIHB1YmxpYyBvbkRpZERlc3Ryb3koY2FsbGJhY2s6ICgpID0+IHZvaWQpIHtcbiAgICByZXR1cm4gdGhpcy5lbWl0dGVyLm9uKCdkaWQtZGVzdHJveScsIGNhbGxiYWNrKVxuICB9XG5cbiAgcHVibGljIGFzeW5jIHNldEJ1ZmZlcihidWZmZXJJbmZvOiBCdWZmZXJJbmZvKSB7XG4gICAgY29uc3QgbmFtZSA9IGF3YWl0IGJ1ZmZlckluZm8uZ2V0TW9kdWxlTmFtZSgpXG4gICAgaWYgKG5hbWUgIT09IHRoaXMubmFtZSkge1xuICAgICAgcmV0dXJuXG4gICAgfVxuICAgIGlmICh0aGlzLmJ1ZmZlclNldC5oYXMoYnVmZmVySW5mby5idWZmZXIpKSB7XG4gICAgICByZXR1cm5cbiAgICB9XG4gICAgdGhpcy5idWZmZXJTZXQuYWRkKGJ1ZmZlckluZm8uYnVmZmVyKVxuICAgIFV0aWwuZGVidWcoYCR7dGhpcy5uYW1lfSBidWZmZXIgaXMgc2V0YClcbiAgICBjb25zdCBkaXNwb3NhYmxlcyA9IG5ldyBDb21wb3NpdGVEaXNwb3NhYmxlKClcbiAgICBkaXNwb3NhYmxlcy5hZGQoXG4gICAgICBidWZmZXJJbmZvLmJ1ZmZlci5vbkRpZFNhdmUoKCkgPT4ge1xuICAgICAgICBVdGlsLmRlYnVnKGAke3RoaXMubmFtZX0gZGlkLXNhdmUgdHJpZ2dlcmVkYClcbiAgICAgICAgdGhpcy51cGRhdGVQcm9taXNlID0gdGhpcy51cGRhdGUodGhpcy5yb290RGlyKVxuICAgICAgfSksXG4gICAgKVxuICAgIGRpc3Bvc2FibGVzLmFkZChcbiAgICAgIGJ1ZmZlckluZm8uYnVmZmVyLm9uRGlkRGVzdHJveSgoKSA9PiB7XG4gICAgICAgIGRpc3Bvc2FibGVzLmRpc3Bvc2UoKVxuICAgICAgICB0aGlzLmJ1ZmZlclNldC5kZWxldGUoYnVmZmVySW5mby5idWZmZXIpXG4gICAgICAgIHRoaXMuZGlzcG9zYWJsZXMucmVtb3ZlKGRpc3Bvc2FibGVzKVxuICAgICAgfSksXG4gICAgKVxuICAgIHRoaXMuZGlzcG9zYWJsZXMuYWRkKGRpc3Bvc2FibGVzKVxuICB9XG5cbiAgcHVibGljIGFzeW5jIHNlbGVjdChcbiAgICBpbXBvcnREZXNjOiBJSW1wb3J0LFxuICAgIHN5bWJvbFR5cGVzPzogU3ltYm9sVHlwZVtdLFxuICAgIHNraXBRdWFsaWZpZWQ6IGJvb2xlYW4gPSBmYWxzZSxcbiAgKSB7XG4gICAgYXdhaXQgdGhpcy51cGRhdGVQcm9taXNlXG4gICAgY2xlYXJUaW1lb3V0KHRoaXMudGltZW91dClcbiAgICB0aGlzLnRpbWVvdXQgPSBzZXRUaW1lb3V0KHRoaXMuZGVzdHJveSwgdGhpcy5pbnZhbGlkYXRlSW50ZXJ2YWwpXG4gICAgbGV0IHN5bWJvbHMgPSB0aGlzLnN5bWJvbHNcbiAgICBpZiAoaW1wb3J0RGVzYy5pbXBvcnRMaXN0KSB7XG4gICAgICBjb25zdCBpbCA9IGltcG9ydERlc2MuaW1wb3J0TGlzdFxuICAgICAgc3ltYm9scyA9IHN5bWJvbHMuZmlsdGVyKChzKSA9PiB7XG4gICAgICAgIGNvbnN0IGluSW1wb3J0TGlzdCA9IGlsLmluY2x1ZGVzKHMubmFtZSlcbiAgICAgICAgY29uc3QgcGFyZW50SW5JbXBvcnRMaXN0ID0gaWwuc29tZShcbiAgICAgICAgICAoaSkgPT4gdHlwZW9mIGkgIT09ICdzdHJpbmcnICYmIHMucGFyZW50ID09PSBpLnBhcmVudCxcbiAgICAgICAgKVxuICAgICAgICBjb25zdCBzaG91bGRTaG93ID0gaW5JbXBvcnRMaXN0IHx8IHBhcmVudEluSW1wb3J0TGlzdFxuICAgICAgICByZXR1cm4gaW1wb3J0RGVzYy5oaWRpbmcgIT09IHNob3VsZFNob3cgLy8gWE9SXG4gICAgICB9KVxuICAgIH1cbiAgICBjb25zdCByZXMgPSBbXVxuICAgIGZvciAoY29uc3Qgc3ltYm9sIG9mIHN5bWJvbHMpIHtcbiAgICAgIGlmIChzeW1ib2xUeXBlcyAmJiAhc3ltYm9sVHlwZXMuaW5jbHVkZXMoc3ltYm9sLnN5bWJvbFR5cGUpKSB7XG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB9XG4gICAgICBjb25zdCBzcGVjaWZpYyA9IHtcbiAgICAgICAgbmFtZTogc3ltYm9sLm5hbWUsXG4gICAgICAgIHR5cGVTaWduYXR1cmU6IHN5bWJvbC50eXBlU2lnbmF0dXJlLFxuICAgICAgICBzeW1ib2xUeXBlOiBzeW1ib2wuc3ltYm9sVHlwZSxcbiAgICAgICAgbW9kdWxlOiBpbXBvcnREZXNjLFxuICAgICAgfVxuICAgICAgY29uc3QgcW4gPSAobjogc3RyaW5nKSA9PiBgJHtpbXBvcnREZXNjLmFsaWFzIHx8IGltcG9ydERlc2MubmFtZX0uJHtufWBcbiAgICAgIGlmICghc2tpcFF1YWxpZmllZCkge1xuICAgICAgICByZXMucHVzaCh7XG4gICAgICAgICAgLi4uc3BlY2lmaWMsXG4gICAgICAgICAgcXBhcmVudDogc3ltYm9sLnBhcmVudCA/IHFuKHN5bWJvbC5wYXJlbnQpIDogdW5kZWZpbmVkLFxuICAgICAgICAgIHFuYW1lOiBxbihzeW1ib2wubmFtZSksXG4gICAgICAgIH0pXG4gICAgICB9XG4gICAgICBpZiAoIWltcG9ydERlc2MucXVhbGlmaWVkKSB7XG4gICAgICAgIHJlcy5wdXNoKHtcbiAgICAgICAgICAuLi5zcGVjaWZpYyxcbiAgICAgICAgICBxcGFyZW50OiBzeW1ib2wucGFyZW50LFxuICAgICAgICAgIHFuYW1lOiBzeW1ib2wubmFtZSxcbiAgICAgICAgfSlcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHJlc1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyB1cGRhdGUocm9vdERpcjogRGlyZWN0b3J5KSB7XG4gICAgVXRpbC5kZWJ1ZyhgJHt0aGlzLm5hbWV9IHVwZGF0aW5nYClcbiAgICB0aGlzLnN5bWJvbHMgPSBhd2FpdCB0aGlzLnByb2Nlc3MucnVuQnJvd3NlKHJvb3REaXIsIFt0aGlzLm5hbWVdKVxuICAgIFV0aWwuZGVidWcoYCR7dGhpcy5uYW1lfSB1cGRhdGVkYClcbiAgfVxufVxuIl19 -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const tooltipActions = [ 4 | { value: '', description: 'Nothing' }, 5 | { value: 'type', description: 'Type' }, 6 | { value: 'info', description: 'Info' }, 7 | { value: 'infoType', description: 'Info, fallback to Type' }, 8 | { value: 'typeInfo', description: 'Type, fallback to Info' }, 9 | { value: 'typeAndInfo', description: 'Type and Info' }, 10 | ]; 11 | exports.config = { 12 | ghcModPath: { 13 | type: 'string', 14 | default: 'ghc-mod', 15 | description: 'Path to ghc-mod', 16 | order: 0, 17 | }, 18 | enableGhcModi: { 19 | type: 'boolean', 20 | default: true, 21 | description: `Using GHC Modi is suggested and noticeably faster, \ 22 | but if experiencing problems, disabling it can sometimes help.`, 23 | order: 70, 24 | }, 25 | lowMemorySystem: { 26 | type: 'boolean', 27 | default: false, 28 | description: `Avoid spawning more than one ghc-mod process; also disables parallel \ 29 | features, which can help with weird stack errors`, 30 | order: 70, 31 | }, 32 | debug: { 33 | type: 'boolean', 34 | default: false, 35 | order: 999, 36 | }, 37 | builderManagement: { 38 | type: 'boolean', 39 | description: `Experimental option to force ghc-mod into using cabal or \ 40 | stack based on ide-haskell-cabal settings; also enables an option to build \ 41 | ghc-mod when using stack`, 42 | default: false, 43 | order: 900, 44 | }, 45 | additionalPathDirectories: { 46 | type: 'array', 47 | default: [], 48 | description: `Add this directories to PATH when invoking ghc-mod. \ 49 | You might want to add path to a directory with \ 50 | ghc, cabal, etc binaries here. \ 51 | Separate with comma.`, 52 | items: { 53 | type: 'string', 54 | }, 55 | order: 0, 56 | }, 57 | cabalSandbox: { 58 | type: 'boolean', 59 | default: true, 60 | description: 'Add cabal sandbox bin-path to PATH', 61 | order: 100, 62 | }, 63 | stackSandbox: { 64 | type: 'boolean', 65 | default: true, 66 | description: 'Add stack bin-path to PATH', 67 | order: 100, 68 | }, 69 | initTimeout: { 70 | type: 'integer', 71 | description: `How long to wait for initialization commands (checking \ 72 | GHC and ghc-mod versions, getting stack sandbox) until \ 73 | assuming those hanged and bailing. In seconds.`, 74 | default: 60, 75 | minimum: 1, 76 | order: 50, 77 | }, 78 | interactiveInactivityTimeout: { 79 | type: 'integer', 80 | description: `Kill ghc-mod interactive process (ghc-modi) after this \ 81 | number of minutes of inactivity to conserve memory. 0 \ 82 | means never.`, 83 | default: 60, 84 | minimum: 0, 85 | order: 50, 86 | }, 87 | interactiveActionTimeout: { 88 | type: 'integer', 89 | description: `Timeout for interactive ghc-mod commands (in seconds). 0 \ 90 | means wait forever.`, 91 | default: 300, 92 | minimum: 0, 93 | order: 50, 94 | }, 95 | onSaveCheck: { 96 | type: 'boolean', 97 | default: true, 98 | description: 'Check file on save', 99 | order: 25, 100 | }, 101 | onSaveLint: { 102 | type: 'boolean', 103 | default: true, 104 | description: 'Lint file on save', 105 | order: 25, 106 | }, 107 | onChangeCheck: { 108 | type: 'boolean', 109 | default: false, 110 | description: 'Check file on change', 111 | order: 25, 112 | }, 113 | onChangeLint: { 114 | type: 'boolean', 115 | default: false, 116 | description: 'Lint file on change', 117 | order: 25, 118 | }, 119 | alwaysInteractiveCheck: { 120 | type: 'boolean', 121 | default: true, 122 | description: `Always use interactive mode for check. Much faster on large \ 123 | projects, but can lead to problems. Try disabling if experiencing slowdowns or \ 124 | crashes`, 125 | order: 26, 126 | }, 127 | onMouseHoverShow: { 128 | type: 'string', 129 | description: 'Contents of tooltip on mouse hover', 130 | default: 'typeAndInfo', 131 | enum: tooltipActions, 132 | order: 30, 133 | }, 134 | onSelectionShow: { 135 | type: 'string', 136 | description: 'Contents of tooltip on selection', 137 | default: '', 138 | enum: tooltipActions, 139 | order: 30, 140 | }, 141 | maxBrowseProcesses: { 142 | type: 'integer', 143 | default: 2, 144 | description: `Maximum number of parallel ghc-mod browse processes, which \ 145 | are used in autocompletion backend initialization. \ 146 | Note that on larger projects it may require a considerable \ 147 | amount of memory.`, 148 | order: 60, 149 | }, 150 | highlightTooltips: { 151 | type: 'boolean', 152 | default: true, 153 | description: 'Show highlighting for type/info tooltips', 154 | order: 40, 155 | }, 156 | suppressRedundantTypeInTypeAndInfoTooltips: { 157 | type: 'boolean', 158 | default: true, 159 | description: `In tooltips with type AND info, suppress type if \ 160 | it's the same as info`, 161 | order: 41, 162 | }, 163 | highlightMessages: { 164 | type: 'boolean', 165 | default: true, 166 | description: 'Show highlighting for output panel messages', 167 | order: 40, 168 | }, 169 | hlintOptions: { 170 | type: 'array', 171 | default: [], 172 | description: 'Command line options to pass to hlint (comma-separated)', 173 | order: 45, 174 | }, 175 | experimental: { 176 | type: 'boolean', 177 | default: false, 178 | description: `Enable experimental features, which are expected to land in \ 179 | next release of ghc-mod. ENABLE ONLY IF YOU KNOW WHAT YOU \ 180 | ARE DOING`, 181 | order: 999, 182 | }, 183 | suppressGhcPackagePathWarning: { 184 | type: 'boolean', 185 | default: false, 186 | description: `Suppress warning about GHC_PACKAGE_PATH environment variable. \ 187 | ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING.`, 188 | order: 999, 189 | }, 190 | ghcModMessages: { 191 | type: 'string', 192 | description: 'How to show warnings/errors reported by ghc-mod (requires restart)', 193 | default: 'console', 194 | enum: [ 195 | { value: 'console', description: 'Developer Console' }, 196 | { value: 'upi', description: 'Output Panel' }, 197 | { value: 'popup', description: 'Error/Warning Popups' }, 198 | ], 199 | order: 42, 200 | }, 201 | maxMemMegs: { 202 | type: 'integer', 203 | descrition: 'Maximum ghc-mod interactive mode memory usage (in megabytes)', 204 | default: 4 * 1024, 205 | minimum: 1024, 206 | order: 50, 207 | }, 208 | }; 209 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLE1BQU0sY0FBYyxHQUFHO0lBQ3JCLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFO0lBQ3JDLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsTUFBTSxFQUFFO0lBQ3RDLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsTUFBTSxFQUFFO0lBQ3RDLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsd0JBQXdCLEVBQUU7SUFDNUQsRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSx3QkFBd0IsRUFBRTtJQUM1RCxFQUFFLEtBQUssRUFBRSxhQUFhLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRTtDQUN2RCxDQUFBO0FBRVksUUFBQSxNQUFNLEdBQUc7SUFDcEIsVUFBVSxFQUFFO1FBQ1YsSUFBSSxFQUFFLFFBQVE7UUFDZCxPQUFPLEVBQUUsU0FBUztRQUNsQixXQUFXLEVBQUUsaUJBQWlCO1FBQzlCLEtBQUssRUFBRSxDQUFDO0tBQ1Q7SUFDRCxhQUFhLEVBQUU7UUFDYixJQUFJLEVBQUUsU0FBUztRQUNmLE9BQU8sRUFBRSxJQUFJO1FBQ2IsV0FBVyxFQUFFOytEQUM4QztRQUMzRCxLQUFLLEVBQUUsRUFBRTtLQUNWO0lBQ0QsZUFBZSxFQUFFO1FBQ2YsSUFBSSxFQUFFLFNBQVM7UUFDZixPQUFPLEVBQUUsS0FBSztRQUNkLFdBQVcsRUFBRTtpREFDZ0M7UUFDN0MsS0FBSyxFQUFFLEVBQUU7S0FDVjtJQUNELEtBQUssRUFBRTtRQUNMLElBQUksRUFBRSxTQUFTO1FBQ2YsT0FBTyxFQUFFLEtBQUs7UUFDZCxLQUFLLEVBQUUsR0FBRztLQUNYO0lBQ0QsaUJBQWlCLEVBQUU7UUFDakIsSUFBSSxFQUFFLFNBQVM7UUFDZixX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QUNWO0NBQ0YsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImNvbnN0IHRvb2x0aXBBY3Rpb25zID0gW1xuICB7IHZhbHVlOiAnJywgZGVzY3JpcHRpb246ICdOb3RoaW5nJyB9LFxuICB7IHZhbHVlOiAndHlwZScsIGRlc2NyaXB0aW9uOiAnVHlwZScgfSxcbiAgeyB2YWx1ZTogJ2luZm8nLCBkZXNjcmlwdGlvbjogJ0luZm8nIH0sXG4gIHsgdmFsdWU6ICdpbmZvVHlwZScsIGRlc2NyaXB0aW9uOiAnSW5mbywgZmFsbGJhY2sgdG8gVHlwZScgfSxcbiAgeyB2YWx1ZTogJ3R5cGVJbmZvJywgZGVzY3JpcHRpb246ICdUeXBlLCBmYWxsYmFjayB0byBJbmZvJyB9LFxuICB7IHZhbHVlOiAndHlwZUFuZEluZm8nLCBkZXNjcmlwdGlvbjogJ1R5cGUgYW5kIEluZm8nIH0sXG5dXG5cbmV4cG9ydCBjb25zdCBjb25maWcgPSB7XG4gIGdoY01vZFBhdGg6IHtcbiAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICBkZWZhdWx0OiAnZ2hjLW1vZCcsXG4gICAgZGVzY3JpcHRpb246ICdQYXRoIHRvIGdoYy1tb2QnLFxuICAgIG9yZGVyOiAwLFxuICB9LFxuICBlbmFibGVHaGNNb2RpOiB7XG4gICAgdHlwZTogJ2Jvb2xlYW4nLFxuICAgIGRlZmF1bHQ6IHRydWUsXG4gICAgZGVzY3JpcHRpb246IGBVc2luZyBHSEMgTW9kaSBpcyBzdWdnZXN0ZWQgYW5kIG5vdGljZWFibHkgZmFzdGVyLCBcXFxuYnV0IGlmIGV4cGVyaWVuY2luZyBwcm9ibGVtcywgZGlzYWJsaW5nIGl0IGNhbiBzb21ldGltZXMgaGVscC5gLFxuICAgIG9yZGVyOiA3MCxcbiAgfSxcbiAgbG93TWVtb3J5U3lzdGVtOiB7XG4gICAgdHlwZTogJ2Jvb2xlYW4nLFxuICAgIGRlZmF1bHQ6IGZhbHNlLFxuICAgIGRlc2NyaXB0aW9uOiBgQXZvaWQgc3Bhd25pbmcgbW9yZSB0aGFuIG9uZSBnaGMtbW9kIHByb2Nlc3M7IGFsc28gZGlzYWJsZXMgcGFyYWxsZWwgXFxcbmZlYXR1cmVzLCB3aGljaCBjYW4gaGVscCB3aXRoIHdlaXJkIHN0YWNrIGVycm9yc2AsXG4gICAgb3JkZXI6IDcwLFxuICB9LFxuICBkZWJ1Zzoge1xuICAgIHR5cGU6ICdib29sZWFuJyxcbiAgICBkZWZhdWx0OiBmYWxzZSxcbiAgICBvcmRlcjogOTk5LFxuICB9LFxuICBidWlsZGVyTWFuYWdlbWVudDoge1xuICAgIHR5cGU6ICdib29sZWFuJyxcbiAgICBkZXNjcmlwdGlvbjogYEV4cGVyaW1lbnRhbCBvcHRpb24gdG8gZm9yY2UgZ2hjLW1vZCBpbnRvIHVzaW5nIGNhYmFsIG9yIFxcXG5zdGFjayBiYXNlZCBvbiBpZGUtaGFza2VsbC1jYWJhbCBzZXR0aW5nczsgYWxzbyBlbmFibGVzIGFuIG9wdGlvbiB0byBidWlsZCBcXFxuZ2hjLW1vZCB3aGVuIHVzaW5nIHN0YWNrYCxcbiAgICBkZWZhdWx0OiBmYWxzZSxcbiAgICBvcmRlcjogOTAwLFxuICB9LFxuICBhZGRpdGlvbmFsUGF0aERpcmVjdG9yaWVzOiB7XG4gICAgdHlwZTogJ2FycmF5JyxcbiAgICBkZWZhdWx0OiBbXSxcbiAgICBkZXNjcmlwdGlvbjogYEFkZCB0aGlzIGRpcmVjdG9yaWVzIHRvIFBBVEggd2hlbiBpbnZva2luZyBnaGMtbW9kLiBcXFxuWW91IG1pZ2h0IHdhbnQgdG8gYWRkIHBhdGggdG8gYSBkaXJlY3Rvcnkgd2l0aCBcXFxuZ2hjLCBjYWJhbCwgZXRjIGJpbmFyaWVzIGhlcmUuIFxcXG5TZXBhcmF0ZSB3aXRoIGNvbW1hLmAsXG4gICAgaXRlbXM6IHtcbiAgICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgIH0sXG4gICAgb3JkZXI6IDAsXG4gIH0sXG4gIGNhYmFsU2FuZGJveDoge1xuICAgIHR5cGU6ICdib29sZWFuJyxcbiAgICBkZWZhdWx0OiB0cnVlLFxuICAgIGRlc2NyaXB0aW9uOiAnQWRkIGNhYmFsIHNhbmRib3ggYmluLXBhdGggdG8gUEFUSCcsXG4gICAgb3JkZXI6IDEwMCxcbiAgfSxcbiAgc3RhY2tTYW5kYm94OiB7XG4gICAgdHlwZTogJ2Jvb2xlYW4nLFxuICAgIGRlZmF1bHQ6IHRydWUsXG4gICAgZGVzY3JpcHRpb246ICdBZGQgc3RhY2sgYmluLXBhdGggdG8gUEFUSCcsXG4gICAgb3JkZXI6IDEwMCxcbiAgfSxcbiAgaW5pdFRpbWVvdXQ6IHtcbiAgICB0eXBlOiAnaW50ZWdlcicsXG4gICAgZGVzY3JpcHRpb246IGBIb3cgbG9uZyB0byB3YWl0IGZvciBpbml0aWFsaXphdGlvbiBjb21tYW5kcyAoY2hlY2tpbmcgXFxcbkdIQyBhbmQgZ2hjLW1vZCB2ZXJzaW9ucywgZ2V0dGluZyBzdGFjayBzYW5kYm94KSB1bnRpbCBcXFxuYXNzdW1pbmcgdGhvc2UgaGFuZ2VkIGFuZCBiYWlsaW5nLiBJbiBzZWNvbmRzLmAsXG4gICAgZGVmYXVsdDogNjAsXG4gICAgbWluaW11bTogMSxcbiAgICBvcmRlcjogNTAsXG4gIH0sXG4gIGludGVyYWN0aXZlSW5hY3Rpdml0eVRpbWVvdXQ6IHtcbiAgICB0eXBlOiAnaW50ZWdlcicsXG4gICAgZGVzY3JpcHRpb246IGBLaWxsIGdoYy1tb2QgaW50ZXJhY3RpdmUgcHJvY2VzcyAoZ2hjLW1vZGkpIGFmdGVyIHRoaXMgXFxcbm51bWJlciBvZiBtaW51dGVzIG9mIGluYWN0aXZpdHkgdG8gY29uc2VydmUgbWVtb3J5LiAwIFxcXG5tZWFucyBuZXZlci5gLFxuICAgIGRlZmF1bHQ6IDYwLFxuICAgIG1pbmltdW06IDAsXG4gICAgb3JkZXI6IDUwLFxuICB9LFxuICBpbnRlcmFjdGl2ZUFjdGlvblRpbWVvdXQ6IHtcbiAgICB0eXBlOiAnaW50ZWdlcicsXG4gICAgZGVzY3JpcHRpb246IGBUaW1lb3V0IGZvciBpbnRlcmFjdGl2ZSBnaGMtbW9kIGNvbW1hbmRzIChpbiBzZWNvbmRzKS4gMCBcXFxubWVhbnMgd2FpdCBmb3JldmVyLmAsXG4gICAgZGVmYXVsdDogMzAwLFxuICAgIG1pbmltdW06IDAsXG4gICAgb3JkZXI6IDUwLFxuICB9LFxuICBvblNhdmVDaGVjazoge1xuICAgIHR5cGU6ICdib29sZWFuJyxcbiAgICBkZWZhdWx0OiB0cnVlLFxuICAgIGRlc2NyaXB0aW9uOiAnQ2hlY2sgZmlsZSBvbiBzYXZlJyxcbiAgICBvcmRlcjogMjUsXG4gIH0sXG4gIG9uU2F2ZUxpbnQ6IHtcbiAgICB0eXBlOiAnYm9vbGVhbicsXG4gICAgZGVmYXVsdDogdHJ1ZSxcbiAgICBkZXNjcmlwdGlvbjogJ0xpbnQgZmlsZSBvbiBzYXZlJyxcbiAgICBvcmRlcjogMjUsXG4gIH0sXG4gIG9uQ2hhbmdlQ2hlY2s6IHtcbiAgICB0eXBlOiAnYm9vbGVhbicsXG4gICAgZGVmYXVsdDogZmFsc2UsXG4gICAgZGVzY3JpcHRpb246ICdDaGVjayBmaWxlIG9uIGNoYW5nZScsXG4gICAgb3JkZXI6IDI1LFxuICB9LFxuICBvbkNoYW5nZUxpbnQ6IHtcbiAgICB0eXBlOiAnYm9vbGVhbicsXG4gICAgZGVmYXVsdDogZmFsc2UsXG4gICAgZGVzY3JpcHRpb246ICdMaW50IGZpbGUgb24gY2hhbmdlJyxcbiAgICBvcmRlcjogMjUsXG4gIH0sXG4gIGFsd2F5c0ludGVyYWN0aXZlQ2hlY2s6IHtcbiAgICB0eXBlOiAnYm9vbGVhbicsXG4gICAgZGVmYXVsdDogdHJ1ZSxcbiAgICBkZXNjcmlwdGlvbjogYEFsd2F5cyB1c2UgaW50ZXJhY3RpdmUgbW9kZSBmb3IgY2hlY2suIE11Y2ggZmFzdGVyIG9uIGxhcmdlIFxcXG5wcm9qZWN0cywgYnV0IGNhbiBsZWFkIHRvIHByb2JsZW1zLiBUcnkgZGlzYWJsaW5nIGlmIGV4cGVyaWVuY2luZyBzbG93ZG93bnMgb3IgXFxcbmNyYXNoZXNgLFxuICAgIG9yZGVyOiAyNixcbiAgfSxcbiAgb25Nb3VzZUhvdmVyU2hvdzoge1xuICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgIGRlc2NyaXB0aW9uOiAnQ29udGVudHMgb2YgdG9vbHRpcCBvbiBtb3VzZSBob3ZlcicsXG4gICAgZGVmYXVsdDogJ3R5cGVBbmRJbmZvJyxcbiAgICBlbnVtOiB0b29sdGlwQWN0aW9ucyxcbiAgICBvcmRlcjogMzAsXG4gIH0sXG4gIG9uU2VsZWN0aW9uU2hvdzoge1xuICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgIGRlc2NyaXB0aW9uOiAnQ29udGVudHMgb2YgdG9vbHRpcCBvbiBzZWxlY3Rpb24nLFxuICAgIGRlZmF1bHQ6ICcnLFxuICAgIGVudW06IHRvb2x0aXBBY3Rpb25zLFxuICAgIG9yZGVyOiAzMCxcbiAgfSxcbiAgbWF4QnJvd3NlUHJvY2Vzc2VzOiB7XG4gICAgdHlwZTogJ2ludGVnZXInLFxuICAgIGRlZmF1bHQ6IDIsXG4gICAgZGVzY3JpcHRpb246IGBNYXhpbXVtIG51bWJlciBvZiBwYXJhbGxlbCBnaGMtbW9kIGJyb3dzZSBwcm9jZXNzZXMsIHdoaWNoIFxcXG5hcmUgdXNlZCBpbiBhdXRvY29tcGxldGlvbiBiYWNrZW5kIGluaXRpYWxpemF0aW9uLiBcXFxuTm90ZSB0aGF0IG9uIGxhcmdlciBwcm9qZWN0cyBpdCBtYXkgcmVxdWlyZSBhIGNvbnNpZGVyYWJsZSBcXFxuYW1vdW50IG9mIG1lbW9yeS5gLFxuICAgIG9yZGVyOiA2MCxcbiAgfSxcbiAgaGlnaGxpZ2h0VG9vbHRpcHM6IHtcbiAgICB0eXBlOiAnYm9vbGVhbicsXG4gICAgZGVmYXVsdDogdHJ1ZSxcbiAgICBkZXNjcmlwdGlvbjogJ1Nob3cgaGlnaGxpZ2h0aW5nIGZvciB0eXBlL2luZm8gdG9vbHRpcHMnLFxuICAgIG9yZGVyOiA0MCxcbiAgfSxcbiAgc3VwcHJlc3NSZWR1bmRhbnRUeXBlSW5UeXBlQW5kSW5mb1Rvb2x0aXBzOiB7XG4gICAgdHlwZTogJ2Jvb2xlYW4nLFxuICAgIGRlZmF1bHQ6IHRydWUsXG4gICAgZGVzY3JpcHRpb246IGBJbiB0b29sdGlwcyB3aXRoIHR5cGUgQU5EIGluZm8sIHN1cHByZXNzIHR5cGUgaWYgXFxcbml0J3MgdGhlIHNhbWUgYXMgaW5mb2AsXG4gICAgb3JkZXI6IDQxLFxuICB9LFxuICBoaWdobGlnaHRNZXNzYWdlczoge1xuICAgIHR5cGU6ICdib29sZWFuJyxcbiAgICBkZWZhdWx0OiB0cnVlLFxuICAgIGRlc2NyaXB0aW9uOiAnU2hvdyBoaWdobGlnaHRpbmcgZm9yIG91dHB1dCBwYW5lbCBtZXNzYWdlcycsXG4gICAgb3JkZXI6IDQwLFxuICB9LFxuICBobGludE9wdGlvbnM6IHtcbiAgICB0eXBlOiAnYXJyYXknLFxuICAgIGRlZmF1bHQ6IFtdLFxuICAgIGRlc2NyaXB0aW9uOiAnQ29tbWFuZCBsaW5lIG9wdGlvbnMgdG8gcGFzcyB0byBobGludCAoY29tbWEtc2VwYXJhdGVkKScsXG4gICAgb3JkZXI6IDQ1LFxuICB9LFxuICBleHBlcmltZW50YWw6IHtcbiAgICB0eXBlOiAnYm9vbGVhbicsXG4gICAgZGVmYXVsdDogZmFsc2UsXG4gICAgZGVzY3JpcHRpb246IGBFbmFibGUgZXhwZXJpbWVudGFsIGZlYXR1cmVzLCB3aGljaCBhcmUgZXhwZWN0ZWQgdG8gbGFuZCBpbiBcXFxubmV4dCByZWxlYXNlIG9mIGdoYy1tb2QuIEVOQUJMRSBPTkxZIElGIFlPVSBLTk9XIFdIQVQgWU9VIFxcXG5BUkUgRE9JTkdgLFxuICAgIG9yZGVyOiA5OTksXG4gIH0sXG4gIHN1cHByZXNzR2hjUGFja2FnZVBhdGhXYXJuaW5nOiB7XG4gICAgdHlwZTogJ2Jvb2xlYW4nLFxuICAgIGRlZmF1bHQ6IGZhbHNlLFxuICAgIGRlc2NyaXB0aW9uOiBgU3VwcHJlc3Mgd2FybmluZyBhYm91dCBHSENfUEFDS0FHRV9QQVRIIGVudmlyb25tZW50IHZhcmlhYmxlLiBcXFxuRU5BQkxFIE9OTFkgSUYgWU9VIEtOT1cgV0hBVCBZT1UgQVJFIERPSU5HLmAsXG4gICAgb3JkZXI6IDk5OSxcbiAgfSxcbiAgZ2hjTW9kTWVzc2FnZXM6IHtcbiAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICBkZXNjcmlwdGlvbjpcbiAgICAgICdIb3cgdG8gc2hvdyB3YXJuaW5ncy9lcnJvcnMgcmVwb3J0ZWQgYnkgZ2hjLW1vZCAocmVxdWlyZXMgcmVzdGFydCknLFxuICAgIGRlZmF1bHQ6ICdjb25zb2xlJyxcbiAgICBlbnVtOiBbXG4gICAgICB7IHZhbHVlOiAnY29uc29sZScsIGRlc2NyaXB0aW9uOiAnRGV2ZWxvcGVyIENvbnNvbGUnIH0sXG4gICAgICB7IHZhbHVlOiAndXBpJywgZGVzY3JpcHRpb246ICdPdXRwdXQgUGFuZWwnIH0sXG4gICAgICB7IHZhbHVlOiAncG9wdXAnLCBkZXNjcmlwdGlvbjogJ0Vycm9yL1dhcm5pbmcgUG9wdXBzJyB9LFxuICAgIF0sXG4gICAgb3JkZXI6IDQyLFxuICB9LFxuICBtYXhNZW1NZWdzOiB7XG4gICAgdHlwZTogJ2ludGVnZXInLFxuICAgIGRlc2NyaXRpb246ICdNYXhpbXVtIGdoYy1tb2QgaW50ZXJhY3RpdmUgbW9kZSBtZW1vcnkgdXNhZ2UgKGluIG1lZ2FieXRlcyknLFxuICAgIGRlZmF1bHQ6IDQgKiAxMDI0LFxuICAgIG1pbmltdW06IDEwMjQsXG4gICAgb3JkZXI6IDUwLFxuICB9LFxufVxuIl19 -------------------------------------------------------------------------------- /lib/ghc-mod/build-stack.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const atom_1 = require("atom"); 4 | const CP = require("child_process"); 5 | const os_1 = require("os"); 6 | const Util = require("../util"); 7 | async function buildStack(opts, upi) { 8 | const messages = []; 9 | const disp = new atom_1.CompositeDisposable(); 10 | try { 11 | const vers = await Util.execPromise('stack', ['--no-install-ghc', '--numeric-version'], opts) 12 | .then(({ stdout }) => stdout.split('.').map((n) => parseInt(n, 10))) 13 | .catch(() => undefined); 14 | const hasCopyCompiler = vers ? Util.versAtLeast(vers, [1, 6, 1]) : false; 15 | return await new Promise((resolve, reject) => { 16 | const args = ['--no-install-ghc', 'build']; 17 | if (hasCopyCompiler) 18 | args.push('--copy-compiler-tool'); 19 | args.push('ghc-mod'); 20 | Util.warn(`Running stack ${args.join(' ')}`); 21 | const proc = CP.spawn('stack', args, opts); 22 | const buffered = () => { 23 | let buffer = ''; 24 | return (data) => { 25 | const output = data.toString('utf8'); 26 | const [first, ...tail] = output.split(os_1.EOL); 27 | buffer += first; 28 | if (tail.length > 0) { 29 | const lines = [buffer, ...tail.slice(0, -1)]; 30 | buffer = tail.slice(-1)[0]; 31 | messages.push(...lines.map((message) => ({ message, severity: 'build' }))); 32 | if (upi) { 33 | upi.setMessages(messages); 34 | } 35 | else { 36 | atom.notifications.addInfo(lines.join('\n')); 37 | } 38 | console.log(lines.join('\n')); 39 | } 40 | }; 41 | }; 42 | proc.stdout.on('data', buffered()); 43 | proc.stderr.on('data', buffered()); 44 | if (upi) { 45 | disp.add(upi.addPanelControl({ 46 | element: 'ide-haskell-button', 47 | opts: { 48 | classes: ['cancel'], 49 | events: { 50 | click: () => { 51 | proc.kill('SIGTERM'); 52 | proc.kill('SIGKILL'); 53 | }, 54 | }, 55 | }, 56 | })); 57 | } 58 | proc.once('exit', (code, signal) => { 59 | if (code === 0) { 60 | resolve(true); 61 | } 62 | else { 63 | reject(new Error(`Stack build exited with nonzero exit status ${code} due to ${signal}`)); 64 | Util.warn(messages.map((m) => m.message).join('\n')); 65 | } 66 | }); 67 | }); 68 | } 69 | catch (e) { 70 | Util.warn(e); 71 | atom.notifications.addError(e.toString(), { 72 | dismissable: true, 73 | detail: messages.map((m) => m.message).join('\n'), 74 | }); 75 | return false; 76 | } 77 | finally { 78 | upi && upi.setMessages([]); 79 | disp.dispose(); 80 | } 81 | } 82 | exports.buildStack = buildStack; 83 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGQtc3RhY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZ2hjLW1vZC9idWlsZC1zdGFjay50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLCtCQUEwQztBQUUxQyxvQ0FBbUM7QUFDbkMsMkJBQXdCO0FBQ3hCLGdDQUErQjtBQUd4QixLQUFLLFVBQVUsVUFBVSxDQUM5QixJQUFnQixFQUNoQixHQUE2QjtJQUU3QixNQUFNLFFBQVEsR0FBa0IsRUFBRSxDQUFBO0lBQ2xDLE1BQU0sSUFBSSxHQUFHLElBQUksMEJBQW1CLEVBQUUsQ0FBQTtJQUN0QyxJQUFJO1FBQ0YsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUNqQyxPQUFPLEVBQ1AsQ0FBQyxrQkFBa0IsRUFBRSxtQkFBbUIsQ0FBQyxFQUN6QyxJQUFJLENBQ0w7YUFDRSxJQUFJLENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO2FBQ25FLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQTtRQUN6QixNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUE7UUFDeEUsT0FBTyxNQUFNLElBQUksT0FBTyxDQUFVLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQ3BELE1BQU0sSUFBSSxHQUFHLENBQUMsa0JBQWtCLEVBQUUsT0FBTyxDQUFDLENBQUE7WUFDMUMsSUFBSSxlQUFlO2dCQUFFLElBQUksQ0FBQyxJQUFJ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QUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQTtpQkFDckQ7WUFDSCxDQUFDLENBQUMsQ0FBQTtRQUNKLENBQUMsQ0FBQyxDQUFBO0tBQ0g7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDWixJQUFJLENBQUMsYUFBYSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7WUFDeEMsV0FBVyxFQUFFLElBQUk7WUFDakIsTUFBTSxFQUFFLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDO1NBQ2xELENBQUMsQ0FBQTtRQUNGLE9BQU8sS0FBSyxDQUFBO0tBQ2I7WUFBUztRQUNSLEdBQUcsSUFBSSxHQUFHLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxDQUFBO1FBQzFCLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQTtLQUNmO0FBQ0gsQ0FBQztBQXJGRCxnQ0FxRkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb3NpdGVEaXNwb3NhYmxlIH0gZnJvbSAnYXRvbSdcbmltcG9ydCB7IElVUElJbnN0YW5jZSwgSVJlc3VsdEl0ZW0gfSBmcm9tICdhdG9tLWhhc2tlbGwtdXBpJ1xuaW1wb3J0ICogYXMgQ1AgZnJvbSAnY2hpbGRfcHJvY2VzcydcbmltcG9ydCB7IEVPTCB9IGZyb20gJ29zJ1xuaW1wb3J0ICogYXMgVXRpbCBmcm9tICcuLi91dGlsJ1xuaW1wb3J0IHsgUnVuT3B0aW9ucyB9IGZyb20gJy4vZ2hjLW1vZGktcHJvY2Vzcy1yZWFsJ1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gYnVpbGRTdGFjayhcbiAgb3B0czogUnVuT3B0aW9ucyxcbiAgdXBpOiBJVVBJSW5zdGFuY2UgfCB1bmRlZmluZWQsXG4pOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgY29uc3QgbWVzc2FnZXM6IElSZXN1bHRJdGVtW10gPSBbXVxuICBjb25zdCBkaXNwID0gbmV3IENvbXBvc2l0ZURpc3Bvc2FibGUoKVxuICB0cnkge1xuICAgIGNvbnN0IHZlcnMgPSBhd2FpdCBVdGlsLmV4ZWNQcm9taXNlKFxuICAgICAgJ3N0YWNrJyxcbiAgICAgIFsnLS1uby1pbnN0YWxsLWdoYycsICctLW51bWVyaWMtdmVyc2lvbiddLFxuICAgICAgb3B0cyxcbiAgICApXG4gICAgICAudGhlbigoeyBzdGRvdXQgfSkgPT4gc3Rkb3V0LnNwbGl0KCcuJykubWFwKChuKSA9PiBwYXJzZUludChuLCAxMCkpKVxuICAgICAgLmNhdGNoKCgpID0+IHVuZGVmaW5lZClcbiAgICBjb25zdCBoYXNDb3B5Q29tcGlsZXIgPSB2ZXJzID8gVXRpbC52ZXJzQXRMZWFzdCh2ZXJzLCBbMSwgNiwgMV0pIDogZmFsc2VcbiAgICByZXR1cm4gYXdhaXQgbmV3IFByb21pc2U8Ym9vbGVhbj4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgY29uc3QgYXJncyA9IFsnLS1uby1pbnN0YWxsLWdoYycsICdidWlsZCddXG4gICAgICBpZiAoaGFzQ29weUNvbXBpbGVyKSBhcmdzLnB1c2goJy0tY29weS1jb21waWxlci10b29sJylcbiAgICAgIGFyZ3MucHVzaCgnZ2hjLW1vZCcpXG4gICAgICBVdGlsLndhcm4oYFJ1bm5pbmcgc3RhY2sgJHthcmdzLmpvaW4oJyAnKX1gKVxuICAgICAgY29uc3QgcHJvYyA9IENQLnNwYXduKCdzdGFjaycsIGFyZ3MsIG9wdHMpXG4gICAgICBjb25zdCBidWZmZXJlZCA9ICgpID0+IHtcbiAgICAgICAgbGV0IGJ1ZmZlciA9ICcnXG4gICAgICAgIHJldHVybiAoZGF0YTogQnVmZmVyKSA9PiB7XG4gICAgICAgICAgY29uc3Qgb3V0cHV0ID0gZGF0YS50b1N0cmluZygndXRmOCcpXG4gICAgICAgICAgY29uc3QgW2ZpcnN0LCAuLi50YWlsXSA9IG91dHB1dC5zcGxpdChFT0wpXG4gICAgICAgICAgYnVmZmVyICs9IGZpcnN0XG4gICAgICAgICAgaWYgKHRhaWwubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgLy8gaXQgbWVhbnMgdGhlcmUncyBhdCBsZWFzdCBvbmUgbmV3bGluZVxuICAgICAgICAgICAgY29uc3QgbGluZXMgPSBbYnVmZmVyLCAuLi50YWlsLnNsaWNlKDAsIC0xKV1cbiAgICAgICAgICAgIGJ1ZmZlciA9IHRhaWwuc2xpY2UoLTEpWzBdXG4gICAgICAgICAgICBtZXNzYWdlcy5wdXNoKFxuICAgICAgICAgICAgICAuLi5saW5lcy5tYXAoKG1lc3NhZ2UpID0+ICh7IG1lc3NhZ2UsIHNldmVyaXR5OiAnYnVpbGQnIH0pKSxcbiAgICAgICAgICAgIClcbiAgICAgICAgICAgIGlmICh1cGkpIHtcbiAgICAgICAgICAgICAgdXBpLnNldE1lc3NhZ2VzKG1lc3NhZ2VzKVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgYXRvbS5ub3RpZmljYXRpb25zLmFkZEluZm8obGluZXMuam9pbignXFxuJykpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zb2xlLmxvZyhsaW5lcy5qb2luKCdcXG4nKSlcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHByb2Muc3Rkb3V0Lm9uKCdkYXRhJywgYnVmZmVyZWQoKSlcbiAgICAgIHByb2Muc3RkZXJyLm9uKCdkYXRhJywgYnVmZmVyZWQoKSlcbiAgICAgIGlmICh1cGkpIHtcbiAgICAgICAgZGlzcC5hZGQoXG4gICAgICAgICAgdXBpLmFkZFBhbmVsQ29udHJvbCh7XG4gICAgICAgICAgICBlbGVtZW50OiAnaWRlLWhhc2tlbGwtYnV0dG9uJyxcbiAgICAgICAgICAgIG9wdHM6IHtcbiAgICAgICAgICAgICAgY2xhc3NlczogWydjYW5jZWwnXSxcbiAgICAgICAgICAgICAgZXZlbnRzOiB7XG4gICAgICAgICAgICAgICAgY2xpY2s6ICgpID0+IHtcbiAgICAgICAgICAgICAgICAgIHByb2Mua2lsbCgnU0lHVEVSTScpXG4gICAgICAgICAgICAgICAgICBwcm9jLmtpbGwoJ1NJR0tJTEwnKVxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB9LFxuICAgICAgICAgIH0pLFxuICAgICAgICApXG4gICAgICB9XG4gICAgICBwcm9jLm9uY2UoJ2V4aXQnLCAoY29kZSwgc2lnbmFsKSA9PiB7XG4gICAgICAgIGlmIChjb2RlID09PSAwKSB7XG4gICAgICAgICAgcmVzb2x2ZSh0cnVlKVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHJlamVjdChcbiAgICAgICAgICAgIG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgYFN0YWNrIGJ1aWxkIGV4aXRlZCB3aXRoIG5vbnplcm8gZXhpdCBzdGF0dXMgJHtjb2RlfSBkdWUgdG8gJHtzaWduYWx9YCxcbiAgICAgICAgICAgICksXG4gICAgICAgICAgKVxuICAgICAgICAgIFV0aWwud2FybihtZXNzYWdlcy5tYXAoKG0pID0+IG0ubWVzc2FnZSkuam9pbignXFxuJykpXG4gICAgICAgIH1cbiAgICAgIH0pXG4gICAgfSlcbiAgfSBjYXRjaCAoZSkge1xuICAgIFV0aWwud2FybihlKVxuICAgIGF0b20ubm90aWZpY2F0aW9ucy5hZGRFcnJvcihlLnRvU3RyaW5nKCksIHtcbiAgICAgIGRpc21pc3NhYmxlOiB0cnVlLFxuICAgICAgZGV0YWlsOiBtZXNzYWdlcy5tYXAoKG0pID0+IG0ubWVzc2FnZSkuam9pbignXFxuJyksXG4gICAgfSlcbiAgICByZXR1cm4gZmFsc2VcbiAgfSBmaW5hbGx5IHtcbiAgICB1cGkgJiYgdXBpLnNldE1lc3NhZ2VzKFtdKVxuICAgIGRpc3AuZGlzcG9zZSgpXG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /lib/ghc-mod/settings.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const atom_1 = require("atom"); 4 | const Util = require("../util"); 5 | async function getSettings(runDir) { 6 | const localSettings = readSettings(runDir.getFile('.haskell-ghc-mod.json')); 7 | const [projectDir] = atom.project 8 | .getDirectories() 9 | .filter((d) => d.contains(runDir.getPath())); 10 | const projectSettings = projectDir 11 | ? readSettings(projectDir.getFile('.haskell-ghc-mod.json')) 12 | : Promise.resolve({}); 13 | const configDir = new atom_1.Directory(atom.getConfigDirPath()); 14 | const globalSettings = readSettings(configDir.getFile('haskell-ghc-mod.json')); 15 | const [glob, prj, loc] = await Promise.all([ 16 | globalSettings, 17 | projectSettings, 18 | localSettings, 19 | ]); 20 | return Object.assign({}, glob, prj, loc); 21 | } 22 | exports.getSettings = getSettings; 23 | async function readSettings(file) { 24 | try { 25 | const contents = await file.read(); 26 | if (contents !== null) { 27 | try { 28 | return JSON.parse(contents); 29 | } 30 | catch (err) { 31 | atom.notifications.addError(`Failed to parse ${file.getPath()}`, { 32 | detail: err, 33 | dismissable: true, 34 | }); 35 | throw err; 36 | } 37 | } 38 | else { 39 | return {}; 40 | } 41 | } 42 | catch (error) { 43 | if (error) { 44 | Util.warn(error); 45 | } 46 | return {}; 47 | } 48 | } 49 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2V0dGluZ3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZ2hjLW1vZC9zZXR0aW5ncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLCtCQUFzQztBQUN0QyxnQ0FBK0I7QUFTeEIsS0FBSyxVQUFVLFdBQVcsQ0FBQyxNQUFpQjtJQUNqRCxNQUFNLGFBQWEsR0FBRyxZQUFZLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDLENBQUE7SUFFM0UsTUFBTSxDQUFDLFVBQVUsQ0FBQyxHQUFHLElBQUksQ0FBQyxPQUFPO1NBQzlCLGNBQWMsRUFBRTtTQUNoQixNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQTtJQUM5QyxNQUFNLGVBQWUsR0FBRyxVQUFVO1FBQ2hDLENBQUMsQ0FBQyxZQUFZLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDO1FBQzNELENBQUMsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBRXZCLE1BQU0sU0FBUyxHQUFHLElBQUksZ0JBQVMsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQyxDQUFBO0lBQ3hELE1BQU0sY0FBYyxHQUFHLFlBQVksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLHNCQUFzQixDQUFDLENBQUMsQ0FBQTtJQUU5RSxNQUFNLENBQUMsSUFBSSxFQUFFLEdBQUcsRUFBRSxHQUFHLENBQUMsR0FBRyxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUM7UUFDekMsY0FBYztRQUNkLGVBQWU7UUFDZixhQUFhO0tBQ2QsQ0FBQyxDQUFBO0lBQ0YseUJBQVksSUFBSSxFQUFLLEdBQUcsRUFBSyxHQUFHLEVBQUU7QUFDcEMsQ0FBQztBQW5CRCxrQ0FtQkM7QUFFRCxLQUFLLFVBQVUsWUFBWSxDQUFDLElBQVU7SUFDcEMsSUFBSTtRQUNGLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksRUFBRSxDQUFBO1FBQ2xDLElBQUksUUFBUSxLQUFLLElBQUksRUFBRTtZQUNyQixJQUFJO2dCQUNGLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQTthQUM1QjtZQUFDLE9BQU8sR0FBRyxFQUFFO2dCQUNaLElBQUksQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLG1CQUFtQixJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsRUFBRTtvQkFDL0QsTUFBTSxFQUFFLEdBQUc7b0JBQ1gsV0FBVyxFQUFFLElBQUk7aUJBQ2xCLENBQUMsQ0FBQTtnQkFDRixNQUFNLEdBQUcsQ0FBQTthQUNWO1NBQ0Y7YUFBTTtZQUNMLE9BQU8sRUFBRSxDQUFBO1NBQ1Y7S0FDRjtJQUFDLE9BQU8sS0FBSyxFQUFFO1FBQ2QsSUFBSSxLQUFLLEVBQUU7WUFDVCxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFBO1NBQ2pCO1FBQ0QsT0FBTyxFQUFFLENBQUE7S0FDVjtBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBGaWxlLCBEaXJlY3RvcnkgfSBmcm9tICdhdG9tJ1xuaW1wb3J0ICogYXMgVXRpbCBmcm9tICcuLi91dGlsJ1xuXG5leHBvcnQgaW50ZXJmYWNlIEdIQ01vZFNldHRpbmdzIHtcbiAgZGlzYWJsZT86IGJvb2xlYW5cbiAgc3VwcHJlc3NFcnJvcnM/OiBib29sZWFuXG4gIGdoY09wdGlvbnM/OiBzdHJpbmdbXVxuICBnaGNNb2RPcHRpb25zPzogc3RyaW5nW11cbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldFNldHRpbmdzKHJ1bkRpcjogRGlyZWN0b3J5KTogUHJvbWlzZTxHSENNb2RTZXR0aW5ncz4ge1xuICBjb25zdCBsb2NhbFNldHRpbmdzID0gcmVhZFNldHRpbmdzKHJ1bkRpci5nZXRGaWxlKCcuaGFza2VsbC1naGMtbW9kLmpzb24nKSlcblxuICBjb25zdCBbcHJvamVjdERpcl0gPSBhdG9tLnByb2plY3RcbiAgICAuZ2V0RGlyZWN0b3JpZXMoKVxuICAgIC5maWx0ZXIoKGQpID0+IGQuY29udGFpbnMocnVuRGlyLmdldFBhdGgoKSkpXG4gIGNvbnN0IHByb2plY3RTZXR0aW5ncyA9IHByb2plY3REaXJcbiAgICA/IHJlYWRTZXR0aW5ncyhwcm9qZWN0RGlyLmdldEZpbGUoJy5oYXNrZWxsLWdoYy1tb2QuanNvbicpKVxuICAgIDogUHJvbWlzZS5yZXNvbHZlKHt9KVxuXG4gIGNvbnN0IGNvbmZpZ0RpciA9IG5ldyBEaXJlY3RvcnkoYXRvbS5nZXRDb25maWdEaXJQYXRoKCkpXG4gIGNvbnN0IGdsb2JhbFNldHRpbmdzID0gcmVhZFNldHRpbmdzKGNvbmZpZ0Rpci5nZXRGaWxlKCdoYXNrZWxsLWdoYy1tb2QuanNvbicpKVxuXG4gIGNvbnN0IFtnbG9iLCBwcmosIGxvY10gPSBhd2FpdCBQcm9taXNlLmFsbChbXG4gICAgZ2xvYmFsU2V0dGluZ3MsXG4gICAgcHJvamVjdFNldHRpbmdzLFxuICAgIGxvY2FsU2V0dGluZ3MsXG4gIF0pXG4gIHJldHVybiB7IC4uLmdsb2IsIC4uLnByaiwgLi4ubG9jIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gcmVhZFNldHRpbmdzKGZpbGU6IEZpbGUpOiBQcm9taXNlPEdIQ01vZFNldHRpbmdzPiB7XG4gIHRyeSB7XG4gICAgY29uc3QgY29udGVudHMgPSBhd2FpdCBmaWxlLnJlYWQoKVxuICAgIGlmIChjb250ZW50cyAhPT0gbnVsbCkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UoY29udGVudHMpXG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgYXRvbS5ub3RpZmljYXRpb25zLmFkZEVycm9yKGBGYWlsZWQgdG8gcGFyc2UgJHtmaWxlLmdldFBhdGgoKX1gLCB7XG4gICAgICAgICAgZGV0YWlsOiBlcnIsXG4gICAgICAgICAgZGlzbWlzc2FibGU6IHRydWUsXG4gICAgICAgIH0pXG4gICAgICAgIHRocm93IGVyclxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4ge31cbiAgICB9XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgaWYgKGVycm9yKSB7XG4gICAgICBVdGlsLndhcm4oZXJyb3IpXG4gICAgfVxuICAgIHJldHVybiB7fVxuICB9XG59XG4iXX0= -------------------------------------------------------------------------------- /lib/haskell-ghc-mod.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const ghc_mod_1 = require("./ghc-mod"); 4 | const atom_1 = require("atom"); 5 | const completion_backend_1 = require("./completion-backend"); 6 | const upi_consumer_1 = require("./upi-consumer"); 7 | const util_1 = require("./util"); 8 | let process; 9 | let disposables; 10 | let tempDisposables; 11 | let completionBackend; 12 | let resolveUpiPromise; 13 | let upiPromise; 14 | var config_1 = require("./config"); 15 | exports.config = config_1.config; 16 | function activate(_state) { 17 | upiPromise = new Promise((resolve) => (resolveUpiPromise = resolve)); 18 | process = new ghc_mod_1.GhcModiProcess(upiPromise); 19 | disposables = new atom_1.CompositeDisposable(); 20 | tempDisposables = new atom_1.CompositeDisposable(); 21 | disposables.add(tempDisposables); 22 | tempDisposables.add(process.onError(util_1.defaultErrorHandler), process.onWarning((detail) => { 23 | atom.notifications.addWarning('ghc-mod warning', { detail }); 24 | })); 25 | disposables.add(atom.commands.add('atom-workspace', { 26 | 'haskell-ghc-mod:shutdown-backend': () => process && process.killProcess(), 27 | })); 28 | } 29 | exports.activate = activate; 30 | function deactivate() { 31 | process && process.destroy(); 32 | process = undefined; 33 | completionBackend = undefined; 34 | disposables && disposables.dispose(); 35 | disposables = undefined; 36 | tempDisposables = undefined; 37 | } 38 | exports.deactivate = deactivate; 39 | function provideCompletionBackend() { 40 | if (!process) { 41 | return undefined; 42 | } 43 | if (!completionBackend) { 44 | completionBackend = new completion_backend_1.CompletionBackend(process, upiPromise); 45 | } 46 | return completionBackend; 47 | } 48 | exports.provideCompletionBackend = provideCompletionBackend; 49 | function consumeUPI(service) { 50 | if (!process || !disposables) { 51 | return undefined; 52 | } 53 | tempDisposables && tempDisposables.dispose(); 54 | tempDisposables = undefined; 55 | const upiConsumer = new upi_consumer_1.UPIConsumer(service, process); 56 | resolveUpiPromise(upiConsumer.upi); 57 | disposables.add(upiConsumer); 58 | return upiConsumer; 59 | } 60 | exports.consumeUPI = consumeUPI; 61 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGFza2VsbC1naGMtbW9kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2hhc2tlbGwtZ2hjLW1vZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHVDQUEwQztBQUMxQywrQkFBMEM7QUFDMUMsNkRBQXdEO0FBQ3hELGlEQUE0QztBQUM1QyxpQ0FBNEM7QUFHNUMsSUFBSSxPQUFtQyxDQUFBO0FBQ3ZDLElBQUksV0FBNEMsQ0FBQTtBQUNoRCxJQUFJLGVBQWdELENBQUE7QUFDcEQsSUFBSSxpQkFBZ0QsQ0FBQTtBQUNwRCxJQUFJLGlCQUFnRCxDQUFBO0FBQ3BELElBQUksVUFBcUMsQ0FBQTtBQUV6QyxtQ0FBaUM7QUFBeEIsMEJBQUEsTUFBTSxDQUFBO0FBRWYsU0FBZ0IsUUFBUSxDQUFDLE1BQWE7SUFDcEMsVUFBVSxHQUFHLElBQUksT0FBTyxDQUN0QixDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsQ0FBQyxpQkFBaUIsR0FBRyxPQUFPLENBQUMsQ0FDM0MsQ0FBQTtJQUNELE9BQU8sR0FBRyxJQUFJLHdCQUFjLENBQUMsVUFBVSxDQUFDLENBQUE7SUFDeEMsV0FBVyxHQUFHLElBQUksMEJBQW1CLEVBQUUsQ0FBQTtJQUN2QyxlQUFlLEdBQUcsSUFBSSwwQkFBbUIsRUFBRSxDQUFBO0lBQzNDLFdBQVcsQ0FBQyxHQUFHLENBQUMsZUFBZSxDQUFDLENBQUE7SUFFaEMsZUFBZSxDQUFDLEdBQUcsQ0FDakIsT0FBTyxDQUFDLE9BQU8sQ0FBQywwQkFBbUIsQ0FBQyxFQUNwQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsTUFBYyxFQUFFLEVBQUU7UUFDbkMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxVQUFVLENBQUMsaUJBQWlCLEVBQUUsRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFBO0lBQzlELENBQUMsQ0FBQyxDQUNILENBQUE7SUFFRCxXQUFXLENBQUMsR0FBRyxDQUNiLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLGdCQUFnQixFQUFFO1FBQ2xDLGtDQUFrQyxFQUFFLEdBQUcsRUFBRSxDQUN2QyxPQUFPLElBQUksT0FBTyxDQUFDLFdBQVcsRUFBRTtLQUNuQyxDQUFDLENBQ0gsQ0FBQTtBQUNILENBQUM7QUF0QkQsNEJBc0JDO0FBRUQsU0FBZ0IsVUFBVTtJQUN4QixPQUFPLElBQUksT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFBO0lBQzVCLE9BQU8sR0FBRyxTQUFTLENBQUE7SUFDbkIsaUJBQWlCLEdBQUcsU0FBUyxDQUFBO0lBQzdCLFdBQVcsSUFBSSxXQUFXLENBQUMsT0FBTyxFQUFFLENBQUE7SUFDcEMsV0FBVyxHQUFHLFNBQVMsQ0FBQTtJQUN2QixlQUFlLEdBQUcsU0FBUyxDQUFBO0FBQzdCLENBQUM7QUFQRCxnQ0FPQztBQUVELFNBQWdCLHdCQUF3QjtJQUN0QyxJQUFJLENBQUMsT0FBTyxFQUFFO1FBQ1osT0FBTyxTQUFTLENBQUE7S0FDakI7SUFDRCxJQUFJLENBQUMsaUJBQWlCLEVBQUU7UUFDdEIsaUJBQWlCLEdBQUcsSUFBSSxzQ0FBaUIsQ0FBQyxPQUFPLEVBQUUsVUFBVSxDQUFDLENBQUE7S0FDL0Q7SUFDRCxPQUFPLGlCQUFpQixDQUFBO0FBQzFCLENBQUM7QUFSRCw0REFRQztBQUVELFNBQWdCLFVBQVUsQ0FBQyxPQUE2QjtJQUN0RCxJQUFJLENBQUMsT0FBTyxJQUFJLENBQUMsV0FBVyxFQUFFO1FBQzVCLE9BQU8sU0FBUyxDQUFBO0tBQ2pCO0lBQ0QsZUFBZSxJQUFJLGVBQWUsQ0FBQyxPQUFPLEVBQUUsQ0FBQTtJQUM1QyxlQUFlLEdBQUcsU0FBUyxDQUFBO0lBQzNCLE1BQU0sV0FBVyxHQUFHLElBQUksMEJBQVcsQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUE7SUFDckQsaUJBQWlCLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBQ2xDLFdBQVcsQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUE7SUFDNUIsT0FBTyxXQUFXLENBQUE7QUFDcEIsQ0FBQztBQVZELGdDQVVDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgR2hjTW9kaVByb2Nlc3MgfSBmcm9tICcuL2doYy1tb2QnXG5pbXBvcnQgeyBDb21wb3NpdGVEaXNwb3NhYmxlIH0gZnJvbSAnYXRvbSdcbmltcG9ydCB7IENvbXBsZXRpb25CYWNrZW5kIH0gZnJvbSAnLi9jb21wbGV0aW9uLWJhY2tlbmQnXG5pbXBvcnQgeyBVUElDb25zdW1lciB9IGZyb20gJy4vdXBpLWNvbnN1bWVyJ1xuaW1wb3J0IHsgZGVmYXVsdEVycm9ySGFuZGxlciB9IGZyb20gJy4vdXRpbCdcbmltcG9ydCAqIGFzIFVQSSBmcm9tICdhdG9tLWhhc2tlbGwtdXBpJ1xuXG5sZXQgcHJvY2VzczogR2hjTW9kaVByb2Nlc3MgfCB1bmRlZmluZWRcbmxldCBkaXNwb3NhYmxlczogQ29tcG9zaXRlRGlzcG9zYWJsZSB8IHVuZGVmaW5lZFxubGV0IHRlbXBEaXNwb3NhYmxlczogQ29tcG9zaXRlRGlzcG9zYWJsZSB8IHVuZGVmaW5lZFxubGV0IGNvbXBsZXRpb25CYWNrZW5kOiBDb21wbGV0aW9uQmFja2VuZCB8IHVuZGVmaW5lZFxubGV0IHJlc29sdmVVcGlQcm9taXNlOiAodjogVVBJLklVUElJbnN0YW5jZSkgPT4gdm9pZFxubGV0IHVwaVByb21pc2U6IFByb21pc2U8VVBJLklVUElJbnN0YW5jZT5cblxuZXhwb3J0IHsgY29uZmlnIH0gZnJvbSAnLi9jb25maWcnXG5cbmV4cG9ydCBmdW5jdGlvbiBhY3RpdmF0ZShfc3RhdGU6IG5ldmVyKSB7XG4gIHVwaVByb21pc2UgPSBuZXcgUHJvbWlzZTxVUEkuSVVQSUluc3RhbmNlPihcbiAgICAocmVzb2x2ZSkgPT4gKHJlc29sdmVVcGlQcm9taXNlID0gcmVzb2x2ZSksXG4gIClcbiAgcHJvY2VzcyA9IG5ldyBHaGNNb2RpUHJvY2Vzcyh1cGlQcm9taXNlKVxuICBkaXNwb3NhYmxlcyA9IG5ldyBDb21wb3NpdGVEaXNwb3NhYmxlKClcbiAgdGVtcERpc3Bvc2FibGVzID0gbmV3IENvbXBvc2l0ZURpc3Bvc2FibGUoKVxuICBkaXNwb3NhYmxlcy5hZGQodGVtcERpc3Bvc2FibGVzKVxuXG4gIHRlbXBEaXNwb3NhYmxlcy5hZGQoXG4gICAgcHJvY2Vzcy5vbkVycm9yKGRlZmF1bHRFcnJvckhhbmRsZXIpLFxuICAgIHByb2Nlc3Mub25XYXJuaW5nKChkZXRhaWw6IHN0cmluZykgPT4ge1xuICAgICAgYXRvbS5ub3RpZmljYXRpb25zLmFkZFdhcm5pbmcoJ2doYy1tb2Qgd2FybmluZycsIHsgZGV0YWlsIH0pXG4gICAgfSksXG4gIClcblxuICBkaXNwb3NhYmxlcy5hZGQoXG4gICAgYXRvbS5jb21tYW5kcy5hZGQoJ2F0b20td29ya3NwYWNlJywge1xuICAgICAgJ2hhc2tlbGwtZ2hjLW1vZDpzaHV0ZG93bi1iYWNrZW5kJzogKCkgPT5cbiAgICAgICAgcHJvY2VzcyAmJiBwcm9jZXNzLmtpbGxQcm9jZXNzKCksXG4gICAgfSksXG4gIClcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGRlYWN0aXZhdGUoKSB7XG4gIHByb2Nlc3MgJiYgcHJvY2Vzcy5kZXN0cm95KClcbiAgcHJvY2VzcyA9IHVuZGVmaW5lZFxuICBjb21wbGV0aW9uQmFja2VuZCA9IHVuZGVmaW5lZFxuICBkaXNwb3NhYmxlcyAmJiBkaXNwb3NhYmxlcy5kaXNwb3NlKClcbiAgZGlzcG9zYWJsZXMgPSB1bmRlZmluZWRcbiAgdGVtcERpc3Bvc2FibGVzID0gdW5kZWZpbmVkXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwcm92aWRlQ29tcGxldGlvbkJhY2tlbmQoKSB7XG4gIGlmICghcHJvY2Vzcykge1xuICAgIHJldHVybiB1bmRlZmluZWRcbiAgfVxuICBpZiAoIWNvbXBsZXRpb25CYWNrZW5kKSB7XG4gICAgY29tcGxldGlvbkJhY2tlbmQgPSBuZXcgQ29tcGxldGlvbkJhY2tlbmQocHJvY2VzcywgdXBpUHJvbWlzZSlcbiAgfVxuICByZXR1cm4gY29tcGxldGlvbkJhY2tlbmRcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvbnN1bWVVUEkoc2VydmljZTogVVBJLklVUElSZWdpc3RyYXRpb24pIHtcbiAgaWYgKCFwcm9jZXNzIHx8ICFkaXNwb3NhYmxlcykge1xuICAgIHJldHVybiB1bmRlZmluZWRcbiAgfVxuICB0ZW1wRGlzcG9zYWJsZXMgJiYgdGVtcERpc3Bvc2FibGVzLmRpc3Bvc2UoKVxuICB0ZW1wRGlzcG9zYWJsZXMgPSB1bmRlZmluZWRcbiAgY29uc3QgdXBpQ29uc3VtZXIgPSBuZXcgVVBJQ29uc3VtZXIoc2VydmljZSwgcHJvY2VzcylcbiAgcmVzb2x2ZVVwaVByb21pc2UodXBpQ29uc3VtZXIudXBpKVxuICBkaXNwb3NhYmxlcy5hZGQodXBpQ29uc3VtZXIpXG4gIHJldHVybiB1cGlDb25zdW1lclxufVxuIl19 -------------------------------------------------------------------------------- /lib/views/import-list-view.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const SelectListView = require("atom-select-list"); 4 | async function importListView(imports) { 5 | let panel; 6 | let res; 7 | try { 8 | res = await new Promise((resolve) => { 9 | const select = new SelectListView({ 10 | items: imports, 11 | itemsClassList: ['ide-haskell'], 12 | elementForItem: (item) => { 13 | const li = document.createElement('li'); 14 | li.innerText = `${item}`; 15 | return li; 16 | }, 17 | didCancelSelection: () => { 18 | resolve(); 19 | }, 20 | didConfirmSelection: (item) => { 21 | resolve(item); 22 | }, 23 | }); 24 | select.element.classList.add('ide-haskell'); 25 | panel = atom.workspace.addModalPanel({ 26 | item: select, 27 | visible: true, 28 | }); 29 | select.focus(); 30 | }); 31 | } 32 | finally { 33 | panel && panel.destroy(); 34 | } 35 | return res; 36 | } 37 | exports.importListView = importListView; 38 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0LWxpc3Qtdmlldy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92aWV3cy9pbXBvcnQtbGlzdC12aWV3LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsbURBQW1EO0FBRzVDLEtBQUssVUFBVSxjQUFjLENBQ2xDLE9BQWlCO0lBRWpCLElBQUksS0FBZ0QsQ0FBQTtJQUNwRCxJQUFJLEdBQXVCLENBQUE7SUFDM0IsSUFBSTtRQUNGLEdBQUcsR0FBRyxNQUFNLElBQUksT0FBTyxDQUFxQixDQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ3RELE1BQU0sTUFBTSxHQUFHLElBQUksY0FBYyxDQUFDO2dCQUNoQyxLQUFLLEVBQUUsT0FBTztnQkFFZCxjQUFjLEVBQUUsQ0FBQyxhQUFhLENBQUM7Z0JBQy9CLGNBQWMsRUFBRSxDQUFDLElBQVksRUFBRSxFQUFFO29CQUMvQixNQUFNLEVBQUUsR0FBRyxRQUFRLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxDQUFBO29CQUN2QyxFQUFFLENBQUMsU0FBUyxHQUFHLEdBQUcsSUFBSSxFQUFFLENBQUE7b0JBQ3hCLE9BQU8sRUFBRSxDQUFBO2dCQUNYLENBQUM7Z0JBQ0Qsa0JBQWtCLEVBQUUsR0FBRyxFQUFFO29CQUN2QixPQUFPLEVBQUUsQ0FBQTtnQkFDWCxDQUFDO2dCQUNELG1CQUFtQixFQUFFLENBQUMsSUFBWSxFQUFFLEVBQUU7b0JBQ3BDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQTtnQkFDZixDQUFDO2FBQ0YsQ0FBQyxDQUFBO1lBQ0YsTUFBTSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxDQUFBO1lBQzNDLEtBQUssR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLGFBQWEsQ0FBQztnQkFDbkMsSUFBSSxFQUFFLE1BQU07Z0JBQ1osT0FBTyxFQUFFLElBQUk7YUFDZCxDQUFDLENBQUE7WUFDRixNQUFNLENBQUMsS0FBSyxFQUFFLENBQUE7UUFDaEIsQ0FBQyxDQUFDLENBQUE7S0FDSDtZQUFTO1FBQ1IsS0FBSyxJQUFJLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQTtLQUN6QjtJQUNELE9BQU8sR0FBRyxDQUFBO0FBQ1osQ0FBQztBQWxDRCx3Q0FrQ0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgU2VsZWN0TGlzdFZpZXcgPSByZXF1aXJlKCdhdG9tLXNlbGVjdC1saXN0JylcbmltcG9ydCB7IFBhbmVsIH0gZnJvbSAnYXRvbSdcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGltcG9ydExpc3RWaWV3KFxuICBpbXBvcnRzOiBzdHJpbmdbXSxcbik6IFByb21pc2U8c3RyaW5nIHwgdW5kZWZpbmVkPiB7XG4gIGxldCBwYW5lbDogUGFuZWw8U2VsZWN0TGlzdFZpZXc8c3RyaW5nPj4gfCB1bmRlZmluZWRcbiAgbGV0IHJlczogc3RyaW5nIHwgdW5kZWZpbmVkXG4gIHRyeSB7XG4gICAgcmVzID0gYXdhaXQgbmV3IFByb21pc2U8c3RyaW5nIHwgdW5kZWZpbmVkPigocmVzb2x2ZSkgPT4ge1xuICAgICAgY29uc3Qgc2VsZWN0ID0gbmV3IFNlbGVjdExpc3RWaWV3KHtcbiAgICAgICAgaXRlbXM6IGltcG9ydHMsXG4gICAgICAgIC8vIGluZm9NZXNzYWdlOiBoZWFkaW5nLFxuICAgICAgICBpdGVtc0NsYXNzTGlzdDogWydpZGUtaGFza2VsbCddLFxuICAgICAgICBlbGVtZW50Rm9ySXRlbTogKGl0ZW06IHN0cmluZykgPT4ge1xuICAgICAgICAgIGNvbnN0IGxpID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnbGknKVxuICAgICAgICAgIGxpLmlubmVyVGV4dCA9IGAke2l0ZW19YFxuICAgICAgICAgIHJldHVybiBsaVxuICAgICAgICB9LFxuICAgICAgICBkaWRDYW5jZWxTZWxlY3Rpb246ICgpID0+IHtcbiAgICAgICAgICByZXNvbHZlKClcbiAgICAgICAgfSxcbiAgICAgICAgZGlkQ29uZmlybVNlbGVjdGlvbjogKGl0ZW06IHN0cmluZykgPT4ge1xuICAgICAgICAgIHJlc29sdmUoaXRlbSlcbiAgICAgICAgfSxcbiAgICAgIH0pXG4gICAgICBzZWxlY3QuZWxlbWVudC5jbGFzc0xpc3QuYWRkKCdpZGUtaGFza2VsbCcpXG4gICAgICBwYW5lbCA9IGF0b20ud29ya3NwYWNlLmFkZE1vZGFsUGFuZWwoe1xuICAgICAgICBpdGVtOiBzZWxlY3QsXG4gICAgICAgIHZpc2libGU6IHRydWUsXG4gICAgICB9KVxuICAgICAgc2VsZWN0LmZvY3VzKClcbiAgICB9KVxuICB9IGZpbmFsbHkge1xuICAgIHBhbmVsICYmIHBhbmVsLmRlc3Ryb3koKVxuICB9XG4gIHJldHVybiByZXNcbn1cbiJdfQ== -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "haskell-ghc-mod", 3 | "main": "./lib/haskell-ghc-mod", 4 | "version": "2.2.6", 5 | "description": "Provides backends for ide-haskell and autocomplete-haskell", 6 | "keywords": [ 7 | "ide-haskell", 8 | "ide", 9 | "haskell", 10 | "ghc-mod", 11 | "backend" 12 | ], 13 | "repository": "https://github.com/atom-haskell/haskell-ghc-mod", 14 | "license": "MIT", 15 | "engines": { 16 | "atom": ">=1.24.0 <2.0.0" 17 | }, 18 | "scripts": { 19 | "build": "tsc -p .", 20 | "prettier": "prettier --write 'src/**/*.ts?(x)' 'spec/**/*.ts?(x)'", 21 | "prettier-check": "prettier -l 'src/**/*.ts?(x)' 'spec/**/*.ts?(x)'", 22 | "typecheck": "tsc --noEmit -p . && tsc --noEmit -p spec", 23 | "lint": "tslint --project . && tslint --project spec", 24 | "test": "npm run typecheck && npm run lint && npm run prettier-check" 25 | }, 26 | "atomTestRunner": "./node_modules/atom-ts-spec-runner/runner.js", 27 | "activationHooks": [ 28 | "language-haskell:grammar-used" 29 | ], 30 | "dependencies": { 31 | "atom-haskell-utils": "^1.0.2", 32 | "atom-select-list": "^0.7.2", 33 | "fuzzaldrin": "^2.1.0", 34 | "opener": "^1.5.1", 35 | "pidusage": ">=2.0.17 <2.0.19", 36 | "promise-queue": "^2.2.5", 37 | "temp": "^0.9.0", 38 | "tslib": "^1.9.3", 39 | "underscore": "^1.9.1" 40 | }, 41 | "consumedServices": { 42 | "ide-haskell-upi": { 43 | "description": "Uses ide-haskell's unified pluggable interface", 44 | "versions": { 45 | "^0.3.0": "consumeUPI" 46 | } 47 | } 48 | }, 49 | "providedServices": { 50 | "haskell-completion-backend": { 51 | "description": "Implements general haskell-completion-backend spec", 52 | "versions": { 53 | "1.0.0": "provideCompletionBackend" 54 | } 55 | } 56 | }, 57 | "devDependencies": { 58 | "@types/atom": "~1.31.1", 59 | "@types/chai": "^4.1.7", 60 | "@types/fuzzaldrin": "^2.1.2", 61 | "@types/mocha": "^5.2.6", 62 | "@types/node": "^8", 63 | "@types/opener": "^1.4.0", 64 | "@types/pidusage": "^2.0.1", 65 | "@types/temp": "^0.8.33", 66 | "@types/underscore": "^1.8.13", 67 | "atom-haskell-tslint-rules": "^0.2.2", 68 | "atom-ts-spec-runner": "^1.1.1", 69 | "chai": "^4.2.0", 70 | "prettier": "^1.16.4", 71 | "ts-node": "^8.0.3", 72 | "tslint": "^5.14.0", 73 | "typescript": "~3.3.4000" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /spec/package.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { join } from 'path' 3 | 4 | const pkg = join(__dirname, '..') 5 | 6 | describe('package', function() { 7 | this.timeout(60000) 8 | it('should activate', async () => { 9 | const packages: any = atom.packages 10 | 11 | // Load package, but it won't activate until the grammar is used 12 | const promise = atom.packages.activatePackage(pkg) 13 | 14 | packages.triggerActivationHook('language-haskell:grammar-used') 15 | packages.triggerDeferredActivationHooks() 16 | 17 | await promise 18 | 19 | expect(atom.packages.isPackageActive('haskell-ghc-mod')).to.be.true 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /spec/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "extends": "../tsconfig.json", 4 | "include": ["./**.ts"], 5 | "exclude": [] 6 | } 7 | -------------------------------------------------------------------------------- /spec/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "no-unused-expression": false, 5 | "no-non-null-assertion": false, 6 | "totality-check": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/atom-config.d.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | declare module 'atom' { 3 | interface ConfigValues { 4 | 'haskell-ghc-mod': Object 5 | 'haskell-ghc-mod.ghcModPath': string 6 | 'haskell-ghc-mod.enableGhcModi': boolean 7 | 'haskell-ghc-mod.lowMemorySystem': boolean 8 | 'haskell-ghc-mod.debug': boolean 9 | 'haskell-ghc-mod.builderManagement': boolean 10 | 'haskell-ghc-mod.additionalPathDirectories': Array 11 | 'haskell-ghc-mod.cabalSandbox': boolean 12 | 'haskell-ghc-mod.stackSandbox': boolean 13 | 'haskell-ghc-mod.initTimeout': number 14 | 'haskell-ghc-mod.interactiveInactivityTimeout': number 15 | 'haskell-ghc-mod.interactiveActionTimeout': number 16 | 'haskell-ghc-mod.onSaveCheck': boolean 17 | 'haskell-ghc-mod.onSaveLint': boolean 18 | 'haskell-ghc-mod.onChangeCheck': boolean 19 | 'haskell-ghc-mod.onChangeLint': boolean 20 | 'haskell-ghc-mod.alwaysInteractiveCheck': boolean 21 | 'haskell-ghc-mod.onMouseHoverShow': 22 | | '' 23 | | 'type' 24 | | 'info' 25 | | 'infoType' 26 | | 'typeInfo' 27 | | 'typeAndInfo' 28 | 'haskell-ghc-mod.onSelectionShow': 29 | | '' 30 | | 'type' 31 | | 'info' 32 | | 'infoType' 33 | | 'typeInfo' 34 | | 'typeAndInfo' 35 | 'haskell-ghc-mod.maxBrowseProcesses': number 36 | 'haskell-ghc-mod.highlightTooltips': boolean 37 | 'haskell-ghc-mod.suppressRedundantTypeInTypeAndInfoTooltips': boolean 38 | 'haskell-ghc-mod.highlightMessages': boolean 39 | 'haskell-ghc-mod.hlintOptions': string[] 40 | 'haskell-ghc-mod.experimental': boolean 41 | 'haskell-ghc-mod.suppressGhcPackagePathWarning': boolean 42 | 'haskell-ghc-mod.ghcModMessages': 'console' | 'upi' | 'popup' 43 | 'haskell-ghc-mod.maxMemMegs': number 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/atom.d.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | declare module 'atom' { 3 | interface CommandRegistryTargetMap { 4 | 'atom-text-editor[data-grammar~="haskell"]': TextEditorElement 5 | } 6 | interface Config { 7 | get( 8 | keyPath: T, 9 | options?: { 10 | sources?: string[] 11 | excludeSources?: string[] 12 | scope?: string[] | ScopeDescriptor 13 | }, 14 | ): ConfigValues[T] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/completion-backend/buffer-info.ts: -------------------------------------------------------------------------------- 1 | import { CompositeDisposable, TextBuffer } from 'atom' 2 | import { 3 | parseHsModuleImports, 4 | IModuleImports, 5 | IImport, 6 | } from 'atom-haskell-utils' 7 | 8 | export { IImport } 9 | 10 | export class BufferInfo { 11 | private disposables: CompositeDisposable 12 | private oldText: string = '' 13 | private oldImports: IModuleImports = { name: 'Main', imports: [] } 14 | 15 | constructor(public readonly buffer: TextBuffer) { 16 | this.disposables = new CompositeDisposable() 17 | this.disposables.add(this.buffer.onDidDestroy(this.destroy)) 18 | } 19 | 20 | public destroy = () => { 21 | this.disposables.dispose() 22 | } 23 | 24 | public async getImports(): Promise { 25 | const parsed = await this.parse() 26 | const imports = parsed ? parsed.imports : [] 27 | // tslint:disable: no-null-keyword 28 | if (!imports.some(({ name }) => name === 'Prelude')) { 29 | imports.push({ 30 | qualified: false, 31 | hiding: false, 32 | name: 'Prelude', 33 | importList: null, 34 | alias: null, 35 | }) 36 | } 37 | // tslint:enable: no-null-keyword 38 | return imports 39 | } 40 | 41 | public async getModuleName(): Promise { 42 | const parsed = await this.parse() 43 | return parsed.name 44 | } 45 | 46 | private async parse(): Promise { 47 | const newText = this.buffer.getText() 48 | if (this.oldText === newText) { 49 | return this.oldImports 50 | } else { 51 | this.oldText = newText 52 | this.oldImports = await parseHsModuleImports(this.buffer.getText()) 53 | return this.oldImports 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/completion-backend/index.ts: -------------------------------------------------------------------------------- 1 | import * as FZ from 'fuzzaldrin' 2 | import { TextBuffer, Point, Disposable, Range, Directory } from 'atom' 3 | import { BufferInfo } from './buffer-info' 4 | import { ModuleInfo } from './module-info' 5 | import { GhcModiProcess } from '../ghc-mod' 6 | import * as Util from '../util' 7 | import * as UPI from 'atom-haskell-upi' 8 | import * as CB from 'atom-haskell-upi/completion-backend' 9 | 10 | const { handleException } = Util 11 | 12 | export class CompletionBackend implements CB.ICompletionBackend { 13 | private bufferMap: WeakMap 14 | private dirMap: WeakMap> 15 | private modListMap: WeakMap 16 | private languagePragmas: WeakMap 17 | private compilerOptions: WeakMap 18 | private isActive: boolean 19 | 20 | constructor( 21 | private process: GhcModiProcess, 22 | public upi: Promise, 23 | ) { 24 | this.bufferMap = new WeakMap() 25 | this.dirMap = new WeakMap() 26 | this.modListMap = new WeakMap() 27 | this.languagePragmas = new WeakMap() 28 | this.compilerOptions = new WeakMap() 29 | 30 | // compatibility with old clients 31 | this.name = this.name.bind(this) 32 | this.onDidDestroy = this.onDidDestroy.bind(this) 33 | this.registerCompletionBuffer = this.registerCompletionBuffer.bind(this) 34 | this.unregisterCompletionBuffer = this.unregisterCompletionBuffer.bind(this) 35 | this.getCompletionsForSymbol = this.getCompletionsForSymbol.bind(this) 36 | this.getCompletionsForType = this.getCompletionsForType.bind(this) 37 | this.getCompletionsForClass = this.getCompletionsForClass.bind(this) 38 | this.getCompletionsForModule = this.getCompletionsForModule.bind(this) 39 | this.getCompletionsForSymbolInModule = this.getCompletionsForSymbolInModule.bind( 40 | this, 41 | ) 42 | this.getCompletionsForLanguagePragmas = this.getCompletionsForLanguagePragmas.bind( 43 | this, 44 | ) 45 | this.getCompletionsForCompilerOptions = this.getCompletionsForCompilerOptions.bind( 46 | this, 47 | ) 48 | this.getCompletionsForHole = this.getCompletionsForHole.bind(this) 49 | 50 | this.process = process 51 | this.isActive = true 52 | this.process.onDidDestroy(() => { 53 | this.isActive = false 54 | }) 55 | } 56 | 57 | /* Public interface below */ 58 | 59 | /* 60 | name() 61 | Get backend name 62 | 63 | Returns String, unique string describing a given backend 64 | */ 65 | public name() { 66 | return 'haskell-ghc-mod' 67 | } 68 | 69 | /* 70 | onDidDestroy(callback) 71 | Destruction event subscription. Usually should be called only on 72 | package deactivation. 73 | callback: () -> 74 | */ 75 | public onDidDestroy(callback: () => void) { 76 | if (!this.isActive) { 77 | throw new Error('Backend inactive') 78 | } 79 | return this.process.onDidDestroy(callback) 80 | } 81 | 82 | /* 83 | registerCompletionBuffer(buffer) 84 | Every buffer that would be used with autocompletion functions has to 85 | be registered with this function. 86 | 87 | buffer: TextBuffer, buffer to be used in autocompletion 88 | 89 | Returns: Disposable, which will remove buffer from autocompletion 90 | */ 91 | public registerCompletionBuffer(buffer: TextBuffer) { 92 | if (!this.isActive) { 93 | throw new Error('Backend inactive') 94 | } 95 | 96 | if (this.bufferMap.has(buffer)) { 97 | return new Disposable(() => { 98 | /* void */ 99 | }) 100 | } 101 | 102 | const { bufferInfo } = this.getBufferInfo({ buffer }) 103 | 104 | setImmediate(async () => { 105 | const { rootDir, moduleMap } = await this.getModuleMap({ bufferInfo }) 106 | 107 | // tslint:disable-next-line:no-floating-promises 108 | this.getModuleInfo({ bufferInfo, rootDir, moduleMap }) 109 | 110 | const imports = await bufferInfo.getImports() 111 | for (const imprt of imports) { 112 | // tslint:disable-next-line:no-floating-promises 113 | this.getModuleInfo({ 114 | moduleName: imprt.name, 115 | bufferInfo, 116 | rootDir, 117 | moduleMap, 118 | }) 119 | } 120 | }) 121 | 122 | return new Disposable(() => this.unregisterCompletionBuffer(buffer)) 123 | } 124 | 125 | /* 126 | unregisterCompletionBuffer(buffer) 127 | buffer: TextBuffer, buffer to be removed from autocompletion 128 | */ 129 | public unregisterCompletionBuffer(buffer: TextBuffer) { 130 | const x = this.bufferMap.get(buffer) 131 | if (x) { 132 | x.destroy() 133 | } 134 | } 135 | 136 | /* 137 | getCompletionsForSymbol(buffer,prefix,position) 138 | buffer: TextBuffer, current buffer 139 | prefix: String, completion prefix 140 | position: Point, current cursor position 141 | 142 | Returns: Promise([symbol]) 143 | symbol: Object, a completion symbol 144 | name: String, symbol name 145 | qname: String, qualified name, if module is qualified. 146 | Otherwise, same as name 147 | typeSignature: String, type signature 148 | symbolType: String, one of ['type', 'class', 'function'] 149 | module: Object, symbol module information 150 | qualified: Boolean, true if module is imported as qualified 151 | name: String, module name 152 | alias: String, module alias 153 | hiding: Boolean, true if module is imported with hiding clause 154 | importList: [String], array of explicit imports/hidden imports 155 | */ 156 | @handleException 157 | public async getCompletionsForSymbol( 158 | buffer: TextBuffer, 159 | prefix: string, 160 | _position: Point, 161 | ): Promise { 162 | if (!this.isActive) { 163 | throw new Error('Backend inactive') 164 | } 165 | 166 | const symbols = await this.getSymbolsForBuffer(buffer) 167 | return this.filter(symbols, prefix, ['qname', 'qparent']) 168 | } 169 | 170 | /* 171 | getCompletionsForType(buffer,prefix,position) 172 | buffer: TextBuffer, current buffer 173 | prefix: String, completion prefix 174 | position: Point, current cursor position 175 | 176 | Returns: Promise([symbol]) 177 | symbol: Same as getCompletionsForSymbol, except 178 | symbolType is one of ['type', 'class'] 179 | */ 180 | @handleException 181 | public async getCompletionsForType( 182 | buffer: TextBuffer, 183 | prefix: string, 184 | _position: Point, 185 | ): Promise { 186 | if (!this.isActive) { 187 | throw new Error('Backend inactive') 188 | } 189 | 190 | const symbols = await this.getSymbolsForBuffer(buffer, ['type', 'class']) 191 | return FZ.filter(symbols, prefix, { key: 'qname' }) 192 | } 193 | 194 | /* 195 | getCompletionsForClass(buffer,prefix,position) 196 | buffer: TextBuffer, current buffer 197 | prefix: String, completion prefix 198 | position: Point, current cursor position 199 | 200 | Returns: Promise([symbol]) 201 | symbol: Same as getCompletionsForSymbol, except 202 | symbolType is one of ['class'] 203 | */ 204 | public async getCompletionsForClass( 205 | buffer: TextBuffer, 206 | prefix: string, 207 | _position: Point, 208 | ): Promise { 209 | if (!this.isActive) { 210 | throw new Error('Backend inactive') 211 | } 212 | 213 | const symbols = await this.getSymbolsForBuffer(buffer, ['class']) 214 | return FZ.filter(symbols, prefix, { key: 'qname' }) 215 | } 216 | 217 | /* 218 | getCompletionsForModule(buffer,prefix,position) 219 | buffer: TextBuffer, current buffer 220 | prefix: String, completion prefix 221 | position: Point, current cursor position 222 | 223 | Returns: Promise([module]) 224 | module: String, module name 225 | */ 226 | public async getCompletionsForModule( 227 | buffer: TextBuffer, 228 | prefix: string, 229 | _position: Point, 230 | ): Promise { 231 | if (!this.isActive) { 232 | throw new Error('Backend inactive') 233 | } 234 | const rootDir = await this.process.getRootDir(buffer) 235 | let modules = this.modListMap.get(rootDir) 236 | if (!modules) { 237 | modules = await this.process.runList(buffer) 238 | this.modListMap.set(rootDir, modules) 239 | // refresh every minute 240 | setTimeout(() => this.modListMap.delete(rootDir), 60 * 1000) 241 | } 242 | return FZ.filter(modules, prefix) 243 | } 244 | 245 | /* 246 | getCompletionsForSymbolInModule(buffer,prefix,position,{module}) 247 | Used in import hiding/list completions 248 | 249 | buffer: TextBuffer, current buffer 250 | prefix: String, completion prefix 251 | position: Point, current cursor position 252 | module: String, module name (optional). If undefined, function 253 | will attempt to infer module name from position and buffer. 254 | 255 | Returns: Promise([symbol]) 256 | symbol: Object, symbol in given module 257 | name: String, symbol name 258 | typeSignature: String, type signature 259 | symbolType: String, one of ['type', 'class', 'function'] 260 | */ 261 | public async getCompletionsForSymbolInModule( 262 | buffer: TextBuffer, 263 | prefix: string, 264 | position: Point, 265 | opts?: { module: string }, 266 | ): Promise { 267 | if (!this.isActive) { 268 | throw new Error('Backend inactive') 269 | } 270 | let moduleName = opts ? opts.module : undefined 271 | if (!moduleName) { 272 | const lineRange = new Range([0, position.row], position) 273 | buffer.backwardsScanInRange( 274 | /^import\s+([\w.]+)/, 275 | lineRange, 276 | ({ match }) => (moduleName = match[1]), 277 | ) 278 | } 279 | 280 | const { bufferInfo } = this.getBufferInfo({ buffer }) 281 | const mis = await this.getModuleInfo({ bufferInfo, moduleName }) 282 | 283 | // tslint:disable: no-null-keyword 284 | const symbols = await mis.moduleInfo.select( 285 | { 286 | qualified: false, 287 | hiding: false, 288 | name: moduleName || mis.moduleName, 289 | importList: null, 290 | alias: null, 291 | }, 292 | undefined, 293 | true, 294 | ) 295 | // tslint:enable: no-null-keyword 296 | return FZ.filter(symbols, prefix, { key: 'name' }) 297 | } 298 | 299 | /* 300 | getCompletionsForLanguagePragmas(buffer,prefix,position) 301 | buffer: TextBuffer, current buffer 302 | prefix: String, completion prefix 303 | position: Point, current cursor position 304 | 305 | Returns: Promise([pragma]) 306 | pragma: String, language option 307 | */ 308 | public async getCompletionsForLanguagePragmas( 309 | buffer: TextBuffer, 310 | prefix: string, 311 | _position: Point, 312 | ): Promise { 313 | if (!this.isActive) { 314 | throw new Error('Backend inactive') 315 | } 316 | 317 | const dir = await this.process.getRootDir(buffer) 318 | 319 | let ps = this.languagePragmas.get(dir) 320 | if (!ps) { 321 | ps = await this.process.runLang(dir) 322 | ps && this.languagePragmas.set(dir, ps) 323 | } 324 | return FZ.filter(ps, prefix) 325 | } 326 | 327 | /* 328 | getCompletionsForCompilerOptions(buffer,prefix,position) 329 | buffer: TextBuffer, current buffer 330 | prefix: String, completion prefix 331 | position: Point, current cursor position 332 | 333 | Returns: Promise([ghcopt]) 334 | ghcopt: String, compiler option (starts with '-f') 335 | */ 336 | public async getCompletionsForCompilerOptions( 337 | buffer: TextBuffer, 338 | prefix: string, 339 | _position: Point, 340 | ): Promise { 341 | if (!this.isActive) { 342 | throw new Error('Backend inactive') 343 | } 344 | 345 | const dir = await this.process.getRootDir(buffer) 346 | 347 | let co = this.compilerOptions.get(dir) 348 | if (!co) { 349 | co = await this.process.runFlag(dir) 350 | this.compilerOptions.set(dir, co) 351 | } 352 | return FZ.filter(co, prefix) 353 | } 354 | 355 | /* 356 | getCompletionsForHole(buffer,prefix,position) 357 | Get completions based on expression type. 358 | It is assumed that `prefix` starts with '_' 359 | 360 | buffer: TextBuffer, current buffer 361 | prefix: String, completion prefix 362 | position: Point, current cursor position 363 | 364 | Returns: Promise([symbol]) 365 | symbol: Same as getCompletionsForSymbol 366 | */ 367 | @handleException 368 | public async getCompletionsForHole( 369 | buffer: TextBuffer, 370 | prefix: string, 371 | position: Point, 372 | ): Promise { 373 | if (!this.isActive) { 374 | throw new Error('Backend inactive') 375 | } 376 | const range = new Range(position, position) 377 | if (prefix.startsWith('_')) { 378 | prefix = prefix.slice(1) 379 | } 380 | const { type } = await this.process.getTypeInBuffer(buffer, range) 381 | const symbols = await this.getSymbolsForBuffer(buffer) 382 | const ts = symbols.filter((s) => { 383 | if (!s.typeSignature) { 384 | return false 385 | } 386 | const tl = s.typeSignature.split(' -> ').slice(-1)[0] 387 | if (tl.match(/^[a-z]$/)) { 388 | return false 389 | } 390 | const ts2 = tl.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&') 391 | const rx = RegExp(ts2.replace(/\b[a-z]\b/g, '.+'), '') 392 | return rx.test(type) 393 | }) 394 | if (prefix.length === 0) { 395 | return ts.sort( 396 | (a, b) => 397 | // tslint:disable-next-line: no-non-null-assertion 398 | FZ.score(b.typeSignature!, type) - FZ.score(a.typeSignature!, type), 399 | ) 400 | } else { 401 | return FZ.filter(ts, prefix, { key: 'qname' }) 402 | } 403 | } 404 | 405 | private async getSymbolsForBuffer( 406 | buffer: TextBuffer, 407 | symbolTypes?: CB.SymbolType[], 408 | ): Promise { 409 | const { bufferInfo } = this.getBufferInfo({ buffer }) 410 | const { rootDir, moduleMap } = await this.getModuleMap({ bufferInfo }) 411 | if (bufferInfo && moduleMap) { 412 | const imports = await bufferInfo.getImports() 413 | const promises = await Promise.all( 414 | imports.map(async (imp) => { 415 | const res = await this.getModuleInfo({ 416 | bufferInfo, 417 | moduleName: imp.name, 418 | rootDir, 419 | moduleMap, 420 | }) 421 | if (!res) { 422 | return [] 423 | } 424 | return res.moduleInfo.select(imp, symbolTypes) 425 | }), 426 | ) 427 | return ([] as typeof promises[0]).concat(...promises) 428 | } else { 429 | return [] 430 | } 431 | } 432 | 433 | private getBufferInfo({ 434 | buffer, 435 | }: { 436 | buffer: TextBuffer 437 | }): { bufferInfo: BufferInfo } { 438 | let bi = this.bufferMap.get(buffer) 439 | if (!bi) { 440 | bi = new BufferInfo(buffer) 441 | this.bufferMap.set(buffer, bi) 442 | } 443 | return { bufferInfo: bi } 444 | } 445 | 446 | private async getModuleMap({ 447 | bufferInfo, 448 | rootDir, 449 | }: { 450 | bufferInfo: BufferInfo 451 | rootDir?: Directory 452 | }): Promise<{ rootDir: Directory; moduleMap: Map }> { 453 | if (!rootDir) { 454 | rootDir = await this.process.getRootDir(bufferInfo.buffer) 455 | } 456 | let mm = this.dirMap.get(rootDir) 457 | if (!mm) { 458 | mm = new Map() 459 | this.dirMap.set(rootDir, mm) 460 | } 461 | 462 | return { 463 | rootDir, 464 | moduleMap: mm, 465 | } 466 | } 467 | 468 | private async getModuleInfo(arg: { 469 | bufferInfo: BufferInfo 470 | moduleName?: string 471 | rootDir?: Directory 472 | moduleMap?: Map 473 | }) { 474 | const { bufferInfo } = arg 475 | let dat 476 | if (arg.rootDir && arg.moduleMap) { 477 | dat = { rootDir: arg.rootDir, moduleMap: arg.moduleMap } 478 | } else { 479 | dat = await this.getModuleMap({ bufferInfo }) 480 | } 481 | const { moduleMap, rootDir } = dat 482 | let moduleName = arg.moduleName 483 | if (!moduleName) { 484 | moduleName = await bufferInfo.getModuleName() 485 | } 486 | if (!moduleName) { 487 | throw new Error(`Nameless module in ${bufferInfo.buffer.getUri()}`) 488 | } 489 | 490 | let moduleInfo = moduleMap.get(moduleName) 491 | if (!moduleInfo) { 492 | moduleInfo = new ModuleInfo(moduleName, this.process, rootDir) 493 | moduleMap.set(moduleName, moduleInfo) 494 | 495 | const mn = moduleName 496 | moduleInfo.onDidDestroy(() => { 497 | moduleMap.delete(mn) 498 | Util.debug(`${moduleName} removed from map`) 499 | }) 500 | } 501 | await moduleInfo.setBuffer(bufferInfo) 502 | return { bufferInfo, rootDir, moduleMap, moduleInfo, moduleName } 503 | } 504 | 505 | private filter( 506 | candidates: T[], 507 | prefix: string, 508 | keys: K[], 509 | ): T[] { 510 | if (!prefix) { 511 | return candidates 512 | } 513 | const list = [] 514 | for (const candidate of candidates) { 515 | const scores = keys.map((key) => { 516 | const ck = candidate[key] 517 | if (ck) { 518 | return FZ.score(ck.toString(), prefix) 519 | } else { 520 | return 0 521 | } 522 | }) 523 | const score = Math.max(...scores) 524 | if (score > 0) { 525 | list.push({ 526 | score, 527 | scoreN: scores.indexOf(score), 528 | data: candidate, 529 | }) 530 | } 531 | } 532 | return list 533 | .sort((a, b) => { 534 | const s = b.score - a.score 535 | if (s === 0) { 536 | return a.scoreN - b.scoreN 537 | } 538 | return s 539 | }) 540 | .map(({ data }) => data) 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /src/completion-backend/module-info.ts: -------------------------------------------------------------------------------- 1 | import { CompositeDisposable, Emitter, TextBuffer, Directory } from 'atom' 2 | import * as Util from '../util' 3 | import { GhcModiProcess, SymbolDesc } from '../ghc-mod' 4 | import { BufferInfo, IImport } from './buffer-info' 5 | import * as CompletionBackend from 'atom-haskell-upi/completion-backend' 6 | 7 | import SymbolType = CompletionBackend.SymbolType 8 | 9 | export class ModuleInfo { 10 | private readonly disposables: CompositeDisposable 11 | private readonly emitter: Emitter<{ 12 | 'did-destroy': undefined 13 | }> 14 | private readonly invalidateInterval = 30 * 60 * 1000 // if module unused for 30 minutes, remove it 15 | private readonly bufferSet: WeakSet 16 | private timeout: NodeJS.Timer 17 | private updatePromise: Promise 18 | private symbols: SymbolDesc[] // module symbols 19 | 20 | constructor( 21 | private readonly name: string, 22 | private readonly process: GhcModiProcess, 23 | private readonly rootDir: Directory, 24 | ) { 25 | Util.debug(`${this.name} created`) 26 | this.symbols = [] 27 | this.disposables = new CompositeDisposable() 28 | this.bufferSet = new WeakSet() 29 | this.emitter = new Emitter() 30 | this.disposables.add(this.emitter) 31 | this.updatePromise = this.update(rootDir) 32 | this.timeout = setTimeout(this.destroy, this.invalidateInterval) 33 | this.disposables.add(this.process.onDidDestroy(this.destroy)) 34 | } 35 | 36 | public destroy = () => { 37 | Util.debug(`${this.name} destroyed`) 38 | clearTimeout(this.timeout) 39 | this.emitter.emit('did-destroy') 40 | this.disposables.dispose() 41 | } 42 | 43 | public onDidDestroy(callback: () => void) { 44 | return this.emitter.on('did-destroy', callback) 45 | } 46 | 47 | public async setBuffer(bufferInfo: BufferInfo) { 48 | const name = await bufferInfo.getModuleName() 49 | if (name !== this.name) { 50 | return 51 | } 52 | if (this.bufferSet.has(bufferInfo.buffer)) { 53 | return 54 | } 55 | this.bufferSet.add(bufferInfo.buffer) 56 | Util.debug(`${this.name} buffer is set`) 57 | const disposables = new CompositeDisposable() 58 | disposables.add( 59 | bufferInfo.buffer.onDidSave(() => { 60 | Util.debug(`${this.name} did-save triggered`) 61 | this.updatePromise = this.update(this.rootDir) 62 | }), 63 | ) 64 | disposables.add( 65 | bufferInfo.buffer.onDidDestroy(() => { 66 | disposables.dispose() 67 | this.bufferSet.delete(bufferInfo.buffer) 68 | this.disposables.remove(disposables) 69 | }), 70 | ) 71 | this.disposables.add(disposables) 72 | } 73 | 74 | public async select( 75 | importDesc: IImport, 76 | symbolTypes?: SymbolType[], 77 | skipQualified: boolean = false, 78 | ) { 79 | await this.updatePromise 80 | clearTimeout(this.timeout) 81 | this.timeout = setTimeout(this.destroy, this.invalidateInterval) 82 | let symbols = this.symbols 83 | if (importDesc.importList) { 84 | const il = importDesc.importList 85 | symbols = symbols.filter((s) => { 86 | const inImportList = il.includes(s.name) 87 | const parentInImportList = il.some( 88 | (i) => typeof i !== 'string' && s.parent === i.parent, 89 | ) 90 | const shouldShow = inImportList || parentInImportList 91 | return importDesc.hiding !== shouldShow // XOR 92 | }) 93 | } 94 | const res = [] 95 | for (const symbol of symbols) { 96 | if (symbolTypes && !symbolTypes.includes(symbol.symbolType)) { 97 | continue 98 | } 99 | const specific = { 100 | name: symbol.name, 101 | typeSignature: symbol.typeSignature, 102 | symbolType: symbol.symbolType, 103 | module: importDesc, 104 | } 105 | const qn = (n: string) => `${importDesc.alias || importDesc.name}.${n}` 106 | if (!skipQualified) { 107 | res.push({ 108 | ...specific, 109 | qparent: symbol.parent ? qn(symbol.parent) : undefined, 110 | qname: qn(symbol.name), 111 | }) 112 | } 113 | if (!importDesc.qualified) { 114 | res.push({ 115 | ...specific, 116 | qparent: symbol.parent, 117 | qname: symbol.name, 118 | }) 119 | } 120 | } 121 | return res 122 | } 123 | 124 | private async update(rootDir: Directory) { 125 | Util.debug(`${this.name} updating`) 126 | this.symbols = await this.process.runBrowse(rootDir, [this.name]) 127 | Util.debug(`${this.name} updated`) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | const tooltipActions = [ 2 | { value: '', description: 'Nothing' }, 3 | { value: 'type', description: 'Type' }, 4 | { value: 'info', description: 'Info' }, 5 | { value: 'infoType', description: 'Info, fallback to Type' }, 6 | { value: 'typeInfo', description: 'Type, fallback to Info' }, 7 | { value: 'typeAndInfo', description: 'Type and Info' }, 8 | ] 9 | 10 | export const config = { 11 | ghcModPath: { 12 | type: 'string', 13 | default: 'ghc-mod', 14 | description: 'Path to ghc-mod', 15 | order: 0, 16 | }, 17 | enableGhcModi: { 18 | type: 'boolean', 19 | default: true, 20 | description: `Using GHC Modi is suggested and noticeably faster, \ 21 | but if experiencing problems, disabling it can sometimes help.`, 22 | order: 70, 23 | }, 24 | lowMemorySystem: { 25 | type: 'boolean', 26 | default: false, 27 | description: `Avoid spawning more than one ghc-mod process; also disables parallel \ 28 | features, which can help with weird stack errors`, 29 | order: 70, 30 | }, 31 | debug: { 32 | type: 'boolean', 33 | default: false, 34 | order: 999, 35 | }, 36 | builderManagement: { 37 | type: 'boolean', 38 | description: `Experimental option to force ghc-mod into using cabal or \ 39 | stack based on ide-haskell-cabal settings; also enables an option to build \ 40 | ghc-mod when using stack`, 41 | default: false, 42 | order: 900, 43 | }, 44 | additionalPathDirectories: { 45 | type: 'array', 46 | default: [], 47 | description: `Add this directories to PATH when invoking ghc-mod. \ 48 | You might want to add path to a directory with \ 49 | ghc, cabal, etc binaries here. \ 50 | Separate with comma.`, 51 | items: { 52 | type: 'string', 53 | }, 54 | order: 0, 55 | }, 56 | cabalSandbox: { 57 | type: 'boolean', 58 | default: true, 59 | description: 'Add cabal sandbox bin-path to PATH', 60 | order: 100, 61 | }, 62 | stackSandbox: { 63 | type: 'boolean', 64 | default: true, 65 | description: 'Add stack bin-path to PATH', 66 | order: 100, 67 | }, 68 | initTimeout: { 69 | type: 'integer', 70 | description: `How long to wait for initialization commands (checking \ 71 | GHC and ghc-mod versions, getting stack sandbox) until \ 72 | assuming those hanged and bailing. In seconds.`, 73 | default: 60, 74 | minimum: 1, 75 | order: 50, 76 | }, 77 | interactiveInactivityTimeout: { 78 | type: 'integer', 79 | description: `Kill ghc-mod interactive process (ghc-modi) after this \ 80 | number of minutes of inactivity to conserve memory. 0 \ 81 | means never.`, 82 | default: 60, 83 | minimum: 0, 84 | order: 50, 85 | }, 86 | interactiveActionTimeout: { 87 | type: 'integer', 88 | description: `Timeout for interactive ghc-mod commands (in seconds). 0 \ 89 | means wait forever.`, 90 | default: 300, 91 | minimum: 0, 92 | order: 50, 93 | }, 94 | onSaveCheck: { 95 | type: 'boolean', 96 | default: true, 97 | description: 'Check file on save', 98 | order: 25, 99 | }, 100 | onSaveLint: { 101 | type: 'boolean', 102 | default: true, 103 | description: 'Lint file on save', 104 | order: 25, 105 | }, 106 | onChangeCheck: { 107 | type: 'boolean', 108 | default: false, 109 | description: 'Check file on change', 110 | order: 25, 111 | }, 112 | onChangeLint: { 113 | type: 'boolean', 114 | default: false, 115 | description: 'Lint file on change', 116 | order: 25, 117 | }, 118 | alwaysInteractiveCheck: { 119 | type: 'boolean', 120 | default: true, 121 | description: `Always use interactive mode for check. Much faster on large \ 122 | projects, but can lead to problems. Try disabling if experiencing slowdowns or \ 123 | crashes`, 124 | order: 26, 125 | }, 126 | onMouseHoverShow: { 127 | type: 'string', 128 | description: 'Contents of tooltip on mouse hover', 129 | default: 'typeAndInfo', 130 | enum: tooltipActions, 131 | order: 30, 132 | }, 133 | onSelectionShow: { 134 | type: 'string', 135 | description: 'Contents of tooltip on selection', 136 | default: '', 137 | enum: tooltipActions, 138 | order: 30, 139 | }, 140 | maxBrowseProcesses: { 141 | type: 'integer', 142 | default: 2, 143 | description: `Maximum number of parallel ghc-mod browse processes, which \ 144 | are used in autocompletion backend initialization. \ 145 | Note that on larger projects it may require a considerable \ 146 | amount of memory.`, 147 | order: 60, 148 | }, 149 | highlightTooltips: { 150 | type: 'boolean', 151 | default: true, 152 | description: 'Show highlighting for type/info tooltips', 153 | order: 40, 154 | }, 155 | suppressRedundantTypeInTypeAndInfoTooltips: { 156 | type: 'boolean', 157 | default: true, 158 | description: `In tooltips with type AND info, suppress type if \ 159 | it's the same as info`, 160 | order: 41, 161 | }, 162 | highlightMessages: { 163 | type: 'boolean', 164 | default: true, 165 | description: 'Show highlighting for output panel messages', 166 | order: 40, 167 | }, 168 | hlintOptions: { 169 | type: 'array', 170 | default: [], 171 | description: 'Command line options to pass to hlint (comma-separated)', 172 | order: 45, 173 | }, 174 | experimental: { 175 | type: 'boolean', 176 | default: false, 177 | description: `Enable experimental features, which are expected to land in \ 178 | next release of ghc-mod. ENABLE ONLY IF YOU KNOW WHAT YOU \ 179 | ARE DOING`, 180 | order: 999, 181 | }, 182 | suppressGhcPackagePathWarning: { 183 | type: 'boolean', 184 | default: false, 185 | description: `Suppress warning about GHC_PACKAGE_PATH environment variable. \ 186 | ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING.`, 187 | order: 999, 188 | }, 189 | ghcModMessages: { 190 | type: 'string', 191 | description: 192 | 'How to show warnings/errors reported by ghc-mod (requires restart)', 193 | default: 'console', 194 | enum: [ 195 | { value: 'console', description: 'Developer Console' }, 196 | { value: 'upi', description: 'Output Panel' }, 197 | { value: 'popup', description: 'Error/Warning Popups' }, 198 | ], 199 | order: 42, 200 | }, 201 | maxMemMegs: { 202 | type: 'integer', 203 | descrition: 'Maximum ghc-mod interactive mode memory usage (in megabytes)', 204 | default: 4 * 1024, 205 | minimum: 1024, 206 | order: 50, 207 | }, 208 | } 209 | -------------------------------------------------------------------------------- /src/ghc-mod/build-stack.ts: -------------------------------------------------------------------------------- 1 | import { CompositeDisposable } from 'atom' 2 | import { IUPIInstance, IResultItem } from 'atom-haskell-upi' 3 | import * as CP from 'child_process' 4 | import { EOL } from 'os' 5 | import * as Util from '../util' 6 | import { RunOptions } from './ghc-modi-process-real' 7 | 8 | export async function buildStack( 9 | opts: RunOptions, 10 | upi: IUPIInstance | undefined, 11 | ): Promise { 12 | const messages: IResultItem[] = [] 13 | const disp = new CompositeDisposable() 14 | try { 15 | const vers = await Util.execPromise( 16 | 'stack', 17 | ['--no-install-ghc', '--numeric-version'], 18 | opts, 19 | ) 20 | .then(({ stdout }) => stdout.split('.').map((n) => parseInt(n, 10))) 21 | .catch(() => undefined) 22 | const hasCopyCompiler = vers ? Util.versAtLeast(vers, [1, 6, 1]) : false 23 | return await new Promise((resolve, reject) => { 24 | const args = ['--no-install-ghc', 'build'] 25 | if (hasCopyCompiler) args.push('--copy-compiler-tool') 26 | args.push('ghc-mod') 27 | Util.warn(`Running stack ${args.join(' ')}`) 28 | const proc = CP.spawn('stack', args, opts) 29 | const buffered = () => { 30 | let buffer = '' 31 | return (data: Buffer) => { 32 | const output = data.toString('utf8') 33 | const [first, ...tail] = output.split(EOL) 34 | buffer += first 35 | if (tail.length > 0) { 36 | // it means there's at least one newline 37 | const lines = [buffer, ...tail.slice(0, -1)] 38 | buffer = tail.slice(-1)[0] 39 | messages.push( 40 | ...lines.map((message) => ({ message, severity: 'build' })), 41 | ) 42 | if (upi) { 43 | upi.setMessages(messages) 44 | } else { 45 | atom.notifications.addInfo(lines.join('\n')) 46 | } 47 | console.log(lines.join('\n')) 48 | } 49 | } 50 | } 51 | proc.stdout.on('data', buffered()) 52 | proc.stderr.on('data', buffered()) 53 | if (upi) { 54 | disp.add( 55 | upi.addPanelControl({ 56 | element: 'ide-haskell-button', 57 | opts: { 58 | classes: ['cancel'], 59 | events: { 60 | click: () => { 61 | proc.kill('SIGTERM') 62 | proc.kill('SIGKILL') 63 | }, 64 | }, 65 | }, 66 | }), 67 | ) 68 | } 69 | proc.once('exit', (code, signal) => { 70 | if (code === 0) { 71 | resolve(true) 72 | } else { 73 | reject( 74 | new Error( 75 | `Stack build exited with nonzero exit status ${code} due to ${signal}`, 76 | ), 77 | ) 78 | Util.warn(messages.map((m) => m.message).join('\n')) 79 | } 80 | }) 81 | }) 82 | } catch (e) { 83 | Util.warn(e) 84 | atom.notifications.addError(e.toString(), { 85 | dismissable: true, 86 | detail: messages.map((m) => m.message).join('\n'), 87 | }) 88 | return false 89 | } finally { 90 | upi && upi.setMessages([]) 91 | disp.dispose() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/ghc-mod/ghc-modi-process-real-factory.ts: -------------------------------------------------------------------------------- 1 | import { GHCModCaps } from './interactive-process' 2 | import * as Util from '../util' 3 | import { GhcModiProcessReal, RunOptions } from './ghc-modi-process-real' 4 | import { Directory, Notification } from 'atom' 5 | import { IUPIInstance } from 'atom-haskell-upi' 6 | import { buildStack } from './build-stack' 7 | 8 | export type GHCModVers = { vers: number[]; comp: string } 9 | 10 | export async function createGhcModiProcessReal( 11 | rootDir: Directory, 12 | upi: IUPIInstance | undefined, 13 | ): Promise { 14 | let opts: RunOptions | undefined 15 | let vers: GHCModVers | undefined 16 | let caps: GHCModCaps | undefined 17 | let builder: { name: string } | undefined 18 | try { 19 | if (upi && atom.config.get('haskell-ghc-mod.builderManagement')) { 20 | // TODO: this is used twice, the second time in ghc-mod/index.ts, should probably fix that 21 | builder = await upi.getOthersConfigParam<{ name: string }>( 22 | 'ide-haskell-cabal', 23 | 'builder', 24 | ) 25 | } 26 | const bn = builder && builder.name 27 | Util.debug(`Using builder ${bn}`) 28 | // TODO: Should prefer stack sandbox when using stack and cabal sanbdox when using cabal! 29 | opts = await Util.getProcessOptions(rootDir.getPath()) 30 | const versP = getVersion(opts) 31 | const bopts = opts 32 | // TODO: this gets checked only once, should check on ghc-mod restart? 33 | const shouldBuild = await checkComp(bopts, versP, bn).catch( 34 | async (e: any) => { 35 | if (e.code === 'ENOENT') { 36 | return askBuild(bn, `Atom couldn't find ghc-mod.`) 37 | } else { 38 | atom.notifications.addError('Failed to check compiler versions', { 39 | detail: e.toString(), 40 | stack: e.stack, 41 | dismissable: true, 42 | }) 43 | return false 44 | } 45 | }, 46 | ) 47 | if (shouldBuild) { 48 | const success = await buildStack(bopts, upi) 49 | if (success) { 50 | return createGhcModiProcessReal(rootDir, upi) 51 | } else { 52 | atom.notifications.addWarning( 53 | 'Building ghc-mod failed, continuing as-is', 54 | ) 55 | } 56 | } 57 | vers = await versP 58 | caps = getCaps(vers) 59 | return new GhcModiProcessReal(caps, rootDir, opts) 60 | } catch (e) { 61 | const err: Error & { code: any } = e 62 | Util.notifySpawnFail({ dir: rootDir.getPath(), err, opts, vers, caps }) 63 | throw e 64 | } 65 | } 66 | 67 | function getCaps({ vers }: { vers: number[] }): GHCModCaps { 68 | const caps: GHCModCaps = { 69 | version: vers, 70 | fileMap: false, 71 | quoteArgs: false, 72 | optparse: false, 73 | typeConstraints: false, 74 | browseParents: false, 75 | interactiveCaseSplit: false, 76 | importedFrom: false, 77 | browseMain: false, 78 | } 79 | 80 | const atLeast = (x: number[]) => Util.versAtLeast(vers, x) 81 | 82 | const exact = (b: number[]) => { 83 | for (let i = 0; i < b.length; i++) { 84 | const v = b[i] 85 | if (vers[i] !== v) { 86 | return false 87 | } 88 | } 89 | return true 90 | } 91 | 92 | if (!atLeast([5, 4])) { 93 | atom.notifications.addError( 94 | `\ 95 | Haskell-ghc-mod: ghc-mod < 5.4 is not supported. \ 96 | Use at your own risk or update your ghc-mod installation`, 97 | { dismissable: true }, 98 | ) 99 | } 100 | if (exact([5, 4])) { 101 | atom.notifications.addWarning( 102 | `\ 103 | Haskell-ghc-mod: ghc-mod 5.4.* is deprecated. \ 104 | Use at your own risk or update your ghc-mod installation`, 105 | { dismissable: true }, 106 | ) 107 | } 108 | if (atLeast([5, 4])) { 109 | caps.fileMap = true 110 | } 111 | if (atLeast([5, 5])) { 112 | caps.quoteArgs = true 113 | caps.optparse = true 114 | } 115 | if (atLeast([5, 6])) { 116 | caps.typeConstraints = true 117 | caps.browseParents = true 118 | caps.interactiveCaseSplit = true 119 | } 120 | if (atom.config.get('haskell-ghc-mod.experimental')) { 121 | caps.importedFrom = true 122 | } 123 | Util.debug(JSON.stringify(caps)) 124 | return caps 125 | } 126 | 127 | async function getVersion(opts: Util.ExecOpts): Promise { 128 | const timeout = atom.config.get('haskell-ghc-mod.initTimeout') * 1000 129 | const cmd = atom.config.get('haskell-ghc-mod.ghcModPath') 130 | const { stdout } = await Util.execPromise(cmd, ['version'], { 131 | timeout, 132 | ...opts, 133 | }) 134 | const versRaw = /^ghc-mod version (\d+)\.(\d+)\.(\d+)(?:\.(\d+))?/.exec( 135 | stdout, 136 | ) 137 | if (!versRaw) { 138 | throw new Error("Couldn't get ghc-mod version") 139 | } 140 | const vers = versRaw.slice(1, 5).map((i) => parseInt(i, 10)) 141 | const compRaw = /GHC (.+)$/.exec(stdout.trim()) 142 | if (!compRaw) { 143 | throw new Error("Couldn't get ghc version") 144 | } 145 | const comp = compRaw[1] 146 | Util.debug(`Ghc-mod ${vers} built with ${comp}`) 147 | return { vers, comp } 148 | } 149 | 150 | async function checkComp( 151 | opts: Util.ExecOpts, 152 | versP: Promise, 153 | builder: string | undefined, 154 | ) { 155 | const { comp } = await versP 156 | const timeout = atom.config.get('haskell-ghc-mod.initTimeout') * 1000 157 | const tryWarn = async (cmd: string, args: string[]) => { 158 | try { 159 | return (await Util.execPromise(cmd, args, { 160 | timeout, 161 | ...opts, 162 | })).stdout.trim() 163 | } catch (error) { 164 | Util.warn(error) 165 | return undefined 166 | } 167 | } 168 | const [stackghc, pathghc] = await Promise.all([ 169 | tryWarn('stack', ['--no-install-ghc', 'ghc', '--', '--numeric-version']), 170 | tryWarn('ghc', ['--numeric-version']), 171 | ]) 172 | Util.debug(`Stack GHC version ${stackghc}`) 173 | Util.debug(`Path GHC version ${pathghc}`) 174 | const warnStack = ['stack', undefined].includes(builder) 175 | const warnCabal = ['cabal', 'none', undefined].includes(builder) 176 | let shouldBuild = false 177 | if (pathghc && pathghc !== comp && warnCabal) { 178 | shouldBuild = 179 | shouldBuild || 180 | (await askBuild( 181 | builder, 182 | `\ 183 | GHC version in your PATH '${pathghc}' doesn't match with \ 184 | GHC version used to build ghc-mod '${comp}'. This can lead to \ 185 | problems when using Cabal or Plain projects`, 186 | )) 187 | } 188 | ///////////////////////////// stack ////////////////////////////////////////// 189 | if (stackghc && stackghc !== comp && warnStack) { 190 | shouldBuild = 191 | shouldBuild || 192 | (await askBuild( 193 | builder, 194 | `\ 195 | GHC version in your Stack '${stackghc}' doesn't match with \ 196 | GHC version used to build ghc-mod '${comp}'. This can lead to \ 197 | problems when using Stack projects.`, 198 | )) 199 | } 200 | return shouldBuild 201 | } 202 | 203 | async function askBuild(builder: string | undefined, msg: string) { 204 | let buttons: 205 | | Array<{ 206 | className?: string 207 | text?: string 208 | onDidClick?(event: MouseEvent): void 209 | }> 210 | | undefined 211 | 212 | return new Promise((resolve) => { 213 | let notif: Notification 214 | if (builder === 'stack') { 215 | // offer to build ghc-mod 216 | buttons = [ 217 | { 218 | className: 'icon icon-zap', 219 | text: 'Build ghc-mod', 220 | onDidClick() { 221 | resolve(true) 222 | notif && notif.dismiss() 223 | }, 224 | }, 225 | { 226 | className: 'icon icon-x', 227 | text: 'No thanks', 228 | onDidClick() { 229 | resolve(false) 230 | notif && notif.dismiss() 231 | }, 232 | }, 233 | ] 234 | } 235 | const warn = `${msg} ${ 236 | buttons ? 'Would you like to attempt building ghc-mod?' : '' 237 | }` 238 | notif = atom.notifications.addWarning(warn, { 239 | dismissable: builder !== undefined, 240 | buttons, 241 | }) 242 | Util.warn(msg) 243 | if (buttons) { 244 | const disp = notif.onDidDismiss(() => { 245 | disp.dispose() 246 | resolve(false) 247 | }) 248 | } else { 249 | resolve(false) 250 | } 251 | }) 252 | } 253 | -------------------------------------------------------------------------------- /src/ghc-mod/ghc-modi-process-real.ts: -------------------------------------------------------------------------------- 1 | import { Directory, Emitter, CompositeDisposable } from 'atom' 2 | import { InteractiveProcess, GHCModCaps } from './interactive-process' 3 | import * as Util from '../util' 4 | const { debug, withTempFile, EOT } = Util 5 | import { EOL } from 'os' 6 | import * as _ from 'underscore' 7 | 8 | export { GHCModCaps } 9 | 10 | export interface RunArgs { 11 | interactive?: boolean 12 | command: string 13 | text?: string 14 | uri?: string 15 | dashArgs?: string[] 16 | args?: string[] 17 | suppressErrors?: boolean 18 | ghcOptions?: string[] 19 | ghcModOptions?: string[] 20 | builder: string | undefined 21 | } 22 | 23 | export interface RunOptions { 24 | cwd: string 25 | encoding: 'utf8' 26 | env: { [key: string]: string | undefined } 27 | maxBuffer: number 28 | } 29 | 30 | export interface IErrorCallbackArgs { 31 | runArgs?: RunArgs 32 | err: Error 33 | caps: GHCModCaps 34 | } 35 | 36 | export class GhcModiProcessReal { 37 | private disposables: CompositeDisposable 38 | private emitter: Emitter< 39 | { 40 | 'did-destroy': void 41 | }, 42 | { 43 | warning: string 44 | error: IErrorCallbackArgs 45 | } 46 | > 47 | private ghcModOptions: string[] | undefined 48 | private proc: InteractiveProcess | undefined 49 | 50 | constructor( 51 | private caps: GHCModCaps, 52 | private rootDir: Directory, 53 | private options: RunOptions, 54 | ) { 55 | this.disposables = new CompositeDisposable() 56 | this.emitter = new Emitter() 57 | this.disposables.add(this.emitter) 58 | } 59 | 60 | public getCaps(): GHCModCaps { 61 | return this.caps 62 | } 63 | 64 | public async run(runArgs: RunArgs) { 65 | let { 66 | interactive, 67 | dashArgs, 68 | args, 69 | suppressErrors, 70 | ghcOptions, 71 | ghcModOptions, 72 | } = runArgs 73 | const { command, text, uri, builder } = runArgs 74 | if (!args) { 75 | args = [] 76 | } 77 | if (!dashArgs) { 78 | dashArgs = [] 79 | } 80 | if (!suppressErrors) { 81 | suppressErrors = false 82 | } 83 | if (!ghcOptions) { 84 | ghcOptions = [] 85 | } 86 | if (!ghcModOptions) { 87 | ghcModOptions = [] 88 | } 89 | ghcModOptions = ghcModOptions.concat( 90 | ...ghcOptions.map((opt) => ['--ghc-option', opt]), 91 | ) 92 | if (atom.config.get('haskell-ghc-mod.lowMemorySystem')) { 93 | interactive = atom.config.get('haskell-ghc-mod.enableGhcModi') 94 | } 95 | if (builder) { 96 | switch (builder) { 97 | case 'cabal': 98 | // in case this looks wrong, remember, we want to disable stack 99 | // and use cabal, so we're setting stack path to emptystring 100 | ghcModOptions.push('--with-stack', '') 101 | break 102 | case 'stack': 103 | // same, if this looks strange, it's not 104 | ghcModOptions.push('--with-cabal', '') 105 | break 106 | case 'none': 107 | // here we want to use neither? 108 | ghcModOptions.push('--with-stack', '') 109 | ghcModOptions.push('--with-cabal', '') 110 | break 111 | default: 112 | atom.notifications.addWarning( 113 | `Haskell-ghc-mod: unknown builder ${builder}, falling back to autodetection`, 114 | ) 115 | } 116 | } 117 | if (this.caps.optparse) { 118 | args = dashArgs.concat(['--']).concat(args) 119 | } else { 120 | args = dashArgs.concat(args) 121 | } 122 | const fun = interactive ? this.runModiCmd : this.runModCmd 123 | try { 124 | let res 125 | if (uri && text && !this.caps.fileMap) { 126 | const myOpts = { ghcModOptions, command, args } 127 | res = withTempFile(text, uri, async (tempuri) => { 128 | const { stdout, stderr } = await fun({ ...myOpts, uri: tempuri }) 129 | return { 130 | stdout: stdout.map((line) => line.split(tempuri).join(uri)), 131 | stderr: stderr.map((line) => line.split(tempuri).join(uri)), 132 | } 133 | }) 134 | } else { 135 | res = fun({ ghcModOptions, command, text, uri, args }) 136 | } 137 | const { stdout, stderr } = await res 138 | if (stderr.join('').length) { 139 | this.emitter.emit('warning', stderr.join('\n')) 140 | } 141 | return stdout.map((line) => line.replace(/\0/g, '\n')) 142 | } catch (err) { 143 | debug(err) 144 | this.emitter.emit('error', { runArgs, err, caps: this.caps }) 145 | return [] 146 | } 147 | } 148 | 149 | public killProcess() { 150 | debug(`Killing ghc-modi process for ${this.rootDir.getPath()}`) 151 | this.proc && this.proc.kill() 152 | } 153 | 154 | public destroy() { 155 | debug('GhcModiProcessBase destroying') 156 | this.killProcess() 157 | this.emitter.emit('did-destroy') 158 | this.disposables.dispose() 159 | } 160 | 161 | public onDidDestroy(callback: () => void) { 162 | return this.emitter.on('did-destroy', callback) 163 | } 164 | 165 | public onWarning(callback: (warning: string) => void) { 166 | return this.emitter.on('warning', callback) 167 | } 168 | 169 | public onError(callback: (error: IErrorCallbackArgs) => void) { 170 | return this.emitter.on('error', callback) 171 | } 172 | 173 | private async spawnProcess( 174 | ghcModOptions: string[], 175 | ): Promise { 176 | if (!atom.config.get('haskell-ghc-mod.enableGhcModi')) { 177 | return undefined 178 | } 179 | debug(`Checking for ghc-modi in ${this.rootDir.getPath()}`) 180 | if (this.proc) { 181 | if (!_.isEqual(this.ghcModOptions, ghcModOptions)) { 182 | debug( 183 | `Found running ghc-modi instance for ${this.rootDir.getPath()}, but ghcModOptions don't match. Old: `, 184 | this.ghcModOptions, 185 | ' new: ', 186 | ghcModOptions, 187 | ) 188 | await this.proc.kill() 189 | return this.spawnProcess(ghcModOptions) 190 | } 191 | debug(`Found running ghc-modi instance for ${this.rootDir.getPath()}`) 192 | return this.proc 193 | } 194 | debug( 195 | `Spawning new ghc-modi instance for ${this.rootDir.getPath()} with`, 196 | this.options, 197 | ) 198 | const modPath = atom.config.get('haskell-ghc-mod.ghcModPath') 199 | this.ghcModOptions = ghcModOptions 200 | this.proc = new InteractiveProcess( 201 | modPath, 202 | ghcModOptions.concat(['legacy-interactive']), 203 | this.options, 204 | this.caps, 205 | ) 206 | this.proc.onceExit((code) => { 207 | debug(`ghc-modi for ${this.rootDir.getPath()} ended with ${code}`) 208 | this.proc = undefined 209 | }) 210 | return this.proc 211 | } 212 | 213 | private runModCmd = async ({ 214 | ghcModOptions, 215 | command, 216 | text, 217 | uri, 218 | args, 219 | }: { 220 | ghcModOptions: string[] 221 | command: string 222 | text?: string 223 | uri?: string 224 | args: string[] 225 | }) => { 226 | const modPath = atom.config.get('haskell-ghc-mod.ghcModPath') 227 | let stdin 228 | const cmd = [...ghcModOptions] 229 | if (text && uri) { 230 | cmd.push('--map-file', uri) 231 | stdin = `${text}${EOT}` 232 | } 233 | cmd.push(command) 234 | if (uri) { 235 | cmd.push(uri) 236 | } 237 | cmd.push(...args) 238 | const { stdout, stderr } = await Util.execPromise( 239 | modPath, 240 | cmd, 241 | this.options, 242 | stdin, 243 | ) 244 | return { 245 | stdout: stdout.split(EOL).slice(0, -1), 246 | stderr: stderr.split(EOL), 247 | } 248 | } 249 | 250 | private runModiCmd = async (o: { 251 | ghcModOptions: string[] 252 | command: string 253 | text?: string 254 | uri?: string 255 | args: string[] 256 | }) => { 257 | const { ghcModOptions, command, text, args } = o 258 | let { uri } = o 259 | debug(`Trying to run ghc-modi in ${this.rootDir.getPath()}`) 260 | const proc = await this.spawnProcess(ghcModOptions) 261 | if (!proc) { 262 | debug('Failed. Falling back to ghc-mod') 263 | return this.runModCmd(o) 264 | } 265 | debug('Success. Resuming...') 266 | if (uri && !this.caps.quoteArgs) { 267 | uri = this.rootDir.relativize(uri) 268 | } 269 | try { 270 | if (uri && text) { 271 | await proc.interact('map-file', [uri], text) 272 | } 273 | const res = await proc.interact(command, uri ? [uri].concat(args) : args) 274 | if (uri && text) { 275 | await proc.interact('unmap-file', [uri]) 276 | } 277 | return res 278 | } finally { 279 | if (uri && text) { 280 | await proc.interact('unmap-file', [uri]) 281 | } 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/ghc-mod/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Range, 3 | Point, 4 | Emitter, 5 | CompositeDisposable, 6 | TextBuffer, 7 | Directory, 8 | TextEditor, 9 | } from 'atom' 10 | import * as Util from '../util' 11 | import { extname, isAbsolute } from 'path' 12 | import Queue = require('promise-queue') 13 | import { unlit } from 'atom-haskell-utils' 14 | import * as CompletionBackend from 'atom-haskell-upi/completion-backend' 15 | import * as UPI from 'atom-haskell-upi' 16 | 17 | import { 18 | GhcModiProcessReal, 19 | GHCModCaps, 20 | RunArgs, 21 | IErrorCallbackArgs, 22 | } from './ghc-modi-process-real' 23 | import { createGhcModiProcessReal } from './ghc-modi-process-real-factory' 24 | import { getSettings } from './settings' 25 | 26 | export { IErrorCallbackArgs, RunArgs, GHCModCaps } 27 | 28 | type Commands = 29 | | 'checklint' 30 | | 'browse' 31 | | 'typeinfo' 32 | | 'find' 33 | | 'init' 34 | | 'list' 35 | | 'lowmem' 36 | 37 | export interface SymbolDesc { 38 | name: string 39 | symbolType: CompletionBackend.SymbolType 40 | typeSignature?: string 41 | parent?: string 42 | } 43 | 44 | export class GhcModiProcess { 45 | private backend: Map> 46 | private disposables: CompositeDisposable 47 | private emitter: Emitter< 48 | { 49 | 'did-destroy': undefined 50 | 'backend-active': undefined 51 | 'backend-idle': undefined 52 | }, 53 | { 54 | warning: string 55 | error: IErrorCallbackArgs 56 | 'queue-idle': { queue: Commands } 57 | } 58 | > 59 | private bufferDirMap: WeakMap 60 | private commandQueues: { [K in Commands]: Queue } 61 | 62 | constructor(private upiPromise: Promise) { 63 | this.disposables = new CompositeDisposable() 64 | this.emitter = new Emitter() 65 | this.disposables.add(this.emitter) 66 | this.bufferDirMap = new WeakMap() 67 | this.backend = new Map() 68 | 69 | if ( 70 | process.env.GHC_PACKAGE_PATH && 71 | !atom.config.get('haskell-ghc-mod.suppressGhcPackagePathWarning') 72 | ) { 73 | Util.warnGHCPackagePath() 74 | } 75 | 76 | this.commandQueues = { 77 | checklint: new Queue(2), 78 | browse: new Queue(atom.config.get('haskell-ghc-mod.maxBrowseProcesses')), 79 | typeinfo: new Queue(1), 80 | find: new Queue(1), 81 | init: new Queue(4), 82 | list: new Queue(1), 83 | lowmem: new Queue(1), 84 | } 85 | this.disposables.add( 86 | atom.config.onDidChange( 87 | 'haskell-ghc-mod.maxBrowseProcesses', 88 | ({ newValue }) => 89 | (this.commandQueues.browse = new Queue(newValue as number)), 90 | ), 91 | ) 92 | } 93 | 94 | public async getRootDir(buffer: TextBuffer): Promise { 95 | let dir 96 | dir = this.bufferDirMap.get(buffer) 97 | if (dir) { 98 | return dir 99 | } 100 | dir = await Util.getRootDir(buffer) 101 | this.bufferDirMap.set(buffer, dir) 102 | return dir 103 | } 104 | 105 | public killProcess() { 106 | for (const bp of this.backend.values()) { 107 | bp.then((b) => b.killProcess()).catch((e: Error) => { 108 | atom.notifications.addError('Error killing ghc-mod process', { 109 | detail: e.toString(), 110 | stack: e.stack, 111 | dismissable: true, 112 | }) 113 | }) 114 | } 115 | this.backend.clear() 116 | } 117 | 118 | public destroy() { 119 | for (const bp of this.backend.values()) { 120 | bp.then((b) => b.destroy()).catch((e: Error) => { 121 | atom.notifications.addError('Error killing ghc-mod process', { 122 | detail: e.toString(), 123 | stack: e.stack, 124 | dismissable: true, 125 | }) 126 | }) 127 | } 128 | this.backend.clear() 129 | this.emitter.emit('did-destroy') 130 | this.disposables.dispose() 131 | } 132 | 133 | public onDidDestroy(callback: () => void) { 134 | return this.emitter.on('did-destroy', callback) 135 | } 136 | 137 | public onWarning(callback: (warning: string) => void) { 138 | return this.emitter.on('warning', callback) 139 | } 140 | 141 | public onError(callback: (error: IErrorCallbackArgs) => void) { 142 | return this.emitter.on('error', callback) 143 | } 144 | 145 | public onBackendActive(callback: () => void) { 146 | return this.emitter.on('backend-active', callback) 147 | } 148 | 149 | public onBackendIdle(callback: () => void) { 150 | return this.emitter.on('backend-idle', callback) 151 | } 152 | 153 | public onQueueIdle(callback: () => void) { 154 | return this.emitter.on('queue-idle', callback) 155 | } 156 | 157 | public async runList(buffer: TextBuffer) { 158 | return this.queueCmd('list', await this.getRootDir(buffer), () => ({ 159 | command: 'list', 160 | })) 161 | } 162 | 163 | public async runLang(dir: Directory) { 164 | return this.queueCmd('init', dir, () => ({ command: 'lang' })) 165 | } 166 | 167 | public async runFlag(dir: Directory) { 168 | return this.queueCmd('init', dir, () => ({ command: 'flag' })) 169 | } 170 | 171 | public async runBrowse( 172 | rootDir: Directory, 173 | modules: string[], 174 | ): Promise { 175 | const lines = await this.queueCmd('browse', rootDir, (caps) => { 176 | const args = caps.browseMain 177 | ? modules 178 | : modules.filter((v) => v !== 'Main') 179 | if (args.length === 0) return undefined 180 | return { 181 | command: 'browse', 182 | dashArgs: caps.browseParents ? ['-d', '-o', '-p'] : ['-d', '-o'], 183 | args, 184 | } 185 | }) 186 | return lines.map((s) => { 187 | // enumFrom :: Enum a => a -> [a] -- from:Enum 188 | const pattern = /^(.*?) :: (.*?)(?: -- from:(.*))?$/ 189 | const match = s.match(pattern) 190 | let name: string 191 | let typeSignature: string | undefined 192 | let parent: string | undefined 193 | if (match) { 194 | name = match[1] 195 | typeSignature = match[2] 196 | parent = match[3] 197 | } else { 198 | name = s 199 | } 200 | let symbolType: CompletionBackend.SymbolType 201 | if (typeSignature && /^(?:type|data|newtype)/.test(typeSignature)) { 202 | symbolType = 'type' 203 | } else if (typeSignature && /^(?:class)/.test(typeSignature)) { 204 | symbolType = 'class' 205 | } else if (/^\(.*\)$/.test(name)) { 206 | symbolType = 'operator' 207 | name = name.slice(1, -1) 208 | } else if (Util.isUpperCase(name[0])) { 209 | symbolType = 'tag' 210 | } else { 211 | symbolType = 'function' 212 | } 213 | return { name, typeSignature, symbolType, parent } 214 | }) 215 | } 216 | 217 | public async getTypeInBuffer(buffer: TextBuffer, crange: Range) { 218 | if (!buffer.getUri()) { 219 | throw new Error('No URI for buffer') 220 | } 221 | crange = Util.tabShiftForRange(buffer, crange) 222 | const rootDir = await this.getRootDir(buffer) 223 | const lines = await this.queueCmd('typeinfo', rootDir, (caps) => ({ 224 | interactive: true, 225 | command: 'type', 226 | uri: buffer.getUri(), 227 | text: buffer.isModified() ? buffer.getText() : undefined, 228 | dashArgs: caps.typeConstraints ? ['-c'] : [], 229 | args: [crange.start.row + 1, crange.start.column + 1].map((v) => 230 | v.toString(), 231 | ), 232 | })) 233 | 234 | const rx = /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+"([^]*)"$/ // [^] basically means "anything", incl. newlines 235 | for (const line of lines) { 236 | const match = line.match(rx) 237 | if (!match) { 238 | continue 239 | } 240 | const [rowstart, colstart, rowend, colend, type] = match.slice(1) 241 | const range = Range.fromObject([ 242 | [parseInt(rowstart, 10) - 1, parseInt(colstart, 10) - 1], 243 | [parseInt(rowend, 10) - 1, parseInt(colend, 10) - 1], 244 | ]) 245 | if (range.isEmpty()) { 246 | continue 247 | } 248 | if (!range.containsRange(crange)) { 249 | continue 250 | } 251 | return { 252 | range: Util.tabUnshiftForRange(buffer, range), 253 | type: type.replace(/\\"/g, '"'), 254 | } 255 | } 256 | throw new Error('No type') 257 | } 258 | 259 | public async doCaseSplit(buffer: TextBuffer, crange: Range) { 260 | if (!buffer.getUri()) { 261 | throw new Error('No URI for buffer') 262 | } 263 | crange = Util.tabShiftForRange(buffer, crange) 264 | const rootDir = await this.getRootDir(buffer) 265 | const lines = await this.queueCmd('typeinfo', rootDir, (caps) => ({ 266 | interactive: caps.interactiveCaseSplit, 267 | command: 'split', 268 | uri: buffer.getUri(), 269 | text: buffer.isModified() ? buffer.getText() : undefined, 270 | args: [crange.start.row + 1, crange.start.column + 1].map((v) => 271 | v.toString(), 272 | ), 273 | })) 274 | 275 | const rx = /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+"([^]*)"$/ // [^] basically means "anything", incl. newlines 276 | const res = [] 277 | for (const line of lines) { 278 | const match = line.match(rx) 279 | if (!match) { 280 | Util.warn(`ghc-mod says: ${line}`) 281 | continue 282 | } 283 | const [rowstart, colstart, rowend, colend, text] = match.slice(1) 284 | res.push({ 285 | range: Range.fromObject([ 286 | [parseInt(rowstart, 10) - 1, parseInt(colstart, 10) - 1], 287 | [parseInt(rowend, 10) - 1, parseInt(colend, 10) - 1], 288 | ]), 289 | replacement: text, 290 | }) 291 | } 292 | return res 293 | } 294 | 295 | public async doSigFill(buffer: TextBuffer, crange: Range) { 296 | if (!buffer.getUri()) { 297 | throw new Error('No URI for buffer') 298 | } 299 | crange = Util.tabShiftForRange(buffer, crange) 300 | const rootDir = await this.getRootDir(buffer) 301 | const lines = await this.queueCmd('typeinfo', rootDir, (caps) => ({ 302 | interactive: caps.interactiveCaseSplit, 303 | command: 'sig', 304 | uri: buffer.getUri(), 305 | text: buffer.isModified() ? buffer.getText() : undefined, 306 | args: [crange.start.row + 1, crange.start.column + 1].map((v) => 307 | v.toString(), 308 | ), 309 | })) 310 | if (lines.length < 2) { 311 | throw new Error(`Could not understand response: ${lines.join('\n')}`) 312 | } 313 | const rx = /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/ // position rx 314 | const match = lines[1].match(rx) 315 | if (!match) { 316 | throw new Error(`Could not understand response: ${lines.join('\n')}`) 317 | } 318 | const [rowstart, colstart, rowend, colend] = match.slice(1) 319 | const range = Range.fromObject([ 320 | [parseInt(rowstart, 10) - 1, parseInt(colstart, 10) - 1], 321 | [parseInt(rowend, 10) - 1, parseInt(colend, 10) - 1], 322 | ]) 323 | return { 324 | type: lines[0], 325 | range, 326 | body: lines.slice(2).join('\n'), 327 | } 328 | } 329 | 330 | public async getInfoInBuffer(editor: TextEditor, crange: Range) { 331 | const buffer = editor.getBuffer() 332 | if (!buffer.getUri()) { 333 | throw new Error('No URI for buffer') 334 | } 335 | const symInfo = Util.getSymbolInRange(editor, crange) 336 | if (!symInfo) { 337 | throw new Error("Couldn't get symbol for info") 338 | } 339 | const { symbol, range } = symInfo 340 | 341 | const lines = await this.queueCmd( 342 | 'typeinfo', 343 | await this.getRootDir(buffer), 344 | () => ({ 345 | interactive: true, 346 | command: 'info', 347 | uri: buffer.getUri(), 348 | text: buffer.isModified() ? buffer.getText() : undefined, 349 | args: [symbol], 350 | }), 351 | ) 352 | 353 | const info = lines.join('\n') 354 | if (info === 'Cannot show info' || !info) { 355 | throw new Error('No info') 356 | } else { 357 | return { range, info } 358 | } 359 | } 360 | 361 | public async findSymbolProvidersInBuffer(editor: TextEditor, crange: Range) { 362 | const buffer = editor.getBuffer() 363 | const symInfo = Util.getSymbolInRange(editor, crange) 364 | if (!symInfo) { 365 | throw new Error("Couldn't get symbol for import") 366 | } 367 | const { symbol } = symInfo 368 | 369 | return this.queueCmd('find', await this.getRootDir(buffer), () => ({ 370 | interactive: true, 371 | command: 'find', 372 | args: [symbol], 373 | })) 374 | } 375 | 376 | public async doCheckBuffer(buffer: TextBuffer, fast: boolean) { 377 | return this.doCheckOrLintBuffer('check', buffer, fast) 378 | } 379 | 380 | public async doLintBuffer(buffer: TextBuffer) { 381 | return this.doCheckOrLintBuffer('lint', buffer, false) 382 | } 383 | 384 | private async getUPI() { 385 | return Promise.race([this.upiPromise, Promise.resolve(undefined)]) 386 | } 387 | 388 | private async initBackend(rootDir: Directory): Promise { 389 | const rootPath = rootDir.getPath() 390 | const cached = this.backend.get(rootPath) 391 | if (cached) { 392 | return cached 393 | } 394 | const backend = this.createBackend(rootDir) 395 | this.backend.set(rootPath, backend) 396 | return backend 397 | } 398 | 399 | private async createBackend(rootDir: Directory): Promise { 400 | const newBackend = createGhcModiProcessReal(rootDir, await this.getUPI()) 401 | const backend = await newBackend 402 | this.disposables.add( 403 | backend.onError((arg) => this.emitter.emit('error', arg)), 404 | backend.onWarning((arg) => this.emitter.emit('warning', arg)), 405 | ) 406 | return backend 407 | } 408 | 409 | private async queueCmd( 410 | queueName: Commands, 411 | dir: Directory, 412 | runArgsFunc: ( 413 | caps: GHCModCaps, 414 | ) => 415 | | { 416 | command: string 417 | text?: string 418 | uri?: string 419 | interactive?: boolean 420 | dashArgs?: string[] 421 | args?: string[] 422 | } 423 | | undefined, 424 | ): Promise { 425 | if (atom.config.get('haskell-ghc-mod.lowMemorySystem')) { 426 | queueName = 'lowmem' 427 | } 428 | const backend = await this.initBackend(dir) 429 | const promise = this.commandQueues[queueName].add(async () => { 430 | this.emitter.emit('backend-active') 431 | try { 432 | const settings = await getSettings(dir) 433 | if (settings.disable) { 434 | throw new Error('Ghc-mod disabled in settings') 435 | } 436 | const runArgs = runArgsFunc(backend.getCaps()) 437 | if (runArgs === undefined) return [] 438 | const upi = await this.getUPI() 439 | let builder: string | undefined 440 | if (upi && atom.config.get('haskell-ghc-mod.builderManagement')) { 441 | // TODO: this is used twice, the second time in ghc-mod-process-real-factory.ts, should probably fix that 442 | const b = await upi.getOthersConfigParam<{ name: string }>( 443 | 'ide-haskell-cabal', 444 | 'builder', 445 | ) 446 | if (b) builder = b.name 447 | } 448 | return backend.run({ 449 | ...runArgs, 450 | builder, 451 | suppressErrors: settings.suppressErrors, 452 | ghcOptions: settings.ghcOptions, 453 | ghcModOptions: settings.ghcModOptions, 454 | }) 455 | } catch (err) { 456 | Util.warn(err) 457 | throw err 458 | } 459 | }) 460 | promise 461 | .then(() => { 462 | const qe = (qn: Commands) => { 463 | const q = this.commandQueues[qn] 464 | return q.getQueueLength() + q.getPendingLength() === 0 465 | } 466 | if (qe(queueName)) { 467 | this.emitter.emit('queue-idle', { queue: queueName }) 468 | if (Object.keys(this.commandQueues).every(qe)) { 469 | this.emitter.emit('backend-idle') 470 | } 471 | } 472 | }) 473 | .catch((e: Error) => { 474 | atom.notifications.addError('Error in GHCMod command queue', { 475 | detail: e.toString(), 476 | stack: e.stack, 477 | dismissable: true, 478 | }) 479 | }) 480 | return promise 481 | } 482 | 483 | private async doCheckOrLintBuffer( 484 | cmd: 'check' | 'lint', 485 | buffer: TextBuffer, 486 | fast: boolean, 487 | ) { 488 | let dashArgs 489 | if (buffer.isEmpty()) { 490 | return [] 491 | } 492 | if (!buffer.getUri()) { 493 | return [] 494 | } 495 | 496 | // A dirty hack to make lint work with lhs 497 | let uri = buffer.getUri() 498 | const olduri = buffer.getUri() 499 | let text: string | undefined 500 | try { 501 | if (cmd === 'lint' && extname(uri) === '.lhs') { 502 | uri = uri.slice(0, -1) 503 | text = await unlit(olduri, buffer.getText()) 504 | } else if (buffer.isModified()) { 505 | text = buffer.getText() 506 | } 507 | } catch (error) { 508 | // TODO: Reject 509 | const m = (error as Error).message.match(/^(.*?):([0-9]+): *(.*) *$/) 510 | if (!m) { 511 | throw error 512 | } 513 | const [uri2, line, mess] = m.slice(1) 514 | return [ 515 | { 516 | uri: uri2, 517 | position: new Point(parseInt(line, 10) - 1, 0), 518 | message: mess, 519 | severity: 'lint', 520 | }, 521 | ] 522 | } 523 | // end of dirty hack 524 | 525 | // tslint:disable-next-line: totality-check 526 | if (cmd === 'lint') { 527 | const opts: string[] = atom.config.get('haskell-ghc-mod.hlintOptions') 528 | dashArgs = [] 529 | for (const opt of opts) { 530 | dashArgs.push('--hlintOpt', opt) 531 | } 532 | } 533 | 534 | const rootDir = await this.getRootDir(buffer) 535 | 536 | const textB = text 537 | const dashArgsB = dashArgs 538 | const lines = await this.queueCmd('checklint', rootDir, () => ({ 539 | interactive: fast, 540 | command: cmd, 541 | uri, 542 | text: textB, 543 | dashArgs: dashArgsB, 544 | })) 545 | 546 | const rx = /^(.*?):([0-9\s]+):([0-9\s]+): *(?:(Warning|Error): *)?([^]*)/ 547 | const res = [] 548 | for (const line of lines) { 549 | const match = line.match(rx) 550 | if (!match) { 551 | if (line.trim().length) { 552 | Util.warn(`ghc-mod says: ${line}`) 553 | } 554 | continue 555 | } 556 | const [file2, row, col, warning, message] = match.slice(1) 557 | if (file2 === 'Dummy' && row === '0' && col === '0') { 558 | if (warning === 'Error') { 559 | this.emitter.emit('error', { 560 | err: Util.mkError('GHCModStdoutError', message), 561 | caps: (await this.initBackend(rootDir)).getCaps(), // TODO: This is not pretty 562 | }) 563 | continue 564 | } else if (warning === 'Warning') { 565 | this.emitter.emit('warning', message) 566 | continue 567 | } 568 | } 569 | 570 | const file = uri.endsWith(file2) ? olduri : file2 571 | const severity = 572 | cmd === 'lint' ? 'lint' : warning === 'Warning' ? 'warning' : 'error' 573 | const messPos = new Point(parseInt(row, 10) - 1, parseInt(col, 10) - 1) 574 | const position = Util.tabUnshiftForPoint(buffer, messPos) 575 | const myuri = isAbsolute(file) ? file : rootDir.getFile(file).getPath() 576 | res.push({ 577 | uri: myuri, 578 | position, 579 | message, 580 | severity, 581 | }) 582 | } 583 | return res 584 | } 585 | } 586 | -------------------------------------------------------------------------------- /src/ghc-mod/interactive-process.ts: -------------------------------------------------------------------------------- 1 | import { Emitter, CompositeDisposable } from 'atom' 2 | import { debug, warn, mkError, EOT } from '../util' 3 | import { EOL } from 'os' 4 | import * as CP from 'child_process' 5 | import Queue = require('promise-queue') 6 | import pidusage = require('pidusage') 7 | if (!Symbol.asyncIterator) { 8 | Object.defineProperty(Symbol, 'asyncIterator', { 9 | value: Symbol.for('Symbol.asyncIterator'), 10 | }) 11 | } 12 | 13 | export interface GHCModCaps { 14 | version: number[] 15 | fileMap: boolean 16 | quoteArgs: boolean 17 | optparse: boolean 18 | typeConstraints: boolean 19 | browseParents: boolean 20 | interactiveCaseSplit: boolean 21 | importedFrom: boolean 22 | browseMain: boolean 23 | } 24 | 25 | export class InteractiveProcess { 26 | private disposables: CompositeDisposable 27 | private emitter: Emitter< 28 | {}, 29 | { 30 | 'did-exit': number 31 | } 32 | > 33 | private proc: CP.ChildProcess 34 | private cwd: string 35 | private timer: number | undefined 36 | private requestQueue: Queue 37 | 38 | constructor( 39 | path: string, 40 | cmd: string[], 41 | options: { cwd: string }, 42 | private caps: GHCModCaps, 43 | ) { 44 | this.caps = caps 45 | this.disposables = new CompositeDisposable() 46 | this.emitter = new Emitter() 47 | this.disposables.add(this.emitter) 48 | this.cwd = options.cwd 49 | this.requestQueue = new Queue(1, 100) 50 | 51 | debug( 52 | `Spawning new ghc-modi instance for ${options.cwd} with options = `, 53 | options, 54 | ) 55 | this.proc = CP.spawn(path, cmd, options) 56 | this.proc.stdout.setEncoding('utf-8') 57 | this.proc.stderr.setEncoding('utf-8') 58 | this.proc.setMaxListeners(100) 59 | this.proc.stdout.setMaxListeners(100) 60 | this.proc.stderr.setMaxListeners(100) 61 | this.resetTimer() 62 | this.proc.once('exit', (code) => { 63 | this.timer && window.clearTimeout(this.timer) 64 | debug(`ghc-modi for ${options.cwd} ended with ${code}`) 65 | this.emitter.emit('did-exit', code) 66 | this.disposables.dispose() 67 | }) 68 | } 69 | 70 | public onceExit(action: (code: number) => void) { 71 | return this.emitter.once('did-exit', action) 72 | } 73 | 74 | public async kill(): Promise { 75 | this.proc.stdin.end() 76 | this.proc.kill() 77 | return new Promise((resolve) => { 78 | this.proc.once('exit', (code) => resolve(code)) 79 | }) 80 | } 81 | 82 | public async interact( 83 | command: string, 84 | args: string[], 85 | data?: string, 86 | ): Promise<{ stdout: string[]; stderr: string[] }> { 87 | return this.requestQueue.add(async () => { 88 | this.proc.stdout.pause() 89 | this.proc.stderr.pause() 90 | 91 | pidusage(this.proc.pid, (err, stat) => { 92 | if (err) { 93 | warn(err) 94 | return 95 | } 96 | if ( 97 | stat.memory > 98 | atom.config.get('haskell-ghc-mod.maxMemMegs') * 1024 * 1024 99 | ) { 100 | this.proc.kill() 101 | } 102 | }) 103 | 104 | debug(`Started interactive action block in ${this.cwd}`) 105 | debug( 106 | `Running interactive command ${command} ${args} ${ 107 | data ? 'with' : 'without' 108 | } additional data`, 109 | ) 110 | let ended = false 111 | try { 112 | const isEnded = () => ended 113 | const stderr: string[] = [] 114 | const stdout: string[] = [] 115 | setImmediate(async () => { 116 | for await (const line of this.readgen(this.proc.stderr, isEnded)) { 117 | stderr.push(line) 118 | } 119 | }) 120 | const readOutput = async () => { 121 | for await (const line of this.readgen(this.proc.stdout, isEnded)) { 122 | debug(`Got response from ghc-modi: ${line}`) 123 | if (line === 'OK') { 124 | ended = true 125 | } else { 126 | stdout.push(line) 127 | } 128 | } 129 | return { stdout, stderr } 130 | } 131 | const exitEvent = async () => 132 | new Promise((_resolve, reject) => { 133 | this.proc.once('exit', () => { 134 | warn(stdout.join('\n')) 135 | reject( 136 | mkError('GHCModInteractiveCrash', `${stdout}\n\n${stderr}`), 137 | ) 138 | }) 139 | }) 140 | const timeoutEvent = async () => 141 | new Promise((_resolve, reject) => { 142 | const tml: number = atom.config.get( 143 | 'haskell-ghc-mod.interactiveActionTimeout', 144 | ) 145 | if (tml) { 146 | setTimeout(() => { 147 | reject( 148 | mkError('InteractiveActionTimeout', `${stdout}\n\n${stderr}`), 149 | ) 150 | }, tml * 1000) 151 | } 152 | }) 153 | 154 | const args2 = this.caps.quoteArgs 155 | ? ['ascii-escape', command].concat(args.map((x) => `\x02${x}\x03`)) 156 | : [command, ...args] 157 | debug(`Running ghc-modi command ${command}`, ...args) 158 | this.proc.stdin.write( 159 | `${args2.join(' ').replace(/(?:\r?\n|\r)/g, ' ')}${EOL}`, 160 | ) 161 | if (data) { 162 | debug('Writing data to stdin...') 163 | this.proc.stdin.write(`${data}${EOT}`) 164 | } 165 | return await Promise.race([readOutput(), exitEvent(), timeoutEvent()]) 166 | } catch (error) { 167 | if (error.name === 'InteractiveActionTimeout') { 168 | this.proc.kill() 169 | } 170 | throw error 171 | } finally { 172 | debug(`Ended interactive action block in ${this.cwd}`) 173 | ended = true 174 | this.proc.stdout.resume() 175 | this.proc.stderr.resume() 176 | } 177 | }) 178 | } 179 | 180 | private resetTimer() { 181 | if (this.timer) { 182 | clearTimeout(this.timer) 183 | } 184 | const tml = atom.config.get('haskell-ghc-mod.interactiveInactivityTimeout') 185 | if (tml) { 186 | this.timer = window.setTimeout(() => { 187 | // tslint:disable-next-line: no-floating-promises 188 | this.kill() 189 | }, tml * 60 * 1000) 190 | } 191 | } 192 | 193 | private async waitReadable(stream: NodeJS.ReadableStream) { 194 | return new Promise((resolve) => 195 | stream.once('readable', () => { 196 | resolve() 197 | }), 198 | ) 199 | } 200 | 201 | private async *readgen(out: NodeJS.ReadableStream, isEnded: () => boolean) { 202 | let buffer = '' 203 | while (!isEnded()) { 204 | const read = out.read() as string | null 205 | // tslint:disable-next-line: no-null-keyword 206 | if (read !== null) { 207 | buffer += read 208 | if (buffer.includes(EOL)) { 209 | const arr = buffer.split(EOL) 210 | buffer = arr.pop() || '' 211 | yield* arr 212 | } 213 | } else { 214 | await this.waitReadable(out) 215 | } 216 | } 217 | if (buffer) { 218 | out.unshift(buffer) 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/ghc-mod/settings.ts: -------------------------------------------------------------------------------- 1 | import { File, Directory } from 'atom' 2 | import * as Util from '../util' 3 | 4 | export interface GHCModSettings { 5 | disable?: boolean 6 | suppressErrors?: boolean 7 | ghcOptions?: string[] 8 | ghcModOptions?: string[] 9 | } 10 | 11 | export async function getSettings(runDir: Directory): Promise { 12 | const localSettings = readSettings(runDir.getFile('.haskell-ghc-mod.json')) 13 | 14 | const [projectDir] = atom.project 15 | .getDirectories() 16 | .filter((d) => d.contains(runDir.getPath())) 17 | const projectSettings = projectDir 18 | ? readSettings(projectDir.getFile('.haskell-ghc-mod.json')) 19 | : Promise.resolve({}) 20 | 21 | const configDir = new Directory(atom.getConfigDirPath()) 22 | const globalSettings = readSettings(configDir.getFile('haskell-ghc-mod.json')) 23 | 24 | const [glob, prj, loc] = await Promise.all([ 25 | globalSettings, 26 | projectSettings, 27 | localSettings, 28 | ]) 29 | return { ...glob, ...prj, ...loc } 30 | } 31 | 32 | async function readSettings(file: File): Promise { 33 | try { 34 | const contents = await file.read() 35 | if (contents !== null) { 36 | try { 37 | return JSON.parse(contents) 38 | } catch (err) { 39 | atom.notifications.addError(`Failed to parse ${file.getPath()}`, { 40 | detail: err, 41 | dismissable: true, 42 | }) 43 | throw err 44 | } 45 | } else { 46 | return {} 47 | } 48 | } catch (error) { 49 | if (error) { 50 | Util.warn(error) 51 | } 52 | return {} 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/haskell-ghc-mod.ts: -------------------------------------------------------------------------------- 1 | import { GhcModiProcess } from './ghc-mod' 2 | import { CompositeDisposable } from 'atom' 3 | import { CompletionBackend } from './completion-backend' 4 | import { UPIConsumer } from './upi-consumer' 5 | import { defaultErrorHandler } from './util' 6 | import * as UPI from 'atom-haskell-upi' 7 | 8 | let process: GhcModiProcess | undefined 9 | let disposables: CompositeDisposable | undefined 10 | let tempDisposables: CompositeDisposable | undefined 11 | let completionBackend: CompletionBackend | undefined 12 | let resolveUpiPromise: (v: UPI.IUPIInstance) => void 13 | let upiPromise: Promise 14 | 15 | export { config } from './config' 16 | 17 | export function activate(_state: never) { 18 | upiPromise = new Promise( 19 | (resolve) => (resolveUpiPromise = resolve), 20 | ) 21 | process = new GhcModiProcess(upiPromise) 22 | disposables = new CompositeDisposable() 23 | tempDisposables = new CompositeDisposable() 24 | disposables.add(tempDisposables) 25 | 26 | tempDisposables.add( 27 | process.onError(defaultErrorHandler), 28 | process.onWarning((detail: string) => { 29 | atom.notifications.addWarning('ghc-mod warning', { detail }) 30 | }), 31 | ) 32 | 33 | disposables.add( 34 | atom.commands.add('atom-workspace', { 35 | 'haskell-ghc-mod:shutdown-backend': () => 36 | process && process.killProcess(), 37 | }), 38 | ) 39 | } 40 | 41 | export function deactivate() { 42 | process && process.destroy() 43 | process = undefined 44 | completionBackend = undefined 45 | disposables && disposables.dispose() 46 | disposables = undefined 47 | tempDisposables = undefined 48 | } 49 | 50 | export function provideCompletionBackend() { 51 | if (!process) { 52 | return undefined 53 | } 54 | if (!completionBackend) { 55 | completionBackend = new CompletionBackend(process, upiPromise) 56 | } 57 | return completionBackend 58 | } 59 | 60 | export function consumeUPI(service: UPI.IUPIRegistration) { 61 | if (!process || !disposables) { 62 | return undefined 63 | } 64 | tempDisposables && tempDisposables.dispose() 65 | tempDisposables = undefined 66 | const upiConsumer = new UPIConsumer(service, process) 67 | resolveUpiPromise(upiConsumer.upi) 68 | disposables.add(upiConsumer) 69 | return upiConsumer 70 | } 71 | -------------------------------------------------------------------------------- /src/upi-consumer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CommandEvent, 3 | CompositeDisposable, 4 | Range, 5 | TextBuffer, 6 | TextEditor, 7 | Point, 8 | TextEditorElement, 9 | } from 'atom' 10 | import { GhcModiProcess, IErrorCallbackArgs } from './ghc-mod' 11 | import { importListView } from './views/import-list-view' 12 | import * as Util from './util' 13 | import * as UPI from 'atom-haskell-upi' 14 | const { handleException } = Util 15 | 16 | const messageTypes = { 17 | error: {}, 18 | warning: {}, 19 | lint: {}, 20 | } 21 | 22 | const addMsgTypes = { 23 | 'ghc-mod': { 24 | uriFilter: false, 25 | autoScroll: true, 26 | }, 27 | } 28 | 29 | const contextScope = 'atom-text-editor[data-grammar~="haskell"]' 30 | 31 | const mainMenu = { 32 | label: 'ghc-mod', 33 | menu: [ 34 | { label: 'Check', command: 'haskell-ghc-mod:check-file' }, 35 | { label: 'Lint', command: 'haskell-ghc-mod:lint-file' }, 36 | { label: 'Stop Backend', command: 'haskell-ghc-mod:shutdown-backend' }, 37 | ], 38 | } 39 | 40 | type TECommandEvent = CommandEvent 41 | type TLastMessages = { 42 | check: UPI.IResultItem[] 43 | lint: UPI.IResultItem[] 44 | } 45 | 46 | export class UPIConsumer { 47 | public upi: UPI.IUPIInstance 48 | private disposables: CompositeDisposable = new CompositeDisposable() 49 | private processMessages: UPI.IResultItem[] = [] 50 | private lastMessages: TLastMessages = { check: [], lint: [] } 51 | private msgBackend = atom.config.get('haskell-ghc-mod.ghcModMessages') 52 | 53 | private contextCommands = { 54 | 'haskell-ghc-mod:show-type': this.tooltipCommand( 55 | this.typeTooltip.bind(this), 56 | ), 57 | 'haskell-ghc-mod:show-info': this.tooltipCommand( 58 | this.infoTooltip.bind(this), 59 | ), 60 | 'haskell-ghc-mod:case-split': this.caseSplitCommand.bind(this), 61 | 'haskell-ghc-mod:sig-fill': this.sigFillCommand.bind(this), 62 | 'haskell-ghc-mod:go-to-declaration': this.goToDeclCommand.bind(this), 63 | 'haskell-ghc-mod:show-info-fallback-to-type': this.tooltipCommand( 64 | this.infoTypeTooltip.bind(this), 65 | ), 66 | 'haskell-ghc-mod:show-type-fallback-to-info': this.tooltipCommand( 67 | this.typeInfoTooltip.bind(this), 68 | ), 69 | 'haskell-ghc-mod:show-type-and-info': this.tooltipCommand( 70 | this.typeAndInfoTooltip.bind(this), 71 | ), 72 | 'haskell-ghc-mod:insert-type': this.insertTypeCommand.bind(this), 73 | 'haskell-ghc-mod:insert-import': this.insertImportCommand.bind(this), 74 | } 75 | 76 | private globalCommands = { 77 | 'haskell-ghc-mod:check-file': this.checkCommand.bind(this), 78 | 'haskell-ghc-mod:lint-file': this.lintCommand.bind(this), 79 | ...this.contextCommands, 80 | } 81 | 82 | private contextMenu: { 83 | label: string 84 | submenu: Array<{ 85 | label: string 86 | command: keyof UPIConsumer['contextCommands'] 87 | }> 88 | } = { 89 | label: 'ghc-mod', 90 | submenu: [ 91 | { label: 'Show Type', command: 'haskell-ghc-mod:show-type' }, 92 | { label: 'Show Info', command: 'haskell-ghc-mod:show-info' }, 93 | { 94 | label: 'Show Type And Info', 95 | command: 'haskell-ghc-mod:show-type-and-info', 96 | }, 97 | { label: 'Case Split', command: 'haskell-ghc-mod:case-split' }, 98 | { label: 'Sig Fill', command: 'haskell-ghc-mod:sig-fill' }, 99 | { label: 'Insert Type', command: 'haskell-ghc-mod:insert-type' }, 100 | { label: 'Insert Import', command: 'haskell-ghc-mod:insert-import' }, 101 | { 102 | label: 'Go To Declaration', 103 | command: 'haskell-ghc-mod:go-to-declaration', 104 | }, 105 | ], 106 | } 107 | 108 | constructor(register: UPI.IUPIRegistration, private process: GhcModiProcess) { 109 | this.disposables.add( 110 | this.process.onError(this.handleProcessError.bind(this)), 111 | this.process.onWarning(this.handleProcessWarning.bind(this)), 112 | ) 113 | 114 | const msgTypes = 115 | this.msgBackend === 'upi' 116 | ? { ...messageTypes, ...addMsgTypes } 117 | : messageTypes 118 | 119 | this.upi = register({ 120 | name: 'haskell-ghc-mod', 121 | menu: mainMenu, 122 | messageTypes: msgTypes, 123 | tooltip: this.shouldShowTooltip.bind(this), 124 | events: { 125 | onDidSaveBuffer: async (buffer) => 126 | this.checkLint( 127 | buffer, 128 | 'Save', 129 | atom.config.get('haskell-ghc-mod.alwaysInteractiveCheck'), 130 | ), 131 | onDidStopChanging: async (buffer) => 132 | this.checkLint(buffer, 'Change', true), 133 | }, 134 | }) 135 | 136 | this.disposables.add( 137 | this.upi, 138 | this.process.onBackendActive(() => 139 | this.upi.setStatus({ status: 'progress', detail: '' }), 140 | ), 141 | this.process.onBackendIdle(() => 142 | this.upi.setStatus({ status: 'ready', detail: '' }), 143 | ), 144 | atom.commands.add(contextScope, this.globalCommands), 145 | ) 146 | const cm = {} 147 | cm[contextScope] = [this.contextMenu] 148 | this.disposables.add(atom.contextMenu.add(cm)) 149 | } 150 | 151 | public dispose() { 152 | this.disposables.dispose() 153 | } 154 | 155 | private async shouldShowTooltip( 156 | editor: TextEditor, 157 | crange: Range, 158 | type: UPI.TEventRangeType, 159 | ): Promise { 160 | const n = 161 | type === 'mouse' 162 | ? 'haskell-ghc-mod.onMouseHoverShow' 163 | : type === 'selection' 164 | ? 'haskell-ghc-mod.onSelectionShow' 165 | : undefined 166 | const t = n && atom.config.get(n) 167 | try { 168 | if (t) return await this[`${t}Tooltip`](editor, crange) 169 | else return undefined 170 | } catch (e) { 171 | Util.warn(e) 172 | return undefined 173 | } 174 | } 175 | 176 | @handleException 177 | private async checkCommand({ currentTarget }: TECommandEvent) { 178 | const editor = currentTarget.getModel() 179 | const res = await this.process.doCheckBuffer( 180 | editor.getBuffer(), 181 | atom.config.get('haskell-ghc-mod.alwaysInteractiveCheck'), 182 | ) 183 | this.setMessages('check', res) 184 | } 185 | 186 | @handleException 187 | private async lintCommand({ currentTarget }: TECommandEvent) { 188 | const editor = currentTarget.getModel() 189 | const res = await this.process.doLintBuffer(editor.getBuffer()) 190 | this.setMessages('lint', res) 191 | } 192 | 193 | private tooltipCommand( 194 | tooltipfun: (e: TextEditor, p: Range) => Promise, 195 | ) { 196 | return async ({ currentTarget, detail }: TECommandEvent) => 197 | this.upi.showTooltip({ 198 | editor: currentTarget.getModel(), 199 | detail, 200 | async tooltip(crange) { 201 | return tooltipfun(currentTarget.getModel(), crange) 202 | }, 203 | }) 204 | } 205 | 206 | @handleException 207 | private async insertTypeCommand({ currentTarget, detail }: TECommandEvent) { 208 | const editor = currentTarget.getModel() 209 | const er = this.upi.getEventRange(editor, detail) 210 | if (er === undefined) { 211 | return 212 | } 213 | const { crange, pos } = er 214 | const symInfo = Util.getSymbolAtPoint(editor, pos) 215 | if (!symInfo) { 216 | return 217 | } 218 | const { scope, range, symbol } = symInfo 219 | if (scope.startsWith('keyword.operator.')) { 220 | return 221 | } // can't correctly handle infix notation 222 | const { type } = await this.process.getTypeInBuffer( 223 | editor.getBuffer(), 224 | crange, 225 | ) 226 | if ( 227 | editor 228 | .getTextInBufferRange([ 229 | range.end, 230 | editor.getBuffer().rangeForRow(range.end.row, false).end, 231 | ]) 232 | .match(/=/) 233 | ) { 234 | let indent = editor.getTextInBufferRange([ 235 | [range.start.row, 0], 236 | range.start, 237 | ]) 238 | let birdTrack = '' 239 | if ( 240 | editor 241 | .scopeDescriptorForBufferPosition(pos) 242 | .getScopesArray() 243 | .includes('meta.embedded.haskell') 244 | ) { 245 | birdTrack = indent.slice(0, 2) 246 | indent = indent.slice(2) 247 | } 248 | if (indent.match(/\S/)) { 249 | indent = indent.replace(/\S/g, ' ') 250 | } 251 | editor.setTextInBufferRange( 252 | [range.start, range.start], 253 | `${symbol} :: ${type}\n${birdTrack}${indent}`, 254 | ) 255 | } else { 256 | editor.setTextInBufferRange( 257 | range, 258 | `(${editor.getTextInBufferRange(range)} :: ${type})`, 259 | ) 260 | } 261 | } 262 | 263 | @handleException 264 | private async caseSplitCommand({ currentTarget, detail }: TECommandEvent) { 265 | const editor = currentTarget.getModel() 266 | const evr = this.upi.getEventRange(editor, detail) 267 | if (!evr) { 268 | return 269 | } 270 | const { crange } = evr 271 | const res = await this.process.doCaseSplit(editor.getBuffer(), crange) 272 | for (const { range, replacement } of res) { 273 | editor.setTextInBufferRange(range, replacement) 274 | } 275 | } 276 | 277 | @handleException 278 | private async sigFillCommand({ currentTarget, detail }: TECommandEvent) { 279 | const editor = currentTarget.getModel() 280 | const evr = this.upi.getEventRange(editor, detail) 281 | if (!evr) { 282 | return 283 | } 284 | const { crange } = evr 285 | const res = await this.process.doSigFill(editor.getBuffer(), crange) 286 | 287 | editor.transact(() => { 288 | const { type, range, body } = res 289 | const sig = editor.getTextInBufferRange(range) 290 | let indent = editor.indentLevelForLine(sig) 291 | const pos = range.end 292 | const text = `\n${body}` 293 | if (type === 'instance') { 294 | indent += 1 295 | if (!sig.endsWith(' where')) { 296 | editor.setTextInBufferRange([range.end, range.end], ' where') 297 | } 298 | } 299 | const newrange = editor.setTextInBufferRange([pos, pos], text) 300 | newrange 301 | .getRows() 302 | .slice(1) 303 | .map((row) => editor.setIndentationForBufferRow(row, indent)) 304 | }) 305 | } 306 | 307 | @handleException 308 | private async goToDeclCommand({ currentTarget, detail }: TECommandEvent) { 309 | const editor = currentTarget.getModel() 310 | const evr = this.upi.getEventRange(editor, detail) 311 | if (!evr) { 312 | return 313 | } 314 | const { crange } = evr 315 | const { info } = await this.process.getInfoInBuffer(editor, crange) 316 | const res = /.*-- Defined at (.+):(\d+):(\d+)/.exec(info) 317 | if (!res) { 318 | return 319 | } 320 | const [fn, line, col] = res.slice(1) 321 | const rootDir = await this.process.getRootDir(editor.getBuffer()) 322 | if (!rootDir) { 323 | return 324 | } 325 | const uri = rootDir.getFile(fn).getPath() || fn 326 | await atom.workspace.open(uri, { 327 | initialLine: parseInt(line, 10) - 1, 328 | initialColumn: parseInt(col, 10) - 1, 329 | }) 330 | } 331 | 332 | @handleException 333 | private async insertImportCommand({ currentTarget, detail }: TECommandEvent) { 334 | const editor = currentTarget.getModel() 335 | const buffer = editor.getBuffer() 336 | const evr = this.upi.getEventRange(editor, detail) 337 | if (!evr) { 338 | return 339 | } 340 | const { crange } = evr 341 | const lines = await this.process.findSymbolProvidersInBuffer(editor, crange) 342 | const mod = await importListView(lines) 343 | if (mod) { 344 | const pi = await new Promise<{ pos: Point; indent: string; end: string }>( 345 | (resolve) => { 346 | buffer.backwardsScan(/^(\s*)(import|module)/, ({ match, range }) => { 347 | let indent = '' 348 | switch (match[2]) { 349 | case 'import': 350 | indent = `\n${match[1]}` 351 | break 352 | case 'module': 353 | indent = `\n\n${match[1]}` 354 | break 355 | } 356 | resolve({ 357 | pos: buffer.rangeForRow(range.start.row, false).end, 358 | indent, 359 | end: '', 360 | }) 361 | }) 362 | // nothing found 363 | resolve({ 364 | pos: buffer.getFirstPosition(), 365 | indent: '', 366 | end: '\n', 367 | }) 368 | }, 369 | ) 370 | editor.setTextInBufferRange( 371 | [pi.pos, pi.pos], 372 | `${pi.indent}import ${mod}${pi.end}`, 373 | ) 374 | } 375 | } 376 | 377 | private async typeTooltip(e: TextEditor, p: Range) { 378 | const { range, type } = await this.process.getTypeInBuffer(e.getBuffer(), p) 379 | return { 380 | range, 381 | text: { 382 | text: type, 383 | highlighter: atom.config.get('haskell-ghc-mod.highlightTooltips') 384 | ? 'hint.type.haskell' 385 | : undefined, 386 | }, 387 | } 388 | } 389 | 390 | private async infoTooltip(e: TextEditor, p: Range) { 391 | const { range, info } = await this.process.getInfoInBuffer(e, p) 392 | return { 393 | range, 394 | text: { 395 | text: info, 396 | highlighter: atom.config.get('haskell-ghc-mod.highlightTooltips') 397 | ? 'source.haskell' 398 | : undefined, 399 | }, 400 | } 401 | } 402 | 403 | private async infoTypeTooltip(e: TextEditor, p: Range) { 404 | try { 405 | return await this.infoTooltip(e, p) 406 | } catch { 407 | return this.typeTooltip(e, p) 408 | } 409 | } 410 | 411 | private async typeInfoTooltip(e: TextEditor, p: Range) { 412 | try { 413 | return await this.typeTooltip(e, p) 414 | } catch { 415 | return this.infoTooltip(e, p) 416 | } 417 | } 418 | 419 | private async typeAndInfoTooltip(e: TextEditor, p: Range) { 420 | const typeP = this.typeTooltip(e, p).catch(() => undefined) 421 | const infoP = this.infoTooltip(e, p).catch(() => undefined) 422 | const [type, info] = await Promise.all([typeP, infoP]) 423 | let range: Range 424 | let text: string 425 | if (type && info) { 426 | range = type.range.union(info.range) 427 | const sup = atom.config.get( 428 | 'haskell-ghc-mod.suppressRedundantTypeInTypeAndInfoTooltips', 429 | ) 430 | if (sup && info.text.text.includes(`:: ${type.text.text}`)) { 431 | text = info.text.text 432 | } else { 433 | text = `:: ${type.text.text}\n${info.text.text}` 434 | } 435 | } else if (type) { 436 | range = type.range 437 | text = `:: ${type.text.text}` 438 | } else if (info) { 439 | range = info.range 440 | text = info.text.text 441 | } else { 442 | throw new Error('Got neither type nor info') 443 | } 444 | const highlighter = atom.config.get('haskell-ghc-mod.highlightTooltips') 445 | ? 'source.haskell' 446 | : undefined 447 | return { range, text: { text, highlighter } } 448 | } 449 | 450 | private setHighlighter() { 451 | if (atom.config.get('haskell-ghc-mod.highlightMessages')) { 452 | return (m: UPI.IResultItem): UPI.IResultItem => { 453 | if (typeof m.message === 'string') { 454 | const message: UPI.IMessageText = { 455 | text: m.message, 456 | highlighter: 'hint.message.haskell', 457 | } 458 | return { ...m, message } 459 | } else { 460 | return m 461 | } 462 | } 463 | } else { 464 | return (m: UPI.IResultItem) => m 465 | } 466 | } 467 | 468 | private setMessages(type: keyof TLastMessages, messages: UPI.IResultItem[]) { 469 | this.lastMessages[type] = messages.map(this.setHighlighter()) 470 | this.sendMessages() 471 | } 472 | 473 | private sendMessages() { 474 | this.upi.setMessages( 475 | this.processMessages.concat( 476 | this.lastMessages.check, 477 | this.lastMessages.lint, 478 | ), 479 | ) 480 | } 481 | 482 | @handleException 483 | private async checkLint( 484 | buffer: TextBuffer, 485 | opt: 'Save' | 'Change', 486 | fast: boolean, 487 | ) { 488 | const check = atom.config.get(`haskell-ghc-mod.on${opt}Check` as 489 | | 'haskell-ghc-mod.onSaveCheck' 490 | | 'haskell-ghc-mod.onChangeCheck') 491 | const lint = atom.config.get(`haskell-ghc-mod.on${opt}Lint` as 492 | | 'haskell-ghc-mod.onSaveLint' 493 | | 'haskell-ghc-mod.onChangeLint') 494 | const promises = [] 495 | if (check) { 496 | promises.push( 497 | this.process.doCheckBuffer(buffer, fast).then((res) => { 498 | this.setMessages('check', res) 499 | }), 500 | ) 501 | } 502 | if (lint) { 503 | promises.push( 504 | this.process.doLintBuffer(buffer).then((res) => { 505 | this.setMessages('lint', res) 506 | }), 507 | ) 508 | } 509 | await Promise.all(promises) 510 | } 511 | 512 | private consoleReport(arg: IErrorCallbackArgs) { 513 | // tslint:disbale-next-line: no-console 514 | console.error(Util.formatError(arg), Util.getErrorDetail(arg)) 515 | } 516 | 517 | private handleProcessError(arg: IErrorCallbackArgs) { 518 | switch (this.msgBackend) { 519 | case 'upi': 520 | this.processMessages.push({ 521 | message: 522 | Util.formatError(arg) + 523 | '\n\nSee console (View → Developer → Toggle Developer Tools → Console tab) for details.', 524 | severity: 'ghc-mod', 525 | }) 526 | this.consoleReport(arg) 527 | this.sendMessages() 528 | break 529 | case 'console': 530 | this.consoleReport(arg) 531 | break 532 | case 'popup': 533 | this.consoleReport(arg) 534 | atom.notifications.addError(Util.formatError(arg), { 535 | detail: Util.getErrorDetail(arg), 536 | dismissable: true, 537 | }) 538 | break 539 | } 540 | } 541 | 542 | private handleProcessWarning(warning: string) { 543 | switch (this.msgBackend) { 544 | case 'upi': 545 | this.processMessages.push({ 546 | message: warning, 547 | severity: 'ghc-mod', 548 | }) 549 | Util.warn(warning) 550 | this.sendMessages() 551 | break 552 | case 'console': 553 | Util.warn(warning) 554 | break 555 | case 'popup': 556 | Util.warn(warning) 557 | atom.notifications.addWarning(warning, { 558 | dismissable: false, 559 | }) 560 | break 561 | } 562 | } 563 | } 564 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { Range, Point, TextBuffer, TextEditor } from 'atom' 2 | import { delimiter, sep, extname } from 'path' 3 | import * as Temp from 'temp' 4 | import * as FS from 'fs' 5 | import * as CP from 'child_process' 6 | import { EOL } from 'os' 7 | import { getRootDirFallback, getRootDir, isDirectory } from 'atom-haskell-utils' 8 | import { RunOptions, IErrorCallbackArgs } from './ghc-mod/ghc-modi-process-real' 9 | import { GHCModVers } from './ghc-mod/ghc-modi-process-real-factory' 10 | import { GHCModCaps } from './ghc-mod/interactive-process' 11 | import * as UPI from 'atom-haskell-upi' 12 | 13 | type ExecOpts = CP.ExecFileOptionsWithStringEncoding 14 | export { getRootDirFallback, getRootDir, isDirectory, ExecOpts } 15 | 16 | let debuglog: Array<{ timestamp: number; messages: string[] }> = [] 17 | const logKeep = 30000 // ms 18 | 19 | function savelog(...messages: string[]) { 20 | const ts = Date.now() 21 | debuglog.push({ 22 | timestamp: ts, 23 | messages, 24 | }) 25 | let ks = 0 26 | for (const v of debuglog) { 27 | if (ts - v.timestamp >= logKeep) { 28 | break 29 | } 30 | ks++ 31 | } 32 | debuglog.splice(0, ks) 33 | } 34 | 35 | function joinPath(ds: string[]) { 36 | const set = new Set(ds) 37 | return Array.from(set).join(delimiter) 38 | } 39 | 40 | export const EOT = `${EOL}\x04${EOL}` 41 | 42 | export function debug(...messages: any[]) { 43 | if (atom.config.get('haskell-ghc-mod.debug')) { 44 | // tslint:disable-next-line: no-console 45 | console.log('haskell-ghc-mod debug:', ...messages) 46 | } 47 | savelog(...messages.map((v) => JSON.stringify(v))) 48 | } 49 | 50 | export function warn(...messages: any[]) { 51 | // tslint:disable-next-line: no-console 52 | console.warn('haskell-ghc-mod warning:', ...messages) 53 | savelog(...messages.map((v) => JSON.stringify(v))) 54 | } 55 | 56 | export function error(...messages: any[]) { 57 | // tslint:disable-next-line: no-console 58 | console.error('haskell-ghc-mod error:', ...messages) 59 | savelog(...messages.map((v) => JSON.stringify(v))) 60 | } 61 | 62 | export function getDebugLog() { 63 | const ts = Date.now() 64 | debuglog = debuglog.filter(({ timestamp }) => ts - timestamp < logKeep) 65 | return debuglog 66 | .map( 67 | ({ timestamp, messages }) => 68 | `${(timestamp - ts) / 1000}s: ${messages.join(',')}`, 69 | ) 70 | .join(EOL) 71 | } 72 | 73 | export async function execPromise( 74 | cmd: string, 75 | args: string[], 76 | opts: ExecOpts, 77 | stdin?: string, 78 | ) { 79 | return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { 80 | debug(`Running ${cmd} ${args} with opts = `, opts) 81 | const child = CP.execFile( 82 | cmd, 83 | args, 84 | opts, 85 | (error, stdout: string, stderr: string) => { 86 | if (stderr.trim().length > 0) { 87 | warn(stderr) 88 | } 89 | if (error) { 90 | warn(`Running ${cmd} ${args} failed with `, error) 91 | if (stdout) { 92 | warn(stdout) 93 | } 94 | error.stack = new Error().stack 95 | reject(error) 96 | } else { 97 | debug(`Got response from ${cmd} ${args}`, { stdout, stderr }) 98 | resolve({ stdout, stderr }) 99 | } 100 | }, 101 | ) 102 | if (stdin) { 103 | debug(`sending stdin text to ${cmd} ${args}`) 104 | child.stdin.write(stdin) 105 | } 106 | }) 107 | } 108 | 109 | export async function getCabalSandbox( 110 | rootPath: string, 111 | ): Promise { 112 | debug('Looking for cabal sandbox...') 113 | const sbc = await parseSandboxConfig(`${rootPath}${sep}cabal.sandbox.config`) 114 | // tslint:disable: no-string-literal 115 | if (sbc && sbc['install-dirs'] && sbc['install-dirs']['bindir']) { 116 | const sandbox: string = sbc['install-dirs']['bindir'] 117 | debug('Found cabal sandbox: ', sandbox) 118 | if (isDirectory(sandbox)) { 119 | return sandbox 120 | } else { 121 | warn('Cabal sandbox ', sandbox, ' is not a directory') 122 | return undefined 123 | } 124 | } else { 125 | warn('No cabal sandbox found') 126 | return undefined 127 | } 128 | // tslint:enable: no-string-literal 129 | } 130 | 131 | export async function getStackSandbox( 132 | rootPath: string, 133 | apd: string[], 134 | env: { [key: string]: string | undefined }, 135 | ) { 136 | debug('Looking for stack sandbox...') 137 | env.PATH = joinPath(apd) 138 | debug('Running stack with PATH ', env.PATH) 139 | try { 140 | const out = await execPromise( 141 | 'stack', 142 | [ 143 | '--no-install-ghc', 144 | 'path', 145 | '--snapshot-install-root', 146 | '--local-install-root', 147 | '--bin-path', 148 | ], 149 | { 150 | encoding: 'utf8', 151 | cwd: rootPath, 152 | env, 153 | timeout: atom.config.get('haskell-ghc-mod.initTimeout') * 1000, 154 | }, 155 | ) 156 | 157 | const lines = out.stdout.split(EOL) 158 | const sir = 159 | lines 160 | .filter((l) => l.startsWith('snapshot-install-root: '))[0] 161 | .slice(23) + `${sep}bin` 162 | const lir = 163 | lines.filter((l) => l.startsWith('local-install-root: '))[0].slice(20) + 164 | `${sep}bin` 165 | const bp = lines 166 | .filter((l) => l.startsWith('bin-path: '))[0] 167 | .slice(10) 168 | .split(delimiter) 169 | .filter((p) => !(p === sir || p === lir || apd.includes(p))) 170 | debug('Found stack sandbox ', lir, sir, ...bp) 171 | return [lir, sir, ...bp] 172 | } catch (err) { 173 | warn('No stack sandbox found because ', err) 174 | return undefined 175 | } 176 | } 177 | 178 | const processOptionsCache = new Map() 179 | 180 | export async function getProcessOptions( 181 | rootPath?: string, 182 | ): Promise { 183 | if (!rootPath) { 184 | // tslint:disable-next-line: no-null-keyword 185 | rootPath = getRootDirFallback(null).getPath() 186 | } 187 | // cache 188 | const cached = processOptionsCache.get(rootPath) 189 | if (cached) { 190 | return cached 191 | } 192 | 193 | debug(`getProcessOptions(${rootPath})`) 194 | const env = { ...process.env } 195 | 196 | // tslint:disable-next-line: totality-check 197 | if (process.platform === 'win32') { 198 | const PATH = [] 199 | const capMask = (str: string, mask: number) => { 200 | const a = str.split('') 201 | for (let i = 0; i < a.length; i++) { 202 | if (mask & Math.pow(2, i)) { 203 | a[i] = a[i].toUpperCase() 204 | } 205 | } 206 | return a.join('') 207 | } 208 | for (let m = 0b1111; m >= 0; m--) { 209 | const vn = capMask('path', m) 210 | if (env[vn]) { 211 | PATH.push(env[vn]) 212 | } 213 | } 214 | env.PATH = PATH.join(delimiter) 215 | } 216 | 217 | const PATH = env.PATH || '' 218 | 219 | const apd = atom.config 220 | .get('haskell-ghc-mod.additionalPathDirectories') 221 | .concat(PATH.split(delimiter)) 222 | const cabalSandbox = atom.config.get('haskell-ghc-mod.cabalSandbox') 223 | ? getCabalSandbox(rootPath) 224 | : Promise.resolve(undefined) 225 | const stackSandbox = atom.config.get('haskell-ghc-mod.stackSandbox') 226 | ? getStackSandbox(rootPath, apd, { ...env }) 227 | : Promise.resolve(undefined) 228 | const [cabalSandboxDir, stackSandboxDirs] = await Promise.all([ 229 | cabalSandbox, 230 | stackSandbox, 231 | ]) 232 | const newp = [] 233 | if (cabalSandboxDir) { 234 | newp.push(cabalSandboxDir) 235 | } 236 | if (stackSandboxDirs) { 237 | newp.push(...stackSandboxDirs) 238 | } 239 | newp.push(...apd) 240 | env.PATH = joinPath(newp) 241 | debug(`PATH = ${env.PATH}`) 242 | const res: RunOptions = { 243 | cwd: rootPath, 244 | env, 245 | encoding: 'utf8', 246 | maxBuffer: Infinity, 247 | } 248 | processOptionsCache.set(rootPath, res) 249 | return res 250 | } 251 | 252 | export function getSymbolAtPoint(editor: TextEditor, point: Point) { 253 | const [scope] = editor 254 | .scopeDescriptorForBufferPosition(point) 255 | .getScopesArray() 256 | .slice(-1) 257 | if (scope) { 258 | const range = editor.bufferRangeForScopeAtPosition(scope, point) 259 | if (range && !range.isEmpty()) { 260 | const symbol = editor.getTextInBufferRange(range) 261 | return { scope, range, symbol } 262 | } 263 | } 264 | return undefined 265 | } 266 | 267 | export function getSymbolInRange(editor: TextEditor, crange: Range) { 268 | const buffer = editor.getBuffer() 269 | if (crange.isEmpty()) { 270 | return getSymbolAtPoint(editor, crange.start) 271 | } else { 272 | return { 273 | symbol: buffer.getTextInRange(crange), 274 | range: crange, 275 | } 276 | } 277 | } 278 | 279 | export async function withTempFile( 280 | contents: string, 281 | uri: string, 282 | gen: (path: string) => Promise, 283 | ): Promise { 284 | const info = await new Promise((resolve, reject) => 285 | Temp.open( 286 | { prefix: 'haskell-ghc-mod', suffix: extname(uri || '.hs') }, 287 | (err, info2) => { 288 | if (err) { 289 | reject(err) 290 | } else { 291 | resolve(info2) 292 | } 293 | }, 294 | ), 295 | ) 296 | return new Promise((resolve, reject) => 297 | FS.write(info.fd, contents, async (err) => { 298 | if (err) { 299 | reject(err) 300 | } else { 301 | resolve(await gen(info.path)) 302 | FS.close(info.fd, () => 303 | FS.unlink(info.path, () => { 304 | /*noop*/ 305 | }), 306 | ) 307 | } 308 | }), 309 | ) 310 | } 311 | 312 | export type KnownErrorName = 313 | | 'GHCModStdoutError' 314 | | 'InteractiveActionTimeout' 315 | | 'GHCModInteractiveCrash' 316 | 317 | export function mkError(name: KnownErrorName, message: string) { 318 | const err = new Error(message) 319 | err.name = name 320 | return err 321 | } 322 | 323 | export interface SandboxConfigTree { 324 | [k: string]: SandboxConfigTree | string 325 | } 326 | 327 | export async function parseSandboxConfig(file: string) { 328 | try { 329 | const sbc = await new Promise((resolve, reject) => 330 | FS.readFile(file, { encoding: 'utf-8' }, (err, sbc2) => { 331 | if (err) { 332 | reject(err) 333 | } else { 334 | resolve(sbc2) 335 | } 336 | }), 337 | ) 338 | const vars: SandboxConfigTree = {} 339 | let scope = vars 340 | const rv = (v: string) => { 341 | for (const k1 of Object.keys(scope)) { 342 | const v1 = scope[k1] 343 | if (typeof v1 === 'string') { 344 | v = v.split(`$${k1}`).join(v1) 345 | } 346 | } 347 | return v 348 | } 349 | for (const line of sbc.split(/\r?\n|\r/)) { 350 | if (!line.match(/^\s*--/) && !line.match(/^\s*$/)) { 351 | const [l] = line.split(/--/) 352 | const m = l.match(/^\s*([\w-]+):\s*(.*)\s*$/) 353 | if (m) { 354 | const [, name, val] = m 355 | scope[name] = rv(val) 356 | } else { 357 | const newscope = {} 358 | scope[line] = newscope 359 | scope = newscope 360 | } 361 | } 362 | } 363 | return vars 364 | } catch (err) { 365 | warn('Reading cabal sandbox config failed with ', err) 366 | return undefined 367 | } 368 | } 369 | 370 | // A dirty hack to work with tabs 371 | export function tabShiftForPoint(buffer: TextBuffer, point: Point) { 372 | const line = buffer.lineForRow(point.row) 373 | const match = line ? line.slice(0, point.column).match(/\t/g) || [] : [] 374 | const columnShift = 7 * match.length 375 | return new Point(point.row, point.column + columnShift) 376 | } 377 | 378 | export function tabShiftForRange(buffer: TextBuffer, range: Range) { 379 | const start = tabShiftForPoint(buffer, range.start) 380 | const end = tabShiftForPoint(buffer, range.end) 381 | return new Range(start, end) 382 | } 383 | 384 | export function tabUnshiftForPoint(buffer: TextBuffer, point: Point) { 385 | const line = buffer.lineForRow(point.row) 386 | let columnl = 0 387 | let columnr = point.column 388 | while (columnl < columnr) { 389 | // tslint:disable-next-line: strict-type-predicates 390 | if (line === undefined || line[columnl] === undefined) { 391 | break 392 | } 393 | if (line[columnl] === '\t') { 394 | columnr -= 7 395 | } 396 | columnl += 1 397 | } 398 | return new Point(point.row, columnr) 399 | } 400 | 401 | export function tabUnshiftForRange(buffer: TextBuffer, range: Range) { 402 | const start = tabUnshiftForPoint(buffer, range.start) 403 | const end = tabUnshiftForPoint(buffer, range.end) 404 | return new Range(start, end) 405 | } 406 | 407 | export function isUpperCase(ch: string): boolean { 408 | return ch.toUpperCase() === ch 409 | } 410 | 411 | export function getErrorDetail({ err, runArgs, caps }: IErrorCallbackArgs) { 412 | return `caps: 413 | ${JSON.stringify(caps, undefined, 2)} 414 | Args: 415 | ${JSON.stringify(runArgs, undefined, 2)} 416 | message: 417 | ${err.message} 418 | log: 419 | ${getDebugLog()}` 420 | } 421 | 422 | export function formatError({ err, runArgs }: IErrorCallbackArgs) { 423 | if (err.name === 'InteractiveActionTimeout' && runArgs) { 424 | return `\ 425 | Haskell-ghc-mod: ghc-mod \ 426 | ${runArgs.interactive ? 'interactive ' : ''}command ${runArgs.command} \ 427 | timed out. You can try to fix it by raising 'Interactive Action \ 428 | Timeout' setting in haskell-ghc-mod settings.` 429 | } else if (runArgs) { 430 | return `\ 431 | Haskell-ghc-mod: ghc-mod \ 432 | ${runArgs.interactive ? 'interactive ' : ''}command ${runArgs.command} \ 433 | failed with error ${err.name}` 434 | } else { 435 | return `There was an unexpected error ${err.name}` 436 | } 437 | } 438 | 439 | export function defaultErrorHandler(args: IErrorCallbackArgs) { 440 | const { err, runArgs, caps } = args 441 | const suppressErrors = runArgs && runArgs.suppressErrors 442 | 443 | if (!suppressErrors) { 444 | atom.notifications.addError(formatError(args), { 445 | detail: getErrorDetail(args), 446 | stack: err.stack, 447 | dismissable: true, 448 | }) 449 | } else { 450 | error(caps, runArgs, err) 451 | } 452 | } 453 | 454 | export function warnGHCPackagePath() { 455 | atom.notifications.addWarning( 456 | 'haskell-ghc-mod: You have GHC_PACKAGE_PATH environment variable set!', 457 | { 458 | dismissable: true, 459 | detail: `\ 460 | This configuration is not supported, and can break arbitrarily. You can try to band-aid it by adding 461 | 462 | delete process.env.GHC_PACKAGE_PATH 463 | 464 | to your Atom init script (Edit → Init Script...) 465 | 466 | You can suppress this warning in haskell-ghc-mod settings.`, 467 | }, 468 | ) 469 | } 470 | 471 | function filterEnv(env: { [name: string]: string | undefined }) { 472 | const fenv = {} 473 | // tslint:disable-next-line: forin 474 | for (const evar in env) { 475 | const evarU = evar.toUpperCase() 476 | if ( 477 | evarU === 'PATH' || 478 | evarU.startsWith('GHC_') || 479 | evarU.startsWith('STACK_') || 480 | evarU.startsWith('CABAL_') 481 | ) { 482 | fenv[evar] = env[evar] 483 | } 484 | } 485 | return fenv 486 | } 487 | 488 | export interface SpawnFailArgs { 489 | dir: string 490 | err: Error & { code?: any } 491 | opts?: RunOptions 492 | vers?: GHCModVers 493 | caps?: GHCModCaps 494 | } 495 | 496 | export function notifySpawnFail(args: Readonly) { 497 | if (spawnFailENOENT(args) || spawnFailEACCESS(args)) return 498 | const debugInfo: SpawnFailArgs = Object.assign({}, args) 499 | if (args.opts) { 500 | const optsclone: RunOptions = Object.assign({}, args.opts) 501 | optsclone.env = filterEnv(optsclone.env) 502 | debugInfo.opts = optsclone 503 | } 504 | atom.notifications.addFatalError( 505 | `Haskell-ghc-mod: ghc-mod failed to launch. 506 | It is probably missing or misconfigured. ${args.err.code}`, 507 | { 508 | detail: `\ 509 | Error was: ${debugInfo.err.name} 510 | ${debugInfo.err.message} 511 | Debug information: 512 | ${JSON.stringify(debugInfo, undefined, 2)} 513 | Config: 514 | ${JSON.stringify(atom.config.get('haskell-ghc-mod'), undefined, 2)} 515 | Environment (filtered): 516 | ${JSON.stringify(filterEnv(process.env), undefined, 2)} 517 | `, 518 | stack: debugInfo.err.stack, 519 | dismissable: true, 520 | }, 521 | ) 522 | } 523 | 524 | function spawnFailENOENT(args: Readonly): boolean { 525 | if (args.err.code === 'ENOENT') { 526 | const exePath = atom.config.get('haskell-ghc-mod.ghcModPath') 527 | const not = atom.notifications.addError( 528 | `Atom couldn't find ghc-mod executable`, 529 | { 530 | detail: `Atom tried to find ${exePath} in "${args.opts && 531 | args.opts.env.PATH}" but failed.`, 532 | dismissable: true, 533 | buttons: [ 534 | { 535 | className: 'icon-globe', 536 | text: 'Open installation guide', 537 | async onDidClick() { 538 | const opener = await import('opener') 539 | not.dismiss() 540 | opener( 541 | 'https://atom-haskell.github.io/installation/installing-binary-dependencies/', 542 | ) 543 | }, 544 | }, 545 | ], 546 | }, 547 | ) 548 | return true 549 | } 550 | return false 551 | } 552 | 553 | function spawnFailEACCESS(args: Readonly): boolean { 554 | if (args.err.code === 'EACCES') { 555 | const exePath = atom.config.get('haskell-ghc-mod.ghcModPath') 556 | const isDir = FS.existsSync(exePath) && FS.statSync(exePath).isDirectory() 557 | if (isDir) { 558 | atom.notifications.addError(`Atom couldn't run ghc-mod executable`, { 559 | detail: `Atom tried to run ${exePath} but it was a directory. Check haskell-ghc-mod package settings.`, 560 | dismissable: true, 561 | }) 562 | } else { 563 | atom.notifications.addError(`Atom couldn't run ghc-mod executable`, { 564 | detail: `Atom tried to run ${exePath} but it wasn't executable. Check access rights.`, 565 | dismissable: true, 566 | }) 567 | } 568 | return true 569 | } 570 | return false 571 | } 572 | 573 | export function handleException( 574 | _target: { upi: UPI.IUPIInstance | Promise }, 575 | _key: string, 576 | desc: TypedPropertyDescriptor<(...args: any[]) => Promise>, 577 | ): TypedPropertyDescriptor<(...args: any[]) => Promise> { 578 | return { 579 | ...desc, 580 | async value(...args: any[]) { 581 | try { 582 | // tslint:disable-next-line: no-non-null-assertion 583 | return await desc.value!.call(this, ...args) 584 | } catch (e) { 585 | debug(e) 586 | const upi: UPI.IUPIInstance = await (this as any).upi 587 | upi.setStatus({ 588 | status: 'warning', 589 | detail: e.toString(), 590 | }) 591 | // TODO: returning a promise that never resolves... ugly, but works? 592 | return new Promise(() => { 593 | /* noop */ 594 | }) 595 | } 596 | }, 597 | } 598 | } 599 | 600 | export function versAtLeast( 601 | vers: { [key: number]: number | undefined }, 602 | b: number[], 603 | ) { 604 | for (let i = 0; i < b.length; i++) { 605 | const v = b[i] 606 | const t = vers[i] 607 | const vv = t !== undefined ? t : 0 608 | if (vv > v) { 609 | return true 610 | } else if (vv < v) { 611 | return false 612 | } 613 | } 614 | return true 615 | } 616 | -------------------------------------------------------------------------------- /src/views/import-list-view.ts: -------------------------------------------------------------------------------- 1 | import SelectListView = require('atom-select-list') 2 | import { Panel } from 'atom' 3 | 4 | export async function importListView( 5 | imports: string[], 6 | ): Promise { 7 | let panel: Panel> | undefined 8 | let res: string | undefined 9 | try { 10 | res = await new Promise((resolve) => { 11 | const select = new SelectListView({ 12 | items: imports, 13 | // infoMessage: heading, 14 | itemsClassList: ['ide-haskell'], 15 | elementForItem: (item: string) => { 16 | const li = document.createElement('li') 17 | li.innerText = `${item}` 18 | return li 19 | }, 20 | didCancelSelection: () => { 21 | resolve() 22 | }, 23 | didConfirmSelection: (item: string) => { 24 | resolve(item) 25 | }, 26 | }) 27 | select.element.classList.add('ide-haskell') 28 | panel = atom.workspace.addModalPanel({ 29 | item: select, 30 | visible: true, 31 | }) 32 | select.focus() 33 | }) 34 | } finally { 35 | panel && panel.destroy() 36 | } 37 | return res 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "isolatedModules": false, 7 | "jsx": "react", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "declaration": false, 11 | "noImplicitAny": true, 12 | "noImplicitUseStrict": false, 13 | "removeComments": true, 14 | "noLib": false, 15 | "jsxFactory": "etch.dom", 16 | "preserveConstEnums": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "outDir": "lib", 19 | "inlineSources": true, 20 | "inlineSourceMap": true, 21 | "strictNullChecks": true, 22 | "allowJs": false, 23 | "lib":["dom", "es2017", "esnext.asyncIterable"], 24 | "strict": true, 25 | "importHelpers": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "noImplicitReturns": true, 28 | "noUnusedLocals": true, 29 | "noUnusedParameters": true, 30 | "typeRoots" : [ 31 | "./typings", 32 | "./node_modules/@types" 33 | ] 34 | }, 35 | "exclude": [ 36 | "node_modules" 37 | ], 38 | "include": [ 39 | "src/**/*.ts", 40 | "src/**/*.tsx", 41 | "src/**/*.js" 42 | ], 43 | "compileOnSave": true, 44 | "experimentalDecorators": true 45 | } 46 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "atom-haskell-tslint-rules" } 2 | --------------------------------------------------------------------------------