Little Helpers

From Apache OpenOffice Wiki
Revision as of 21:39, 8 September 2006 by Thorsten (Talk | contribs)

Jump to: navigation, search

Here are some Little Helpers for the enthusiastic developer, such as editor configurations and small scripts that help to tame the OOo code base.


Tools for maintaining identifier databases

GNU ID-utils

The GNU ID-utils are a powerful set of utilities to generate and query a database of file names and identifiers, invoke your favourite editor with the resulting set of files, or accomplish any other task with the resultset. Be sure to not use the original version 3.2, use the Debian package instead, or maybe some other distribution's package. For details why and how to patch a self-compiled version 3.2d see this blog entry.

The ID-utils come with an
;;; id-utils.el -- emacs interface to `lid -R grep', a.k.a. `gid'
For how to interface with the vim editor see below. Generally, the editor invoking functionality of ID-utils uses the shell variables VISUAL or EDITOR, EIDARG, EIDLDEL and EIDRDEL, so it should be possible to invoke any capable editor. Citing from the documentation:

   `eid' invokes the editor defined by the environment variable
`VISUAL'.  If `VISUAL' is undefined, it uses the environment variable
`EDITOR' instead.  If `EDITOR' is undefined, it defaults to `vi'.  It
is possible for `eid' to pass the editor an initial search pattern so
that your cursor will immediately alight on the token of interest.
This feature is controlled by the following environment variables:
`EIDARG'
     A printf(3) format string for the editor argument to search for the
     matching token.  For `vi', this should be `+/%s/'.

`EIDLDEL'
     The regular-expression meta-character(s) for delimiting the
     beginning of a word (the ``eid' Left DELimiter').  `eid' inserts
     this in front of the matching token when a word-search is desired.
     For `vi', this should be `\<'.

`EIDRDEL'
     The regular-expression meta-character(s) for delimiting the end of
     a word (the ``eid' Right DELimiter').  `eid' inserts this in end
     of the matching token when a word-search is desired.  For `vi',
     this should be `\>'.

The ID-utils use an extension → language mapping file id-lang.map. Either edit the system-wide file of your installation or a local copy thereof. Here we're assuming a local copy as $HOME/devbase/id-lang.map, add the following entries:

# Treat OOo/SO resource header files as C files
*.hrc           C
# Treat OOo/SO resource files as C files
*.src           C
# Treat OOo/SO *.mk files as makefiles
*.mk            make

Note: The *.mk make entry doesn't seem to work, the makefile.mk's IDs are not added to the database, didn't investigate yet why.

Then the script below may be invoked either

  • In a module's directory without parameters, creating an ID database for just the single module.
  • In the $SRC_ROOT directory for all modules: mkid-script '*'
    Note the literal '*' asterisk that is not to be expanded by the shell. Also note that the positive list has to be maintained whenever a module introduces an unforseen directory structure, which fortunately happens very rarely.
    The file size of the generated ID file using this method currently (m182) is 28MB.
  • In the $SRC_ROOT directory for specified modules: mkid-script '{module1,module2,solver}'
    Again, note the literal argument enclosed in single quotes for the file name expansion, which is done by the script. If the argument was not passed literally, only the first module would be included, and other strange things may happen.. Wildcards may be used as usual.
    It is a good idea to include the solver module to gather identifiers from delivered header files.
    Ignore messages like
    mkid: can't lstat `unxlngi4.pro' from `.../SRC680/src.m180/solver' or
    mkid: can't lstat `build' from `.../SRC680/src.m180/module1/unxlngi4.pro/misc'
    They result from the INPATH relevant arguments passed for each module and don't harm.

You may also manually invoke mkid in the $SRC_ROOT directory for all modules, this is a safe bet but may include more than you want if you have output directories for multiple platforms, all identifiers present there will get duplicated, triplicated, ... the command line for this is
mkid --lang-map=$HOME/devbase/id-lang.map --statistics

mkid-script

The module-wise script, suggested name: $HOME/devbase/mkid-script

#!/bin/tcsh

# Either the current module, or all modules can be ID'ed if $1 is
# specified as literal '*' unexpanded. Will be expanded here.
# Instead of '*' _any_ shell wildcard should be possible, e.g. '{.,bf_*}'.

