Comment/uncomment line(s) is easy in Emacs

It's easy if you use evil-nerd-commenter. As its README said, comment/uncomment line is only pressing ",ci" in evil-mode.

You can also select the region and press "M-;" to comment the lines in the region. If part of the line is not in the region, the whole line will still be commented out. This saves your extra key pressing for moving cursor to the beginning of the first line or moving the cursor to the end of the last line.

The reason to make commenting so efficient is that I need comment/uncomment code for debug purpose.

Some people say comment out the code is not a right way to debug. The right way is using debugger.

But if you debug some huge project, using the debugger makes no sense for many reasons:

  • debugging DSL the middleware whose debugger simply does not exist
  • The bug can be reproduced on production server
  • Too much overhead to set up debugger. Say I'm debugging component from other teams and developers and the technologies they use make configuring debugger very harder.

发表满一周年的感悟

原创日期: 2013-01-31 四

2012年1月31日我写了一年成为Emacs高手(像神一样使用编辑器) (后文简称<一年>),到今天(2013年1月31日)正好一周年.

这一年中,我不断地维护更新该文,力求能够反映最新社区咨询,解决读者的实际问题,这个过程本身使我受益匪浅.

最大的收获就是静下心来花时间把一件事情做到极致,对个人收获是最大的.

例如,我不断维护<一年>一文,使得这篇文章成为我所有文章中最受欢迎的文章,其评论数和读者私下联系我的次数超过了我所有其他文章的总和.很多读者还热心给我推荐了Emacs其他好用的功能(如插件switch-window),学习这些功能靠我自己摸索可能要花大量的时间.

该文之所以能够受欢迎,我想主要是因为文章质量比较高,信息丰富.我的总结是,只有花大量时间,反复修改,才能写出高质量的文章.例如,<一年成为Emacs高手>我前前后后改了十多个版本,前后花了一年时间,最新版本是两天前更新的.

持之以恒的最大优点是可以达到很深的深度,获得不可替代性,而报酬(读者的赞扬回馈,我个人知识智力的增长)是和我钻研的深度成正比的.

例如,在反复修改<一年>一文中,我自己对于该文的中心思想认识越来越深刻了.该中心思想就是,一个人能够达到的高度,是由他的设的起点有多高决定的,而最好的起点就是世界级的顶尖高手.这个道理除了写博客和Emacs外,还可以应用到很多地方.

持之以恒的道理我从小都懂,但是在维护<一年>的过程我才算真正领悟了这个道理(纸面上知道不算知道,做了才算真正知道).就我个人来讲,主要是因为对自己的智力比较自负,总觉得追求有趣新奇的东西才配得上自己的天赋,对于已知的比较枯燥的小修小补的工作有点不屑一顾.事实上,修补完善的过程是很轻松的,不需要花太多时间精力,而获得的回报是巨大的.我要做得就是适当地抑制一下自己追新求变的热情.

接下来是我给读者的问题.

如果你一年前读过了<一年成为Emacs高手>,你现在是否成为了Emacs高手?你的一周年感悟是什么?

无论你已经是高手了,或正在成为高手的路上,把你的感悟告诉我.我会汇总后发表在我的博客上.

这是我的twittergoogle plus以及微博,也可以通过我的email<chenbin DOT sh AT GMAIL DOT COM>联系我.我也在新浪weibo.com上开通账号emacsguru.

Open url in Emacs with external browser

Here is some handy elisp function which use browse-url-generic to open the url in external browser (firefox, for example).

The key point is how to detect the url. If we are in w3m-mode, we will use the link under cursor or the URI of current web page. Or else, we will let browse-url-generic to detect the url.

(defun w3mext-open-link-or-image-or-url ()
  "Opens the current link or image or current page's uri or any url-like text under cursor in firefox."
  (interactive)
  (let (url)
    (if (string= major-mode "w3m-mode")
        (setq url (or (w3m-anchor) (w3m-image) w3m-current-url)))
    (browse-url-generic (if url url (car (browse-url-interactive-arg "URL: "))))
    ))
