repo fix first commit

This commit is contained in:
tonydero 2024-09-19 19:03:06 -06:00
parent 4ea459b6e7
commit bd32153557
42 changed files with 5172 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
# ---> Godot
# Godot 4+ specific ignores
.godot/
/android/
# Godot-specific ignores
.import/
export.cfg
export_presets.cfg
# Imported translations (automatically generated from CSV files)
*.translation
# Mono-specific ignores
.mono/
data_*/
mono_crash.*.json

File diff suppressed because it is too large Load Diff

179
addons/godot-vim/icon.svg Normal file
View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="409.99237"
height="551.82874"
version="1.1"
id="svg3763"
sodipodi:docname="iconsvg.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<defs
id="defs3767" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="3840"
inkscape:window-height="2089"
id="namedview3765"
showgrid="false"
inkscape:zoom="0.88187112"
inkscape:cx="-391.82984"
inkscape:cy="385.67966"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg3763" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g3845"
transform="matrix(4.3859694,0,0,4.3859694,-75.706218,-87.385926)">
<g
id="g3823"
transform="matrix(0.10073078,0,0,0.10073078,12.425923,2.256365)"
style="stroke-width:9.92745972">
<path
id="path3807"
d="m 0,0 c 0,0 -0.325,1.994 -0.515,1.976 l -36.182,-3.491 c -2.879,-0.278 -5.115,-2.574 -5.317,-5.459 l -0.994,-14.247 -27.992,-1.997 -1.904,12.912 c -0.424,2.872 -2.932,5.037 -5.835,5.037 h -38.188 c -2.902,0 -5.41,-2.165 -5.834,-5.037 l -1.905,-12.912 -27.992,1.997 -0.994,14.247 c -0.202,2.886 -2.438,5.182 -5.317,5.46 l -36.2,3.49 c -0.187,0.018 -0.324,-1.978 -0.511,-1.978 l -0.049,-7.83 30.658,-4.944 1.004,-14.374 c 0.203,-2.91 2.551,-5.263 5.463,-5.472 l 38.551,-2.75 c 0.146,-0.01 0.29,-0.016 0.434,-0.016 2.897,0 5.401,2.166 5.825,5.038 l 1.959,13.286 h 28.005 l 1.959,-13.286 c 0.423,-2.871 2.93,-5.037 5.831,-5.037 0.142,0 0.284,0.005 0.423,0.015 l 38.556,2.75 c 2.911,0.209 5.26,2.562 5.463,5.472 l 1.003,14.374 30.645,4.966 z"
inkscape:connector-curvature="0"
style="fill:#ffffff"
transform="matrix(4.162611,0,0,-4.162611,919.24059,771.67186)" />
<path
id="path3809"
transform="matrix(4.162611,0,0,-4.162611,104.69892,525.90697)"
d="m 0,0 v -47.514 -6.035 -5.492 c 0.108,-0.001 0.216,-0.005 0.323,-0.015 l 36.196,-3.49 c 1.896,-0.183 3.382,-1.709 3.514,-3.609 l 1.116,-15.978 31.574,-2.253 2.175,14.747 c 0.282,1.912 1.922,3.329 3.856,3.329 h 38.188 c 1.933,0 3.573,-1.417 3.855,-3.329 l 2.175,-14.747 31.575,2.253 1.115,15.978 c 0.133,1.9 1.618,3.425 3.514,3.609 l 36.182,3.49 c 0.107,0.01 0.214,0.014 0.322,0.015 v 4.711 l 0.015,0.005 V 0 c 5.09692,6.4164715 9.92323,13.494208 13.621,19.449 -5.651,9.62 -12.575,18.217 -19.976,26.182 -6.864,-3.455 -13.531,-7.369 -19.828,-11.534 -3.151,3.132 -6.7,5.694 -10.186,8.372 -3.425,2.751 -7.285,4.768 -10.946,7.118 1.09,8.117 1.629,16.108 1.846,24.448 -9.446,4.754 -19.519,7.906 -29.708,10.17 -4.068,-6.837 -7.788,-14.241 -11.028,-21.479 -3.842,0.642 -7.702,0.88 -11.567,0.926 v 0.006 c -0.027,0 -0.052,-0.006 -0.075,-0.006 -0.024,0 -0.049,0.006 -0.073,0.006 V 63.652 C 93.903,63.606 90.046,63.368 86.203,62.726 82.965,69.964 79.247,77.368 75.173,84.205 64.989,81.941 54.915,78.789 45.47,74.035 45.686,65.695 46.225,57.704 47.318,49.587 43.65,47.237 39.795,45.22 36.369,42.469 32.888,39.791 29.333,37.229 26.181,34.097 19.884,38.262 13.219,42.176 6.353,45.631 -1.048,37.666 -7.968,29.069 -13.621,19.449 -9.1783421,12.475308 -4.4130298,5.4661124 0,0 Z"
inkscape:connector-curvature="0"
style="fill:#478cbf" />
<path
id="path3811"
d="m 0,0 -1.121,-16.063 c -0.135,-1.936 -1.675,-3.477 -3.611,-3.616 l -38.555,-2.751 c -0.094,-0.007 -0.188,-0.01 -0.281,-0.01 -1.916,0 -3.569,1.406 -3.852,3.33 l -2.211,14.994 H -81.09 l -2.211,-14.994 c -0.297,-2.018 -2.101,-3.469 -4.133,-3.32 l -38.555,2.751 c -1.936,0.139 -3.476,1.68 -3.611,3.616 L -130.721,0 -163.268,3.138 c 0.015,-3.498 0.06,-7.33 0.06,-8.093 0,-34.374 43.605,-50.896 97.781,-51.086 h 0.066 0.067 c 54.176,0.19 97.766,16.712 97.766,51.086 0,0.777 0.047,4.593 0.063,8.093 z"
inkscape:connector-curvature="0"
style="fill:#478cbf"
transform="matrix(4.162611,0,0,-4.162611,784.07144,817.24284)" />
<path
id="path3813"
transform="matrix(4.162611,0,0,-4.162611,389.21484,625.67104)"
d="m 0,0 c 0,-12.052 -9.765,-21.815 -21.813,-21.815 -12.042,0 -21.81,9.763 -21.81,21.815 0,12.044 9.768,21.802 21.81,21.802 C -9.765,21.802 0,12.044 0,0"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
id="path3815"
transform="matrix(4.162611,0,0,-4.162611,367.36686,631.05679)"
d="m 0,0 c 0,-7.994 -6.479,-14.473 -14.479,-14.473 -7.996,0 -14.479,6.479 -14.479,14.473 0,7.994 6.483,14.479 14.479,14.479 C -6.479,14.479 0,7.994 0,0"
inkscape:connector-curvature="0"
style="fill:#414042" />
<path
id="path3817"
transform="matrix(4.162611,0,0,-4.162611,511.99336,724.73954)"
d="m 0,0 c -3.878,0 -7.021,2.858 -7.021,6.381 v 20.081 c 0,3.52 3.143,6.381 7.021,6.381 3.878,0 7.028,-2.861 7.028,-6.381 V 6.381 C 7.028,2.858 3.878,0 0,0"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
id="path3819"
transform="matrix(4.162611,0,0,-4.162611,634.78706,625.67104)"
d="m 0,0 c 0,-12.052 9.765,-21.815 21.815,-21.815 12.041,0 21.808,9.763 21.808,21.815 0,12.044 -9.767,21.802 -21.808,21.802 C 9.765,21.802 0,12.044 0,0"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
id="path3821"
transform="matrix(4.162611,0,0,-4.162611,656.64056,631.05679)"
d="m 0,0 c 0,-7.994 6.477,-14.473 14.471,-14.473 8.002,0 14.479,6.479 14.479,14.473 0,7.994 -6.477,14.479 -14.479,14.479 C 6.477,14.479 0,7.994 0,0"
inkscape:connector-curvature="0"
style="fill:#414042" />
</g>
</g>
<g
id="layer1"
transform="matrix(0.7294074,0,0,0.7294074,-45.912295,80.565241)">
<g
id="g3699"
transform="matrix(1.532388,0,0,1.3939671,-54.912136,-41.792396)">
<path
id="path3650"
d="m 114.65715,353.09353 h 47.80701 l 2.91261,3.20613 v 9.83953 l -2.31001,3.09557 h -5.22261 v 48.86596 l 44.99482,-48.86596 h -7.43218 l -2.61131,-3.09557 v -10.39231 l 2.41044,-2.43224 h 48.40962 l 2.41043,2.65335 v 9.72897 L 136.55196,489.29907 h -12.45393 l -3.60484,-2.291 V 368.79254 h -6.03691 l -2.20956,-2.43224 v -10.39231 z"
style="fill:none;stroke:#000000;stroke-width:8.34521198;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path3640"
d="m 162.97227,358.09475 2.6987,-1.5635 -2.76971,-3.04884 h -48.22135 l -2.45013,2.69704 v 10.20187 l 2.71645,2.9902 1.29608,-2.9902 -1.70443,-1.87621 v -7.19212 l 1.27832,-1.2508 h 46.01979 z"
style="fill:#478cbf;fill-opacity:1;stroke:#000000;stroke-width:0.41726059px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
d="m 197.06456,355.74729 -1.70266,1.87425 v 6.88137 l 1.49138,1.64168 h 7.87946 v 6.6488 l -52.12379,58.1565 v -64.72321 h 8.66244 l 1.77723,-1.95634 v -6.96346 l -1.64052,-1.39542 h -45.58657 l -1.49138,1.64168 v 7.11394 l 1.51624,1.66904 h 7.92918 v 119.18594 l 1.49138,1.64168 h 9.01043 L 243.50867,363.0938 v -5.47226 l -1.70266,-1.87425 z"
id="path3632"
style="fill:none;stroke:#000000;stroke-width:0.41726059px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path3646"
d="m 123.69629,366.13919 v 119.40096 l 1.40609,1.7689 -1.10266,2.31328 -3.1156,-3.41884 V 369.23476 Z"
style="fill:#478cbf;fill-opacity:1;stroke:#000000;stroke-width:0.41726059px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path3644"
d="m 115.90579,366.13919 -0.80348,2.87446 h 5.82523 l 3.21391,-2.87446 z"
style="fill:#478cbf;fill-opacity:1;stroke:#000000;stroke-width:0.41726059px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path3638"
d="m 195.92471,369.36762 1.27833,-2.89248 -1.84647,-1.87621 v -6.41037 l 2.13055,-2.34526 h 44.45738 l 1.70443,2.50161 2.41462,-1.87621 -2.48563,-2.73613 h -47.79524 l -2.37911,2.61887 v 10.28004 l 2.46788,2.56024 m -38.62501,49.36179 -4.64282,12.40054 52.41142,-57.84966 v -6.87942 z"
style="fill:#478cbf;fill-opacity:1;stroke:#000000;stroke-width:0.41726059px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path3642"
d="m 162.86589,357.7369 2.31001,-1.65835 v 10.06064 l -2.66153,2.92974 h -5.1724 v 49.58456 l -4.72044,12.27178 v -64.78608 h 8.6374 l 1.60696,-1.43724 z"
style="fill:#478cbf;fill-opacity:1;stroke:#000000;stroke-width:0.41726059px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
d="m 197.06456,355.74729 -1.70266,1.87425 v 6.88137 l 1.49138,1.64168 h 7.87946 v 6.6488 l -52.12379,58.1565 v -64.72321 h 8.66244 l 1.77723,-1.95634 v -6.96346 l -1.64052,-1.39542 h -45.58657 l -1.49138,1.64168 v 7.11394 l 1.51624,1.66904 h 7.92918 v 119.18594 l 1.49138,1.64168 h 9.01043 L 243.50867,363.0938 v -5.47226 l -1.70266,-1.87425 z"
id="path3622"
style="fill:#478cbf;fill-opacity:1;stroke:#000000;stroke-width:0.41726059px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path3636"
d="m 243.64893,357.78205 2.426,-1.54181 v 9.67203 L 136.04072,489.68148 h -11.68216 l 1.11611,-2.44127 h 8.9483 L 243.5069,363.25432 Z"
style="fill:#478cbf;fill-opacity:1;stroke:#000000;stroke-width:0.41726059px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path3652"
d="m 204.79746,366.30501 -2.46065,2.8192 h -6.42784 l 1.50652,-2.8192 c 0.0502,0 7.38197,0 7.38197,0 z"
style="fill:#478cbf;fill-opacity:1;stroke:#000000;stroke-width:0.41726059px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
<g
transform="matrix(0.90138601,0,0,0.99222542,-92.530288,-192.23791)"
id="g3673">
<path
style="fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path3671"
d="m 399.78125,560 a 1.2330102,1.2330102 0 0 0 -0.5625,0.28125 l -5.3125,4.5625 A 1.2330102,1.2330102 0 0 0 393.5625,565.375 L 388.25,580.25 a 1.2330102,1.2330102 0 0 0 0.28125,1.28125 l 4.0625,4.0625 a 1.2330102,1.2330102 0 0 0 0.875,0.34375 H 409.875 a 1.2330102,1.2330102 0 0 0 0.875,-0.34375 l 4.28125,-4.3125 a 1.2330102,1.2330102 0 0 0 0.3125,-0.53125 l 4.5625,-15.65625 a 1.2330102,1.2330102 0 0 0 -0.3125,-1.21875 l -3.53125,-3.53125 A 1.2330102,1.2330102 0 0 0 415.1875,560 h -15.15625 a 1.2330102,1.2330102 0 0 0 -0.25,0 z m -30.0625,41.9375 a 1.2330102,1.2330102 0 0 0 -0.9375,0.90625 l -2.03125,8.0625 a 1.2330102,1.2330102 0 0 0 1.1875,1.53125 h 9.65625 l -23.9375,68.34375 a 1.2330102,1.2330102 0 0 0 1.15625,1.625 h 34.84375 a 1.2330102,1.2330102 0 0 0 1.1875,-0.84375 l 2.28125,-7.34375 a 1.2330102,1.2330102 0 0 0 -1.1875,-1.59375 h -7.875 L 407.75,603.5625 a 1.2330102,1.2330102 0 0 0 -1.15625,-1.625 h -36.625 a 1.2330102,1.2330102 0 0 0 -0.25,0 z m 110.875,0.25 a 1.2330102,1.2330102 0 0 0 -0.6875,0.40625 l -7.25,8.1875 H 461.125 l -7.6875,-7.96875 a 1.2330102,1.2330102 0 0 0 -0.875,-0.375 H 425.03125 A 1.2330102,1.2330102 0 0 0 423.875,603.25 l -2.53125,7.5625 a 1.2330102,1.2330102 0 0 0 1.15625,1.625 h 7.375 l -22.9375,67.59375 a 1.2330102,1.2330102 0 0 0 1.15625,1.625 h 29.3125 a 1.2330102,1.2330102 0 0 0 1.15625,-0.8125 l 2.25,-6.59375 a 1.2330102,1.2330102 0 0 0 -1.15625,-1.625 h -5.125 l 14.625,-46.03125 H 475.625 l -16.6875,53.46875 a 1.2330102,1.2330102 0 0 0 1.1875,1.59375 h 28.28125 a 1.2330102,1.2330102 0 0 0 1.125,-0.75 l 2.53125,-6.0625 a 1.2330102,1.2330102 0 0 0 -1.125,-1.6875 h -5.125 l 14.875,-46.8125 h 25.1875 l -16.9375,53.71875 a 1.2330102,1.2330102 0 0 0 1.1875,1.59375 h 31.0625 a 1.2330102,1.2330102 0 0 0 1.15625,-0.78125 l 2.53125,-6.59375 a 1.2330102,1.2330102 0 0 0 -1.15625,-1.65625 h -6.15625 l 18.71875,-60.78125 a 1.2330102,1.2330102 0 0 0 -0.1875,-1.125 l -5.8125,-7.8125 a 1.2330102,1.2330102 0 0 0 -1,-0.46875 H 527.0625 a 1.2330102,1.2330102 0 0 0 -0.90625,0.375 l -7,7.6875 h -12.25 l -7.25,-7.9375 a 1.2330102,1.2330102 0 0 0 -0.90625,-0.375 h -17.90625 a 1.2330102,1.2330102 0 0 0 -0.25,0 z"
inkscape:connector-curvature="0" />
<path
d="m 400.03125,561.21875 -5.3125,4.5625 -5.3125,14.875 4.0625,4.0625 H 409.875 l 4.28125,-4.3125 4.5625,-15.65625 -3.53125,-3.53125 z m -30.0625,41.9375 -2.03125,8.0625 h 11.375 l -24.5,69.96875 h 34.84375 l 2.28125,-7.34375 h -9.59375 l 24.25,-70.6875 z m 110.875,0.25 L 473.25,612 h -12.625 l -8.0625,-8.34375 h -27.53125 l -2.53125,7.5625 h 9.09375 l -23.5,69.21875 h 29.3125 l 2.25,-6.59375 h -6.8125 L 448.25,625.375 h 29.0625 l -17.1875,55.0625 h 28.28125 l 2.53125,-6.0625 h -6.8125 l 15.65625,-49.25 h 27.78125 l -17.4375,55.3125 h 31.0625 l 2.53125,-6.59375 H 535.875 l 19.21875,-62.375 -5.8125,-7.8125 H 527.0625 l -7.34375,8.0625 h -13.375 l -7.59375,-8.3125 z"
id="path3665"
style="fill:#478cbf;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://kinglqst816b"
path="res://.godot/imported/icon.svg-5744b51718b6a64145ec5798797f7631.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot-vim/icon.svg"
dest_files=["res://.godot/imported/icon.svg-5744b51718b6a64145ec5798797f7631.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,7 @@
[plugin]
name="godot-vim"
description="VIM bindings for godot4"
author="Original: Josh N; Forked by Wenqiang Wang"
version="4.3.0"
script="godot-vim.gd"

View File

@ -0,0 +1,93 @@
extends LineEdit
const Cursor = preload("res://addons/godot_vim/cursor.gd")
const StatusBar = preload("res://addons/godot_vim/status_bar.gd")
const Constants = preload("res://addons/godot_vim/constants.gd")
const MODE = Constants.Mode
const Goto = preload("res://addons/godot_vim/commands/goto.gd")
const Find = preload("res://addons/godot_vim/commands/find.gd")
var code_edit: CodeEdit
var cursor: Cursor
var status_bar: StatusBar
var globals: Dictionary
var is_paused: bool = false
var search_pattern: String = ""
func _ready():
placeholder_text = "Enter command..."
show()
text_submitted.connect(_on_text_submitted)
text_changed.connect(_on_text_changed)
editable = true
func set_command(cmd: String):
text = cmd
caret_column = text.length()
func _on_text_changed(cmd: String):
if !cmd.begins_with("/"):
return
# Update search
var pattern: String = cmd.substr(1)
var rmatch: RegExMatch = globals.vim_plugin.search_regex(
code_edit, pattern, cursor.get_caret_pos() + Vector2i.RIGHT
)
if rmatch == null:
code_edit.remove_secondary_carets()
return
var pos: Vector2i = globals.vim_plugin.idx_to_pos(code_edit, rmatch.get_start())
if code_edit.get_caret_count() < 2:
code_edit.add_caret(pos.y, pos.x)
code_edit.select(pos.y, pos.x, pos.y, pos.x + rmatch.get_string().length(), 1)
# code_edit.center_viewport_to_caret(1) # why no work, eh?
code_edit.scroll_vertical = (
code_edit.get_scroll_pos_for_line(pos.y) - code_edit.get_visible_line_count() / 2
)
func handle_command(cmd: String):
if cmd.begins_with("/"):
var find = Find.new()
find.execute(globals, cmd)
return
if cmd.trim_prefix(":").is_valid_int():
var goto = Goto.new()
goto.execute(globals, cmd.trim_prefix(":"))
return
if globals.vim_plugin.dispatch(cmd) == OK:
set_paused(true)
return
status_bar.display_error('Unknown command: "%s"' % [cmd.trim_prefix(":")])
set_paused(true)
func close():
hide()
clear()
set_paused(false)
# Wait for user input
func set_paused(paused: bool):
is_paused = paused
text = "Press ENTER to continue" if is_paused else ""
editable = !is_paused
func _on_text_submitted(new_text: String):
if is_paused:
cursor.set_mode(MODE.NORMAL)
status_bar.main_label.text = ""
return
handle_command(new_text)

View File

@ -0,0 +1,14 @@
const Constants = preload("res://addons/godot_vim/constants.gd")
const StatusBar = preload("res://addons/godot_vim/status_bar.gd")
const MODE = Constants.Mode
const LINE_START_IDX: int = 8
const COL_START_IDX: int = 16
const FILE_START_IDX: int = 25
func execute(api: Dictionary, _args):
api.marks = {}
api.status_bar.display_text("Deleted all marks")
api.cursor.set_mode(MODE.NORMAL)

View File

@ -0,0 +1,16 @@
const Constants = preload("res://addons/godot_vim/constants.gd")
const MODE = Constants.Mode
func execute(api: Dictionary, args: String):
api.command_line.search_pattern = args.substr(1)
var rmatch: RegExMatch = api.vim_plugin.search_regex(
api.code_edit, api.command_line.search_pattern, api.cursor.get_caret_pos() + Vector2i.RIGHT
)
if rmatch != null:
var pos: Vector2i = api.vim_plugin.idx_to_pos(api.code_edit, rmatch.get_start())
api.cursor.set_caret_pos(pos.y, pos.x)
# api.code_edit.center_viewport_to_caret()
else:
api.status_bar.display_error('Pattern not found: "%s"' % [api.command_line.search_pattern])
api.cursor.set_mode(MODE.NORMAL)

View File

@ -0,0 +1,7 @@
const Constants = preload("res://addons/godot_vim/constants.gd")
const MODE = Constants.Mode
func execute(api, args):
api.cursor.set_caret_pos(args.to_int() - 1, 0)
api.cursor.set_mode(MODE.NORMAL)

View File

@ -0,0 +1,61 @@
const Constants = preload("res://addons/godot_vim/constants.gd")
const StatusBar = preload("res://addons/godot_vim/status_bar.gd")
const MODE = Constants.Mode
const LINE_START_IDX: int = 8
const COL_START_IDX: int = 16
const FILE_START_IDX: int = 25
## Display format:
## (1) LINE_START_IDX
## (2) COL_START_IDX
## (3) FILE_START_IDX
##
## List of all marks:
## (1) (2) (3)
## | | |
## mark line col file
## a 123 456 res://some_file
func row_string(mark: String, line: String, col: String, file: String) -> String:
var text: String = mark
text += " ".repeat(LINE_START_IDX - mark.length()) + line
text += " ".repeat(COL_START_IDX - text.length()) + col
text += " ".repeat(FILE_START_IDX - text.length()) + file
return text
func mark_string(key: String, m: Dictionary) -> String:
var pos: Vector2i = m.get("pos", Vector2i())
var file: String = m.get("file", "")
return row_string(key, str(pos.y), str(pos.x), file)
func execute(api, _args):
var marks: Dictionary = api.get("marks", {})
if marks.is_empty():
api.status_bar.display_error("No marks set")
api.cursor.set_mode(MODE.NORMAL)
return
var text: String = "[color=%s]List of all marks[/color]" % StatusBar.SPECIAL_COLOR
text += "\n" + row_string("mark", "line", "col", "file")
# Display user-defined marks first (alphabet)
for key in marks.keys():
if !is_key_alphabet(key):
continue
text += "\n" + mark_string(key, marks[key])
# Then display 'number' marks
for key in marks.keys():
if is_key_alphabet(key) or key == "-1":
continue
text += "\n" + mark_string(key, marks[key])
api.status_bar.display_text(text)
func is_key_alphabet(key: String) -> bool:
var unicode: int = key.unicode_at(0)
return (unicode >= 65 and unicode <= 90) or (unicode >= 97 and unicode <= 122)

View File

@ -0,0 +1,2 @@
func execute(api, args):
api.cursor.move_column(int(args))

View File

@ -0,0 +1,2 @@
func execute(api, args):
api.cursor.move_line(int(args))

View File

@ -0,0 +1,4 @@
func execute(api: Dictionary, _args):
print("[Godot VIM] Reloading...")
api.status_bar.display_text("Reloading plugin...")
api.vim_plugin.initialize(true)

View File

@ -0,0 +1,9 @@
const Constants = preload("res://addons/godot_vim/constants.gd")
const StatusBar = preload("res://addons/godot_vim/status_bar.gd")
func execute(api: Dictionary, _args):
print("[Godot VIM] Please run :reload in the command line after changing your keybinds")
var script: Script = api.key_map.get_script()
# Line 45 is where KeyMap::map() is
EditorInterface.edit_script(script, 40, 0, false)

View File

@ -0,0 +1,17 @@
const Constants = preload("res://addons/godot_vim/constants.gd")
const MODE = Constants.Mode
func execute(api, _args: String):
#EditorInterface.save_scene()
press_save_shortcut()
api.cursor.set_mode(MODE.NORMAL)
func press_save_shortcut():
var a = InputEventKey.new()
a.keycode = KEY_S
a.ctrl_pressed = true
a.alt_pressed = true
a.pressed = true
Input.parse_input_event(a)

View File

@ -0,0 +1,17 @@
const Constants = preload("res://addons/godot_vim/constants.gd")
const MODE = Constants.Mode
func execute(api, _args: String):
#EditorInterface.save_scene()
press_save_shortcut()
api.cursor.set_mode(MODE.NORMAL)
func press_save_shortcut():
var a = InputEventKey.new()
a.keycode = KEY_S
a.shift_pressed = true
a.alt_pressed = true
a.pressed = true
Input.parse_input_event(a)

View File

@ -0,0 +1,19 @@
enum Mode { NORMAL = 0, INSERT, VISUAL, VISUAL_LINE, COMMAND }
enum Language {
GDSCRIPT,
SHADER,
}
const KEYWORDS: String = ".,\"'-=+!@#$%^&*()[]{}?~/\\<>:;`"
const DIGITS: String = "0123456789"
const SPACES: String = " \t"
const PAIRS: Dictionary = {
'"': '"',
"'": "'",
"`": "`",
"(": ")",
"[": "]",
"{": "}",
}
const BRACES: String = "([{}])"

1135
addons/godot_vim/cursor.gd Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
extends Object
var handlers: Dictionary = {
"goto": preload("res://addons/godot_vim/commands/goto.gd"),
"find": preload("res://addons/godot_vim/commands/find.gd"),
"marks": preload("res://addons/godot_vim/commands/marks.gd"),
"delmarks": preload("res://addons/godot_vim/commands/delmarks.gd"),
"moveline": preload("res://addons/godot_vim/commands/moveline.gd"),
"movecolumn": preload("res://addons/godot_vim/commands/movecolumn.gd"),
"w": preload("res://addons/godot_vim/commands/w.gd"),
"wa": preload("res://addons/godot_vim/commands/wa.gd"),
# GodotVIM speficic commands:
"reload": preload("res://addons/godot_vim/commands/reload.gd"),
"remap": preload("res://addons/godot_vim/commands/remap.gd"),
}
var aliases: Dictionary = {"delm": ":delmarks"}
var globals: Dictionary
## Returns [enum @GlobalScope.Error]
func dispatch(command: String, do_allow_aliases: bool = true) -> int:
var command_idx_end: int = command.find(" ", 1)
if command_idx_end == -1:
command_idx_end = command.length()
var handler_name: String = command.substr(1, command_idx_end - 1)
if do_allow_aliases and aliases.has(handler_name):
return dispatch(aliases[handler_name], false)
if not handlers.has(handler_name):
return ERR_DOES_NOT_EXIST
var handler = handlers.get(handler_name)
var handler_instance = handler.new()
var args: String = command.substr(command_idx_end, command.length())
handler_instance.execute(globals, args)
return OK

Binary file not shown.

View File

@ -0,0 +1,34 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://4q1f672e7ux2"
path="res://.godot/imported/hack_regular.ttf-0fe21890026c2274f233cdc75cf86ba7.fontdata"
[deps]
source_file="res://addons/godot_vim/hack_regular.ttf"
dest_files=["res://.godot/imported/hack_regular.ttf-0fe21890026c2274f233cdc75cf86ba7.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

741
addons/godot_vim/key_map.gd Normal file
View File

@ -0,0 +1,741 @@
class_name KeyMap extends RefCounted
## Hanldes input stream and key mapping
##
## You may also set your keybindings in the [method map] function
## * SET YOUR KEYBINDINGS HERE *
## Also see the "COMMANDS" section at the bottom of cursor.gd
## E.g. the command for
## KeyRemap.new(...) .motion("foo", { "bar": 1 })
## is handled in Cursor::cmd_foo(args: Dictionary)
## where `args` is `{ "type": "foo", "bar": 1 }`
## Example:
## [codeblock]
## return [
## # Move 5 characters to the right with "L"
## KeyRemap.new([ "L" ])
## .motion("move_by_chars", { "move_by": 5 }),
##
## # Let's remove "d" (the delete operator) and replace it with "q"
## # You may additionally specify the type and context of the cmd to remove
## # using .operator() (or .motion() or .action() etc...) and .with_context()
## KeyRemap.new([ "d" ])
## .remove(),
## # "q" is now the new delete operator
## KeyRemap.new([ "q" ])
## .operator("delete"),
##
## # Delete this line along with the next two with "Z"
## # .operator() and .motion() automatically merge together
## KeyRemap.new([ "Z" ])
## .operator("delete")
## .motion("move_by_lines", { "move_by": 2, "line_wise": true }),
##
## # In Insert mode, return to Normal mode with "jk"
## KeyRemap.new([ "j", "k" ])
## .action("normal", { "backspaces": 1, "offset": 1 })
## .with_context(Mode.INSERT),
## ]
## [/codeblock]
static func map() -> Array[KeyRemap]:
# Example:
return [
# In Insert mode, return to Normal mode with "jk"
# KeyRemap.new([ "j", "k" ])
# .action("normal", { "backspaces": 1, "offset": 0 })
# .with_context(Mode.INSERT),
# Make "/" search in case insensitive mode
# KeyRemap.new([ "/" ])
# .action("command", { "command": "/(?i)" })
# .replace(),
# In Insert mode, return to Normal mode with "Ctrl-["
# KeyRemap.new([ "<C-[>" ])
# .action("normal")
# .with_context(Mode.INSERT),
]
const Constants = preload("res://addons/godot_vim/constants.gd")
const Mode = Constants.Mode
const INSERT_MODE_TIMEOUT_MS: int = 700
enum {
## Moves the cursor. Can be used in tandem with Operator
Motion,
## Operators (like delete, change, yank) work on selections
## In Normal mode, they need a Motion or another Operator bound to them (e.g. dj, yy)
Operator,
## Operator but with a motion already bound to it
## Can only be executed in Normal mode
OperatorMotion,
## A single action (e.g. i, o, v, J, u)
## Cannot be executed in Visual mode unless specified with "context": Mode.VISUAL
Action,
Incomplete, ## Incomplete command
NotFound, ## Command not found
}
#region key_map
# Also see the "COMMANDS" section at the bottom of cursor.gd
# Command for { "type": "foo", ... } is handled in Cursor::cmd_foo(args: Dictionary)
# where `args` is ^^^^^ this Dict ^^^^^^
var key_map: Array[Dictionary] = [
# MOTIONS
{"keys": ["h"], "type": Motion, "motion": {"type": "move_by_chars", "move_by": -1}},
{"keys": ["l"], "type": Motion, "motion": {"type": "move_by_chars", "move_by": 1}},
{
"keys": ["j"],
"type": Motion,
"motion": {"type": "move_by_lines", "move_by": 1, "line_wise": true}
},
{
"keys": ["k"],
"type": Motion,
"motion": {"type": "move_by_lines", "move_by": -1, "line_wise": true}
},
# About motions: the argument `inclusive` is used with Operators (see execute_operator_motion())
{
"keys": ["w"],
"type": Motion,
"motion": {"type": "move_by_word", "forward": true, "word_end": false}
},
{
"keys": ["e"],
"type": Motion,
"motion": {"type": "move_by_word", "forward": true, "word_end": true, "inclusive": true}
},
{
"keys": ["b"],
"type": Motion,
"motion": {"type": "move_by_word", "forward": false, "word_end": false}
},
{
"keys": ["g", "e"],
"type": Motion,
"motion": {"type": "move_by_word", "forward": false, "word_end": true}
},
{
"keys": ["W"],
"type": Motion,
"motion": {"type": "move_by_word", "forward": true, "word_end": false, "big_word": true}
},
{
"keys": ["E"],
"type": Motion,
"motion":
{
"type": "move_by_word",
"forward": true,
"word_end": true,
"big_word": true,
"inclusive": true
}
},
{
"keys": ["B"],
"type": Motion,
"motion": {"type": "move_by_word", "forward": false, "word_end": false, "big_word": true}
},
{
"keys": ["g", "E"],
"type": Motion,
"motion": {"type": "move_by_word", "forward": false, "word_end": true, "big_word": true}
},
# Find & search
{
"keys": ["f", "{char}"],
"type": Motion,
"motion": {"type": "find_in_line", "forward": true, "inclusive": true}
},
{
"keys": ["t", "{char}"],
"type": Motion,
"motion": {"type": "find_in_line", "forward": true, "stop_before": true, "inclusive": true}
},
{"keys": ["F", "{char}"], "type": Motion, "motion": {"type": "find_in_line", "forward": false}},
{
"keys": ["T", "{char}"],
"type": Motion,
"motion": {"type": "find_in_line", "forward": false, "stop_before": true}
},
{"keys": [";"], "type": Motion, "motion": {"type": "find_in_line_again", "invert": false}},
{"keys": [","], "type": Motion, "motion": {"type": "find_in_line_again", "invert": true}},
{"keys": ["n"], "type": Motion, "motion": {"type": "find_again", "forward": true}},
{"keys": ["N"], "type": Motion, "motion": {"type": "find_again", "forward": false}},
{"keys": ["0"], "type": Motion, "motion": {"type": "move_to_bol"}},
{"keys": ["$"], "type": Motion, "motion": {"type": "move_to_eol"}},
{"keys": ["^"], "type": Motion, "motion": {"type": "move_to_first_non_whitespace_char"}},
{
"keys": ["{"],
"type": Motion,
"motion": {"type": "move_by_paragraph", "forward": false, "line_wise": true}
},
{
"keys": ["}"],
"type": Motion,
"motion": {"type": "move_by_paragraph", "forward": true, "line_wise": true}
},
{
"keys": ["[", "["],
"type": Motion,
"motion": {"type": "move_by_section", "forward": false, "line_wise": true}
},
{
"keys": ["]", "]"],
"type": Motion,
"motion": {"type": "move_by_section", "forward": true, "line_wise": true}
},
{"keys": ["g", "g"], "type": Motion, "motion": {"type": "move_to_bof", "line_wise": true}},
{"keys": ["G"], "type": Motion, "motion": {"type": "move_to_eof", "line_wise": true}},
{"keys": ["g", "m"], "type": Motion, "motion": {"type": "move_to_center_of_line"}},
{
"keys": ["<C-u>"],
"type": Motion,
"motion": {"type": "move_by_screen", "percentage": -0.5, "line_wise": true}
},
{
"keys": ["<C-d>"],
"type": Motion,
"motion": {"type": "move_by_screen", "percentage": 0.5, "line_wise": true}
},
{"keys": ["%"], "type": Motion, "motion": {"type": "jump_to_next_brace_pair"}},
# TEXT OBJECTS
{
"keys": ["a", "w"],
"type": Motion,
"motion": {"type": "text_object_word", "around": true, "inclusive": true}
},
{
"keys": ["a", "W"],
"type": Motion,
"motion": {"type": "text_object_word", "around": true, "inclusive": true}
},
{"keys": ["i", "w"], "type": Motion, "motion": {"type": "text_object_word", "inclusive": true}},
{
"keys": ["i", "W"],
"type": Motion,
"motion": {"type": "text_object_word", "big_word": true, "inclusive": true}
},
{
"keys": ["i", "p"],
"type": Motion,
"motion": {"type": "text_object_paragraph", "line_wise": true}
},
{
"keys": ["a", "p"],
"type": Motion,
"motion": {"type": "text_object_paragraph", "around": true, "line_wise": true}
},
{
"keys": ["i", '"'],
"type": Motion,
"motion": {"type": "text_object", "object": '"', "inclusive": true, "inline": true}
},
{
"keys": ["a", '"'],
"type": Motion,
"motion":
{"type": "text_object", "object": '"', "around": true, "inclusive": true, "inline": true}
},
{
"keys": ["i", "'"],
"type": Motion,
"motion": {"type": "text_object", "object": "'", "inclusive": true, "inline": true}
},
{
"keys": ["a", "'"],
"type": Motion,
"motion":
{"type": "text_object", "object": "'", "around": true, "inclusive": true, "inline": true}
},
{
"keys": ["i", "`"],
"type": Motion,
"motion": {"type": "text_object", "object": "`", "inclusive": true, "inline": true}
},
{
"keys": ["a", "`"],
"type": Motion,
"motion":
{"type": "text_object", "object": "`", "around": true, "inclusive": true, "inline": true}
},
# "i" + any of "(", ")", or "b"
{
"keys": ["i", ["(", ")", "b"]],
"type": Motion,
"motion": {"type": "text_object", "object": "(", "inclusive": true}
},
{
"keys": ["a", ["(", ")", "b"]],
"type": Motion,
"motion": {"type": "text_object", "object": "(", "around": true, "inclusive": true}
},
{
"keys": ["i", ["[", "]"]],
"type": Motion,
"motion": {"type": "text_object", "object": "[", "inclusive": true}
},
{
"keys": ["a", ["[", "]"]],
"type": Motion,
"motion": {"type": "text_object", "object": "[", "around": true, "inclusive": true}
},
{
"keys": ["i", ["{", "}", "B"]],
"type": Motion,
"motion": {"type": "text_object", "object": "{", "inclusive": true}
},
{
"keys": ["a", ["{", "}", "B"]],
"type": Motion,
"motion": {"type": "text_object", "object": "{", "around": true, "inclusive": true}
},
# OPERATORS
{"keys": ["d"], "type": Operator, "operator": {"type": "delete"}},
{
"keys": ["D"],
"type": OperatorMotion,
"operator": {"type": "delete"},
"motion": {"type": "move_to_eol"}
},
{
"keys": ["x"],
"type": OperatorMotion,
"operator": {"type": "delete"},
"motion": {"type": "move_by_chars", "move_by": 1}
},
{"keys": ["x"], "type": Operator, "context": Mode.VISUAL, "operator": {"type": "delete"}},
{"keys": ["y"], "type": Operator, "operator": {"type": "yank"}},
{
"keys": ["Y"],
"type": OperatorMotion,
"operator": {"type": "yank", "line_wise": true}, # No motion. Same as yy
},
{"keys": ["c"], "type": Operator, "operator": {"type": "change"}},
{
"keys": ["C"],
"type": OperatorMotion,
"operator": {"type": "change"},
"motion": {"type": "move_to_eol"}
},
{
"keys": ["s"],
"type": OperatorMotion,
"operator": {"type": "change"},
"motion": {"type": "move_by_chars", "move_by": 1}
},
{"keys": ["s"], "type": Operator, "context": Mode.VISUAL, "operator": {"type": "change"}},
{
"keys": ["p"],
"type": OperatorMotion,
"operator": {"type": "paste"},
"motion": {"type": "move_by_chars", "move_by": 1}
},
{"keys": ["p"], "type": Operator, "context": Mode.VISUAL, "operator": {"type": "paste"}},
{"keys": ["p"], "type": Operator, "context": Mode.VISUAL_LINE, "operator": {"type": "paste"}},
{"keys": [">"], "type": Operator, "operator": {"type": "indent", "forward": true}},
{"keys": ["<"], "type": Operator, "operator": {"type": "indent", "forward": false}},
{
"keys": ["g", "c", "c"],
"type": OperatorMotion,
"operator": {"type": "comment"},
"motion": {"type": "move_by_chars", "move_by": 1}
},
{"keys": ["g", "c"], "type": Operator, "operator": {"type": "comment"}},
{
"keys": ["~"],
"type": OperatorMotion,
"operator": {"type": "toggle_uppercase"},
"motion": {"type": "move_by_chars", "move_by": 1}
},
{
"keys": ["~"],
"type": Operator,
"context": Mode.VISUAL,
"operator": {"type": "toggle_uppercase"}
},
{
"keys": ["u"],
"type": Operator,
"context": Mode.VISUAL,
"operator": {"type": "set_uppercase", "uppercase": false}
},
{
"keys": ["U"],
"type": Operator,
"context": Mode.VISUAL,
"operator": {"type": "set_uppercase", "uppercase": true}
},
{
"keys": ["V"],
"type": Operator,
"context": Mode.VISUAL,
"operator": {"type": "visual", "line_wise": true}
},
{
"keys": ["v"],
"type": Operator,
"context": Mode.VISUAL_LINE,
"operator": {"type": "visual", "line_wise": false}
},
# ACTIONS
{"keys": ["i"], "type": Action, "action": {"type": "insert"}},
{"keys": ["a"], "type": Action, "action": {"type": "insert", "offset": "after"}},
{"keys": ["I"], "type": Action, "action": {"type": "insert", "offset": "bol"}},
{"keys": ["A"], "type": Action, "action": {"type": "insert", "offset": "eol"}},
{"keys": ["o"], "type": Action, "action": {"type": "insert", "offset": "new_line_below"}},
{"keys": ["O"], "type": Action, "action": {"type": "insert", "offset": "new_line_above"}},
{"keys": ["v"], "type": Action, "action": {"type": "visual"}},
{"keys": ["V"], "type": Action, "action": {"type": "visual", "line_wise": true}},
{"keys": ["u"], "type": Action, "action": {"type": "undo"}},
{"keys": ["<C-r>"], "type": Action, "action": {"type": "redo"}},
{"keys": ["r", "{char}"], "type": Action, "action": {"type": "replace"}},
{"keys": [":"], "type": Action, "action": {"type": "command"}},
{"keys": ["/"], "type": Action, "action": {"type": "command", "command": "/"}},
{"keys": ["J"], "type": Action, "action": {"type": "join"}},
{"keys": ["z", "z"], "type": Action, "action": {"type": "center_caret"}},
{"keys": ["m", "{char}"], "type": Action, "action": {"type": "mark"}},
{"keys": ["`", "{char}"], "type": Action, "action": {"type": "jump_to_mark"}},
# MISCELLANEOUS
{
"keys": ["o"],
"type": Operator,
"context": Mode.VISUAL,
"operator": {"type": "visual_jump_to_other_end"}
},
{
"keys": ["o"],
"type": Operator,
"context": Mode.VISUAL_LINE,
"operator": {"type": "visual_jump_to_other_end"}
},
]
#endregion key_map
# Keys we won't handle
const BLACKLIST: Array[String] = [
"<C-s>", # Save
"<C-b>", # Bookmark
]
enum KeyMatch {
None = 0, # Keys don't match
Partial = 1, # Keys match partially
Full = 2, # Keys match totally
}
var input_stream: Array[String] = []
var cursor: Control
var last_insert_mode_input_ms: int = 0
func _init(cursor_: Control):
cursor = cursor_
apply_remaps(KeyMap.map())
## Returns: Dictionary with the found command: { "type": Motion or Operator or OperatorMotion or Action or Incomplete or NotFound, ... }
## Warning: the returned Dict can be empty in if the event wasn't processed
func register_event(event: InputEventKey, with_context: Mode) -> Dictionary:
# Stringify event
var ch: String = event_to_string(event)
if ch.is_empty():
return {} # Invalid
if BLACKLIST.has(ch):
return {}
# Handle Insert mode timeout
if with_context == Mode.INSERT:
if handle_insert_mode_timeout():
clear()
return {}
# Process input stream
# print("[KeyMap::register_event()] ch = ", ch) # DEBUG
input_stream.append(ch)
var cmd: Dictionary = parse_keys(input_stream, with_context)
if !is_cmd_valid(cmd):
return {"type": NotFound}
execute(cmd)
return cmd
func parse_keys(keys: Array[String], with_context: Mode) -> Dictionary:
var blacklist: Array = get_blacklist_types_in_context(with_context)
var cmd: Dictionary = find_cmd(keys, with_context, blacklist)
if cmd.is_empty() or cmd.type == NotFound:
call_deferred(&"clear")
return cmd
if cmd.type == Incomplete:
# print(cmd)
return cmd
# Execute the operation as-is if in VISUAL mode
# If in NORMAL mode, await further input
if cmd.type == Operator and with_context == Mode.NORMAL:
var op_args: Array[String] = keys.slice(cmd.keys.size()) # Get the rest of keys for motion
if op_args.is_empty(): # Incomplete; await further input
return {"type": Incomplete}
var next: Dictionary = find_cmd(op_args, with_context, [Action, OperatorMotion])
if next.is_empty() or next.type == NotFound: # Invalid sequence
call_deferred(&"clear")
return {"type": NotFound}
elif next.type == Incomplete:
return {"type": Incomplete}
cmd.modifier = next
call_deferred(&"clear")
return cmd
## The returned cmd will always have a 'type' key
# TODO use bitmask instead of Array?
func find_cmd(keys: Array[String], with_context: Mode, blacklist: Array = []) -> Dictionary:
var partial: bool = false # In case none were found
var is_visual: bool = with_context == Mode.VISUAL or with_context == Mode.VISUAL_LINE
for cmd in key_map:
# FILTERS
# Don't allow anything in Insert mode unless specified
if with_context == Mode.INSERT and cmd.get("context", -1) != Mode.INSERT:
continue
if blacklist.has(cmd.type):
continue
# Skip if contexts don't match
if cmd.has("context") and with_context != cmd.context:
continue
# CHECK KEYS
var m: KeyMatch = match_keys(cmd.keys, keys)
partial = partial or m == KeyMatch.Partial # Set/keep partial = true if it was a partial match
if m != KeyMatch.Full:
continue
var cmd_mut: Dictionary = cmd.duplicate(true) # 'mut' ('mutable') because key_map is read-only
# Keep track of selected character, which will later be copied into the fucntion call for the command
# (See execute() where we check if cmd.has('selected_char'))
if cmd.keys[-1] is String and cmd.keys[-1] == "{char}":
cmd_mut.selected_char = keys.back()
return cmd_mut
return {"type": Incomplete if partial else NotFound}
# TODO use bitmask instead of Array?
func get_blacklist_types_in_context(context: Mode) -> Array:
match context:
Mode.VISUAL, Mode.VISUAL_LINE:
return [OperatorMotion, Action]
_:
return []
func execute_operator_motion(cmd: Dictionary):
if cmd.has("motion"):
if cmd.has("selected_char"):
cmd.motion.selected_char = cmd.selected_char
operator_motion(cmd.operator, cmd.motion)
else:
call_cmd(cmd.operator)
func execute_operator(cmd: Dictionary):
# print("[KeyMay::execute()] op: ", cmd) # DEBUG
if !cmd.has("modifier"): # Execute as-is
call_cmd(cmd.operator)
return
var mod: Dictionary = cmd.modifier
# Execute with motion
if mod.type == Motion:
if mod.has("selected_char"):
mod.motion.selected_char = mod.selected_char
operator_motion(cmd.operator, mod.motion)
# Execute with `line_wise = true` if repeating operations (e.g. dd, yy)
elif mod.type == Operator and mod.operator.type == cmd.operator.type:
var op_cmd: Dictionary = cmd.operator.duplicate()
op_cmd.line_wise = true
call_cmd(op_cmd)
func execute_action(cmd: Dictionary):
if cmd.has("selected_char"):
cmd.action.selected_char = cmd.selected_char
call_cmd(cmd.action)
func execute_motion(cmd: Dictionary):
if cmd.has("selected_char"):
cmd.motion.selected_char = cmd.selected_char
var pos = call_cmd(cmd.motion) # Vector2i for normal motion, or [Vector2i, Vector2i] for text object
if pos is Vector2i:
cursor.set_caret_pos(pos.y, pos.x)
elif pos is Array:
assert(pos.size() == 2)
# print("[execute_motion() -> text obj] pos = ", pos)
cursor.select(pos[0].y, pos[0].x, pos[1].y, pos[1].x)
func execute(cmd: Dictionary):
if !is_cmd_valid(cmd):
return
match cmd.type:
Motion:
execute_motion(cmd)
OperatorMotion:
execute_operator_motion(cmd)
Operator:
execute_operator(cmd)
Action:
execute_action(cmd)
_:
push_error("[KeyMap::execute()] Unknown command type: %s" % cmd.type)
func operator_motion(operator: Dictionary, motion: Dictionary):
# print("[KeyMay::execute_operator_motion()] op = ", operator, ", motion = ", motion) # DEBUG
# Execute motion before operation
var p = call_cmd(motion) # Vector2i for normal motion, or [Vector2i, Vector2i] for text object
if p is Vector2i:
var p0: Vector2i = cursor.get_caret_pos()
if motion.get("inclusive", false):
p.x += 1
cursor.code_edit.select(p0.y, p0.x, p.y, p.x)
elif p is Array:
assert(p.size() == 2)
if motion.get("inclusive", false):
p[1].x += 1
cursor.code_edit.select(p[0].y, p[0].x, p[1].y, p[1].x)
# Add line_wise flag if line wise motion
var op: Dictionary = operator.duplicate()
op.line_wise = motion.get("line_wise", false)
call_cmd(op)
## Unsafe: does not check if the function exists
func call_cmd(cmd: Dictionary) -> Variant:
var func_name: StringName = StringName("cmd_" + cmd.type)
return cursor.call(func_name, cmd)
static func is_cmd_valid(cmd: Dictionary):
return !cmd.is_empty() and cmd.type != Incomplete and cmd.type != NotFound
static func event_to_string(event: InputEventKey) -> String:
# Special chars
if event.keycode == KEY_ENTER:
return "<CR>"
if event.keycode == KEY_TAB:
return "<TAB>"
if event.keycode == KEY_ESCAPE:
return "<ESC>"
# Ctrl + key
if event.is_command_or_control_pressed():
if !OS.is_keycode_unicode(event.keycode):
return ""
var c: String = char(event.keycode)
return "<C-%s>" % [c if event.shift_pressed else c.to_lower()]
# You're not special.
return char(event.unicode)
## Matches single command keys
## expected_keys: Array[key: String] or Array[any_of_these_keys: Array[key: String]]
static func match_keys(expected_keys: Array, input_keys: Array) -> KeyMatch:
var in_size: int = input_keys.size()
var ex_size: int = expected_keys.size()
if expected_keys[-1] is String and expected_keys[-1] == "{char}":
# If everything + {char} matches
if _do_keys_match(input_keys.slice(0, -1), expected_keys.slice(0, -1)):
return KeyMatch.Full
# If everything up until {char} matches
elif _do_keys_match(input_keys, expected_keys.slice(0, in_size)):
return KeyMatch.Partial
else:
# Check for full match
if _do_keys_match(input_keys, expected_keys):
return KeyMatch.Full
# Check for incomplete command (e.g. "ge", "gcc")
elif _do_keys_match(input_keys, expected_keys.slice(0, in_size)):
return KeyMatch.Partial
# Cases with operators like "dj", "ce"
elif _do_keys_match(input_keys.slice(0, ex_size), expected_keys) and in_size > ex_size:
return KeyMatch.Full
return KeyMatch.None
## input_keys: Array[key: String]
## expected_keys: Array[key: String] or Array[any_of_these_keys: Array[key: String]]
static func _do_keys_match(input_keys: Array, match_keys: Array) -> bool:
if match_keys.size() != input_keys.size():
return false
for i in input_keys.size():
var key: String = input_keys[i]
if match_keys[i] is String:
if !match_keys[i] == key:
return false
elif match_keys[i] is Array:
if !match_keys[i].has(key):
return false
else:
push_error("expected String or Array[String], found ", match_keys[i])
return false
return true
## Clears the input stream
func clear():
input_stream = []
func get_input_stream_as_string() -> String:
return "".join(PackedStringArray(input_stream))
## Returns whether the Insert mode input has timed out, in which case we
## don't want to process it
func handle_insert_mode_timeout() -> bool:
var current_tick_ms: int = Time.get_ticks_msec()
if input_stream.is_empty():
last_insert_mode_input_ms = current_tick_ms
return false
if current_tick_ms - last_insert_mode_input_ms > INSERT_MODE_TIMEOUT_MS:
last_insert_mode_input_ms = current_tick_ms
return true
last_insert_mode_input_ms = current_tick_ms
return false
func apply_remaps(map: Array[KeyRemap]):
if map.is_empty():
return
print("[Godot VIM] Applying keybind remaps...")
for remap in map:
remap.apply(key_map)

View File

@ -0,0 +1,7 @@
[plugin]
name="GodotVim"
description=""
author="Bernardo Bruning"
version="0.1"
script="plugin.gd"

354
addons/godot_vim/plugin.gd Normal file
View File

@ -0,0 +1,354 @@
@tool
extends EditorPlugin
const StatusBar = preload("res://addons/godot_vim/status_bar.gd")
const CommandLine = preload("res://addons/godot_vim/command_line.gd")
const Cursor = preload("res://addons/godot_vim/cursor.gd")
const Dispatcher = preload("res://addons/godot_vim/dispatcher.gd")
const Constants = preload("res://addons/godot_vim/constants.gd")
const DIGITS = Constants.DIGITS
const LANGUAGE = Constants.Language
var cursor: Cursor
var key_map: KeyMap
var command_line: CommandLine
var status_bar: StatusBar
var globals: Dictionary = {}
var dispatcher: Dispatcher
func _enter_tree():
EditorInterface.get_script_editor().connect("editor_script_changed", _on_script_changed)
var shader_tabcontainer = get_shader_tabcontainer() as TabContainer
if shader_tabcontainer != null:
shader_tabcontainer.tab_changed.connect(_on_shader_tab_changed)
shader_tabcontainer.visibility_changed.connect(_on_shader_tab_visibility_changed)
else:
push_error(
"[Godot VIM] Failed to get shader editor's TabContainer. Vim will be disabled in the shader editor"
)
globals = {}
initialize(true)
func initialize(forced: bool = false):
_load(forced)
print("[Godot VIM] Initialized.")
print(" If you wish to set keybindings, please run :remap in the command line")
func _on_script_changed(script: Script):
if !script:
return
mark_recent_file(script.resource_path)
_load()
func _on_shader_tab_changed(_tab: int):
call_deferred(&"_load")
func _on_shader_tab_visibility_changed():
call_deferred(&"_load")
func mark_recent_file(path: String):
if !globals.has("marks"):
globals.marks = {}
var marks: Dictionary = globals.marks
# Check if path is already in the recent files (stored in start_index)
# This is to avoid flooding the recent files list with the same files
var start_index: int = 0
while start_index <= 9:
var m: String = str(start_index)
if !marks.has(m) or marks[m].file == path: # Found
break
start_index += 1
# Shift all files from start_index down one
for i in range(start_index, -1, -1):
var m: String = str(i)
var prev_m: String = str(i - 1)
if !marks.has(prev_m):
continue
marks[m] = marks[prev_m]
# Mark "-1" won't be accessible to the user
# It's just the current file, and will be indexed next time the
# loop above ^^^ is called
marks["-1"] = {"file": path, "pos": Vector2i(-1, 0)}
func edit_script(path: String, pos: Vector2i):
var script = load(path)
if script == null:
status_bar.display_error('Could not open file "%s"' % path)
return ""
EditorInterface.edit_script(script, pos.y, pos.x)
#region LOAD
func _init_cursor(code_edit: CodeEdit, language: LANGUAGE):
if cursor != null:
cursor.queue_free()
cursor = Cursor.new()
code_edit.select(
code_edit.get_caret_line(),
code_edit.get_caret_column(),
code_edit.get_caret_line(),
code_edit.get_caret_column() + 1
)
cursor.code_edit = code_edit
cursor.language = language
cursor.globals = globals
func _init_command_line(code_edit: CodeEdit):
if command_line != null:
command_line.queue_free()
command_line = CommandLine.new()
command_line.code_edit = code_edit
cursor.command_line = command_line
command_line.cursor = cursor
command_line.globals = globals
command_line.hide()
func _init_status_bar():
if status_bar != null:
status_bar.queue_free()
status_bar = StatusBar.new()
cursor.status_bar = status_bar
command_line.status_bar = status_bar
func _load(forced: bool = false):
if globals == null:
globals = {}
var result: Dictionary = find_code_edit()
if result.is_empty():
return
var code_edit: CodeEdit = result.code_edit
var language: LANGUAGE = result.language
_init_cursor(code_edit, language)
_init_command_line(code_edit)
_init_status_bar()
# KeyMap
if key_map == null or forced:
key_map = KeyMap.new(cursor)
else:
key_map.cursor = cursor
cursor.key_map = key_map
var script_editor = EditorInterface.get_script_editor()
if script_editor == null:
return
var script_editor_base = script_editor.get_current_editor()
if script_editor_base == null:
return
globals.command_line = command_line
globals.status_bar = status_bar
globals.code_edit = code_edit
globals.cursor = cursor
globals.script_editor = script_editor
globals.vim_plugin = self
globals.key_map = key_map
dispatcher = Dispatcher.new()
dispatcher.globals = globals
# Add nodes
if language != LANGUAGE.SHADER:
script_editor_base.add_child(cursor)
script_editor_base.add_child(status_bar)
script_editor_base.add_child(command_line)
return
# Get shader editor VBoxContainer
var shaders_container = code_edit
for i in 3:
shaders_container = shaders_container.get_parent()
if shaders_container == null:
# We do not print an error here because for this to fail,
# get_shader_code_edit() (through find_code_edit()) must have
# already failed
return
shaders_container.add_child(cursor)
shaders_container.add_child(status_bar)
shaders_container.add_child(command_line)
#endregion LOAD
func dispatch(command: String):
return dispatcher.dispatch(command)
## Finds whatever CodeEdit is open
func find_code_edit() -> Dictionary:
var code_edit: CodeEdit = get_shader_code_edit()
var language: LANGUAGE = LANGUAGE.SHADER
# Shader panel not open; normal gdscript code edit
if code_edit == null:
code_edit = get_regular_code_edit()
language = LANGUAGE.GDSCRIPT
if code_edit == null:
return {}
return {
"code_edit": code_edit,
"language": language,
}
## Gets the regular GDScript CodeEdit
func get_regular_code_edit():
var editor = EditorInterface.get_script_editor().get_current_editor()
return _select(editor, ["VSplitContainer", "CodeTextEditor", "CodeEdit"])
# FIXME Handle cases where the shader editor is its own floating window
## Gets the shader editor's CodeEdit
## Returns Option<CodeEdit> (aka CodeEdit or null)
func get_shader_code_edit():
var container = get_shader_tabcontainer()
if container == null:
push_error(
"[Godot VIM] Failed to get shader editor's TabContainer. Vim will be disabled in the shader editor"
)
return null
# Panel not open
if !container.is_visible_in_tree():
return null
var editors = container.get_children(false)
for tse in editors:
if !tse.visible: # Not open
continue
var code_edit = _select(
tse, ["VBoxContainer", "VSplitContainer", "ShaderTextEditor", "CodeEdit"]
)
if code_edit == null:
push_error(
"[Godot Vim] Failed to get shader editor's CodeEdit. Vim will be disabled in the shader editor"
)
return null
return code_edit
## Returns Option<TabContainer> (aka either TabContainer or null if it fails)
func get_shader_tabcontainer():
# Get the VSplitContainer containing the script editor and bottom panels
var container = EditorInterface.get_script_editor()
for i in 6:
container = container.get_parent()
if container == null:
# We don't print an error here, let us handle this exception elsewhere
return null
# Get code edit
container = _select(
container,
["PanelContainer", "VBoxContainer", "WindowWrapper", "HSplitContainer", "TabContainer"]
)
return container
func _select(obj: Node, types: Array[String]):
if not obj:
return null
for type in types:
for child in obj.get_children():
if child.is_class(type):
obj = child
continue
return obj
func _exit_tree():
if cursor != null:
cursor.queue_free()
if command_line != null:
command_line.queue_free()
if status_bar != null:
status_bar.queue_free()
# -------------------------------------------------------------
# ** UTIL **
# -------------------------------------------------------------
func search_regex(text_edit: TextEdit, pattern: String, from_pos: Vector2i) -> RegExMatch:
var regex: RegEx = RegEx.new()
var err: int = regex.compile(pattern)
var idx: int = pos_to_idx(text_edit, from_pos)
var res: RegExMatch = regex.search(text_edit.text, idx)
if res == null:
return regex.search(text_edit.text, 0)
return res
func search_regex_backwards(text_edit: TextEdit, pattern: String, from_pos: Vector2i) -> RegExMatch:
var regex: RegEx = RegEx.new()
var err: int = regex.compile(pattern)
var idx: int = pos_to_idx(text_edit, from_pos)
# We use pop_back() so it doesn't print an error
var res: RegExMatch = regex.search_all(text_edit.text, 0, idx).pop_back()
if res == null:
return regex.search_all(text_edit.text).pop_back()
return res
func pos_to_idx(text_edit: TextEdit, pos: Vector2i) -> int:
text_edit.select(0, 0, pos.y, pos.x)
var len: int = text_edit.get_selected_text().length()
text_edit.deselect()
return len
func idx_to_pos(text_edit: TextEdit, idx: int) -> Vector2i:
var line: int = text_edit.text.count("\n", 0, idx)
var col: int = idx - text_edit.text.rfind("\n", idx) - 1
return Vector2i(col, line)
func get_first_non_digit_idx(str: String) -> int:
if str.is_empty():
return -1
if str[0] == "0":
return 0 # '0...' is an exception
for i in str.length():
if !DIGITS.contains(str[i]):
return i
return -1 # All digits
## Repeat the function `f` and accumulate the result. A bit like Array::reduce()
## f: func(T) -> T where T is the previous output
func repeat_accum(count: int, inital_value: Variant, f: Callable) -> Variant:
var value: Variant = inital_value
for _index in count:
value = f.call(value)
return value

178
addons/godot_vim/remap.gd Normal file
View File

@ -0,0 +1,178 @@
class_name KeyRemap extends RefCounted
enum ApplyMode {
## Append this keybind to the end of the list
APPEND,
## Insert this keybind at the start of the list
PREPEND,
## Insert this keybind at the specified index
INSERT,
## Remove the specified keybind
REMOVE,
## Replace a keybind with this one
REPLACE,
}
const Constants = preload("res://addons/godot_vim/constants.gd")
const MODE = Constants.Mode
# Inner cmd
var inner: Dictionary = {}
var options: Dictionary = {"apply_mode": ApplyMode.APPEND}
func _init(keys: Array[String]):
assert(!keys.is_empty(), "cmd_keys cannot be empty")
inner = {"keys": keys}
## Returns self
func motion(motion_type: String, args: Dictionary = {}) -> KeyRemap:
var m: Dictionary = {"type": motion_type}
m.merge(args, true)
inner.motion = m
# Operator + Motion = OperatorMotion
if inner.get("type") == KeyMap.Operator:
inner.type = KeyMap.OperatorMotion
else:
inner.type = KeyMap.Motion
return self
## Returns self
func operator(operator_type: String, args: Dictionary = {}) -> KeyRemap:
var o: Dictionary = {"type": operator_type}
o.merge(args, true)
inner.operator = o
# Motion + Operator = OperatorMotion
if inner.get("type") == KeyMap.Motion:
inner.type = KeyMap.OperatorMotion
else:
inner.type = KeyMap.Operator
return self
## Returns self
func action(action_type: String, args: Dictionary = {}) -> KeyRemap:
var a: Dictionary = {"type": action_type}
a.merge(args, true)
inner.action = a
inner.type = KeyMap.Action
return self
## Returns self
func with_context(mode: MODE) -> KeyRemap:
inner["context"] = mode
return self
# `key_map` = KeyMap::key_map
func apply(key_map: Array[Dictionary]):
match options.get("apply_mode", ApplyMode.APPEND):
ApplyMode.APPEND:
key_map.append(inner)
ApplyMode.PREPEND:
var err: int = key_map.insert(0, inner)
if err != OK:
push_error("[Godot VIM] Failed to prepend keybind: %s" % error_string(err))
ApplyMode.INSERT:
var index: int = options.get("index", 0)
var err: int = key_map.insert(index, inner)
if err != OK:
push_error(
(
"[Godot VIM] Failed to insert keybind at index %s: %s"
% [index, error_string(err)]
)
)
ApplyMode.REMOVE:
var index: int = _find(key_map, inner)
if index == -1:
return
key_map.remove_at(index)
ApplyMode.REPLACE:
var constraints: Dictionary = {"keys": inner.get("keys", [])}
var index: int = _find(key_map, constraints)
if index == -1:
return
# print('replacing at index ', index)
key_map[index] = inner
#region Apply options
## Append this keybind to the end of the list
## Returns self
func append() -> KeyRemap:
options = {"apply_mode": ApplyMode.APPEND}
return self
## Insert this keybind at the start of the list
## Returns self
func prepend() -> KeyRemap:
options = {"apply_mode": ApplyMode.PREPEND}
return self
## Insert this keybind at the specified index
func insert_at(index: int) -> KeyRemap:
options = {
"apply_mode": ApplyMode.INSERT,
"index": index,
}
return self
## Removes the keybind from the list
## Returns self
func remove() -> KeyRemap:
options = {"apply_mode": ApplyMode.REMOVE}
return self
## Replaces the keybind from the list with this new one
## Returns self
func replace():
options = {"apply_mode": ApplyMode.REPLACE}
return self
#endregion
func _find(key_map: Array[Dictionary], constraints: Dictionary) -> int:
var keys: Array[String] = constraints.get("keys")
if keys == null:
push_error("[Godot VIM::KeyRemap::_find()] Failed to find keybind: keys not specified")
return -1
if keys.is_empty():
push_error("[Godot VIM::KeyRemap::_find()] Failed to find keybind: keys cannot be empty")
return -1
for i in key_map.size():
var cmd: Dictionary = key_map[i]
# Check keys
var m: KeyMap.KeyMatch = KeyMap.match_keys(cmd.keys, keys)
if m != KeyMap.KeyMatch.Full:
continue
# If types DON'T match (if specified, ofc), skip
if constraints.has("type") and constraints.type != cmd.type:
continue
# If contexts DON'T match (if specified, ofc), skip
if constraints.get("context", -1) != cmd.get("context", -1):
continue
return i
return -1

View File

@ -0,0 +1,88 @@
extends HBoxContainer
const ERROR_COLOR: String = "#ff8866"
const SPECIAL_COLOR: String = "#fcba03"
const Constants = preload("res://addons/godot_vim/constants.gd")
const MODE = Constants.Mode
var mode_label: Label
var main_label: RichTextLabel
var key_label: Label
func _ready():
var font = load("res://addons/godot_vim/hack_regular.ttf")
mode_label = Label.new()
mode_label.text = ""
mode_label.add_theme_color_override(&"font_color", Color.BLACK)
var stylebox: StyleBoxFlat = StyleBoxFlat.new()
stylebox.bg_color = Color.GOLD
stylebox.content_margin_left = 4.0
stylebox.content_margin_right = 4.0
stylebox.content_margin_top = 2.0
stylebox.content_margin_bottom = 2.0
mode_label.add_theme_stylebox_override(&"normal", stylebox)
mode_label.add_theme_font_override(&"font", font)
add_child(mode_label)
main_label = RichTextLabel.new()
main_label.bbcode_enabled = true
main_label.text = ""
main_label.fit_content = true
main_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
main_label.add_theme_font_override(&"normal_font", font)
add_child(main_label)
key_label = Label.new()
key_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
key_label.text = ""
key_label.add_theme_font_override(&"font", font)
key_label.custom_minimum_size.x = 120
add_child(key_label)
func display_text(text: String):
main_label.text = text
func display_error(text: String):
main_label.text = "[color=%s]%s" % [ERROR_COLOR, text]
func display_special(text: String):
main_label.text = "[color=%s]%s" % [SPECIAL_COLOR, text]
func set_mode_text(mode: MODE):
var stylebox: StyleBoxFlat = mode_label.get_theme_stylebox(&"normal")
match mode:
MODE.NORMAL:
mode_label.text = "NORMAL"
stylebox.bg_color = Color.LIGHT_SALMON
MODE.INSERT:
mode_label.text = "INSERT"
stylebox.bg_color = Color.POWDER_BLUE
MODE.VISUAL:
mode_label.text = "VISUAL"
stylebox.bg_color = Color.PLUM
MODE.VISUAL_LINE:
mode_label.text = "VISUAL LINE"
stylebox.bg_color = Color.PLUM
MODE.COMMAND:
mode_label.text = "COMMAND"
stylebox.bg_color = Color.TOMATO
_:
pass
func set_keys_text(text: String):
key_label.text = text
func clear():
main_label.text = ""
func clear_keys():
key_label.text = ""

1
icon.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

37
icon.svg.import Normal file
View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b10c1776j6j60"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

28
project.godot Normal file
View File

@ -0,0 +1,28 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Semi-Idle ARPG"
run/main_scene="res://scenes/testScene.tscn"
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg"
[display]
window/stretch/scale=3.0
[dotnet]
project/assembly_name="Semi-Idle ARPG"
[editor_plugins]
enabled=PackedStringArray("res://addons/godot-vim/plugin.cfg")

56
scenes/character.tscn Normal file
View File

@ -0,0 +1,56 @@
[gd_scene load_steps=2 format=3 uid="uid://ba27ufs8eak0b"]
[ext_resource type="Script" path="res://scripts/character.gd" id="2_bft53"]
[node name="Character" type="Control"]
layout_mode = 3
anchors_preset = 0
script = ExtResource("2_bft53")
[node name="CharacterPanel" type="PanelContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = 567.0
offset_top = 300.0
offset_right = 675.0
offset_bottom = 350.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4
[node name="VBoxContainer" type="VBoxContainer" parent="CharacterPanel"]
layout_mode = 2
size_flags_horizontal = 4
[node name="CharacterName" type="Label" parent="CharacterPanel/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
text = "Name"
horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="CharacterPanel/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
[node name="CharacterCurrHealth" type="Label" parent="CharacterPanel/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
text = "Current"
[node name="CharacterHealthSep" type="Label" parent="CharacterPanel/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "/"
[node name="CharacterMaxHealth" type="Label" parent="CharacterPanel/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
text = "Max
"

6
scenes/mapTile.tscn Normal file
View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bavjvxaourccu"]
[ext_resource type="Script" path="res://scripts/map_tile.gd" id="1_jtqny"]
[node name="MapTile" type="Node2D"]
script = ExtResource("1_jtqny")

12
scenes/npc.tscn Normal file
View File

@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=3 uid="uid://cdu3sqa0k8dgs"]
[ext_resource type="Script" path="res://scripts/npc.gd" id="2_a7mo0"]
[node name="NPC" type="Control"]
layout_mode = 3
anchors_preset = 0
offset_left = 669.0
offset_top = 330.0
offset_right = 669.0
offset_bottom = 330.0
script = ExtResource("2_a7mo0")

67
scenes/testScene.tscn Normal file
View File

@ -0,0 +1,67 @@
[gd_scene load_steps=3 format=3 uid="uid://dhvk3terpgsp3"]
[ext_resource type="Script" path="res://scripts/test_scene.gd" id="1_mj8nf"]
[sub_resource type="LabelSettings" id="LabelSettings_ppp5s"]
[node name="TestScene" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_mj8nf")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_vertical = 8
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="OutputLabels" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
[node name="TestMaxHealth" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer/OutputLabels"]
layout_mode = 2
size_flags_horizontal = 8
text = "Max Health:"
horizontal_alignment = 2
[node name="Output" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
[node name="TestMaxHealthVal" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer/Output"]
layout_mode = 2
size_flags_vertical = 3
text = "Value"
label_settings = SubResource("LabelSettings_ppp5s")
[node name="Controls" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 8
[node name="TestButton" type="Button" parent="MarginContainer/VBoxContainer/Controls"]
layout_mode = 2
text = "Test"
[node name="ExitButton" type="Button" parent="MarginContainer/VBoxContainer/Controls"]
layout_mode = 2
text = "Exit"
[connection signal="pressed" from="MarginContainer/VBoxContainer/Controls/TestButton" to="." method="_on_test_button_pressed"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/Controls/ExitButton" to="." method="_on_exit_button_pressed"]

12
scripts/character.gd Normal file
View File

@ -0,0 +1,12 @@
class_name Character
extends Control
@export var charName := "Character"
@export var maxHealth := 10
@export var dmgTaken := 0
func _init() -> void:
%CharacterName.text = self.charName
%CharacterMaxHealth.text = str(self.maxHealth)
%CharacterCurrHealth.text = str(self.maxHealth - self.dmgTaken)

17
scripts/equipmentGen.gd Normal file
View File

@ -0,0 +1,17 @@
# this script handles everything related to equipment generation
extends SceneTree
var gearItem = load("gearRand.gd")
# func genPiece(tier: int, quality: int):
# # gernarate a single piece of equipment of tier and quality
# var gearPiece = gearItem
#
# return statValues
func _init() -> void:
var gearPiece = gearItem.new(tier=2, quality=4)
print (gearPiece.statValues)
quit(

22
scripts/gear.gd Normal file
View File

@ -0,0 +1,22 @@
class_name Gear
extends "item.gd"
# the basic stats that occur on gear
const STATLIST = [
"str", "dex", "int",
"con", "res", "spd",
"dot", "crd", "crc"
]
var statValues = {}
var tier: int = 0
var slot: int = 0
var skill: int = 0
func _init():
for stat in STATLIST:
statValues[stat] = 0

39
scripts/gearRand.gd Normal file
View File

@ -0,0 +1,39 @@
class_name GearRandom extends "gear.gd"
func _init():
# generate random stats piece of gear with tier and quality
randomize()
var randStatValues = {}
# max tier of 10, max stats of 1000
var randStatMax = tier*100
match quality:
0:
# white, common
randStatValues["con"] = randi_range(0,tier*100)
1:
# green, uncommon
var statListQual1 = STATLIST.slice(0, 4)
for stat in statListQual1:
randStatValues[stat] = randi_range(0, randStatMax)
2:
# blue, rare
for stat in STATLIST:
randStatValues[stat] = randi_range(0, randStatMax)
3:
# purple, epic
for stat in STATLIST:
randStatValues[stat] = randi_range(0.3*randStatMax, randStatMax)
4:
# orange?, legendary
for stat in STATLIST:
randStatValues[stat] = randi_range(0.6*randStatMax, randStatMax)
5:
# yellow or red?, artifact
for stat in STATLIST:
randStatValues[stat] = randi_range(0.9*randStatMax, randStatMax)
_:
# should never occur, but just in case...
print("Unknown Equipment Quality")
self.statValues = randStatValues

10
scripts/item.gd Normal file
View File

@ -0,0 +1,10 @@
class_name Item
extends Object
var quality: int = 0
func _init() -> void:
# simple random integer from 0 to 5 for the different item qualities
randomize()
self.quality = randi_range(0, 5)

12
scripts/map_tile.gd Normal file
View File

@ -0,0 +1,12 @@
extends Node2D
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass

37
scripts/npc.gd Normal file
View File

@ -0,0 +1,37 @@
class_name NPC
extends Character
enum npcDifficulties { MINION, NORMAL, MINIBOSS, BOSS, ELITEBOSS, BBEG }
@export var npcDifficulty: npcDifficulties
@export var tier: int
func _random_mod_health() -> void:
randomize()
# noise factor
var noise_factor = randf_range(7, 10)
self.maxHealth *= noise_factor
# difficulty factor
match self.npcDifficulty:
npcDifficulties.MINION:
self.maxHealth /= 2
npcDifficulties.MINIBOSS:
self.maxHealth *= 2
npcDifficulties.BOSS:
self.maxHealth *= 4
npcDifficulties.ELITEBOSS:
self.maxHealth *= 8
npcDifficulties.BBEG:
self.maxHealth *= 16
# tier factor
self.maxHealth *= exp(self.tier)
# fun factor (just additional factor to tweak)
self.maxHealth *= 10
func _init(npcDifficulty: npcDifficulties = npcDifficulties.NORMAL, tier: int = 0) -> void:
self.npcDifficulty = npcDifficulty
self.tier = tier
_random_mod_health()

33
scripts/test_scene.gd Normal file
View File

@ -0,0 +1,33 @@
extends Control
func _on_exit_button_pressed() -> void:
get_tree().quit()
func _on_test_button_pressed() -> void:
var rand_difficulty = NPC.npcDifficulties.values().pick_random()
var rand_tier = randi_range(0, 10)
var anNPC = NPC.new(rand_difficulty, rand_tier)
var TestMaxHealthVal = $MarginContainer/HBoxContainer/Output/TestMaxHealthVal
TestMaxHealthVal.text = str(anNPC.maxHealth)
#TestMaxHealthVal.label_settings = LabelSettings.new()
TestMaxHealthVal.label_settings.outline_size = 4
TestMaxHealthVal.label_settings.outline_color = Color("#1D2021")
match anNPC.npcDifficulty:
NPC.npcDifficulties.MINION:
TestMaxHealthVal.label_settings.font_color = Color.LIGHT_GRAY
NPC.npcDifficulties.NORMAL:
TestMaxHealthVal.label_settings.font_color = Color.SEA_GREEN
NPC.npcDifficulties.MINIBOSS:
TestMaxHealthVal.label_settings.font_color = Color.ROYAL_BLUE
NPC.npcDifficulties.BOSS:
TestMaxHealthVal.label_settings.font_color = Color.PURPLE
NPC.npcDifficulties.ELITEBOSS:
TestMaxHealthVal.label_settings.font_color = Color.ORANGE
NPC.npcDifficulties.BBEG:
TestMaxHealthVal.label_settings.font_color = Color.GOLD
TestMaxHealthVal.label_settings.outline_size = 8
TestMaxHealthVal.label_settings.outline_color = Color.DARK_RED
#anNPC.position = Vector2(64, 64)
#add_child(anNPC)