if ( { ( which mkid >> /dev/null ) } ) then
    echo generating IDs
    if ( "$1" == "" ) then
        set module="."
        #set exclude=./{common,unxlngi6,unxsols4,wntmsci10}{,.pro}
        # wildcards not only make updates unnecessary but also suppress "no match" messages
        set exclude=./{common*,unxlng*,unxsol*,wntmsc*}
    else if ( "$1" == "*" ) then
        set module="$1"
        echo module: $module
        # prevent command line overflow with a full exclude
        set exclude="x"
        echo exclude: $exclude
    else
        set module="$1"
        echo module: $module
        set exclude=$1/{common*,unxlng*,unxsol*,wntmsc*}
        echo exclude: $exclude
    endif

    if ( "$1" == "*" ) then
        # only positive list
        set dirs="$module/inc $module/unx $module/mow $module/win{,32} $module/mac{,osx} $module/$INPATH/inc $module/$INPATH/misc/build $module/source $module/src $module/osl $module/rtl $module/systools $module/textenc"
        echo dirs: $dirs
        mkid --lang-map=$HOME/devbase/id-lang.map --statistics $dirs
    else
        # all but negative list plus INPATH relevants
        mkid --lang-map=$HOME/devbase/id-lang.map --statistics --prune="$exclude" $module $module/$INPATH/inc $module/$INPATH/misc/build
    endif

    # all, for copy&paste
    # mkid --lang-map=$HOME/devbase/id-lang.map --statistics

else
    echo no ID-utils
endif

Exuberant Ctags

The Exuberant Ctags utility interfaces nicely with Emacs and Vim and enables quick lookup for declarations and definitions of macros, typedefs, constants, enums, variables, structs and classes and their methods and implementation. You should get the latest version available at http://ctags.sourceforge.net/, which currently (2006-08-27) is v5.6, older versions included by distributions for example don't automatically recognize .mk files as makefiles, and v5.6 also added a patch needed for the vim 7.0 omni-completion feature. Ctags creates a file tags in the current directory, if not told otherwise.

As creating a tags file over all modules and directories exceeded the 2GB file size limit, a positive directory list was used to go along with a '*' parameter, identical to the one used in the mkid-script above. The file size of the tags file generated for '*' currently (m182) is 398MB.

ctags-script

For use with the OOo code base a script with a syntax similar to the ID-utils script above comes handy, suggested name: $HOME/devbase/ctags-script

#!/bin/tcsh

# Either the current module, or all modules can be ctag'ed if $1 is
# specified as literal '*' unexpanded. Will be expanded here.
# Instead of '*' _any_ shell wildcard should be possible, e.g. '{.,bf_*}'.
# NOTE: ctags on '*' exceeded 2GB file size limit if for _all_ OOo modules and
# no positive dirs list was used.

if ( { ( which ctags >> /dev/null ) } ) then
    echo generating ctags
    # Options necessary for the Vim OmniCppComplete plugin,
    # http://www.vim.org/scripts/script.php?script_id=1520
    # --c++-kinds=+p  : Adds prototypes in the database for C++ files.
    # --fields=+iaS   : Adds inheritance (i), access (a) and function 
    #                   signatures (S) informations.
    # --extra=+q      : Adds context to the tag name. Note: Without this
    #                   option, the script cannot get class members.
    set omnicppoptions="--c++-kinds=+p --fields=+iaS --extra=+q"
    if ( "$1" == "--global" ) then
        if ( ! $?UPDMINOREXT ) set UPDMINOREXT=""
        ctags -h "+.hdl.hrc" --langmap=c:+.hdl.hrc.src $omnicppoptions -R $SOLARVERSION/$INPATH/inc$UPDMINOREXT
    else
        if ( "$1" == "" ) then
            set module="."
        else
            set module="$1"
            echo module: $module
        endif
        set dirs="$module/inc $module/unx $module/mow $module/win{,32} $module/mac{,osx} $module/$INPATH/inc $module/$INPATH/misc/build $module/source $module/src $module/osl $module/rtl $module/systools $module/textenc"
        ctags -h "+.hdl.hrc" --langmap=c:+.hdl.hrc.src $omnicppoptions -R $dirs
    endif
else
    echo no ctags
endif

Ignore messages like
ctags: Warning: cannot open source file "./win32" : No such file or directory
or consider them purely informational.

