Using Emacs as a better Source Insight
I wrote an article about using Emacs and its free software friends for
code reading, but it is all in Chinese. So here I will show you how I
used Emacs as a better Source Insight, in English😊
1 Use grep-mode relentlessly
For most functions of source insight, we need only 1 function in
Emacs, and that is grep-mode. Need search for definition of a
function? Use grep-mode. Need search for where a function is called?
Use grep-mode. You just need several very different, very fast GREP(1)
commands. In my setup, there are a couple of them: beagrep
, gtags
,
ctags-exuberant
, grep-gtags
, grep-func-call
,
grep-func-call-all
.
1.1 Grepping for something random
Here's the result of using beagrep for searching where something
occurs. This is very slow in Source Insight, and especially if the
project is very large (for e.g., the whole Android project), Source
Insight will melt down. But I can grep a very familiar string
("hello world\n")
in 9 gigabytes of Android source code in one third
of a second. Here's the result:
-*- mode: grep; default-directory: "~/src/android/external/chromium_org/tools/gyp/test/make/" -*- Grep started at Wed Aug 20 09:28:31 beagrep -e "(\"hello world\\\\n\")" /home/bhj/src/android/external/chromium_org/tools/gyp/test/make/main.cc:10: printf("hello world\n"); /home/bhj/src/android/external/clang/bindings/python/tests/cindex/INPUTS/hello.cpp:4: printf("hello world\n"); /home/bhj/src/android/external/clang/www/get_started.html:213:int main(int argc, char **argv) { printf("hello world\n"); } /home/bhj/src/android/external/compiler-rt/BlocksRuntime/tests/localisglobal.c:19:void (^global)(void) = ^{ printf("hello world\n"); }; /home/bhj/src/android/external/compiler-rt/BlocksRuntime/tests/shorthandexpression.c:18: void (^b)(void) = ^(void)printf("hello world\n"); /home/bhj/src/android/prebuilts/python/darwin-x86/2.7.5/lib/python2.7/test/test_memoryio.py:81: buf = self.buftype("hello world\n") /home/bhj/src/android/prebuilts/python/linux-x86/2.7.5/lib/python2.7/test/test_memoryio.py:81: buf = self.buftype("hello world\n") Grep finished (matches found) at Wed Aug 20 09:28:31
Beagrep source code is here and blog is here.
Note how I didn't use a screenshot for the above grep output! I customized my org-mode setup so that grep-mode and compilation-mode can be htmlized too😝
Also note how I put 4 backslashes before the n to grep the newline
character \n
in C. This is automatic, I wrote some Emacs Lisp for
grep-mode, so that the correct grep regeex can be generated
automatically.
1.2 Grepping for definitions
Here's how to grep for a function definition:
-*- mode: grep; default-directory: "~/src/android/" -*- Grep started at Wed Aug 20 09:33:29 grep-gtags -e "getExternalStorageDirectory" -v .xml Found total 5/13 definitions: Entering directory `/home/bhj/src/android' frameworks/base/core/java/android/os/Environment.java:177: method: <android.os.Environment.UserEnvironment.getExternalStorageDirectory> : public File getExternalStorageDirectory() { frameworks/base/core/java/android/os/Environment.java:383: method: <android.os.Environment.getExternalStorageDirectory> : public static File getExternalStorageDirectory() { frameworks/base/core/java/android/os/Environment.java:387: method: <android.os.Environment.getExternalStorageDirectory> : public static File getExternalStorageDirectory() { external/robolectric/src/main/java/com/xtremelabs/robolectric/shadows/ShadowEnvironment.java:26: method: <com.xtremelabs.robolectric.shadows.ShadowEnvironment.getExternalStorageDirectory> : public static File getExternalStorageDirectory() { external/chromium_org/base/android/java/src/org/chromium/base/PathUtils.java:114: method: <org.chromium.base.PathUtils.getExternalStorageDirectory> : public static String getExternalStorageDirectory() { Grep finished (matches found) at Wed Aug 20 09:33:30
Note how I hacked grep-mode to show some respect to the Entering
directory `/home/bhj/src/android'#
thing, which used to be for
compilation-mode only😊 I did this so that the grep output can be a
bit shorter: instead of
/home/bhj/src/android/frameworks/base/core/java/android/os/Environment.java:177:
,
I need only start with frameworks/
.
grep-gtags
is a shell/perl script that I wrote which integrated
ctags-exuberant
and gtags
from GNU global. You may want to check
out Ajoke.el which is a package for doing Java completion in Emacs
(code here and blog here).
1.3 Grepping for where a definition is used
I use grep-func-call-all
and grep-func-call
for finding out how a
function/variable/class etc. is used.
Here's an example:
-*- mode: grep; default-directory: "~/src/android/" -*- Grep started at Wed Aug 20 09:52:01 grep-func-call -e "ACTION_MEDIA_SCANNER_SCAN_FILE" --nc -a Entering directory `/home/bhj/src/android/development/sdk' Entering directory `/home/bhj/src/android/external/chromium_org/ui/android/java/src/org/chromium/ui' SelectFileDialog.java:141: <= public void onIntentCompleted(WindowAndroid window, int resultCode, SelectFileDialog.java:158: => Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mCameraOutputUri)); Entering directory `/home/bhj/src/android/frameworks/base/core/java/android/content' Entering directory `/home/bhj/src/android/packages/apps/Backup/src/com/smartisanos/backup/activity' BackupActivity.java:430: <= private void notifyMediaScan(File file) { BackupActivity.java:431: => Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); Entering directory `/home/bhj/src/android/packages/apps/BrowserSmartisan/src/com/android/browser' UploadHandler.java:65: <= void onResult(int resultCode, Intent intent) { UploadHandler.java:90: => new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); Entering directory `/home/bhj/src/android/packages/apps/Browser/src/com/android/browser' UploadHandler.java:59: <= void onResult(int resultCode, Intent intent) { UploadHandler.java:84: => new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); Entering directory `/home/bhj/src/android/packages/apps/ContactsSmartisan/src/com/android/contacts/vcard' ExportProcessor.java:96: <= private void runInternal() { ExportProcessor.java:211: => mService.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Entering directory `/home/bhj/src/android/packages/apps/MmsSmartisan/src/com/android/mms/ui' ComposeMessageActivity.java:2310: <= private boolean copyPart(PduPart part, String fallback) { ComposeMessageActivity.java:2402: => sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Entering directory `/home/bhj/src/android/packages/apps/MmsSmartisan/src/com/android/mms/ui' MessageUtils.java:804: <= public static String saveAttachment(Context context, Uri data, String name) { MessageUtils.java:853: => context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Entering directory `/home/bhj/src/android/packages/apps/Mms/src/com/android/mms/ui' ComposeMessageActivity.java:1669: <= private boolean copyPart(PduPart part, String fallback) { ComposeMessageActivity.java:1751: => sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Entering directory `/home/bhj/src/android/packages/apps/SoundRecorderSmartisan/src/com/smartisanos/recorder/provider' RecorderProvider.java:86: <= public Uri insert(Uri uri, ContentValues values) { RecorderProvider.java:100: => Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, RecorderProvider.java:136: <= public int update(Uri uri, ContentValues values, String selection, RecorderProvider.java:155: => Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Entering directory `/home/bhj/src/android/packages/apps/SoundRecorder/src/com/android/soundrecorder' SoundRecorder.java:897: <= private Uri addToMediaDB(File file) { SoundRecorder.java:944: => sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); Entering directory `/home/bhj/src/android/packages/apps/UnifiedEmailSmartisan/src/com/android/mail/providers' EmlAttachmentProvider.java:255: <= private int copyAttachment(Uri uri, ContentValues values) { EmlAttachmentProvider.java:340: => final Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); Entering directory `/home/bhj/src/android/packages/apps/UnifiedEmail/src/com/android/mail/providers' EmlAttachmentProvider.java:253: <= private int copyAttachment(Uri uri, ContentValues values) { EmlAttachmentProvider.java:338: => final Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); Entering directory `/home/bhj/src/android/packages/apps/VideoEditor/src/com/android/videoeditor/service' ApiService.java:4297: <= private Uri exportToGallery(String filename) { ApiService.java:4305: => sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Entering directory `/home/bhj/src/android/packages/providers/MediaProvider/src/com/android/providers/media' MediaScannerReceiver.java:35: <= public void onReceive(Context context, Intent intent) { MediaScannerReceiver.java:64: => } else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) && Entering directory `/home/bhj/src/android/vendor/qcom/opensource/fm/FMRecord/src/com/codeaurora/fmrecording' FMRecordingService.java:391: <= private Uri addToMediaDB(File file) { FMRecordingService.java:431: => sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); Grep finished (matches found) at Wed Aug 20 09:52:06
Here's how it works. First I use beagrep (which is very fast) to find
out all the files that contain ACTION_MEDIA_SCANNER_SCAN_FILE
, then
I use ctags-exuberant
to parse each of these files. For e.g., in the
file FMRecordingService.java
above, ctags-exuberant
tells me
private Uri addToMediaDB(File file) {
appears on line 391 and
beagrep tells me that ACTION_MEDIA_SCANNER_SCAN_FILE
is on line 431
in this file, and ctags-exuberant
also tells me that there is no
other function definition in between line 391 and 431, so I can safely
assume that ACTION_MEDIA_SCANNER_SCAN_FILE
is called by (actually
used by, because it's not a callable thing but you know what I
mean😊) addToMediaDB
.
2 Use sawfish to customize more key binding
I don't know why I don't do it in Emacs itself. Anyway, I have
customized my sawfish desktop so that when I press F1
, it will
synthesize some events to the focused window (i.e. Emacs):
And the synthesized keys will invoke grep-func-call
for me in Emacs.
(bind-keys global-keymap "F1" '(synthesize-multiple-events "ESC" "g" "f" "RET"))
I used the ScrLk
key in sawfish to toggle a code reading mode for
Emacs (think ECS/i
toggle in vim😊) so that when this mode is on, I
can press F1-F5 for all the above mentioned grepping commands, with
just a single key, so this is very convinient, just like Source
Insight, right?
And in the code reading mode, I can use Home/End keys to jump to the
prev/next grep result, Left/Right to back/forth where I started
grepping. The idea is from xcscope.el where the tags can be popped, I
took it a step further, that they can also be pushed. So that when
code reading, I can follow very deep down where the functions are
called one by one using grep-func-call
again and again, and then I
can press Left key again and again to come back where I started. Then
I can use the Right key to go down the call stack again.
For e.g., here's the binding in sawfish for Home/End:
(bind-keys global-keymap "Home" '(synthesize-multiple-events "ESC" "g" "p")) (bind-keys global-keymap "End" '(synthesize-multiple-events "ESC" "g" "n"))
And here's the help for the synthesized key in Emacs:
M-g p (translated from <escape> g p) runs the command previous-error (found in global-map), which is an interactive compiled Lisp function in `simple.el'.
3 How to try out my setup
I must apologize that I have not put too much thinking (actually I put
a lot but not enough) into how other people can use my setup. On
github some Emacs gurus have published a complete emacs.d which is
well organized. My setup is one step further, I not only want to mess
with your .emacs
and your emacs.d
, but also your .bashrc
and
~/system-config/bin
, sigh…
But, in case you are not daunted by my mess, here's how to use my setup:
- Get debian. Ubuntu will do (I tested). Maybe get a new user account (or back up all of your account's config files). Get sudo for your account.
cd ~; git clone --recursive https://github.com/baohaojun/system-config
~/system-config/bin/Linux/after-check-out.sh
Here's how to try out beagrep after you do the above setup:
cd ~/system-config/; for-code-reading
cd ~/system-config/; beagrep -e "(\"hello world\\\\n\")" #
Bug reports are welcome😊 Even more welcome are success stories of you using my set up😊 The most welcome is patches😊
Last but not least, can you read my emojis in this article? 😝