You might know; but always remember that when operating on strings in VIM script most (all?) functions do not know or care about what a line is.
https://vimhelp.org/builtin.txt.html#string-match
There is a layer between buffer and file written to disk. When a file is read into a buffer VIM detects line-endings, fileformat, and replaces them internally; As to mean a buffer is end-of-line byte(s) agnostic. Look at it as a list. When writing to disk the lines are joined by <EOL> according to fileformat.
When one are working with string functions like substitute() etc. VIM does not know (or try to detect) what line endings are, e.g. \n vs \r\n. This is also why one typically use lists instead of strings when working with multiple lines in buffers from scripts. This way one do not need to worry about fileformat. One typically get multiple lines as a list and put multiple lines using lists. This is also why for example $ does not mean end-of-line in a string, but end-of-string, \n means literal LF / 0x0a and not end-of-line etc.
Also note for example :h substitute()
[…] the matching with {pat} is always done like the 'magic' option is set and 'cpoptions' is empty (to make scripts portable).
You have some options e.g. append() and setline(). Both can take a list as input. For example by using append() you can do something like:
"" Get line, substitue and split to list let list = getline('.') \ ->substitute(') AND (', ') AND \\\n(', 'g') \ ->split('\n') "" Delete current line delete _ "" Append call append(line('.') - 1, list)
Note that if you use setline() you would first have to make room for the new lines as it replaces (set) lines.