Note that also here, if more than one module is to be specified, the shell filename glob-patterns and metanotations have to be passed in single quotes. Though tagging an entire OOo code base would result in a "file overflow" with more than 2GB this isn't a problem in practice, at least with Vim, don't know about other editors, since it may search the tags files going up the directory hierarchy if a tag is not found in the current directory's tags file. So generate the tags file in the modules you are interested in, using the script without parameters, then cd $SRC_ROOT and invoke scriptname --global to produce a tags file for the solver.

Cscope

Cscope is another utility to query a database for code elements. It comes with a cumbersome screen-oriented interactive tool to browse source files, but also interfaces nicely with Vim and Emacs. Unfortunately it is not able to cope with the entire OOo source base it seems, so for '*' even when called with a list of files to work on, at the end it displayed Input string too long, limit 50251 and no output was generated. Didn't investigate yet what exactly is the problem. However, using it on a subset of several modules is possible.

cscope-script

This script may be used to generate a cscope.out database file, same parameter syntax as the mkid-script and ctags-script above. Suggested name: $HOME/devbase/cscope-script

#!/bin/tcsh

# Either the current module, or all modules can be tagID'ed if $1 is
# specified as literal '*' unexpanded. Will be expanded here.
# Instead of '*' _any_ shell wildcard should be possible, e.g. '{.,bf_*}'.
# NOTE: cscope on '*' hanged if for _all_ OOo modules and no positive dirs list
# was used. Even with the generated cscope.files it still refuses to complete
# its work.

if ( { ( which cscope >> /dev/null ) } ) then
    echo generating cscope
    if ( "$1" == "" ) then
        set module="."
    else
        set module="$1"
        echo module: $module
    endif
    set dirs="$module/inc $module/unx $module/mow $module/win{,32} $module/mac{,osx} $module/$INPATH/inc $module/$INPATH/misc/build $module/source $module/src $module/osl $module/rtl $module/systools $module/textenc"
    ( find $dirs -name '*.[hc]' -o -name '*.[hc]xx' -o -name '*.[hc]pp' -o -name '*.[hs]rc' >cscope.files ) >>& /dev/null
    if ( `uname` == "SunOS" ) then
        cscope -b -c
    else
        cscope -b -c -q
    endif
else
    echo no cscope
endif

Note that the find command is executed in a subshell with stderr redirected to /dev/null to suppress all the find: ./win32: No such file or directory and the like warning messages. Remove the parentheses and the redirection in case you suspect errors in your environment.

All together now

Surely we don't want to invoke all these scripts separately all the times, so here's a small wrapper just to prove that the suggested names actually make sense ;-) let's call it $HOME/devbase/tagsID

#!/bin/tcsh

# Either the current module, or all modules can be tagID'ed if $1 is
# specified as literal '*' unexpanded. Will be expanded here.
# Instead of '*' _any_ shell wildcard should be possible, e.g. '{.,bf_*}'.
# NOTE: ctags on '*' exceeds 2GB file size limit if for _all_ OOo modules.
# NOTE: cscope on '*' hangs if for _all_ OOo modules.
# Which actually is the reason we invoke it last just in case we forgot..

${0:h}/ctags-script "$1"
${0:h}/mkid-script "$1"
${0:h}/cscope-script "$1"

The same syntax as with the other scripts applies: filename glob-patterns and metanotations have to be passed as literals, enquoted with single quotes. Invoking it in a module's directory without passing an argument creates databases for that single module.


Emacs

Emacs and Cscope

Emacs has a premade file for cscope, generally packaged with cscope. So adding

