runtime(vim): Improve syntax script generator for Vim Script

closes: #16331

Signed-off-by: h-east <h.east.727@gmail.com>
Signed-off-by: Doug Kearns <dougkearns@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/runtime/syntax/generator/Makefile b/runtime/syntax/generator/Makefile
index a7ccba3..2a51e64 100644
--- a/runtime/syntax/generator/Makefile
+++ b/runtime/syntax/generator/Makefile
@@ -1,43 +1,50 @@
-VIM_SRCDIR = ../../../src
-RUN_VIMPROG = $(VIM_SRCDIR)/vim -N -u NONE -i NONE -n
-REVISION ?= $(shell date +%Y-%m-%dT%H:%M:%S%:z)
+#
+# Makefile for generate runtime/syntax/vim.vim
+#
+VIM_SRCDIR := ../../../src
+RUN_VIMPROG := $(VIM_SRCDIR)/vim -N -u NONE -i NONE -n
+TARGET := ../vim.vim
+CHECK_HELP_DOC := 0
 
-SRC =	$(VIM_SRCDIR)/eval.c $(VIM_SRCDIR)/ex_cmds.h $(VIM_SRCDIR)/ex_docmd.c \
+SRC :=	$(VIM_SRCDIR)/eval.c $(VIM_SRCDIR)/ex_cmds.h $(VIM_SRCDIR)/ex_docmd.c \
 		$(VIM_SRCDIR)/fileio.c $(VIM_SRCDIR)/option.c $(VIM_SRCDIR)/syntax.c
 
 export VIM_SRCDIR
+export CHECK_HELP_DOC
 
-.PHONY: generate clean
+.PHONY: generate check_doc clean
 all: generate
 
-generate: vim.vim
+generate: $(TARGET)
 
-vim.vim: vim.vim.rc update_date.vim
-	@echo "Generating vim.vim ..."
-	@cp -f vim.vim.rc ../vim.vim
-	@$(RUN_VIMPROG) -S update_date.vim
+check_doc: CHECK_HELP_DOC := 1
+check_doc: clean $(TARGET)
+
+clean:
+	rm -f vim.vim.rc $(TARGET)
+	rm -f sanity_check.err generator.err
+
+$(TARGET): vim.vim.rc update_date.vim
+	@echo "Generating $(TARGET) ..."
+	@cp -f vim.vim.rc $(TARGET)
+	@$(RUN_VIMPROG) -S update_date.vim $(TARGET)
 	@echo "done."
 
 vim.vim.rc: gen_syntax_vim.vim vim.vim.base $(SRC)
 	@echo "Generating vim.vim.rc ..."
 	@rm -f sanity_check.err generator.err
-	@$(RUN_VIMPROG) -S gen_syntax_vim.vim
+	@$(RUN_VIMPROG) -S gen_syntax_vim.vim $(TARGET)
 	@if test -f sanity_check.err ; then \
 		echo ; \
 		echo "Sanity errors:" ; \
 		cat sanity_check.err ; \
-		exit 1 ; \
 	fi
 	@if test -f generator.err ; then \
 		echo ; \
 		echo "Generator errors:" ; \
 		cat generator.err ; \
-		echo ; \
+	fi
+	@if test -f sanity_check.err || test -f generator.err ; then \
 		exit 1 ; \
 	fi
 	@echo "done."
-
-clean:
-	rm -f vim.vim.rc
-	rm -f vim.vim
-	rm -f sanity_check.err generator.err
diff --git a/runtime/syntax/generator/gen_syntax_vim.vim b/runtime/syntax/generator/gen_syntax_vim.vim
index 7626f49..1badb35 100644
--- a/runtime/syntax/generator/gen_syntax_vim.vim
+++ b/runtime/syntax/generator/gen_syntax_vim.vim
@@ -1,14 +1,15 @@
 " Vim syntax file generator
 " Language: Vim script
 " Maintainer: Hirohito Higashi (h_east)
-" Last Change: 2024 Oct 04
+" Last Change: 2024 Dec 29
 
 let s:keepcpo= &cpo
 set cpo&vim
 
 language C
+let s:log_write_dir = getcwd() . '/'
 
-function! s:parse_vim_option(opt, missing_opt, term_out_code)
+function s:parse_vim_option(opt, missing_opt, term_out_code)
 	try
 		let file_name = $VIM_SRCDIR . '/optiondefs.h'
 		let item = {}
@@ -65,7 +66,7 @@
 	endtry
 endfunc
 
