├── README.md └── emaXcode.el /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Emacs for Objective-C, Xcode 3 | 4 | auto-complete + yasnippet 5 | 6 | ![auto-complete + yasnippet](https://raw.githubusercontent.com/ShingoFukuyama/images/master/emaXcode/emaXcode1.gif) 7 | 8 | helm + yasnippet 9 | 10 | ![helm + yasnippet](https://raw.githubusercontent.com/ShingoFukuyama/images/master/emaXcode/emaXcode2.gif) 11 | 12 | ## Environment 13 | 14 | I don't care other than this environment. Additionary, I change this package on a whim. 15 | 16 | * Mac OSX 10.8.5 (MBPR2012) 17 | * Xcode 5.1.1 18 | * Emacs Cocoa 24.3.1 (from Homebrew) 19 | 20 | ## Requirement 21 | 22 | As of May 26 2014, these are latest package 23 | 24 | * auto-complete.el 1.4.0 25 | * yasnippet.el 0.8.0 26 | * helm.el 1.5.6 27 | * s.el 1.9.0 28 | 29 | ## Simple premise setting for each requirement 30 | 31 | Load requirements in this order. 32 | 33 | ### auto-complete 34 | 35 | ```cl 36 | (require 'auto-complete-config) 37 | (ac-config-default) 38 | (setq ac-use-menu-map t) 39 | ``` 40 | 41 | ### yasnippet 42 | 43 | ```cl 44 | (require 'yasnippet) 45 | (setq yas-snippet-dirs "~/.emacs.d/lib/snippet") ;; for example 46 | (yas-global-mode 1) 47 | (setq yas-trigger-key (kbd "TAB")) 48 | (yas--initialize) 49 | ``` 50 | 51 | ### helm 52 | 53 | ```cl 54 | (require 'helm-config) 55 | (helm-mode 1) 56 | ``` 57 | 58 | ### This package 59 | 60 | ```cl 61 | (require 'emaXcode) 62 | ``` 63 | 64 | ## Convert messages from Apple's header files to yasnippet 65 | 66 | Extract about 4,460 messages/functions from header directories (Foundation.framework, UIKit.framework), and convert them to a yasnippet file `.yas-compiled-snippets.el` with your existing snippets in objc-mode folder. 67 | 68 | If header directory paths changed by Xcode's upgrade, set correct paths list to `emaXcode-yas-objc-header-directories-list`. 69 | 70 | ```cl 71 | M-x emaXcode-yas-get-objc-messages-from-header-files 72 | ``` 73 | After this converting done, `.yas-compiled-snippets.el` is generated at your objc-mode snippets directory. 74 | Restart Emacs, and open Objective-C file. You would realize that it takes a while to load a huge yasnippet file, but is only once. 75 | 76 | 77 | ## Switch between header and implementation files 78 | 79 | ```cl 80 | M-x emaXcode-open-corresponding-file 81 | ``` 82 | 83 | ## Make new files subclassing from NSObject 84 | 85 | ```cl 86 | M-x emaXcode-make-new-files-subclass-of-NSObject 87 | ``` 88 | 89 | ## helm + yasnippet 90 | 91 | * insert snippet: M-x yas-insert-snippet 92 | * visit & edit snippet: M-x emaXcode-helm-yas-visit-snippet-file 93 | * make new snippet from the region: M-x emaXcode-yas-new-snippet-from-region 94 | 95 | 96 | ## Reference 97 | 98 | * http://sakito.jp/emacs/emacsobjectivec.html 99 | -------------------------------------------------------------------------------- /emaXcode.el: -------------------------------------------------------------------------------- 1 | ;;; emaXcode.el --- My Emacs setting for Objective-C, Xcode -*- coding: utf-8; lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014 by Shingo Fukuyama 4 | 5 | ;; Version: 1.0 6 | ;; Author: Shingo Fukuyama - http://fukuyama.co 7 | ;; URL: https://github.com/ShingoFukuyama/emaXcode 8 | ;; Created: May 26 2014 9 | ;; Keywords: xcode, objective-c, yasnippet, auto-complete, helm 10 | ;; Package-Requires: ((auto-complete "1.4.0) (yasnippet "0.8.0") (helm "1.5.6") (s "1.9.0) (emacs "24.3.5")) 11 | 12 | ;; This program is free software; you can redistribute it and/or 13 | ;; modify it under the terms of the GNU General Public License as 14 | ;; published by the Free Software Foundation; either version 2 of 15 | ;; the License, or (at your option) any later version. 16 | 17 | ;; This program is distributed in the hope that it will be 18 | ;; useful, but WITHOUT ANY WARRANTY; without even the implied 19 | ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 20 | ;; PURPOSE. See the GNU General Public License for more details. 21 | 22 | ;;; Commentary: 23 | 24 | ;; 25 | 26 | ;;; Code: 27 | 28 | (require 'auto-complete) 29 | (require 'yasnippet) 30 | (require 'helm) 31 | (require 's) 32 | 33 | (defgroup emaXcode nil 34 | "Group for emaXcode.el" 35 | :prefix "emaXcode-" :group 'development) 36 | 37 | 38 | ;; Make new NSObject subclass template files ----------------------------------- 39 | 40 | (defvar emaXcode-your-name-for-template "Your Name") 41 | 42 | (defun emaXcode-make-new-files-subclass-of-NSObject (class-name) 43 | "Make .h and .m file subclassing from NSObject in the current directory." 44 | (interactive "sClass Name: ") 45 | (setq class-name (s-trim class-name)) 46 | (let* ((header (expand-file-name (concat "./" class-name ".h"))) 47 | (implement (expand-file-name (concat "./" class-name ".m"))) 48 | (time (current-time)) 49 | (date (format-time-string "%Y/%m/%d" time)) 50 | (year (format-time-string "%Y" time))) 51 | (unless (or (file-exists-p header) 52 | (file-exists-p implement)) 53 | (with-temp-file header 54 | (insert (format "// 55 | // %s.h 56 | // Project Name 57 | // 58 | // Created by %s on %s. 59 | // Copyright (c) %s %s. All rights reserved. 60 | // 61 | 62 | #import 63 | 64 | @interface %s : NSObject 65 | 66 | @end 67 | " 68 | class-name 69 | emaXcode-your-name-for-template 70 | date 71 | year 72 | emaXcode-your-name-for-template 73 | class-name))) 74 | (with-temp-file implement 75 | (insert (format "// 76 | // %s.m 77 | // Project Name 78 | // 79 | // Created by %s on %s. 80 | // Copyright (c) %s %s. All rights reserved. 81 | // 82 | 83 | #import \"%s.h\" 84 | 85 | @implementation %s 86 | 87 | @end 88 | " 89 | class-name 90 | emaXcode-your-name-for-template 91 | date 92 | year 93 | emaXcode-your-name-for-template 94 | class-name 95 | class-name))) 96 | (if (eq major-mode 'dired-mode) (revert-buffer))))) 97 | 98 | 99 | 100 | ;; Make compiled yasnippet file from Apple's header files ---------------------- 101 | 102 | (defcustom emaXcode-yas-objc-header-directories-list 103 | '("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/System/Library/Frameworks/Foundation.framework/Headers" 104 | "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/System/Library/Frameworks/UIKit.framework/Headers") 105 | "List header directory paths" 106 | :group 'emaXcode 107 | :type 'list) 108 | (defvar emaXcode-yas-root (if (listp yas-snippet-dirs) 109 | (car yas-snippet-dirs) 110 | yas-snippet-dirs)) 111 | (defvar emaXcode-yas-objc-root (concat emaXcode-yas-root "/objc-mode")) 112 | (defvar emaXcode-yas-objc-compiled-file (concat emaXcode-yas-objc-root "/.yas-compiled-snippets.el")) 113 | 114 | (defun emaXcode-yas-compile-subdir () 115 | "Make .yas-compiled-snippets.el file from existing files under the objc-mode snippet directory" 116 | (unless (file-directory-p emaXcode-yas-root) 117 | (error (format "Yasnippet directory doesn't exist: %s" emaXcode-yas-root))) 118 | (unless (file-directory-p emaXcode-yas-objc-root) 119 | (make-directory emaXcode-yas-objc-root)) 120 | (let ((yas--creating-compiled-snippets t) 121 | (dir emaXcode-yas-objc-root)) 122 | (let* ((major-mode-and-parents (yas--compute-major-mode-and-parents 123 | (concat dir "/dummy"))) 124 | (mode-sym (car major-mode-and-parents)) 125 | (parents (cdr major-mode-and-parents))) 126 | (yas--define-parents mode-sym parents) 127 | (yas--menu-keymap-get-create mode-sym) 128 | (let ((fun `(lambda () 129 | (yas--load-directory-1 ',dir ',mode-sym)))) 130 | (funcall fun))))) 131 | 132 | (defun emaXcode-yas-get-objc-messages-from-header-files () 133 | (interactive) 134 | (let ($header-files 135 | $yas-compiled-snippets 136 | ;;$return-type 137 | $fn-name 138 | $template-content 139 | $key-for-expand 140 | $list) 141 | (emaXcode-yas-compile-subdir) ;; (yas-recompile-all) 142 | 143 | (mapc (lambda ($path) 144 | (unless (file-directory-p $path) 145 | (error (format "Apples's directory doesn't exist: %s" $path))) 146 | 147 | (mapc (lambda ($file) 148 | (with-temp-buffer 149 | (insert-file-contents-literally $file) 150 | (goto-char (point-min)) 151 | (while (re-search-forward "^[\t ]*[+-][\t ]*\\(([^)]+)\\)\\([^;]+\\);" nil t) 152 | (setq $key-for-expand "") 153 | ;;(setq $return-type (match-string 1)) 154 | (setq $fn-name (match-string 2)) 155 | (setq $template-content "") 156 | ;; Set yasnippet placeholder 157 | (if (string-match ":" $fn-name) 158 | (with-temp-buffer 159 | (insert $fn-name) 160 | (goto-char (point-min)) 161 | (while (re-search-forward "[a-zA-Z0-9_]*:" nil t) 162 | (setq $key-for-expand (concat $key-for-expand (match-string 0)))) 163 | (goto-char (point-min)) 164 | (let (($num 1) $po1 $po2 $rep) 165 | (while (re-search-forward ":[\t ]*(" nil t) 166 | (backward-char 1) 167 | (setq $po1 (point)) 168 | (forward-sexp) 169 | (re-search-forward "[\t ]*[a-zA-Z0-9_$]*" nil t) 170 | (setq $po2 (point)) 171 | (setq $rep (buffer-substring-no-properties $po1 $po2)) 172 | (delete-region $po1 $po2) 173 | (insert (format "${%d:%s}" $num $rep)) 174 | (setq $num (1+ $num)))) 175 | (setq $template-content (buffer-string))) 176 | (setq $key-for-expand $fn-name) 177 | (setq $template-content $fn-name)) 178 | (when (equal $key-for-expand "") 179 | (setq $key-for-expand nil)) 180 | ;; (when (string-match "[ (]" $key-for-expand) 181 | ;; (setq $key-for-expand (car (split-string $key-for-expand "[ (]")))) 182 | (setq $list (cons 183 | (list 184 | $key-for-expand ; key 185 | $template-content ; template-content 186 | $fn-name ; name 187 | nil ; condition 188 | nil ; group 189 | '((yas/indent-line 'fixed) (yas/wrap-around-region 'nil)) nil nil nil) 190 | $list))) 191 | ;; Extract UIKIT_EXTERN functions 192 | (goto-char (point-min)) 193 | (while (re-search-forward "^UIKIT\_EXTERN[\t ]*\\([a-zA-Z0-9]*\\)[\t ]*\\*?\\([^;]*\\)" nil t) 194 | (setq $key-for-expand "") 195 | ;; (setq $return-type (match-string 1)) 196 | (setq $fn-name (match-string 2)) 197 | (setq $template-content "") 198 | ;; Set yasnippet placeholder 199 | (if (string-match "([^)]+)" $fn-name) 200 | (with-temp-buffer 201 | (insert $fn-name) 202 | (goto-char (point-min)) 203 | (when (re-search-forward "(" nil t) 204 | (setq $key-for-expand (buffer-substring-no-properties (point-min) (1- (point))))) 205 | (let (($num 1) ($po (point)) $rep) 206 | (while (re-search-forward "\\([^,]+\\)" (1- (point-max)) t) 207 | (setq $rep (buffer-substring-no-properties $po (point))) 208 | (delete-region $po (point)) 209 | ;; For a space after a preceding comma 210 | (if (string-match "^\\s-" $rep) 211 | (insert (format " ${%d:%s}" $num $rep)) 212 | (insert (format "${%d:%s}" $num $rep))) 213 | (re-search-forward "," nil t) 214 | (setq $po (point)) 215 | (setq $num (1+ $num)))) 216 | (setq $template-content (buffer-string))) 217 | (setq $key-for-expand $fn-name) 218 | (setq $template-content $fn-name)) 219 | (when (equal $key-for-expand "") 220 | (setq $key-for-expand nil)) 221 | (setq $list (cons 222 | (list 223 | $key-for-expand ; key 224 | $template-content ; template-content 225 | $fn-name ; name 226 | nil ; condition 227 | nil ; group 228 | '((yas/indent-line 'fixed) (yas/wrap-around-region 'nil)) nil nil nil) 229 | $list))))) 230 | ;; Make header files list 231 | (split-string 232 | (shell-command-to-string 233 | (concat "find " $path " -name '*.h' -type f"))))) 234 | emaXcode-yas-objc-header-directories-list) 235 | 236 | (if (file-exists-p emaXcode-yas-objc-compiled-file) 237 | (setq $yas-compiled-snippets 238 | (or (ignore-errors 239 | (read (with-temp-buffer 240 | (insert-file-contents-literally emaXcode-yas-objc-compiled-file) 241 | (buffer-string)))) 242 | '(yas-define-snippets 'objc-mode '()))) 243 | (setq $yas-compiled-snippets '(yas-define-snippets 'objc-mode '()))) 244 | 245 | (setf (nth 1 (nth 2 $yas-compiled-snippets)) 246 | (append $list (nth 1 (nth 2 $yas-compiled-snippets)))) 247 | 248 | (with-temp-file emaXcode-yas-objc-compiled-file 249 | (insert (prin1-to-string $yas-compiled-snippets))) 250 | (message "Extract %s messages and save to %s." (length $list) emaXcode-yas-objc-compiled-file))) 251 | 252 | ;; (emaXcode-yas-get-objc-messages-from-header-files) 253 | 254 | ;; Chenge yasnippet settings --------------------------------------------------- 255 | 256 | (when (and (string-match "apple-darwin" system-configuration) 257 | (memq 'yas-x-prompt yas-prompt-functions)) 258 | ;; Eliminate `yas-x-prompt' for MacOSX 259 | (setq yas-prompt-functions (delq 'yas-x-prompt yas-prompt-functions))) 260 | 261 | (defun emaXcode-helm-dir-files ($dir) 262 | (interactive) 263 | (helm :sources 264 | `((name . "emaXcode helm dir files") 265 | (candidates . (lambda () (directory-files ,$dir t))) 266 | (type . file)) 267 | :buffer "*emaXcode helm dir files*")) 268 | 269 | (defun emaXcode-helm-yas-visit-snippet-file () 270 | (interactive) 271 | (let* ((yas-dir (if (listp yas-snippet-dirs) 272 | (car yas-snippet-dirs) 273 | yas-snippet-dirs)) 274 | (target-dir (format "%s/%s" yas-dir major-mode))) 275 | (when (file-directory-p target-dir) 276 | (emaXcode-helm-dir-files target-dir)))) 277 | 278 | (defun emaXcode-yas-new-snippet-from-region (beg end) 279 | "Pops a new buffer for writing a snippet from the region." 280 | (interactive "r") 281 | (let ((guessed-directories (yas--guess-snippet-directories)) 282 | (subst (buffer-substring-no-properties beg end))) 283 | 284 | (switch-to-buffer "*new snippet*") 285 | (erase-buffer) 286 | (kill-all-local-variables) 287 | (snippet-mode) 288 | (yas-minor-mode 1) 289 | (set (make-local-variable 'yas--guessed-modes) (mapcar #'(lambda (d) 290 | (yas--table-mode (car d))) 291 | guessed-directories)) 292 | (insert subst) 293 | (goto-char (point-min)) 294 | (yas-expand-snippet yas-new-snippet-default))) 295 | 296 | ;; Set snippets to auto-complete ----------------------------------------------- 297 | 298 | (defvar emaXcode-yas-name-key (make-hash-table :test 'equal :size 5000)) 299 | (defvar emaXcode-yas-name-list nil) 300 | 301 | (defun emaXcode-yas-set-source () 302 | "Read objc snippets from objc-mode snippet folder, and set name list and name-key pair list." 303 | (when (not emaXcode-yas-name-list) 304 | (let ((template (yas--all-templates (yas--get-snippet-tables))) 305 | name) 306 | (when template 307 | (setq template 308 | (sort template #'(lambda (t1 t2) 309 | (< (length (yas--template-name t1)) 310 | (length (yas--template-name t2)))))) 311 | (setq emaXcode-yas-name-list 312 | (mapcar (lambda (temp) 313 | (setq name (yas--template-name temp)) 314 | (puthash name 315 | (yas--template-key temp) 316 | emaXcode-yas-name-key) 317 | name) 318 | template)))) 319 | (when emaXcode-yas-name-list 320 | (ac-define-source emaXcode-yasnippet 321 | `((depends yasnippet) 322 | (candidates . emaXcode-yas-name-list) 323 | (action . (lambda () 324 | (let* ((undo-inhibit-record-point t) 325 | (position (point)) 326 | (completed (cdr ac-last-completion)) 327 | (length (length completed)) 328 | (beginning (- position length))) 329 | (delete-region beginning position) 330 | (insert (gethash completed emaXcode-yas-name-key)) 331 | (yas-expand-from-trigger-key)))) 332 | (candidate-face . ac-yasnippet-candidate-face) 333 | (selection-face . ac-yasnippet-selection-face) 334 | (symbol . "yas")))))) 335 | 336 | (defun emaXcode-yas-ac-objc-setup () 337 | (emaXcode-yas-set-source) ;; Set lists if name list has not set 338 | (add-to-list 'ac-modes 'objc-mode) 339 | (if (and (symbolp 'auto-complete-mode) (not (symbol-value 'auto-complete-mode))) 340 | (auto-complete-mode 1)) 341 | (set (make-local-variable 'ac-delay) 0.2) 342 | (set (make-local-variable 'ac-auto-show-menu) 0.5) 343 | (set (make-local-variable 'ac-ignore-case) t) 344 | (setq ac-sources (delq 'ac-source-yasnippet ac-sources)) 345 | (add-to-list 'ac-sources 'ac-source-emaXcode-yasnippet)) 346 | 347 | (add-hook 'objc-mode-hook 'emaXcode-yas-ac-objc-setup t) 348 | 349 | ;; Check error ----------------------------------------------------------------- 350 | 351 | (require 'flymake) 352 | 353 | (defcustom emaXcode-check-error nil 354 | "Checking error or warnings for objc-mode" 355 | :group 'emaXcode 356 | :type 'boolean) 357 | 358 | (defcustom emaXcode-xcode:sdkpath 359 | "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk" 360 | "Developer directory path" 361 | :group 'emaXcode 362 | :type 'string) 363 | (defvar emaXcode-flymake-objc-compiler "/usr/bin/gcc") 364 | (defvar emaXcode-flymake-objc-compile-default-options 365 | (list "-Wall" "-Wextra" "-fsyntax-only" "-ObjC" "-std=c99" "-isysroot" emaXcode-xcode:sdkpath)) 366 | (defvar emaXcode-flymake-objc-compile-options '("-I.")) 367 | 368 | (defun emaXcode-flymake-objc-init () 369 | (let* ((temp-file (flymake-init-create-temp-buffer-copy 370 | 'flymake-create-temp-inplace)) 371 | (local-file (file-relative-name 372 | temp-file 373 | (file-name-directory buffer-file-name)))) 374 | (list emaXcode-flymake-objc-compiler 375 | (append emaXcode-flymake-objc-compile-default-options 376 | emaXcode-flymake-objc-compile-options (list local-file))))) 377 | (defun emaXcode-flymake-objc-setup () 378 | (when emaXcode-check-error 379 | ;; Need to place before flymake-mode enabled 380 | (push '("\\.mm?$" emaXcode-flymake-objc-init) flymake-allowed-file-name-masks) 381 | (push '("\\.h$" emaXcode-flymake-objc-init) flymake-allowed-file-name-masks) 382 | ;; File exists and writable 383 | (if (and (not (null buffer-file-name)) (file-writable-p buffer-file-name)) 384 | (flymake-mode t)))) 385 | 386 | (add-hook 'objc-mode-hook 'emaXcode-flymake-objc-setup) 387 | 388 | (defun emaXcode-flymake-display-err-minibuffer () 389 | "Display error or warnings on the minibuffer." 390 | (interactive) 391 | (when emaXcode-check-error 392 | (let* ((line-no (flymake-current-line-no)) 393 | (line-err-info-list (nth 0 (flymake-find-err-info flymake-err-info line-no))) 394 | (count (length line-err-info-list))) 395 | (while (> count 0) 396 | (when line-err-info-list 397 | (let* ( 398 | ;; (file (flymake-ler-file (nth (1- count) line-err-info-list))) 399 | ;; (full-file (flymake-ler-full-file (nth (1- count) line-err-info-list))) 400 | (text (flymake-ler-text (nth (1- count) line-err-info-list))) 401 | (line (flymake-ler-line (nth (1- count) line-err-info-list)))) 402 | (message "[%s] %s" line text))) 403 | (setq count (1- count)))))) 404 | 405 | (defadvice flymake-mode (before post-command-stuff activate compile) 406 | "To display error on the minibuffer, add function to `post-command-hook'" 407 | (when emaXcode-check-error 408 | (set (make-local-variable 'post-command-hook) 409 | (add-hook 'post-command-hook 'emaXcode-flymake-display-err-minibuffer)))) 410 | 411 | ;; Relevant paths -------------------------------------------------------------- 412 | 413 | (defun emaXcode-open-application-directory () 414 | (interactive) 415 | (let ((path (expand-file-name "~/Library/Application Support/iPhone Simulator"))) 416 | (if (file-exists-p path) 417 | (dired path) 418 | (error (format "Directory not found: %s" path))))) 419 | 420 | ;; Set up objc-mode ------------------------------------------------------------ 421 | 422 | (or (boundp 'ff-other-file-alist) 423 | (defvar ff-other-file-alist nil)) 424 | 425 | (defun emaXcode-etc-objc-setup () 426 | (auto-revert-mode 1) 427 | (set (make-local-variable 'ff-other-file-alist) 428 | '(("\\.mm?$" (".h")) 429 | ("\\.cc$" (".hh" ".h")) 430 | ("\\.hh$" (".cc" ".C")) 431 | 432 | ("\\.c$" (".h")) 433 | ("\\.h$" (".c" ".cc" ".C" ".CC" ".cxx" ".cpp" ".m" ".mm")) 434 | 435 | ("\\.C$" (".H" ".hh" ".h")) 436 | ("\\.H$" (".C" ".CC")) 437 | 438 | ("\\.CC$" (".HH" ".H" ".hh" ".h")) 439 | ("\\.HH$" (".CC")) 440 | 441 | ("\\.cxx$" (".hh" ".h")) 442 | ("\\.cpp$" (".hpp" ".hh" ".h")) 443 | 444 | ("\\.hpp$" (".cpp" ".c"))))) 445 | 446 | (defalias 'emaXcode-open-corresponding-file 'ff-find-other-file) 447 | 448 | (add-hook 'objc-mode-hook 'emaXcode-etc-objc-setup) 449 | 450 | (add-to-list 'auto-mode-alist '("\\.mm?$" . objc-mode)) 451 | (add-to-list 'auto-mode-alist '("\\.h$" . objc-mode)) 452 | 453 | 454 | (provide 'emaXcode) 455 | ;;; emaXcode.el ends here 456 | --------------------------------------------------------------------------------