Index: lisp/files.el =================================================================== RCS file: /sources/emacs/emacs/lisp/files.el,v retrieving revision 1.932 diff -u -r1.932 files.el --- lisp/files.el 15 Oct 2007 02:07:46 -0000 1.932 +++ lisp/files.el 17 Oct 2007 19:51:46 -0000 @@ -2059,7 +2059,7 @@ ("\\.\\(as\\|mi\\|sm\\)2\\'" . snmpv2-mode) ("\\.\\(diffs?\\|patch\\|rej\\)\\'" . diff-mode) ("\\.\\(dif\\|pat\\)\\'" . diff-mode) ; for MSDOG - ("\\.[eE]?[pP][sS]\\'" . ps-mode) + ("\\(?:DVI\\|EPS\\|P\\(?:DF\\|S\\)\\|dvi\\|eps\\|p\\(?:df\\|s\\)\\)" . doc-view-mode) ("configure\\.\\(ac\\|in\\)\\'" . autoconf-mode) ("BROWSE\\'" . ebrowse-tree-mode) ("\\.ebrowse\\'" . ebrowse-tree-mode) Index: lisp/doc-view.el =================================================================== RCS file: /sources/emacs/emacs/lisp/doc-view.el,v retrieving revision 1.4 diff -u -r1.4 doc-view.el --- lisp/doc-view.el 3 Oct 2007 23:39:58 -0000 1.4 +++ lisp/doc-view.el 17 Oct 2007 19:51:48 -0000 @@ -5,7 +5,7 @@ ;; Author: Tassilo Horn ;; Maintainer: Tassilo Horn ;; Keywords: files, pdf, ps, dvi -;; Version: <2007-10-02 Tue 18:21> +;; Version: <2007-10-17 Wed 21:22> ;; This file is part of GNU Emacs. @@ -37,16 +37,19 @@ ;; inside an Emacs buffer. This buffer uses `doc-view-mode' which provides ;; convenient key bindings for browsing the document. ;; -;; To use it simply do +;; To use it simply open a document file with ;; -;; M-x doc-view RET +;; C-x C-f ~/path/to/document RET ;; -;; and you'll be queried for a document to open. +;; and the document will be converted and displayed, if your emacs supports png +;; images. With `C-c C-c' you can toggle between the rendered images +;; representation and the source text representation of the document. With +;; `C-c C-e' you can switch to an appropriate editing mode for the document. ;; ;; Since conversion may take some time all the PNG images are cached in a ;; subdirectory of `doc-view-cache-directory' and reused when you want to view -;; that file again. This reusing can be omitted if you provide a prefx -;; argument to `doc-view'. To delete all cached files use +;; that file again. To reconvert a document hit `g' (`doc-view-reconvert-doc') +;; when displaying the document. To delete all cached files use ;; `doc-view-clear-cache'. To open the cache with dired, so that you can tidy ;; it out use `doc-view-dired-cache'. ;; @@ -67,8 +70,6 @@ ;; bottom-right corner of the desired slice. To reset the slice use ;; `doc-view-reset-slice' (bound to `s r'). ;; -;; Dired users should have a look at `doc-view-dired'. -;; ;; You can also search within the document. The command `doc-view-search' ;; (bound to `C-s') queries for a search regexp and initializes a list of all ;; matching pages and messages how many match-pages were found. After that you @@ -80,17 +81,16 @@ ;; conversion. When that finishes and you're still viewing the document ;; (i.e. you didn't switch to another buffer) you're queried for the regexp ;; then. +;; +;; Dired users can simply hit `v' on a document file. If it's a PS, PDF or DVI +;; it will be opened using `doc-view-mode'. +;; ;;; Configuration: -;; Basically doc-view should be quite usable with its standard settings, so -;; putting -;; -;; (require 'doc-view) -;; -;; into your `user-init-file' should be enough. If the images are too small or -;; too big you should set the "-rXXX" option in `doc-view-ghostscript-options' -;; to another value. (The bigger your screen, the higher the value.) +;; If the images are too small or too big you should set the "-rXXX" option in +;; `doc-view-ghostscript-options' to another value. (The bigger your screen, +;; the higher the value.) ;; ;; This and all other options can be set with the customization interface. ;; Simply do @@ -201,7 +201,10 @@ (defvar doc-view-current-info nil "Only used internally.") -;;;; DocView Keymap +(defvar doc-view-current-display nil + "Only used internally.") + +;;;; DocView Keymaps (defvar doc-view-mode-map (let ((map (make-sparse-keymap))) @@ -235,9 +238,26 @@ (define-key map (kbd "M-v") 'scroll-down) ;; Show the tooltip (define-key map (kbd "C-t") 'doc-view-show-tooltip) + ;; Toggle between text and image display or editing + (define-key map (kbd "C-c C-c") 'doc-view-toggle-display) + (define-key map (kbd "C-c C-e") 'doc-view-edit-doc) + ;; Reconvert the current document + (define-key map (kbd "g") 'doc-view-reconvert-doc) (suppress-keymap map) map) - "Keymap used by `doc-view-mode'.") + "Keymap used by `doc-view-mode' when displaying a doc as a set of images.") + +(defvar doc-view-mode-text-map + (let ((map (make-sparse-keymap))) + ;; Toggle between text and image display or editing + (define-key map (kbd "C-c C-c") 'doc-view-toggle-display) + (define-key map (kbd "C-c C-e") 'doc-view-edit-doc) + ;; Killing/burying the buffer (and the process) + (define-key map (kbd "q") 'bury-buffer) + (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer) + (define-key map (kbd "C-x k") 'doc-view-kill-proc-and-buffer) + map) + "Keymap used by `doc-view-mode' when displaying a document as text.") ;;;; Navigation Commands @@ -271,16 +291,16 @@ (setq contexts (concat contexts " - \"" m "\"\n"))) contexts))))) ;; Update the buffer - (setq inhibit-read-only t) - (erase-buffer) - (let ((beg (point))) - (doc-view-insert-image (nth (1- page) doc-view-current-files) - :pointer 'arrow) - (put-text-property beg (point) 'help-echo doc-view-current-info)) - (insert "\n" doc-view-current-info) - (goto-char (point-min)) - (forward-char) - (setq inhibit-read-only nil))) + (let ((inhibit-read-only t)) + (erase-buffer) + (let ((beg (point))) + (doc-view-insert-image (nth (1- page) doc-view-current-files) + :pointer 'arrow) + (put-text-property beg (point) 'help-echo doc-view-current-info)) + (insert "\n" doc-view-current-info) + (goto-char (point-min)) + (forward-char)) + (set-buffer-modified-p nil))) (defun doc-view-next-page (&optional arg) "Browse ARG pages forward." @@ -317,42 +337,62 @@ (error (doc-view-previous-page) (goto-char (point-max))))) +(defun doc-view-kill-proc () + "Kill the current converter process." + (interactive) + (when doc-view-current-converter-process + (kill-process doc-view-current-converter-process)) + (when doc-view-current-timer + (cancel-timer doc-view-current-timer) + (setq doc-view-current-timer nil)) + (setq mode-line-process nil)) + (defun doc-view-kill-proc-and-buffer () "Kill the current converter process and buffer." (interactive) + (doc-view-kill-proc) (when (eq major-mode 'doc-view-mode) - (when doc-view-current-converter-process - (kill-process doc-view-current-converter-process)) - (when doc-view-current-timer - (cancel-timer doc-view-current-timer) - (setq doc-view-current-timer nil)) (kill-buffer (current-buffer)))) ;;;; Conversion Functions -(defun doc-view-file-name-to-directory-name (file) - "Return the directory where the png files of FILE should be saved. - -It'a a subdirectory of `doc-view-cache-directory'." +(defun doc-view-reconvert-doc (&rest args) + "Reconvert the current document. +Should be invoked when the cached images aren't up-to-date." + (interactive) + (let ((inhibit-read-only t) + (doc doc-view-current-doc)) + (doc-view-kill-proc) + ;; Clear the old cached files + (when (file-exists-p (doc-view-current-cache-dir)) + (dired-delete-file (doc-view-current-cache-dir) 'always)) + (doc-view-kill-proc-and-buffer) + (find-file doc))) + +(defun doc-view-current-cache-dir () + "Return the directory where the png files of the current doc should be saved. +It's a subdirectory of `doc-view-cache-directory'." (if doc-view-current-cache-dir doc-view-current-cache-dir - (file-name-as-directory - (concat (file-name-as-directory doc-view-cache-directory) - (with-temp-buffer - (insert-file-contents-literally file) - (md5 (current-buffer))))))) + (setq doc-view-current-cache-dir + (file-name-as-directory + (concat (file-name-as-directory doc-view-cache-directory) + (let ((doc doc-view-current-doc)) + (with-temp-buffer + (insert-file-contents-literally doc) + (md5 (current-buffer))))))))) (defun doc-view-dvi->pdf-sentinel (proc event) "If DVI->PDF conversion was successful, convert the PDF to PNG now." (if (not (string-match "finished" event)) (message "DocView: dvi->pdf process changed status to %s." event) (set-buffer (process-get proc 'buffer)) - (setq doc-view-current-converter-process nil) + (setq doc-view-current-converter-process nil + mode-line-process nil) (message "DocView: finished conversion from DVI to PDF!") ;; Now go on converting this PDF to a set of PNG files. (let* ((pdf (process-get proc 'pdf-file)) - (png (concat (doc-view-file-name-to-directory-name - doc-view-current-doc) + (png (concat (doc-view-current-cache-dir) "page-%d.png"))) (doc-view-pdf/ps->png pdf png)))) @@ -360,9 +400,10 @@ "Convert DVI to PDF asynchrounously." (message "DocView: converting DVI to PDF now!") (setq doc-view-current-converter-process - (start-process "doc-view-dvi->pdf" doc-view-conversion-buffer + (start-process "dvi->pdf" doc-view-conversion-buffer doc-view-dvipdfm-program - "-o" pdf dvi)) + "-o" pdf dvi) + mode-line-process (list (format ":%s" doc-view-current-converter-process))) (set-process-sentinel doc-view-current-converter-process 'doc-view-dvi->pdf-sentinel) (process-put doc-view-current-converter-process 'buffer (current-buffer)) @@ -373,7 +414,8 @@ (if (not (string-match "finished" event)) (message "DocView: converter process changed status to %s." event) (set-buffer (process-get proc 'buffer)) - (setq doc-view-current-converter-process nil) + (setq doc-view-current-converter-process nil + mode-line-process nil) (when doc-view-current-timer (cancel-timer doc-view-current-timer) (setq doc-view-current-timer nil)) @@ -386,11 +428,12 @@ (message "DocView: converting PDF or PS to PNG now!") (setq doc-view-current-converter-process (apply 'start-process - (append (list "doc-view-pdf/ps->png" doc-view-conversion-buffer + (append (list "pdf/ps->png" doc-view-conversion-buffer doc-view-ghostscript-program) doc-view-ghostscript-options (list (concat "-sOutputFile=" png)) - (list pdf-ps)))) + (list pdf-ps))) + mode-line-process (list (format ":%s" doc-view-current-converter-process))) (process-put doc-view-current-converter-process 'buffer (current-buffer)) (set-process-sentinel doc-view-current-converter-process @@ -398,7 +441,7 @@ (when doc-view-conversion-refresh-interval (setq doc-view-current-timer (run-at-time "1 secs" doc-view-conversion-refresh-interval - 'doc-view-display + 'doc-view-display-maybe doc-view-current-doc)))) (defun doc-view-pdf->txt-sentinel (proc event) @@ -407,7 +450,8 @@ (let ((current-buffer (current-buffer)) (proc-buffer (process-get proc 'buffer))) (set-buffer proc-buffer) - (setq doc-view-current-converter-process nil) + (setq doc-view-current-converter-process nil + mode-line-process nil) (message "DocView: finished conversion from PDF to TXT!") ;; If the user looks at the DocView buffer where the conversion was ;; performed, search anew. This time it will be queried for a regexp. @@ -418,9 +462,10 @@ "Convert PDF to TXT asynchrounously." (message "DocView: converting PDF to TXT now!") (setq doc-view-current-converter-process - (start-process "doc-view-pdf->txt" doc-view-conversion-buffer + (start-process "pdf->txt" doc-view-conversion-buffer doc-view-pdftotext-program "-raw" - pdf txt)) + pdf txt) + mode-line-process (list (format ":%s" doc-view-current-converter-process))) (set-process-sentinel doc-view-current-converter-process 'doc-view-pdf->txt-sentinel) (process-put doc-view-current-converter-process 'buffer (current-buffer))) @@ -429,65 +474,44 @@ (if (not (string-match "finished" event)) (message "DocView: converter process changed status to %s." event) (set-buffer (process-get proc 'buffer)) - (setq doc-view-current-converter-process nil) + (setq doc-view-current-converter-process nil + mode-line-process nil) (message "DocView: finished conversion from PS to PDF!") ;; Now we can transform to plain text. (doc-view-pdf->txt (process-get proc 'pdf-file) - (concat (doc-view-file-name-to-directory-name - doc-view-current-doc) + (concat (doc-view-current-cache-dir) "doc.txt")))) (defun doc-view-ps->pdf (ps pdf) "Convert PS to PDF asynchronously." (message "DocView: converting PS to PDF now!") (setq doc-view-current-converter-process - (start-process "doc-view-ps->pdf" doc-view-conversion-buffer + (start-process "ps->pdf" doc-view-conversion-buffer doc-view-ps2pdf-program - ps pdf)) + ps pdf) + mode-line-process (list (format ":%s" doc-view-current-converter-process))) (set-process-sentinel doc-view-current-converter-process 'doc-view-ps->pdf-sentinel) (process-put doc-view-current-converter-process 'buffer (current-buffer)) (process-put doc-view-current-converter-process 'pdf-file pdf)) -(defun doc-view-convert-doc (doc) - "Convert DOC to a set of png files, one file per page. - -Those files are saved in the directory given by -`doc-view-file-name-to-directory-name'." +(defun doc-view-convert-current-doc () + "Convert `doc-view-current-doc' to a set of png files, one file per page. +Those files are saved in the directory given by the function +`doc-view-current-cache-dir'." (clear-image-cache) - (let* ((dir (doc-view-file-name-to-directory-name doc)) - (png-file (concat (file-name-as-directory dir) "page-%d.png"))) - (when (file-exists-p dir) - (dired-delete-file dir 'always)) - (make-directory dir t) - (if (not (string= (file-name-extension doc) "dvi")) + (let ((png-file (concat (doc-view-current-cache-dir) + "page-%d.png"))) + (make-directory doc-view-current-cache-dir t) + (if (not (string= (file-name-extension doc-view-current-doc) "dvi")) ;; Convert to PNG images. - (doc-view-pdf/ps->png doc png-file) + (doc-view-pdf/ps->png doc-view-current-doc png-file) ;; DVI files have to be converted to PDF before GhostScript can process ;; it. - (doc-view-dvi->pdf doc - (concat (file-name-as-directory dir) + (doc-view-dvi->pdf doc-view-current-doc + (concat (file-name-as-directory doc-view-current-cache-dir) "doc.pdf"))))) -;;;; DocView Mode - -(define-derived-mode doc-view-mode nil "DocView" - "Major mode in DocView buffers. - -\\{doc-view-mode-map}" - :group 'doc-view - (setq buffer-read-only t) - (make-local-variable 'doc-view-current-files) - (make-local-variable 'doc-view-current-doc) - (make-local-variable 'doc-view-current-image) - (make-local-variable 'doc-view-current-page) - (make-local-variable 'doc-view-current-converter-process) - (make-local-variable 'doc-view-current-timer) - (make-local-variable 'doc-view-current-slice) - (make-local-variable 'doc-view-current-cache-dir) - (make-local-variable 'doc-view-current-info) - (make-local-variable 'doc-view-current-search-matches)) - ;;;; Slicing (defun doc-view-set-slice (x y width height) @@ -555,19 +579,22 @@ nil (string< a b)))) +(defun doc-view-display-maybe (doc) + "Call `doc-view-display' iff we're in the image display." + (when (eq doc-view-current-display 'image) + (doc-view-display doc))) + (defun doc-view-display (doc) "Start viewing the document DOC." - (let ((dir (doc-view-file-name-to-directory-name doc))) - (set-buffer (format "*DocView: %s*" doc)) - (setq doc-view-current-files - (sort (directory-files dir t "page-[0-9]+\\.png" t) - 'doc-view-sort)) - (when (> (length doc-view-current-files) 0) - (doc-view-goto-page doc-view-current-page)))) + (set-buffer (get-file-buffer doc)) + (setq doc-view-current-files + (sort (directory-files (doc-view-current-cache-dir) t + "page-[0-9]+\\.png" t) + 'doc-view-sort)) + (when (> (length doc-view-current-files) 0) + (doc-view-goto-page doc-view-current-page))) (defun doc-view-buffer-message () - (setq inhibit-read-only t) - (erase-buffer) (insert (propertize "Welcome to DocView!" 'face 'bold) "\n" " @@ -580,12 +607,58 @@ `q' : Bury this buffer. Conversion will go on in background. `k' : Kill the conversion process and this buffer.\n") - (setq inhibit-read-only nil)) + (set-buffer-modified-p nil)) (defun doc-view-show-tooltip () (interactive) (tooltip-show doc-view-current-info)) +;;;;; Toggle between text and image display + +(defun doc-view-toggle-display () + "Start or stop displaying a document file as a set of images. +This command toggles between showing the text of the document +file and showing the document as a set of images." + (interactive) + (if (get-text-property (point-min) 'display) + ;; Switch to text display + (let ((inhibit-read-only t)) + (erase-buffer) + (insert-file-contents doc-view-current-doc) + (use-local-map doc-view-mode-text-map) + (setq mode-name "DocView[text]" + doc-view-current-display 'text) + (if (called-interactively-p) + (message "Repeat this command to go back to displaying the file as images"))) + ;; Switch to image display + (let ((inhibit-read-only t)) + (erase-buffer) + (doc-view-buffer-message) + (setq doc-view-current-page (or doc-view-current-page 1)) + (if (file-exists-p (doc-view-current-cache-dir)) + (progn + (message "DocView: using cached files!") + (doc-view-display doc-view-current-doc)) + (doc-view-convert-current-doc)) + (use-local-map doc-view-mode-map) + (setq mode-name (format "DocView") + doc-view-current-display 'image) + (if (called-interactively-p) + (message "Repeat this command to go back to displaying the file as text")))) + (set-buffer-modified-p nil)) + +;;;;; Leave doc-view-mode and open the file for edit + +(defun doc-view-edit-doc () + "Leave `doc-view-mode' and open the current doc with an appropriate editing mode." + (interactive) + (let ((filename doc-view-current-doc) + (auto-mode-alist (append '(("\\.[eE]?[pP][sS]\\'" . ps-mode) + ("\\.\\(pdf\\|PDF\\|dvi\\|DVI\\)$" . fundamental-mode)) + auto-mode-alist))) + (kill-buffer (current-buffer)) + (find-file filename))) + ;;;; Searching (defun doc-view-search-internal (regexp file) @@ -636,8 +709,7 @@ (interactive) ;; New search, so forget the old results. (setq doc-view-current-search-matches nil) - (let ((txt (concat (doc-view-file-name-to-directory-name - doc-view-current-doc) + (let ((txt (concat (doc-view-current-cache-dir) "doc.txt"))) (if (file-readable-p txt) (progn @@ -660,14 +732,12 @@ ;; Doc is a PS, so convert it to PDF (which will be converted to ;; TXT thereafter). (doc-view-ps->pdf doc-view-current-doc - (concat (doc-view-file-name-to-directory-name - doc-view-current-doc) + (concat (doc-view-current-cache-dir) "doc.pdf"))) ((string= ext "dvi") ;; Doc is a DVI. This means that a doc.pdf already exists in its ;; cache subdirectory. - (doc-view-pdf->txt (concat (doc-view-file-name-to-directory-name - doc-view-current-doc) + (doc-view-pdf->txt (concat (doc-view-current-cache-dir) "doc.pdf") txt)) (t (error "DocView doesn't know what to do")))))))) @@ -698,52 +768,42 @@ (y-or-n-p "No more matches before current page. Wrap to last match? ")) (doc-view-goto-page (caar (last doc-view-current-search-matches))))))) -;;;; User Interface Commands +;;;; User interface commands and the mode + +(put 'doc-view-mode 'mode-class 'special) ;;;###autoload -(defun doc-view (no-cache &optional file) - "Convert FILE to png and start viewing it. -If no FILE is given, query for on. -If this FILE is still in the cache, don't convert and use the -existing page files. With prefix arg NO-CACHE, don't use the -cached files and convert anew." - (interactive "P") - (if (not (and (image-type-available-p 'png) - (display-images-p))) - (message "DocView: your emacs or display doesn't support png images.") - (let* ((doc (or file - (expand-file-name - (let ((completion-ignored-extensions - ;; Don't hide files doc-view can display - (remove-if (lambda (str) - (string-match "\\.\\(ps\\|pdf\\|dvi\\)$" - str)) - completion-ignored-extensions))) - (read-file-name "File: " nil nil t))))) - (buffer (get-buffer-create (format "*DocView: %s*" doc))) - (dir (doc-view-file-name-to-directory-name doc))) - (switch-to-buffer buffer) - (doc-view-buffer-message) - (doc-view-mode) - (setq doc-view-current-doc doc) - (setq doc-view-current-page 1) - (if (not (and (file-exists-p dir) - (not no-cache))) - (progn - (setq doc-view-current-cache-dir nil) - (doc-view-convert-doc doc-view-current-doc)) - (message "DocView: using cached files!") - (doc-view-display doc-view-current-doc))))) - -(defun doc-view-dired (no-cache) - "View the current dired file with doc-view. -NO-CACHE is the same as in `doc-view'. - -You might want to bind this command to a dired key, e.g. - - (define-key dired-mode-map (kbd \"C-c d\") 'doc-view-dired)" - (interactive "P") - (doc-view no-cache (dired-get-file-for-visit))) +(define-derived-mode doc-view-mode nil "DocView" + "Major mode in DocView buffers. +You can use \\\\[doc-view-toggle-display] to +toggle between display as a set of images and display as text." + :group 'doc-view + (make-local-variable 'doc-view-current-files) + (make-local-variable 'doc-view-current-doc) + (make-local-variable 'doc-view-current-image) + (make-local-variable 'doc-view-current-page) + (make-local-variable 'doc-view-current-converter-process) + (make-local-variable 'doc-view-current-timer) + (make-local-variable 'doc-view-current-slice) + (make-local-variable 'doc-view-current-cache-dir) + (make-local-variable 'doc-view-current-info) + (make-local-variable 'doc-view-current-search-matches) + (setq doc-view-current-doc (buffer-file-name)) + (insert-file-contents doc-view-current-doc) + (use-local-map doc-view-mode-text-map) + (setq mode-name "DocView[text]" + doc-view-current-display 'text + buffer-read-only t + revert-buffer-function 'doc-view-reconvert-doc) + ;; Switch to image display if possible + (if (and (display-images-p) + (image-type-available-p 'png) + (not (get-text-property (point-min) 'display))) + (doc-view-toggle-display)) + (message + "%s" + (substitute-command-keys + "Type \\[doc-view-toggle-display] to toggle between image and text display."))) (defun doc-view-clear-cache () "Delete the whole cache (`doc-view-cache-directory')." Index: lisp/bindings.el =================================================================== RCS file: /sources/emacs/emacs/lisp/bindings.el,v retrieving revision 1.185 diff -u -r1.185 bindings.el --- lisp/bindings.el 6 Oct 2007 22:15:43 -0000 1.185 +++ lisp/bindings.el 17 Oct 2007 19:51:49 -0000 @@ -536,7 +536,7 @@ '(".elc" ".lof" ".glo" ".idx" ".lot" ;; TeX-related - ".dvi" ".fmt" ".tfm" ".pdf" + ".fmt" ".tfm" ;; Java compiled ".class" ;; CLISP Index: lisp/ChangeLog =================================================================== RCS file: /sources/emacs/emacs/lisp/ChangeLog,v retrieving revision 1.11948 diff -u -r1.11948 ChangeLog --- lisp/ChangeLog 17 Oct 2007 16:24:33 -0000 1.11948 +++ lisp/ChangeLog 17 Oct 2007 19:51:55 -0000 @@ -1,3 +1,16 @@ +2007-10-17 Tassilo Horn + + * bindings.el (completion-ignored-extensions): Remove pdf and dvi + extensions since they can be viewed with doc-view. + + * files.el (auto-mode-alist): Make doc-view-mode the default mode + for pdf, ps and dvi files. + + * doc-view.el: Make doc-view-mode the standard mode for viewing + pdf, [e]ps and dvi files and add binding C-c C-c to toggle between + text and image display. Add binding C-c C-e to switch to an + editing mode. + 2007-10-17 Stefan Monnier * progmodes/compile.el (compilation-next-error-function): Fix timestamp