-function! s:append_syn_vimopt(lnum, str_info, opt_list, prefix, bool_only)
+function s:append_syn_vimopt(lnum, str_info, opt_list, prefix, bool_only)
 	let ret_lnum = a:lnum
 	let str = a:str_info.start
 
@@ -96,7 +97,7 @@
 endfunc
 
 " ------------------------------------------------------------------------------
-function! s:parse_vim_command(cmd)
+function s:parse_vim_command(cmd)
 	try
 		let file_name = $VIM_SRCDIR . '/ex_cmds.h'
 		let item = {}
@@ -131,41 +132,28 @@
 				if my > 0
 					let omit_idx = (key =~# '\l') ? 1 : 0
 					for idx in range(1, strlen(lcmd[key][my]))
-            let spec=0
-            if lcmd[key][my] ==# 'ex'
-              let spec=1
-              echo "cmd name:" lcmd[key][my]
-            endif
 						let matched = 0
 						for pre in range(my - 1, 0, -1)
-              if spec
-                echo "pre:" pre ", my:" my
-              endif
 							if pre == my
-                if spec
-                  echo "continue"
-                endif
 								continue
 							endif
-							" for weird abbreviations for delete. (See :help :d)
-							" And k{char} is used as mark. (See :help :k)
+							" Avoiding conflicts shortened command and special commands
+							" - weird abbreviations for delete. (See :help :d)
+							" - k{char} is used as mark. (See :help :k)
+							" - :s commsnds repeat. (See :help :substitute-repeat)
 							if lcmd[key][my][:idx] ==# lcmd[key][pre][:idx] ||
 							\	(key ==# 'd' &&
 							\		lcmd[key][my][:idx] =~# '^d\%[elete][lp]$')
 							\	|| (key ==# 'k' &&
 							\		lcmd[key][my][:idx] =~# '^k[a-zA-Z]$')
+							\	|| (key ==# 's' &&
+							\		lcmd[key][my][:idx] =~# '^s\%(c\%([^sr][^ip]\=\)\=$\|g\|i[^mlg]\=$\|I\|r[^e]\=$\)')
 								let matched = 1
 								let omit_idx = idx + 1
-                if spec
-                  echo "match. break. omit_idx:" omit_idx
-                endif
 								break
 							endif
 						endfor
 						if !matched
-              if spec
-                echo "not match. break"
-              endif
 							break
 						endif
 					endfor
@@ -185,46 +173,6 @@
 			endfor
 		endfor
 
-		" Check exists in the help. (Usually it does not check...)
-		let doc_dir = './vim/runtime/doc'
-		if 0
-			for vimcmd in a:cmd
-				let find_ptn = '^|:' . vimcmd.name . '|\s\+'
-				exec "silent! vimgrep /" . find_ptn . "/gj " . doc_dir . "/index.txt"
-				let li = getqflist()
-				if empty(li)
-					call s:err_sanity(printf('Ex-cmd `:%s` is not found in doc/index.txt.', vimcmd.name))
-				elseif len(li) > 1
-					call s:err_sanity(printf('Ex-cmd `:%s` is duplicated in doc/index.txt.', vimcmd.name))
-				else
-					let doc_syn_str = substitute(li[0].text, find_ptn . '\(\S\+\)\s\+.*', '\1', '')
-					if doc_syn_str ==# vimcmd.syn_str
-						call s:err_sanity(printf('Ex-cmd `%s` short name differ in doc/index.txt. code: `%s`, document: `%s`', vimcmd.name, vimcmd.syn_str, doc_syn_str))
-					endif
-				endif
-
-				if 1
-				for i in range(2)
-					if i || vimcmd.omit_idx >= 0
-						if !i
-							let base_ptn = vimcmd.name[:vimcmd.omit_idx]
-						else
-							let base_ptn = vimcmd.name
-						endif
-						let find_ptn = '\*:' . base_ptn . '\*'
-						exec "silent! vimgrep /" . find_ptn . "/gj " . doc_dir . "/*.txt"
-						let li = getqflist()
-						if empty(li)
-							call s:err_sanity(printf('Ex-cmd `:%s`%s is not found in the help tag.', base_ptn, !i ? ' (short name of `:' . vimcmd.name . '`)' : ''))
-						elseif len(li) > 1
-							call s:err_sanity(printf('Ex-cmd `:%s`%s is duplicated in the help tag.', base_ptn, !i ? ' (short name of `:' . vimcmd.name . '`)' : ''))
-						endif
-					endif
-				endfor
-			endif
-			endfor
-		endif
-
 		" Add weird abbreviations for delete. (See :help :d)
 		for i in ['l', 'p']
 			let str = 'delete'
@@ -258,7 +206,7 @@
 	endtry
 endfunc
 
-function! s:get_vim_command_type(cmd_name)
+function s:get_vim_command_type(cmd_name)
 	" Return value:
 	"   0: normal
 	"   1: (Reserved)
@@ -364,7 +312,7 @@
 	return ret
 endfunc
 
-function! s:append_syn_vimcmd(lnum, str_info, cmd_list, type)
+function s:append_syn_vimcmd(lnum, str_info, cmd_list, type)
 	let ret_lnum = a:lnum
 	let str = a:str_info.start
 
@@ -392,7 +340,7 @@
 endfunc
 
 " ------------------------------------------------------------------------------
-function! s:parse_vim_event(li)
+function s:parse_vim_event(li)
 	try
 		let file_name = $VIM_SRCDIR . '/autocmd.c'
 		let item = {}
@@ -424,7 +372,7 @@
 endfunc
 
 " ------------------------------------------------------------------------------
-function! s:parse_vim_function(li)
+function s:parse_vim_function(li)
 	try
 		let file_name = $VIM_SRCDIR . '/evalfunc.c'
 		let item = {}
@@ -459,7 +407,7 @@
 endfunc
 
 " ------------------------------------------------------------------------------
-function! s:parse_vim_hlgroup(li)
+function s:parse_vim_hlgroup(li)
 	try
 		let file_name = $VIM_SRCDIR . '/highlight.c'
 		let item = {}
@@ -533,7 +481,7 @@
 endfunc
 
 " ------------------------------------------------------------------------------
-function! s:parse_vim_complete_name(li)
+function s:parse_vim_complete_name(li)
 	try
 		let file_name = $VIM_SRCDIR . '/usercmd.c'
 		let item = {}
@@ -566,7 +514,7 @@
 endfunc
 
 " ------------------------------------------------------------------------------
-function! s:parse_vim_addr_name(li)
+function s:parse_vim_addr_name(li)
 	try
 		let file_name = $VIM_SRCDIR . '/usercmd.c'
 		let item = {}
@@ -604,7 +552,7 @@
 endfunc
 
 " ------------------------------------------------------------------------------
-function! s:append_syn_any(lnum, str_info, li)
+function s:append_syn_any(lnum, str_info, li)
 	let ret_lnum = a:lnum
 	let str = a:str_info.start
 
@@ -629,7 +577,8 @@
 	return ret_lnum
 endfunc
 
-function! s:update_syntax_vim_file(vim_info)
+" ------------------------------------------------------------------------------
+function s:update_syntax_vim_file(vim_info)
 	try
 		function! s:search_and_check(kword, base_fname, str_info)
 			let a:str_info.start = ''
@@ -746,15 +695,96 @@
 endfunc
 
 " ------------------------------------------------------------------------------
-function! s:err_gen(arg)
-	call s:write_error(a:arg, 'generator.err')
+function s:check_help_doc(vim_info)
+	try
+		new
+		let cwd_save = getcwd()
+		cd ../../../runtime/doc
+
+		let exclude_cmd =<< trim END
+			deletel
+			deletep
+			a
+			i
+		END
+
+		" Check the Ex-command is listed in index.txt
+		split index.txt
+		for vimcmd in a:vim_info.cmd
+			if index(exclude_cmd, vimcmd.name) != -1
+				continue
+			endif
+			norm! gg
+			let find_ptn = '^|:' . vimcmd.name . '|\s\+'
+			let lnum = search(find_ptn, 'eW')
+			if lnum == 0
+				call s:err_sanity($'Ex-cmd ":{vimcmd.name}" is not found in index.txt.')
+			elseif search(find_ptn, 'eW') > 0
+				call s:err_sanity($'Ex-cmd ":{vimcmd.name}" is duplicated in index.txt.')
+			else
+				let doc_syn_str = substitute(getline(lnum), find_ptn . ':\(\S\+\)\s\+.*', '\1', '')
+				if doc_syn_str !=# vimcmd.syn_str
+					call s:err_sanity($'Ex-cmd "{vimcmd.name}" short name differ in index.txt. expect: "{vimcmd.syn_str}", but: "{doc_syn_str}"')
+				endif
+			endif
+		endfor
+		quit!
+
+		" Check the existence of the help tag for Ex-command.
+		set wildignore=version*.txt,todo.txt,usr_*.txt
+		for vimcmd in a:vim_info.cmd
+			if index(exclude_cmd, vimcmd.name) != -1
+				continue
+			endif
+			let find_ptn = '\s\*:' . vimcmd.name . '\*\_s'
+			exec "silent! vimgrep /" . find_ptn . "/gj *.txt"
+			let qfl = getqflist()
+			if empty(qfl)
+				call s:err_sanity($'Help tag for Ex-cmd ":{vimcmd.name}" not found.')
+			elseif len(qfl) > 1
+				call s:err_sanity($'Help tag for Ex-cmd ":{vimcmd.name}" is duplicated.')
+			else
+				" Check the existence of Ex-command notation.
+				cc
+				norm! 2k
+				let end_lnum = qfl[0].lnum + 10
+				let find_ptn = '^:.*\<' . vimcmd.syn_str->escape('[]')
+				let lnum = search(find_ptn, 'W', end_lnum)
+				if lnum == 0
+					if vimcmd.omit_idx != -1
+						" Check the existence of the shorten help tag for Ex-command.
+						cc
+						norm! k
+						let end_lnum = qfl[0].lnum + 10
+						let find_ptn = '\s\*:' . vimcmd.name[:vimcmd.omit_idx] . '\*\_s'
+						let lnum = search(find_ptn, 'W', end_lnum)
+					else
+						let lnum = 1
+					endif
+					if lnum == 0
+						call s:err_sanity($'Shorten help tag "{vimcmd.name[:vimcmd.omit_idx]}" for Ex-cmd "{vimcmd.name}" not found.')
+					endif
+				endif
+			endif
+		endfor
+	catch /.*/
+		call s:err_gen('')
+		throw 'exit'
+	finally
+		exec 'cd ' . cwd_save
+	endtry
 endfunc
 
-function! s:err_sanity(arg)
-	call s:write_error(a:arg, 'sanity_check.err')
+" ------------------------------------------------------------------------------
+function s:err_gen(arg)
+	call s:write_error(a:arg, s:log_write_dir .. 'generator.err')
 endfunc
 
-function! s:write_error(arg, fname)
+function s:err_sanity(arg)
+	call s:write_error(a:arg, s:log_write_dir .. 'sanity_check.err')
+endfunc
+
+function s:write_error(arg, fname)
 	let li = []
 	if !empty(v:throwpoint)
 		call add(li, v:throwpoint)
@@ -791,16 +821,21 @@
 	let s:vim_info.addr_name = []
 
 	set lazyredraw
-	silent call s:parse_vim_option(s:vim_info.opt, s:vim_info.missing_opt,
-	\						s:vim_info.term_out_code)
-	silent call s:parse_vim_command(s:vim_info.cmd)
-	silent call s:parse_vim_event(s:vim_info.event)
-	silent call s:parse_vim_function(s:vim_info.func)
-	silent call s:parse_vim_hlgroup(s:vim_info.hlgroup)
-	silent call s:parse_vim_complete_name(s:vim_info.compl_name)
-	silent call s:parse_vim_addr_name(s:vim_info.addr_name)
+	if !$CHECK_HELP_DOC
+		silent call s:parse_vim_option(s:vim_info.opt, s:vim_info.missing_opt,
+		\						s:vim_info.term_out_code)
+		silent call s:parse_vim_command(s:vim_info.cmd)
+		silent call s:parse_vim_event(s:vim_info.event)
+		silent call s:parse_vim_function(s:vim_info.func)
+		silent call s:parse_vim_hlgroup(s:vim_info.hlgroup)
+		silent call s:parse_vim_complete_name(s:vim_info.compl_name)
+		silent call s:parse_vim_addr_name(s:vim_info.addr_name)
 
-	call s:update_syntax_vim_file(s:vim_info)
+		call s:update_syntax_vim_file(s:vim_info)
+	else
+		silent call s:parse_vim_command(s:vim_info.cmd)
+		silent call s:check_help_doc(s:vim_info)
+	endif
 	set nolazyredraw
 
 finally
diff --git a/runtime/syntax/generator/update_date.vim b/runtime/syntax/generator/update_date.vim
index 3556189..bb4c207 100644
--- a/runtime/syntax/generator/update_date.vim
+++ b/runtime/syntax/generator/update_date.vim
@@ -2,7 +2,6 @@
 "     '" Last Change:  '
 "
 language C
-silent new ../vim.vim
 normal gg
 let pat = '^"\s*Last\s*Change:\s\+'
 let lnum = search(pat, 'We', 10)