Search code examples
vimvim-plugin

How to search and replace in epub file opened with vim.zip


Adding a simple line to my .vimrc I managed to open an ePub archive with zip.vim on a vim buffer. This basically opens a numbered list of htm, css, jpg, xml files that one can access individually. I want to perform a search and replace through all these numbered files as it is too painful to edit each of them and do it - there's around 400 files. :args and :argdo did not really work or at least I could not make them. Any ideas?


Solution

  • I'm not sure it will work for you, and I'm going to assume that when you hit Enter on a filepath inside your Vim buffer, a new viewport is opened (by the ZipBrowseSelect() function defined in $VIMRUNTIME/autoload/zip.vim) to display the contents of the file.

    If this is the case, you could try the following method.

    :%argd
    

    This command deletes all the paths in the current arglist. Then, you would have to visually select all the lines containing a path to a file you want to modify. From normal mode, you could hit vip, for example, and adjust the visual selection to exclude some lines if needed.

    :'<,'>g/^/exe "norm \r" | argadd % | close
    

    This command should hit Enter on each line inside your visual selection, add the file which has been opened in a new viewport, and close the latter to get back to the original window.

    :vim /pattern/ ##
    

    This command should populate the quickfix list with all the lines containing the pattern you're looking for.

    :cfdo %s/pattern/replacement/ge | update
    

    This command should replace pattern with replacement in each file present in the quickfix list, and save the file if it has been changed.


    The last step uses the :cfdo command which was introduced in Vim version 7.4.858. If your Vim version is not new enough to support :cfdo, you could bypass the last 2 steps, and directly execute:

    :argdo %s/pattern/replacement/ge | update
    

    The benefit of:

    :vim /pattern/ ##
    :cfdo %s/pattern/replacement/ge | update
    

    ... is to prune the arglist from the files which don't contain your pattern, so that the substitutions commands are only executed when needed.

    If you don't have :cfdo but still want to prune the arglist, you could source this custom command:

    com! -nargs=0 -bar Qargs exe 'args '.s:qfl_names()
    fu! s:qfl_names() abort
        let buffer_numbers = {}
        for qf_item in getqflist()
            let buffer_numbers[qf_item['bufnr']] = bufname(qf_item['bufnr'])
        endfor
        return join(map(values(buffer_numbers), 'fnameescape(v:val)'))
    endfu
    

    I've copied it from this video: Project-wide find and replace. Most of the other commands are also taken from this video, so it might help you to have a look at it, if you don't have already.

    Then, you would replace the :cfdo command, with:

    :Qargs
    :argdo %s/pattern/replacement/ge | update
    

    To summarize, you could try one of these 3 methods:

    :%argd
    visually select the paths of the files
    :'<,'>g/^/exe "norm \r" | argadd % | close
    :argdo %s/pattern/replacement/ge | update
    

    Or:

    :%argd
    visually select the paths of the files
    :'<,'>g/^/exe "norm \r" | argadd % | close
    :vim /pattern/ ##
    :cfdo %s/pattern/replacement/ge | update
    

    Or:

    :%argd
    visually select the paths of the files
    :'<,'>g/^/exe "norm \r" | argadd % | close
    :vim /pattern/ ##
    :Qargs
    :argdo %s/pattern/replacement/ge | update
    

    Edit:

    You could also try to visually select the paths of the files, then execute:

    :'<,'>g/^/e `='zipfile:'.expand('%:p').'::'.getline(".")` | %s/pattern/replacement/ge | update | b#
    

    This method relies on the fact that the path to a file in the archive seems to follow this scheme:

    zipfile:/path/to/epub::path/to/file/in/archive
    

    So, you can get the path to a file under the cursor with the Vim expression:

    'zipfile:'.expand('%:p').'::'.getline(".")
    

    And you can edit this file using backticks (see :h `=):

    :e `=Vim expression`
    →
    :e `='zipfile:'.expand('%:p').'::'.getline(".")`
    

    From there, you need the global command, to repeat the edition on each line inside the visual selection.

    In the epub I tested, all the paths were below the line mimetype. If this is the case for you, then you could merge the 2 steps (visual selection + global command) in a single command:

    1/mimetype/+,$g/^/e `='zipfile:'.expand('%:p').'::'.getline(".")` | %s/pattern/replacement/ge | update | b#