(global-set-key (kbd "C-c b") 'w3mext-open-link-or-image-or-url)

To specify the external browser like firefox, add below code into ~/.emacs:

;; C-h v browse-url-generic-program RET to see the documentation

;; is-a-mac and linux is the boolean constants defined by me

(setq browse-url-generic-program (cond (is-a-mac "open") (linux (executable-find "firefox")) ))

Simplest workflow on email git commit in Emacs (No magit needed)

Sometimes I just want to email my trivial patch to the original author and forget it. In this case "github fork" or "git format-patch" is too heavy weight for me.

So here is the simplest workflow I can figure out:

  • Step 1, command "M-x vc-dir" whose hotkey is "C-x v d"
  • Step 2, command "M-x git-print-log" whose hotkey is "C-x v l"
  • Step 3, Move focus to the specific commit and run command "M-x log-view-diff" whose hotkey is "d".
  • Step 4, Yank the commit/diff
  • Step 5, Compose new email (command "M-x compose-mail" whose hotkey is "C-x m")
  • Step 6, Make sure the subject of email start with "[PATCH]". See this patch sumbit guide for reasons.
  • Step 7, Paste the content of commit/diff into email body.
  • Step 8, Send the email by command "M-x message-send-and-exit" whose hotkey is "C-c C-c"

Please note:

  • Step 1 is usually necessary unless you only need the diff of current file.
  • Step 2 and step 3 could be replaced with command "M-x vc-diff" or hotkey "C-x v =" if you want to email the diff of work directory.

Import Gmail contacts into BBDB

  • Go to http://contacts.google.com, click the menu "More>>Export…>>vCard format (blah, blah …)".
  • Click "Export" button. Download the contacts.vcf.
  • Make sure bbdb-vcard.el installed.
  • In Emacs run "M-x bbdb-vcard-import-file" and input the full path of contacts.vcf
  • In Emacs run "M-x bbdb-save"
  • Remove contacts whose names are empty. These are Google Plus people inserted automatically by Gmail:

Run the command in shell:

# the orginal ~/.bbdb will be saved as ~/.bbdb.bak
sed -i.bak '/["" ""/d' ~/.bbdb

Search at both stackoverflow and google code in Emacs

Two programming sites are most valuable to me:

stackoverflow.com
solution design and code prototype
google code search
code sample from real product

So I write some elisp code w3mext-hacker-search to automate the workflow.

Copy the into ~/.emacs and you can use hotkey "C-c ; s" to open search results in external browser (firefox, for example):

; external browser should be firefox
(setq browse-url-generic-program
      (cond
       (is-a-mac "open")
       (linux (executable-find "firefox"))
       ))

;; use external browser to search

(defun w3mext-hacker-search () "search word under cursor in google code search and stackoverflow.com" (interactive) (require 'w3m) (let ((keyword (w3m-url-encode-string (thing-at-point 'symbol)))) (browse-url-generic (concat "http://code.google.com/codesearch?q=" keyword)) (browse-url-generic (concat "http://www.google.com.au/search?hl=en&q=" keyword "+site:stackoverflow.com" ))) )

