I'd like to be able to get the file path of a link in emacs org-mode as a string, which I could then parse in various ways and return to org-open-file. So, e.g., the link [[file:/path/to/file.org]][link text] would return the string /path/to/file.org. I'm betting this is basic elisp, but I'm new to elisp.
You can access this information from the Org element API. Here
is an example that gets the path and opens it in a Dired buffer.
(defun km/org-link-dired-jump ()
"Open Dired for directory of file link at point."
(interactive)
(let ((el (org-element-lineage (org-element-context) '(link) t)))
(unless (and el (equal (org-element-property :type el) "file"))
(user-error "Not on file link"))
(dired-jump 'other-window
(expand-file-name (org-element-property :path el)))))
(This depends on Org version 8.3 or greater.)
Related
I have a list of org files under a directory:
> org-file1.org
> org-file2.org
> org-file3.org
> ...
> org-fileN.org
I want to extract their titles (using #+title tag) as links as follows:
[[file:org-file1.org][title for org file 1]]
[[file:org-file2.org][title for org file 2]]
[[file:org-file3.org][title for org file 3]]
...
[[file:org-fileN.org][title for org file N]]
How can I extract these as a list using Emacs Lisp?
You could use the org-element-api for this, but in this case it is probably easier/faster to simply search by regexp using looking-at-p. To get all files in a directory, there is directory-files(-recursively). Then finally you could achieve it using the following function:
(defun extract-org-directory-titles-as-list (&optional dir)
(interactive "D")
(print
(delete nil
(let ((case-fold-search t))
(mapcar (lambda (f)
(when (string-match "org$" f)
(with-temp-buffer
(insert-file-contents-literally
(concat (file-name-as-directory dir) f))
(while (and (not (looking-at-p "#\\+TITLE:"))
(not (eobp)))
(forward-line))
(when (not (eobp))
(cons f (substring (thing-at-point 'line) 9 -1))))))
(directory-files dir))))))
(defun insert-directory-org-file-titles (&optional dir)
(interactive "D")
(let ((files-titles (extract-org-directory-titles-as-list dir)))
(dolist (ft files-titles)
(insert (concat "[[file:" (car ft)"][" (cdr ft) "]]\n")))))
If you prefer to just have it as a (a)list, like you asked, then just use extract-org-directory-titles-as-list.
Here's some code that can be used on a single file to get the title. It's part of an org mode file that can be used for testing. Save it as an Org mode file, visit it in Emacs and type C-c C-c on the code block. Change the title and evaluate the code block again.
#+OPTIONS: toc:nil
#+TITLE: This is a very nice title, don't you think?
#+AUTHOR: A.U. Thor
* foo
bar
* Code
#+begin_src elisp :results drawer
(save-excursion
(goto-char (point-min))
(let ((case-fold-search t))
(search-forward-regexp "^#\\+TITLE:")
(org-element-property :value (org-element-context (org-element-at-point)))))
#+end_src
#+RESULTS:
:results:
This is a very nice title, don't you think?
:end:
The code block goes to the beginning of the buffer, searches forward for the string #+TITLE: at the beginning of a line (the search is case-insensitive) and then uses some functions from the org-element library to parse the buffer at point, get the context and get the :value property out of it.
In order to make it into a complete solution, you have to:
make it more robust: check that this is a title keyword not just some random junk that happened to satisfy the match, although that's unlikely; but better safe than sorry.
wrap it in a loop that does a find-file on every file in the directory and gets the title for each file and returns a list of tuples: (filename title) that you can easily use to create your links.
I use dired to browse a directory and its sub-directories with i (dired-maybe-insert-subdir)
When the cursor is in a sub-directory, and I do C-x C-f, the default directory is the root of my buffer, is there a way to change that?
So if I visit /foo/bar and then I insert in bar's buffer the content of /foo/bar/baz and have my cursor there, get a mini-buffer with /foo/bar/baz/ when I ask to visit a file (with the default keybinding or another, that doesn't matter to me).
The following is my own solution.
(defun dired-subdir-aware (orig-fun &rest args)
(if (eq major-mode 'dired-mode)
(let ((default-directory (dired-current-directory)))
(apply orig-fun args))
(apply orig-fun args)))
(advice-add 'find-file-read-args :around 'dired-subdir-aware)
I just learned how to use the advice function to augment existing functions (and macros). See (elisp) Advising Functions.
Now if you go to a subdir under dired and run C-x C-f, you will be prompted with a correct path.
Update:
Recently, I began to play with (ido) Top. With dired-subdir-aware defined, I easily extend ido-find-file to recognize subdir with the following code.
(dolist (fun '(find-file-read-args ido-expand-directory))
(advice-add fun :around 'dired-subdir-aware))
No, default-directory is local to a buffer, not just part of a buffer. This is by design, and there is no way to change it.
But you could of course define a command that picks up the subdir directory and then binds default-directory to that while it reads a file name to visit, etc.).
For example, this command will read a file name with the default directory being what you want, and then visit that file:
(defun foo (file)
"..."
(interactive
(let ((default-directory (dired-current-directory)))
(list (read-file-name "File: "))))
(find-file file))
And, out of the box, you can certainly visit the subdir in another Dired buffer, where default-directory is what you want. Commands such as dired-do-find-marked-files (F) and dired-display-file (C-o) do that.
But why do you want default-directory to reflect the subdir whose listing you are in? What's your use case?
I refined Drew's answer to a function that does exactly what I wanted:
(defun dired-find-file ()
"Find a file in the current dired directory"
(interactive)
(let ((default-directory (dired-current-directory)))
(find-file (read-file-name "Find file:"))))
I then bound this function to a convenient key combination:
(add-hook 'dired-mode-hook
(lambda ()
(local-set-key (kbd "C-x M-f") 'dired-find-file)))
I have the function below in my .emacs which I use quite frequently to put the filename/path of a local file in the current buffer. It is working nicely, however, I'd like it to have ido completion. But I seem unable to achieve that... maybe you can help me.
(defun insert-file-name (filename &optional args)
"Insert name of file FILENAME into buffer after point.
Prefixed with \\[universal-argument], expand the file name to
its fully canocalized path. See `expand-file-name'.
Prefixed with \\[negative-argument], use relative path to file
name from current directory, `default-directory'. See
`file-relative-name'.
The default with no prefix is to insert the file name exactly as
it appears in the minibuffer prompt."
;; Based on insert-file in Emacs -- ashawley 20080926
(interactive "*fInsert file name: \nP")
(cond ((eq '- args)
(insert (expand-file-name filename)))
((not (null args))
(insert (filename)))
(t
(insert (file-relative-name filename)))))
With ido-everywhere turned-on, (interactive "f") will normally use ido-read-file-name, which will not only provide automatic completion for your function, but also almost everywhere.
If you want to have ido completion only for this function, but not everywhere, you can explicitly call ido-read-file-name in the interactive form. One side effect of using ido in your case is that it seems to always return a full path, making the distinction between filename and (expand-file-name filename) effectless.
(defun insert-file-name (filename &optional args)
"Insert name of file FILENAME into buffer after point.
Prefixed with \\[universal-argument], expand the file name to
its fully canocalized path. See `expand-file-name'.
Prefixed with \\[negative-argument], use relative path to file
name from current directory, `default-directory'. See
`file-relative-name'.
The default with no prefix is to insert the file name exactly as
it appears in the minibuffer prompt."
;; Based on insert-file in Emacs -- ashawley 20080926
(interactive `(,(ido-read-file-name "File Name: ")
,current-prefix-arg))
(cond ((eq '- args)
(insert (expand-file-name filename)))
((not (null args))
(insert filename))
(t
(insert (file-relative-name filename)))))
I am using org-mode in Emacs to document my development activities. One of the tasks which I must continuously do by hand is to describe areas of code. Emacs has a very nice Bookmark List: create a bookmark with CTRL-x r m, list them with CTRL-x r l. This is very useful, but is not quite what I need.
Org-mode has the concept of link, and the command org-store-link will record a link to the current position in any file, which can be pasted to the org-file. The problem with this is two-fold:
It is stored as an org-link, and the linked position is not directly visible (just the description).
It is stored in the format file/search, which is not what I want.
I need to have the bookmark in textual form, so that I can copy paste it into org-mode, end edit it if needed, with a simple format like this:
absolute-file-path:line
And this must be obtained from the current point position. The workflow would be as simple as:
Go to the position which I want to record
Call a function: position-to-kill-ring (I would bind this to a keyboard shortcut)
Go to the org-mode buffer.
Yank the position.
Edit if needed (sometimes I need to change absolute paths by relative paths, since my code is in a different location in different machines)
Unfortunately my lisp is non-existent, so I do not know how to do this. Is there a simple solution to my problem?
(defun position-to-kill-ring ()
"Copy to the kill ring a string in the format \"file-name:line-number\"
for the current buffer's file name, and the line number at point."
(interactive)
(kill-new
(format "%s:%d" (buffer-file-name) (save-restriction
(widen) (line-number-at-pos)))))
You want to use the org-create-file-search-functions and org-execute-file-search-functions hooks.
For example, if you need the search you describe for text-mode files, use this:
(add-hook 'org-create-file-search-functions
'(lambda ()
(when (eq major-mode 'text-mode)
(number-to-string (line-number-at-pos)))))
(add-hook 'org-execute-file-search-functions
'(lambda (search-string)
(when (eq major-mode 'text-mode)
(goto-line (string-to-number search-string)))))
Then M-x org-store-link RET will do the right thing (store a line number as the search string) and C-c C-o (i.e. M-x org-open-at-point RET) will open the file and go to this line number.
You can of course check for other modes and/or conditions.
An elisp beginner myself I though of it as a good exercise et voila:
Edit: Rewrote it using the format methode, but I still think not storing it to the kill-ring is less intrusive in my workflow (don't know about you). Also I have added the capability to add column position.
(defvar current-file-reference "" "Global variable to store the current file reference")
(defun store-file-line-and-col ()
"Stores the current file, line and column point is at in a string in format \"file-name:line-number-column-number\". Insert the string using \"insert-file-reference\"."
(interactive)
(setq current-file-reference (format "%s:%d:%d" (buffer-file-name) (line-number-at-pos) (current-column))))
(defun store-file-and-line ()
"Stores the current file and line oint is at in a string in format \"file-name:line-number\". Insert the string using \"insert-file-reference\"."
(interactive)
(setq current-file-reference (format "%s:%d" (buffer-file-name) (line-number-at-pos))))
(defun insert-file-reference ()
"Inserts the value stored for current-file-reference at point."
(interactive)
(if (string= "" current-file-reference)
(message "No current file/line/column set!")
(insert current-file-reference)))
Not tested extensively but working for me. Just hit store-file-and-line or store-file-line-and-col to store current location and insert-file-reference to insert the stored value at point.
BTW, if you want something better than FILE:LINE, you can try to use add-log-current-defun (in add-log.el) which should return the name of the current function.
;; Insert a org link to the function in the next window
(defun insert-org-link-to-func ()
(interactive)
(insert (with-current-buffer (window-buffer (next-window))
(org-make-link-string
(concat "file:" (buffer-file-name)
"::" (number-to-string (line-number-at-pos)))
(which-function)
))))
This func generates link with the function name as the description.
Open two windows, one is the org file and the other is src code.
Then M-x insert-org-link-to-func RET
I have generated the TAGS file using ctags for the *.h and *.cpp file in a directory.
How to find the files in TAGS file.
Assuming i have generated the TAGS file for the files one.h two.h three.h. What is the command to find the file one.h, two.h, three.h not the tags in those files.
Assuming that you simply want to know how to use the TAGS file...
Load the TAGS file with:
M-x visit-tags-table RET TAGS file or parent directory RET
Then you can use it with:
M-. (i.e. find-tag)
M-x tags-search RET pattern RET
(with M-, to move to each successive match)
M-x tags-apropos RET pattern RET
M-x tags-query-replace RET pattern RET replacement RET
Those are the defaults. Naturally there are enhancements available:
http://www.emacswiki.org/emacs/EmacsTags
Personally I use etags-select (which you can obtain via ELPA), and I have M-. bound to etags-select-find-tag.
I wrote this a couple of years ago, I haven't gotten around to releasing it yet, though... Enjoy!
The function tags-extra-find-file will let you visit a file in the current tags table, complete with file-name completion. This is perfect if you have many source files spread out over a large number of directories. (Honestly, I use this at least one hundred times every day...)
(defun tags-extra-get-all-tags-files ()
"Return all, fully qualified, file names."
(save-excursion
(let ((first-time t))
(while (visit-tags-table-buffer (not first-time))
(setq first-time nil)
(setq res
(append res (mapcar 'expand-file-name (tags-table-files)))))))
res))
(defun tags-extra-find-file (name)
"Edit file named NAME that is part of the current tags table.
The file name should not include parts of the path."
(interactive
(list
(completing-read "Name of file: "
;; Make an a-list of all files without path.
(mapcar
(lambda (file)
(cons (file-name-nondirectory file) nil))
(tags-extra-get-all-tags-files)))))
(let ((files (tags-extra-get-all-tags-files))
(done nil)
(name-re (concat "^" (regexp-quote name) "$")))
(while (and (not done)
files)
(let ((case-fold-search t))
(if (string-match name-re (file-name-nondirectory (car files)))
(setq done t)
(setq files (cdr files)))))
(if files
(find-file (car files))
(error "File not found in the tags table."))))
This correction works in emacs 26.3. With an old etags, M-. would accept a file name, such as Setup.cpp, and visit the file wherever etags found it. Very handy with lots of files in many directories. No need to remember what directory the file was in to visit it. I'm surprised that's not an out-of-the-box feature!
(defun tags-extra-get-all-tags-files ()
"Return all, fully qualified, file names."
(setq res nil)
(save-excursion
(let ((first-time t))
(while (visit-tags-table-buffer (not first-time))
(setq first-time nil)
(setq res
(append res (mapcar 'expand-file-name (tags-table-files)))))))
res)
Something like this? It might not be entirely robust.
(defun visit-tags-table-and-files (file)
"Run `visit-tags-table FILE', then visit all the referenced files."
(interactive "fTags file: ")
(visit-tags-table file)
(save-excursion
(set-buffer (get-file-buffer tags-file-name))
(mapc #'find-file (tags-table-files)) ) )
Thanks #Lindydancer's answer and emacswiki ido. The emacswiki version only support one tag file. Combining them which allows me jumping to any file in all TAG files. Petty close to the sublime text's goto anything.
Here is the code.
;;; using ido find file in tag files
(defun tags-extra-get-all-tags-files ()
"Return all, fully qualified, file names."
(save-excursion
(let ((first-time t)
(res nil))
(while (visit-tags-table-buffer (not first-time))
(setq first-time nil)
(setq res
(append res (mapcar 'expand-file-name (tags-table-files)))))
res)))
(defun ido-find-file-in-tag-files ()
(interactive)
(find-file
(expand-file-name
(ido-completing-read
"Files: " (tags-extra-get-all-tags-files) nil t))))