From 0eea82ef3d109746d8a9e019df5a5f377c50b2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Tempel?= Date: Wed, 31 Jul 2024 17:04:35 +0200 Subject: [PATCH] Write files line-wise Instead of joining the list of lines into a single string and writing that at once using `write-string`. This is much more efficient for large files as otherwise the majority of runtime is spent joining the list to a string. This was discovered while editing large files in Guix (e.g. the package definitions from gnu/packages/python-xyz.scm) using the edward text editor. --- lib/ed/cmd.scm | 16 +++++++++++++--- lib/ed/cmd.sld | 2 +- lib/ed/posix.scm | 10 +++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/ed/cmd.scm b/lib/ed/cmd.scm index 24ce829..7d8fac4 100644 --- a/lib/ed/cmd.scm +++ b/lib/ed/cmd.scm @@ -503,15 +503,25 @@ (values #t (string-copy fn 1)) (values #f fn)))) -;;> Write given data to given filename. If filename starts with `!` (i.e. +;;> Write a list of `lines` (represented as a string without a terminating +;;> newline) to a given `filename`. If `filename` starts with `!` (i.e. ;;> is a command according to [filename-cmd?](#filename-cmd?)), write data ;;> to standard input of given command string. +;;> +;;> Returns amount of bytes written to the `filename` on success and false +;;> if an error occurred. -(define (write-to filename data) +(define (write-lines filename lines) (let-values (((fn-cmd? fn) (filename-unwrap filename))) (with-io-error-handler fn (lambda () - (let ((proc (lambda (port) (write-string data port)))) + (let ((proc (lambda (port) + (fold (lambda (line num) + (let* ((line (string-append line "\n")) + (bytes (string->utf8 line))) + (write-bytevector bytes port) + (+ num (bytevector-length bytes)))) + 0 lines)))) (if fn-cmd? (call-with-output-pipe fn proc) (call-with-output-file fn proc))))))) diff --git a/lib/ed/cmd.sld b/lib/ed/cmd.sld index 4e2ee12..37384e4 100644 --- a/lib/ed/cmd.sld +++ b/lib/ed/cmd.sld @@ -30,7 +30,7 @@ ;; Utility procedures to define custom editor commands. (export define-edit-cmd define-input-cmd define-print-cmd define-file-cmd parse-cmd-char parse-cmd parse-re parse-re-pair unwrap-command-list - exec-command-list parse-filename parse-file-cmd write-to + exec-command-list parse-filename parse-file-cmd write-lines read-from call-when-confirmed subst-nomatch-handler filename-cmd? register-command exec-command-list-interactive) diff --git a/lib/ed/posix.scm b/lib/ed/posix.scm index 86383f5..1fb564d 100644 --- a/lib/ed/posix.scm +++ b/lib/ed/posix.scm @@ -356,12 +356,12 @@ ;;;; (define (exec-write editor lines filename) - (let ((fn (editor-filename editor filename)) - (data (lines->string (editor-get-lines editor lines)))) - (unless (write-to fn data) + (let* ((fn (editor-filename editor filename)) + (lines (editor-get-lines editor lines)) + (written (write-lines fn lines))) + (unless written (editor-raise "cannot open output file")) - ;; Assuming write-to *always* writes all bytes. - (editor-verbose editor (count-bytes data)) + (editor-verbose editor written) (unless (filename-cmd? filename) (if (empty-string? (text-editor-filename editor))