(add-hook 'prog-mode-hook '( lambda () (local-set-key (kbd "C-c ; s") 'w3mext-hacker-search)) )

Toggle http proxy in emacs-w3m

emacs-w3m is a browser embedded in Emacs. It can handle all my basic web surfing needs.

For some reason, I need switch my http proxy frequently when accessing the internet in China. So I need figure out a easy way doing this.

It turns out all I need to do is to set/unset the environment variable "http_proxy" in elisp code. Restarting the w3m session after change the environment variable is not needed at all.

Here is my code:


(defun toggle-env-http-proxy ()
  "set/unset the environment variable http_proxy which w3m uses"
  (interactive)
  (let ((proxy "http://127.0.0.1:8000"))
    (if (string= (getenv "http_proxy") proxy)
        ;; clear the proxy
        (progn
          (setenv "http_proxy" "")
          (message "env http_proxy is empty now")
          )
      ;; set the proxy
      (setenv "http_proxy" proxy)
      (message "env http_proxy is %s now" proxy)
        )
    ))

Use POPFile at Linux

CREATED: <2012-12-28>

UPDATED: <2017-03-12 Sun>

POPFile automatically sorts emails.

1 Install POPFile

2 Install third party packages

Run:

cpan DBD::SQLite DBI Date::Format Date::Parse HTML::Tagset HTML::Template IO::Socket::SSL Net::IDN::Encode Mozilla::CA

When being asked by cpan, say you prefer installing local lib.

This is my cpan setup,

$CPAN::Config = {
  'applypatch' => q[],
  'auto_commit' => q[0],
  'build_cache' => q[100],
  'build_dir' => q[/home/cb/.cpan/build],
  'build_dir_reuse' => q[0],
  'build_requires_install_policy' => q[yes],
  'bzip2' => q[/bin/bzip2],
  'cache_metadata' => q[1],
  'check_sigs' => q[0],
  'colorize_output' => q[0],
  'commandnumber_in_prompt' => q[1],
  'connect_to_internet_ok' => q[1],
  'cpan_home' => q[/home/cb/.cpan],
  'ftp_passive' => q[1],
  'ftp_proxy' => q[],
  'getcwd' => q[cwd],
  'gpg' => q[/usr/bin/gpg],
  'gzip' => q[/bin/gzip],
  'halt_on_failure' => q[0],
  'histfile' => q[/home/cb/.cpan/histfile],
  'histsize' => q[100],
  'http_proxy' => q[],
  'inactivity_timeout' => q[0],
  'index_expire' => q[90],
  'inhibit_startup_message' => q[0],
  'keep_source_where' => q[/home/cb/.cpan/sources],
  'load_module_verbosity' => q[none],
  'make' => q[/usr/bin/make],
  'make_arg' => q[],
  'make_install_make_command' => q[/usr/bin/make],
  'mbuild_arg' => q[--install-dirs site],
  'mbuild_install_arg' => q[./Build],
  'mbuild_install_build_command' => q[./Build],
  'mbuildpl_arg' => q[--installdirs site],
  'no_proxy' => q[],
  'pager' => q[/usr/bin/less],
  'patch' => q[/usr/bin/patch],
  'perl5lib_verbosity' => q[none],
  'prefer_external_tar' => q[1],
  'prefer_installer' => q[MB],
  'prefs_dir' => q[/home/cb/.cpan/prefs],
  'prerequisites_policy' => q[follow],
  'recommends_policy' => q[1],
  'scan_cache' => q[atstart],
  'shell' => q[/bin/bash],
  'show_unparsable_versions' => q[0],
  'show_upload_date' => q[0],
  'show_zero_versions' => q[0],
  'suggests_policy' => q[0],
  'tar' => q[/bin/tar],
  'tar_verbosity' => q[none],
  'term_is_latin' => q[1],
  'term_ornaments' => q[1],
  'test_report' => q[0],
  'trust_test_report_history' => q[0],
  'unzip' => q[/usr/bin/unzip],
  'urllist' => [q[http://mirror.optusnet.com.au/CPAN/]],
  'use_prompt_default' => q[0],
  'use_sqlite' => q[0],
  'version_timeout' => q[15],
  'wget' => q[/usr/bin/wget],
  'yaml_load_code' => q[0],
  'yaml_module' => q[YAML],
};
__END__

cpan will append setup ~/.bashrc,

PATH="$HOME/perl5/bin${PATH:+:${PATH}}"; export PATH;
PERL5LIB="$HOME/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
PERL_LOCAL_LIB_ROOT="$HOME/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
PERL_MB_OPT="--install_base \"$HOME/perl5\""; export PERL_MB_OPT;
PERL_MM_OPT="INSTALL_BASE=$HOME/perl5"; export PERL_MM_OPT;

Double check installed packages:

perl -MDBI -e 'print $MIME::DBI::VERSION'

If there is any error:

perl -V
rm -rf ~/perl5/*
cpan DBD::SQLite DBI Date::Format Date::Parse HTML::Tagset HTML::Template IO::Socket::SSL Net::IDN::Encode MOZILLA::CA

3 Setup

I only use IMAP. The purpose of tweak POP3 port is only to make the program executable without root permission.

See http://getpopfile.org/docs/howtos:mayneedroot for details.

Run the below command to fix the root issue.

sed -i 's/pop3_port \+110/pop3_port 1110/g' ~/bin/popfile/popfile.cfg

Here is my popfile setup for Davmail.

Davmail is a gateway to convert Outlook/Exchange service to IMAP/POP3). It's required if and only if you use Microsoft's Exchange/Outlook.

For other email service, you DON'T need Davmail.

If you don't use Davmail and fetch mails directly, you'd better set imap_expunge 1 to fix Microsoft Office 365 has some issue.

Please note ssl is disabled in my setup because I'm running local Davmail Server. Or else, you need enable ssl.

bayes_bad_sqlite_version 4.0.0
bayes_corpus corpus
bayes_database popfile.db
bayes_dbauth 
bayes_dbconnect dbi:SQLite:dbname=$dbname
bayes_dbuser 
bayes_hostname 192-168-1-3.tpgi.com.au
bayes_localhostname 
bayes_nihongo_parser kakasi
bayes_sqlite_journal_mode delete
bayes_sqlite_tweaks 4294967295
bayes_subject_mod_left [
bayes_subject_mod_pos 1
bayes_subject_mod_right ]
bayes_unclassified_weight 100
bayes_xpl_angle 0
config_pidcheck_interval 5
config_piddir ./
GLOBAL_debug 1
GLOBAL_last_update_check 1407715200
GLOBAL_message_cutoff 100000
GLOBAL_msgdir messages/
GLOBAL_ssl_verify_peer_certs 0
GLOBAL_timeout 60
GLOBAL_update_check 1
history_archive 0
history_archive_classes 0
history_archive_dir archive
history_history_days 2
html_cache_templates 0
html_column_characters 0
html_columns +inserted,+from,+to,-cc,+subject,-date,-size,+bucket
html_date_format 
html_language English
html_last_reset Sat Dec  1 11:06:48 2012
html_local 0
html_page_size 20
html_password b6b3637136ad630eba43aa5ee7106780
html_port 8888
html_search_filter_highlight 0
html_send_stats 1
html_session_dividers 1
html_show_bucket_help 1
html_show_training_help 0
html_skin simplyblue
html_strict_templates 0
html_test_language 0
html_wordtable_format 
imap_bucket_folder_mappings job-->job-->geek-->geek-->business-->business-->ad-->ad-->unclassified-->INBOX-->work-->work-->
imap_enabled 1
imap_expunge 1
imap_hostname localhost
imap_login binc1
imap_password password1
imap_port 1143
imap_training_mode 0
imap_uidnexts work-->749-->job-->5914-->INBOX-->20736-->geek-->5009-->ad-->1296-->business-->15-->
imap_uidvalidities job-->98-->work-->107-->ad-->100-->business-->106-->geek-->99-->INBOX-->2-->
imap_update_interval 1000
imap_use_ssl 0
imap_watched_folders INBOX-->INBOX-->
logger_format default
logger_level 0
logger_logdir ./
nntp_enabled 0
nntp_force_fork 1
nntp_headtoo 0
nntp_local 1
nntp_port 119
nntp_separator :
nntp_socks_port 1080
nntp_socks_server 
nntp_welcome_string NNTP POPFile (v1.1.3) server ready
pop3_enabled 0
pop3_force_fork 1
pop3_local 1
pop3_port 1110
pop3_secure_port 110
pop3_secure_server 
pop3_separator :
pop3_socks_port 1080
pop3_socks_server 
pop3_toptoo 0
pop3_welcome_string POP3 POPFile (v1.1.3) server ready
smtp_chain_port 25
smtp_chain_server 
smtp_enabled 0
smtp_force_fork 1
smtp_local 1
smtp_port 25
smtp_socks_port 1080
smtp_socks_server 
smtp_welcome_string SMTP POPFile (v1.1.3) welcome
xmlrpc_enabled 0
xmlrpc_local 1
xmlrpc_port 8081

The only remaining issue is user name. If it contains "\", you should replace it with "\\" when inputting in Popfile!

4 Run the program

See http://getpopfile.org/docs/howtos:runlocation for details.

cd ~/bin/popfile;perl popfile.pl

5 Start the POPFile automatically when bash login

Insert below code in ~/.bashrc:

OS_NAME=`uname -s`
if [ $OS_NAME == Linux ]; then
   # start popfile
   if [ -f $HOME/bin/popfile/popfile.pl ]; then
      # @see http://getpopfile.org/docs/howtos:runlocation
      pop_pid=`ps -ef | grep perl | grep popfile.pl | gawk '{print $2}'`
      if [ "${pop_pid}" = "" ] ; then
         cd $HOME/bin/popfile
         perl popfile.pl >> /dev/null 2>&1 &
      fi
      cd $HOME
   fi
fi

6 Start the POPFile systemd service as a user (OPTIONAL)

My user name is cb. Put popfile.service in usr/lib/systemd/system. Then run `systemctl enable popfile` to install service. Run `systemctl start popfile` to start the service. My Linux distribution is ArchLinux.

here is content of popfile.service.

[Unit]
Description=POPFile (Automatic Email Classification) Service

[Service]
WorkingDirectory=/home/cb/bin/popfile
ExecStart=/usr/bin/perl popfile.pl
User=cb

[Install]
WantedBy=multi-user.target

7 Backup

Please backup popfile.cfg and popfile.db.

See http://getpopfile.org/docs/howtos:backup for details.

8 Summary

  • Portable
  • No root privilege needed
  • No X windows needed. I can manage mails in remote shell

Emacs Lisp并不难学

作者:陈斌 (redguardtoo)

原创日期: 2012-11-30 五

本文的目的是把Lisp去神秘化.

我学的是Emacs Lisp(Emacs Lisp),开发环境也是Emacs.所以我举的例子都是只基于Elisp.

本文针对的读者是有相当经验的开发者,目的是尽可能简明扼要地突出重点.

我对于目前的主流开发语言都很熟悉,但是Lisp还只能算是入门水准,所以错过很多Lisp精彩之处是很可能的.

我所谓的重点,主要是指Lisp对于开发一个现实世界的产品有什么优点.

所有的函数和数据可以在系统运行时改变

其意义是,假设你一个系统上线了,你可以随时改变正在运行的代码,不需要重启系统.

我的理解是,一个函数实际上就是带有字符串Key(该key就是函数名)的数据,运行某个函数就是在系统运行时根据Key找到对应的数据(或者代码,在Lips中是一回事)运行之.

例如,我可以在hook中申明某个函数将被调用,而这个函数的定义可以还不存在.系统在运行时才会去寻找这个函数.解释一下,hook可以认为是事件触发机制,就是在系统运行的某个时刻调用用户自定义的函数)

优点是灵活性很高.缺点是Lisp写的东西快不了.

没有namespace,所有的函数默认都是可以全局访问的

正因为这两条,所以要多打字(每个函数都要手动输入名字前缀).

这点我完全可以接受,没有namespace就是多打点字而已.那些不了解历史或者没有用过C的初级程序员可能会大惊小怪.实际上没什么大不了的.

所有函数都可以访问在Emacs场景下有个巨大的优点,见下一条.

defadivce可以改变系统中任意函数的行为.

defadvice可以重定义系统中的任意函数.原因如前文所说.

循环语句和条件判断语句

和其他语言没什么不同,foreach之类的语法糖也不少.

我唯一不喜欢的是没有C中的return,break,和continue语句,这样的缺点是有可能让代码嵌套过深.

不过不是什么大不了的问题,Lisp也提供了一些替代语法.

另外其他所有语言提供了这些语法糖又怎么样呢,我遇到的超过50%的程序员还是一样瞎写.

不同寻常的语法

Lisp语法是操作符号在前,被操作对象在后.

例如2+3+4,在Lisp的语法中是这样的:

(+ 2 3 4)

所有的语法都是这样的前缀表达式(Polish Notation),很多人不习惯这样的语法,但是它有一些突出优点:

  1. 这样的语法做语法解析特别容易,所以第三方支持工具很容易开发. 在大规模系统开发时,这点很有用.例如,分析大型项目的源代码时,你能唯一依靠的grep和正则表达式,ELisp简单严格的语法使得正则表达式很好写.
  2. 实际编程时少打很多运算符号,这对于有实战经验的程序员是巨大的优点.如果你和我说什么重要的是思想和设计模式,打字速度不重要,那么菜鸟请走开.
  3. 最重要的优点是,这种语法相当于一种过滤机制,能够接受这种语法的人通常都是头脑比较开放思维敏捷的人.说到底产品开发的决定性因素是人,所以这个能过滤人的优点是决定性的.

函数可内嵌文档,且该文档可被Emacs帮助系统调用

现在能做到这点的系统也没几家.从细节我们可以看出Emacs和Elisp的完美之处.

How to use ctags with Emacs effectively

See http://emacswiki.org/emacs/EmacsTags for general usage of tags file. A file name of tags files is 'TAGS' by default. But in Emacs manual the 'tags file' is named 'tags table'. Either 'tags file' or 'tags table' or 'TAGS' is only different name for the same thing.

According wiki page, people usually use ctags to do two things,

  1. Code navigation
  2. Auto-complete/Intellisense

    In my opinion, ctags is OK for code navigation because it's integrated into Emacs well and ctags supports more programming language. But ctags only uses regular expression to analyze the code. So it's not as precise as professional tools like cscope or GNU Global. According to my own experience, ctags plus grep is actually good enough for code navigation in big enterprise projects with mixed environments. If you are among a few talented geeks who are developing Linux kenel in the most efficient way, you should use better tools like GNU Global. See the head up from Nick Alcock. His summary of the Pro/Con of GNU Global is great.

    Intellisense is a different story. For programming languages like Javascript, ctags provide some intellisense with is better than nothing. C++ is a more complex language which is a litter harder for ctags to handle unless you are not using those "advanced" features of C++.

    I found one missing piece from Emacswiki is the tip on how to manage tags file easily when the TAGS is built from some code of third party libraries (QT, wxWidgets, GTK, Boost, just name a few).

    My typical work flow is:

  3. Load the tags file for my own code.
  4. Load other tags files built from third party libraries related to my current task.
  5. Unload third party tags immediately if its not needed.

    The purpose to load tags files is to look up API easily. For example, I use C++ library wxWidgets to develop my application. I want to use its API called wxWindow::Maximize. I type 'M-x find-tag" and input the keyword "Maximize". So I can check the function definition and know what parameters I need fill in to use "Maximize".

    The reason to do the unload thing is to avoid function name confliction from some tags files. Emacswiki has a section "Choosing Among Multiple Tags for the Same Name" to solve this problem. But unloading is more convenient. In theory, it would be annoying if I need repeat load and unload the same tags files. In reality, it seldom happens because if I need work on Javascript (so I unload C++ related TAGS), I will usually keep working on Javascript until the end of the day.

Produce tags files for all the environments

For exmaple, I produce tags files of Library A for Mac and Linux. I put tags file from Mac at directory "/home/cb/tags/Library_A/mac" and file from Linux at "/home/cb/tags/Library_A/linux"

Use git and github to manage the tags files

So I can have copies of the tags files at github server and my local computers. That's also a part of my strategy to setup the development environment. My projects on different computers usually use same external library.

Emacswiki discusses how to update your tags file frequently because your own code are updated frequently. But the tags file from a big library like wxWidgets need not be updated to often because I only use limited old APIs in that library.

Write emacs lisp code to load/unload these tags files

Here is the code. In this example, if I need load tags file for wxWidgets, I call the function "add-wx-tags". If I need unload the tags file, I press "Ctrl-u" and call the same function "add-wx-tags". Since I use Smex, calling function doesn't mean pressing many keys.

; Set up tags built from third party libraries ===begin
; define tags alias like "wx-tags" here
(setq wx-tags (expand-file-name "~/tags/wx/osx/TAGS"))

; @see <Selecting a Tags Table> in Emacs manual for details.
; We only change the list "tags-table-list". It is documented officialy.
(defun insert-into-tags-table-list(e)
  (add-to-list 'tags-table-list e t)
  )

(defun delete-from-tags-table-list (e)
  (setq tags-table-list (delete e tags-table-list))
  )

; This is a sample command, all you need is copy/paste this template
; for other new commands
(defun add-wx-tags (&optional del)
  "Add or delete(C-u) wxWidgets tags into tags-table-list"
  (interactive "P")
  (let (mytags)
    ; here add your third party tags files
    ; Usually you need load/unload tags files combination in one command
    ; change below line to add them
    (setq mytags (list wx-tags))
    (if del (mapc 'delete-from-tags-table-list mytags)
      (mapc 'insert-into-tags-table-list mytags)
      )
    )
  )
; === end

This solution only uses one standard variable "tags-table-list" in Emacs. So it will always work with any other Emacs plugins you install. And you can use all the old hotkeys and functions ("find-tag", for example) without any problem.

See Emacs manual for technical details.