;;cscope integration
(require 'xcscope)

is enough to activate it. Major shortcut are :

  • Find-symbol : C-c s s
  • Find-global-definition : C-c s g
  • Next-symbol : C-c s n

See the documentation for more

Useful Keyboard Macros

Here is a macro for inserting include-guards for auto-generated UNO headers. If you typed

#include <com/sun/star/module/XInterfaceName.hpp>

you can call the following keyboard macro:

(fset 'bm-make-default-ifndef
   [home ?\C-s ?< ?\C-  ?\C-s ?> left ?\M-w home ?\C-m up ?# ?i ?f ?n ?d ?e ?f ?  ?_
   ?\C-y ?_ ?\C-  home C-right right ?\M-x ?r ?e ?p ?l ?a ?c ?e ?- ?s ?t ?r ?i ?n ?g
   ?\C-m ?/ ?\C-m ?_ ?\C-m end ?\C-  home C-right right ?\C-x ?\C-u end ?\C-  home
   C-right right ?\M-x ?r ?e ?p ?l ?a ?c ?e ?- ?s ?t ?r ?i ?n ?g ?\C-m ?. ?\C-m ?_
   ?\C-m home down end ?\C-m ?# ?e ?n ?d ?i ?f home up])

to get the following:

#ifndef _COM_SUN_STAR_MODULE_XINTERFACENAME_HPP_
#include <com/sun/star/module/XInterfaceName.hpp>
#endif


C++ Mode for OOo Development

This is what I (bm) use for developing OOo sources in Emacs. The only thing that bothers me from time to time is that when having large templates, the indentation is not very good. I have not found a solution for that.

(defconst ooo-c-style
  '((c-backslash-column . 70)
    (c-basic-offset . 4)
    (c-cleanup-list scope-operator)
    (c-comment-only-line-offset . 0)
    (c-electric-pound-behavior)
    (c-hungry-delete-key t)
    (c-hanging-braces-alist
     (brace-list-open)
     (brace-entry-open)
     (substatement-open after)
     (block-close . c-snug-do-while)
     (extern-lang-open after)
     (inexpr-class-close before))
    (c-hanging-colons-alist)
    (c-hanging-comment-starter-p . t)
    (c-hanging-comment-ender-p . t)
    (c-offsets-alist
     (string . c-lineup-dont-change)
     (c . c-lineup-C-comments)
     (defun-open . 0)
     (defun-close . 0)
     (defun-block-intro . +)
     (class-open . 0)
     (class-close . 0)
     (inline-open . 0)
     (inline-close . 0)
     (func-decl-cont . +)
     (knr-argdecl-intro . +)
     (knr-argdecl . 0)
     (topmost-intro . 0)
     (topmost-intro-cont . +)
     (member-init-intro . +)
     (member-init-cont . 0)
     (inher-intro . +)
     (inher-cont . c-lineup-multi-inher)
     (block-open . 0)
     (block-close . 0)
     (brace-list-open . 0)
     (brace-list-close . 0)
     (brace-list-intro . +)
     (brace-list-entry . 0)
     (brace-entry-open . 0)
     (statement . 0)
     (statement-cont . +)
     (statement-block-intro . +)
     (statement-case-intro . +)
     (statement-case-open . 0)
     (substatement . +)
     (substatement-open . 0)
     (case-label . +)
     (access-label . -)
     (label . 2)
     (do-while-closure . 0)
     (else-clause . 0)
     (catch-clause . 0)
     (comment-intro . c-lineup-comment)
     (arglist-intro . +)
     (arglist-cont . 0)
     (arglist-cont-nonempty . c-lineup-arglist)
     (arglist-close . +)
     (stream-op . c-lineup-streamop)
     (inclass . +)
     (cpp-macro . -)
     (cpp-macro-cont . c-lineup-dont-change)
     (friend . 0)
     (objc-method-intro . -1000)
     (objc-method-args-cont . c-lineup-ObjC-method-args)
     (objc-method-call-cont . c-lineup-ObjC-method-call)
     (extern-lang-open . 0)
     (extern-lang-close . 0)
     (inextern-lang . +)
     (namespace-open . 0)
     (namespace-close . 0)
     (innamespace . 0)
     (template-args-cont . ++)
     (inlambda . c-lineup-inexpr-block)
     (lambda-intro-cont . +)
     (inexpr-statement . 0)
     (inexpr-class . +)))
  "OOo Programming Style")

(defun ooo-c-mode-common-hook ()
  (c-add-style "ooo" ooo-c-style t)
  (c-set-style "ooo")
  (c-set-offset 'member-init-intro '++)
  (setq tab-width 4)
  (setq indent-tabs-mode nil)	;; use spaces instead of tabs
  (c-toggle-auto-state 1)
  (define-key c-mode-base-map "\C-m" 'newline-and-indent)
  )

(add-hook 'c-mode-common-hook 'ooo-c-mode-common-hook)

Keywords for Syntax Highlighting

What is also useful are some keywords that can be used for syntax-highlighting (font-locking).

Keywords for C++ Code

(defconst bm-throw-face-keywords
  (cons
   (regexp-opt
	(list
	 "SAL_CALL" "SAL_STATIC_CAST" "SAL_CONST_CAST" "SAL_REINTERPRET_CAST"
	 "__EXPORT" "NULL"
	 ) 'words)
   font-lock-throws-face
  ))
(defconst bm-additional-constant-keywords
  (cons
   (regexp-opt
	(list
	 "TRUE" "FALSE" "sal_True" "sal_False"
	 ) 'words)
   font-lock-constant-face
  ))
(defconst bm-strparam-keywords
  (cons
   (regexp-opt
	(list
	 "RTL_CONSTASCII_STRINGPARAM" "RTL_CONSTASCII_USTRINGPARAM"
	 "RTL_CONSTASCII_LENGTH" "RTL_TEXTENCODING_ASCII_US"
	 ) 'words)
   font-lock-constant-face
  ))
(defconst bm-exit-keywords
  (cons
   (regexp-opt
	(list
	 "return" "exit" "break" "continue" "goto"
	 ) 'words)
   font-lock-exit-face
  ))
(defconst bm-assert-keywords
  (cons
   (concat "\\<\\("
	(regexp-opt
	 (list
	  "OSL_DEBUG_ONLY" "OSL_TRACE" "OSL_ASSERT" "OSL_VERIFY" "OSL_ENSURE" "OSL_PRECOND" "OSL_POSTCOND"
	  ))
	"\\|DBG_\\sw+\\)\\>")
   font-lock-dbg-face
  ))
(defconst bm-const-keywords
  (list
   "\\<\\(\\(S\\|W\\)ID\\)_\\(\\sw+\\)"
   '(1 font-lock-sid-face)
   '(3 font-lock-constant-face)
  ))
(defconst bm-brace-keywords
  (cons
   "[][(){}]"
   font-lock-brace-face
  ))
(defconst bm-operator-keywords
  (cons
   "[|&]+"
   font-lock-bool-face
  ))

(font-lock-add-keywords
 'c++-mode
 (list
  bm-brace-keywords
  bm-operator-keywords
  bm-exit-keywords
  bm-additional-constant-keywords
  bm-const-keywords
  bm-throw-face-keywords
  bm-assert-keywords
  bm-strparam-keywords
  ))

Keywords for IDL Files

(defconst bm-idl-keywords
  (cons
   (regexp-opt
	(list
	 "module" "interface" "service" "constants" "enum" "typedef" "struct"
	 "singleton" "exception" "raises" "property" "oneway"
	 ) 'words)
   font-lock-keyword-face
  ))
(defconst bm-idl-unused-keywords
  (cons
   (regexp-opt
	(list
	 "inout" "out" "observe" "needs" "attribute"
	 "union" "switch" "case" "array"
	 ) 'words)
   font-lock-throws-face
  ))
(defconst bm-idl-types
  (cons
   (regexp-opt
	(list
	 "short" "long" "int" "hyper" "sequence" "boolean" "void" "string" "any"
	 "type" "float" "double" "byte" "unsigned"
	 ) 'words) font-lock-type-face ) )
(defconst bm-idl-builtin
  (cons
   (regexp-opt
	(list
	 "ifndef" "include" "endif" "define"
	 ) 'words)
   font-lock-builtin-face
  ))
(defconst bm-idl-property-flags
  (cons
   (regexp-opt
	(list
	 "readonly" "bound" "constrained" "maybeambiguous" "maybedefault"
	 "maybevoid" "optional" "removable" "transient" "in"
	 ) 'words)
   font-lock-constant-face
  ))

;; Add the keyword groups you want to show
(font-lock-add-keywords
 'idl-mode
 (list
  bm-idl-keywords
  bm-idl-types
  bm-idl-builtin
  bm-brace-keywords
  bm-idl-property-flags
  bm-idl-unused-keywords
))

(font-lock-add-keywords
 'idl-mode
 '(( "\\(</?\\)\\(atom\\|type\\|member\\|const\\|listing\\)\\(>\\)"
	 (1 font-lock-tag-face)
	 (2 font-lock-constant-face)
	 (3 font-lock-tag-face))
   ( "\\(<\\)\\(TRUE\\|FALSE\\|NULL\\|void\\)\\(/>\\)"
	 (1 font-lock-tag-face)
	 (2 font-lock-constant-face)
	 (3 font-lock-tag-face))
   ( "\\(@\\)\\(author\\|see\\|param\\|throws\\|returns?\\|version\\)"
	 (1 font-lock-tag-face)
	 (2 font-lock-constant-face))
 ))

Some font definitions for additional Keywords

In the above section there are some faces used, that are not part of the default set of font-locking faces. The definitions are here. You can customize them, of course, if you do not like the colors.

(defface font-lock-dbg-face
  '((((class color) (background light)) (:foreground "DeepPink"))
    (((class color) (background dark)) (:foreground "Red3"))
    (t (:bold t :italic t)))
  "Font Lock mode face used for OOo Debug messages."
  :group 'font-lock-highlighting-faces)
(defvar font-lock-dbg-face		'font-lock-dbg-face
  "Face name to use for OOo Debug messages.")

(defface font-lock-throws-face
  '((((class color) (background light)) (:foreground "Gray60"))
    (((class color) (background dark)) (:foreground "Gray80"))
    (t (:bold t :italic t)))
  "Font Lock mode face used for OOo THROWS statements."
  :group 'font-lock-highlighting-faces)
(defvar font-lock-throws-face		'font-lock-throws-face
  "Face name to use for OOo THROWS statements.")

(defface font-lock-bugid-face
  '((((class color) (background light)) (:foreground "DarkGreen" :background "Wheat1"))
    (((class color) (background dark)) (:foreground "DarkRed" :background "Gray70"))
    (t (:bold t :italic t)))
  "Font Lock mode face used for OOo BugIDs."
  :group 'font-lock-highlighting-faces)
(defvar font-lock-bugid-face		'font-lock-bugid-face
  "Face name to use for OOo BugIDs.")

(defface font-lock-sid-face
  '((((class color) (background light)) (:foreground "Aquamarine4"))
    (((class color) (background dark)) (:foreground "White"))
    (t (:bold t :italic t)))
  "Font Lock mode face used for OOo Slot IDs."
  :group 'font-lock-highlighting-faces)
(defvar font-lock-sid-face		'font-lock-sid-face
  "Face name to use for OOo Slot IDs.")

(defface font-lock-brace-face
  '((((class color) (background light)) (:foreground "Red2"))
    (((class color) (background dark)) (:foreground "White"))
    (t (:bold t :italic t)))
  "Font Lock mode face used for braces ()[]{} and the comma."
  :group 'font-lock-highlighting-faces)
(defvar font-lock-brace-face		'font-lock-brace-face
  "Face name to use for braces.")

(defface font-lock-bool-face
  '((((class color) (background light)) (:foreground "forest green"))
    (((class color) (background dark)) (:foreground "lime green"))
    (t (:bold t :italic t)))
  "Font Lock mode face used for boolean operators."
  :group 'font-lock-highlighting-faces)
(defvar font-lock-bool-face		'font-lock-bool-face
  "Face name to use for boolean operators.")

(defface font-lock-exit-face
  '((((class color) (background light)) (:background "ivory1" :foreground "blue3"))
    (((class color) (background dark)) (:foreground "yellow"))
    (t (:bold t :italic t)))
  "Font Lock mode face used for exit operations like return, break and exit."
  :group 'font-lock-highlighting-faces)
(defvar font-lock-exit-face		'font-lock-exit-face
  "Face name to use for exit operations like return, break and exit.")

(defface font-lock-tag-face
  '((t (:foreground "dodger blue")))
  "Font Lock mode face used for tags in IDL files."
  :group 'font-lock-highlighting-faces)
(defvar font-lock-tag-face		'font-lock-tag-face
  "Face name to use for tags in IDL files.")

A note for developers on WIN\DOS

Please don't complicate life for developers on other platforms by interspersing sources with CarriageReturn characters before linefeeds, which especially is extremely nasty in patches contributed. As you probably can't patch your kernel's IO to simply not automatically write CrLf instead of Lf in text files, when using Vim use this setting:

"set fileformats=dos,unix  " Vim's default on DOS/WIN/OS2
set fileformats=unix,dos  " Vim's default on UNX, also use it on DOS
Personal tools