;;; swift-mode.el --- Major-mode for Apple's Swift programming language -*- lexical-binding: t -*-

;; Copyright (C) 2014-2021 taku0, Chris Barrett, Bozhidar Batsov,
;;                         Arthur Evstifeev

;; Author: taku0 <mxxouy6x3m_github@tatapa.org>
;;       Chris Barrett <chris.d.barrett@me.com>
;;       Bozhidar Batsov <bozhidar@batsov.com>
;;       Arthur Evstifeev <lod@pisem.net>
;; Maintainer: taku0 <mxxouy6x3m_github@tatapa.org>
;;
;; Version: 10.0.0
;; Package-Requires: ((emacs "25.2"))
;; Keywords: languages swift
;; URL: https://github.com/swift-emacs/swift-mode

;; This file is not part of GNU Emacs.

;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; Major-mode for Apple's Swift programming language.

;;; Code:

(require 'swift-mode-lexer)
(require 'swift-mode-indent)
(require 'swift-mode-fill)
(require 'swift-mode-font-lock)
(require 'swift-mode-beginning-of-defun)
(require 'swift-mode-repl)
(require 'swift-mode-imenu)

;;;###autoload
(defgroup swift nil
  "Major-mode for Apple's Swift programming language."
  :group 'languages
  :prefix "swift-mode:")

;; WORKAROUND: `update-directory-autoloads' does not handle `:group'.
;; Fixed in 29.1 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=58015.
;; commit: 212e94c3f445ebe1388f6fab134133ebad9316d0
;;;###autoload (custom-add-load 'languages 'swift-mode)

;; WORKAROUND: `cus-load' overrides `custom-loads'
;; Fixed in 29.1 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=58015.
;; commit: 75b3f4d0ac00bf47459629615ab2246c8a34b4c6
;;;###autoload (with-eval-after-load 'cus-load
;;;###autoload   (custom-add-load 'languages 'swift-mode))

;;; Keymap

(defvar swift-mode-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map prog-mode-map)
    (define-key map [remap indent-new-comment-line]
                #'swift-mode:indent-new-comment-line)
    (define-key map (kbd "C-c C-z") #'swift-mode:run-repl)
    (define-key map (kbd "C-c C-f") #'swift-mode:send-buffer)
    (define-key map (kbd "C-c C-r") #'swift-mode:send-region)
    (define-key map [remap beginning-of-defun] #'swift-mode:beginning-of-defun)
    (define-key map [remap end-of-defun] #'swift-mode:end-of-defun)
    (define-key map [remap mark-defun] #'swift-mode:mark-defun)
    (define-key map [remap narrow-to-defun] #'swift-mode:narrow-to-defun)
    (define-key map [remap backward-sentence] #'swift-mode:backward-sentence)
    (define-key map [remap forward-sentence] #'swift-mode:forward-sentence)
    (define-key map [remap kill-sentence] #'swift-mode:kill-sentence)
    (define-key map [remap backward-kill-sentence]
                #'swift-mode:backward-kill-sentence)
    ;; (define-key map (kbd "???") #'swift-mode:mark-sentence)
    (define-key map [remap narrow-to-sentence] #'swift-mode:narrow-to-sentence)

    (easy-menu-define swift-menu map "Swift Mode menu"
      `("Swift"
        :help "Swift-specific Features"
        ["Run REPL" swift-mode:run-repl
         :help "Run Swift REPL"]
        ["Send buffer to REPL" swift-mode:send-buffer
         :help "Send the current buffer's contents to the REPL"]
        ["Send region to REPL" swift-mode:send-region
         :help "Send currently selected region to the REPL"]
        ["Build Swift module" swift-mode:build-swift-module
         :help "Build current Swift module"]
        ["Build iOS app" swift-mode:build-ios-app
         :help "Build current iOS app"]
        ["Debug Swift module" swift-mode:debug-swift-module
         :help "Debug current Swift module"]
        ["Debug iOS app" swift-mode:debug-ios-app
         :help "Debug current iOS app with simulator"]))
    map)
  "Swift mode key map.")

;;; `forward-sexp-function'

(defun swift-mode:forward-sexp (&optional arg)
  "Move forward/backward a token or list.

See `forward-sexp for ARG."
  (setq arg (or arg 1))
  (when (swift-mode:chunk-after)
    (goto-char (swift-mode:chunk:start (swift-mode:chunk-after))))
  (if (< 0 arg)
      (while (< 0 arg)
        (while (eq (swift-mode:token:type (swift-mode:forward-sexp-1))
                   'implicit-\;))
        (setq arg (1- arg))))
  (while (< arg 0)
    (while (eq (swift-mode:token:type (swift-mode:backward-sexp-1))
               'implicit-\;))
    (setq arg (1+ arg))))

(defun swift-mode:forward-sexp-1 ()
  "Move forward a token or list.

Signal `scan-error' if it hits closing parentheses."
  (let ((token (swift-mode:forward-token-or-list))
        (pos (point)))
    (when (memq (swift-mode:token:type token) '(\] \) }))
      (goto-char pos)
      (signal 'scan-error
              (list "Unbalanced parentheses"
                    (swift-mode:token:start token)
                    (swift-mode:token:end token))))
    token))

(defun swift-mode:backward-sexp-1 ()
  "Move backward a token or list.

Signal `scan-error' if it hits opening parentheses."
  (let ((token (swift-mode:backward-token-or-list))
        (pos (point)))
    (when (memq (swift-mode:token:type token) '(\[ \( {))
      (goto-char pos)
      (signal 'scan-error
              (list "Unbalanced parentheses"
                    (swift-mode:token:start token)
                    (swift-mode:token:end token))))
    token))

(declare-function speedbar-add-supported-extension "speedbar" (extension))

;;;###autoload
(defsubst swift-mode:add-supported-extension-for-speedbar ()
  "Register .swift to speedbar."
  ;; FIXME: Use `with-eval-after-load' when `package-lint' allows it.
  ;; See also https://github.com/swift-emacs/swift-mode/pull/179
  (if (fboundp 'speedbar-add-supported-extension)
      (speedbar-add-supported-extension ".swift")
    (add-hook 'speedbar-load-hook
              (lambda ()
                (speedbar-add-supported-extension ".swift")))))

;;;###autoload
(define-derived-mode swift-mode prog-mode "Swift"
  "Major mode for editing Swift code.

\\{swift-mode-map}"
  :syntax-table swift-mode:syntax-table
  :group 'swift

  (setq font-lock-defaults '(swift-mode:font-lock-keywords))
  (if (boundp 'font-lock-fontify-syntactically-function)
      (setq-local font-lock-fontify-syntactically-function
                  #'swift-mode:fontify-syntactically)
    (advice-add 'font-lock-fontify-syntactically-region
                :around
                #'swift-mode:fontify-syntactically-advice))

  (setq-local comment-start "// ")
  (setq-local comment-end "")
  ;; ":" is for Playground Rich Comments Markup Syntax:
  ;; https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/PlaygroundRichComments.html
  (setq-local comment-start-skip
              (concat
               "\\s *"
               "\\(?:"
               ;; Single-line comment
               "//+" ":?" "\\|"
               ;; Multi-line comment
               "/\\*+" ":?" "\\|"
               ;; Middle of multi-line-comment
               "\\*+ "
               "\\)"
               "\\s *"))
  (setq-local adaptive-fill-regexp
              (concat
               "\\s *"
               "\\(?:"
               ;; Single-line comment
               "//+" ":?" "\\|"
               ;; Middle of multi-line-comment
               "\\*+ "
               "\\)"
               "\\s *"))
  (setq-local fill-indent-according-to-mode t)
  (setq-local comment-multi-line t)
  (setq-local comment-line-break-function #'swift-mode:indent-new-comment-line)
  (setq-local fill-paragraph-function #'swift-mode:fill-paragraph)
  (setq-local fill-forward-paragraph-function
              #'swift-mode:fill-forward-paragraph)
  (setq-local normal-auto-fill-function #'swift-mode:do-auto-fill)
  (swift-mode:install-fill-region-as-paragraph-advice)
  (swift-mode:install-current-fill-column-advice)

  (setq-local parse-sexp-lookup-properties t)
  (add-hook 'syntax-propertize-extend-region-functions
            #'swift-mode:syntax-propertize-extend-region
            nil t)
  (setq-local syntax-propertize-function #'swift-mode:syntax-propertize)

  (setq-local indent-tabs-mode nil)
  (setq-local indent-line-function #'swift-mode:indent-line)

  (setq-local forward-sexp-function #'swift-mode:forward-sexp)

  (setq-local electric-indent-chars
              (append "{}()[]:;,." electric-indent-chars))

  (add-hook 'post-self-insert-hook #'swift-mode:post-self-insert nil t)

  (setq-local imenu-create-index-function #'swift-mode:imenu-create-index)

  (setq-local beginning-of-defun-function #'swift-mode:beginning-of-defun)
  (setq-local end-of-defun-function #'swift-mode:end-of-defun)

  (setq-local swift-mode:anchor-overlay
              (make-overlay (point-min) (point-min) nil t))

  (delete-overlay swift-mode:anchor-overlay)

  (add-hook 'which-func-functions
            (lambda ()
              (when (equal (with-current-buffer (current-buffer) major-mode)
                           'swift-mode)
                (swift-mode:current-defun-name))))
  (setq-local add-log-current-defun-function #'swift-mode:current-defun-name)

  (swift-mode:setup-swift-testing))

;;;###autoload (add-to-list 'auto-mode-alist
;;;###autoload              '("\\.swift\\(interface\\)?\\'" . swift-mode))

;;;###autoload (swift-mode:add-supported-extension-for-speedbar)

(provide 'swift-mode)

;;; swift-mode.el ends here
