1
- import { Option } from "./types.ts" ;
1
+ import { isOptionType , type Option , type OptionType } from "./types.ts" ;
2
2
import { regexIndexOf } from "./utils.ts" ;
3
3
4
+ const fallbackTypes : Record < string , OptionType > = {
5
+ "encoding" : "string" , // not defined type in nvim 0.8.0
6
+ } ;
7
+
4
8
/**
5
9
* Parse Vim/Neovim help.
6
10
*
7
11
* It extract a option definition block like below and return
8
- * a list of `Definition `.
12
+ * a list of `Option `.
9
13
*
10
14
* ```text
11
15
* *'aleph'* *'al'* *aleph* *Aleph*
@@ -17,44 +21,57 @@ import { regexIndexOf } from "./utils.ts";
17
21
* ```
18
22
*/
19
23
export function parse ( content : string ) {
24
+ // Remove modeline
25
+ content = content . replace ( / \n v i m : [ ^ \n ] * \s * $ / , "" ) ;
26
+
20
27
const options : Option [ ] = [ ] ;
21
- const optionNames = [ ... content . matchAll ( / \* ' ( \w + ) ' \* / g) ] . map ( ( m ) => m [ 1 ] ) ;
22
- for ( const name of optionNames ) {
23
- const pattern = new RegExp ( `\n' ${ name } ' (?:'\\w+'\\s*)*\\s+(\\w+)` ) ;
24
- const m1 = content . match ( pattern ) ;
25
- if ( ! m1 ) {
26
- continue ;
28
+ for ( const match of content . matchAll ( / \* ' ( \w + ) ' \* / g) ) {
29
+ const name = match [ 1 ] ;
30
+ const block = extractBlock ( content , match . index ?? 0 ) ;
31
+ const option = parseBlock ( name , block ) ;
32
+ if ( option ) {
33
+ options . push ( option ) ;
27
34
}
28
- const type = m1 [ 1 ] ;
29
- const block = extractBlock ( content , m1 . index || 0 ) ;
30
-
31
- const m2 = block . match ( / \n \t \t \t ( g l o b a l o r l o c a l | g l o b a l | l o c a l ) \s / ) ;
32
- const scope = ( m2 ? m2 [ 1 ] : "global" ) . split ( " or " ) ;
33
-
34
- const docs = block
35
- . substring ( block . indexOf ( "\n" , ( m2 ? m2 . index || 0 : 0 ) + 1 ) )
36
- . split ( "\n" )
37
- . map ( ( v ) => v . replace ( / ^ \t / , "" ) )
38
- . join ( "\n" ) ;
39
- options . push ( {
40
- name,
41
- type,
42
- scope,
43
- docs,
44
- } ) ;
45
35
}
46
36
return options ;
47
37
}
48
38
49
39
function extractBlock ( content : string , index : number ) : string {
50
40
const s = content . lastIndexOf ( "\n" , index ) ;
51
- const ms = regexIndexOf ( content , / \n [ < > \s ] / , index ) ;
52
- const me = regexIndexOf ( content , / \n [ ^ < > \t ] / , ms ) ;
41
+ const ms = regexIndexOf ( content , / \n [ ^ < > \s ] | $ / , s ) ;
42
+ const me = regexIndexOf ( content , / \n [ ^ < > \s ] | $ / , ms + 1 ) ;
53
43
const e = content . lastIndexOf ( "\n" , me ) ;
54
44
const block = content
55
45
. substring ( s , e )
56
- . replaceAll ( / \* .+ ?\* / g, "" ) // Remove tags
57
- . replaceAll ( / \s + \n / g, "\n" ) // Remove trailing '\s'
58
- . trim ( ) ;
46
+ . replace ( / ( \n < ? ) (?: \s + \* \S + ?\* ) + \s * $ / , "$1" ) // Remove next block tag
47
+ . trimEnd ( ) ;
59
48
return block ;
60
49
}
50
+
51
+ function parseBlock ( name : string , body : string ) : Option | undefined {
52
+ const m1 = body . match (
53
+ new RegExp ( `^'${ name } '(?:\\s+'\\w+')*\\s+(\\w+)\\s` , "m" ) ,
54
+ ) ;
55
+ const type = m1 ?. [ 1 ] ?? fallbackTypes [ name ] ;
56
+ if ( ! isOptionType ( type ) ) {
57
+ return ;
58
+ }
59
+
60
+ const m2 = body . match ( / \n \t { 3 , } ( g l o b a l o r l o c a l | g l o b a l | l o c a l ) (?: \s | $ ) / ) ;
61
+ const scope = ( m2 ?. [ 1 ] . split ( " or " ) ?? [ "global" ] ) as Array <
62
+ "global" | "local"
63
+ > ;
64
+
65
+ const s = regexIndexOf ( body , / \n | $ / , ( m2 ?. index ?? m1 ?. index ?? 0 ) + 1 ) ;
66
+ body = body . substring ( s ) ;
67
+
68
+ // Remove tags
69
+ body = body . replaceAll ( / \* \S + ?\* / g, "" ) ;
70
+ // Remove trailing spaces
71
+ body = body . split ( "\n" ) . map ( ( v ) => v . trimEnd ( ) ) . join ( "\n" ) ;
72
+ // Remove indent
73
+ body = body . split ( "\n" ) . map ( ( v ) => v . replace ( / ^ \t / , "" ) ) . join ( "\n" ) ;
74
+
75
+ const docs = body ;
76
+ return { name, type, scope, docs } ;
77
+ }
0 commit comments