(wip) advanced langtons ant

(wip) advanced langtons ant
This commit is contained in:
Djairo Hougee 2025-11-12 00:58:34 +01:00
parent c57f07ffc2
commit 25ec8d2585
42 changed files with 3283 additions and 0 deletions

View File

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
langtons-ant/.gitattributes vendored Normal file
View File

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

4
langtons-ant/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Godot 4+ specific ignores
.godot/
project.godot
*.import

21
langtons-ant/LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Joshua Najera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

55
langtons-ant/README.md Normal file
View File

@ -0,0 +1,55 @@
VIM bindings for Godot 4
recently improved thanks to wenqiangwang
If you would like ctrl+F to be move-forward by page then uncomment the following line
#"Ctrl+F": 1, ## Uncomment if you want Ctrl+F for move forward by page
### Supported Mode
- Normal mode
- Insert mode
- Visual mode
- Visual line mode
### Supported motions
h, l, j, k, +, -
^, 0, $, |
H, L, M,
c-f, c-b, c-d, c-u,
G, gg
w, W, e, E, b, ge
%, f, F, t, T, ;
*, #, /, n, N
aw, a(, a{, a[, a", a'
iw, i(, i{, i[, i", i'
### Supported operator
c, C,
d, D, x, X,
y, Y,
u, U, ~
### Supported actions
p,
u, c-r,
c-o, c-i,
za, zM, zR,
q, @, .,
>, <
m, '
### Override Default Godot Shortcuts with `godot-vim`'s ones
Note that all non-ascii character mappings that are already mapped in the default Godot editor have to be unmapped from the Editor settings (Editor >> Editor Settings >> Shorcuts) before being usable with `godot-vim`.
This currently goes for:
- `Ctrl+R`
- `Ctrl+U`
- `Ctrl+D`
See the full list of non-ascii shortucts that may already be mapped by Godot and thus wouldn't work in `godot-vim` before releasing them in Godot settings: https://github.com/joshnajera/godot-vim/blob/main/addons/godot-vim/godot-vim.gd#L135

2
langtons-ant/TODO Normal file
View File

@ -0,0 +1,2 @@
Ant navigation is wonky -> check if next_dir behaves as expected.
ant sprite does not budge from centre of screen no matter what i try -> fix that

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
uid://t4imnbu2n5vw

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,7 @@
[plugin]
name="godot-vim"
description="VIM bindings for godot4"
author="Josh N"
version="0.3"
script="godot-vim.gd"

View File

@ -0,0 +1,86 @@
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 Dispatcher = preload("res://addons/godot_vim/dispatcher.gd")
const Mode = Constants.Mode
const Marks = preload("res://addons/godot_vim/commands/marks.gd")
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 dispatcher: Dispatcher
var is_paused: bool = false
var search_pattern: String = ''
func _ready():
dispatcher = Dispatcher.new()
dispatcher.globals = globals
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
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.scroll_vertical = code_edit.get_scroll_pos_for_line(pos.y)
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 dispatcher.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)
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 @@
uid://lk5et75ba1pr

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)
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 @@
uid://dp7ck72w5b66n

View File

@ -0,0 +1,6 @@
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(), 0)
api.cursor.set_mode(Mode.NORMAL)

View File

@ -0,0 +1 @@
uid://cu4md7kr2qrp

View File

@ -0,0 +1,29 @@
const Contants = preload("res://addons/godot_vim/constants.gd")
const StatusBar = preload("res://addons/godot_vim/status_bar.gd")
const Mode = Contants.Mode
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 display_mark = func(key: String, m: Dictionary) -> String:
var pos: Vector2i = m.get('pos', Vector2i())
var file: String = m.get('file', '')
return "\n%s\t\t%s \t%s \t\t %s" % [key, pos.y, pos.x, file]
var text: String = "[color=%s]List of all marks[/color]\nmark\tline\tcol \t file" % StatusBar.SPECIAL_COLOR
for key in marks.keys():
var unicode: int = key.unicode_at(0)
if (unicode < 65 or unicode > 90) and (unicode < 97 or unicode > 122):
continue
text += display_mark.call(key, marks[key])
for key in marks.keys():
var unicode: int = key.unicode_at(0)
if (unicode >= 65 and unicode <= 90) or (unicode >= 97 and unicode <= 122) or key == "-1":
continue
text += display_mark.call(key, marks[key])
api.status_bar.display_text(text, Control.TEXT_DIRECTION_LTR)

View File

@ -0,0 +1 @@
uid://cf1an2q01i3ix

View File

@ -0,0 +1,8 @@
enum Mode { NORMAL, INSERT, VISUAL, VISUAL_LINE, COMMAND }
# Used for commands like "w" "b" and "e" respectively
enum WordEdgeMode { WORD, BEGINNING, END }
const SPACES: String = " \t"
const KEYWORDS: String = ".,\"'-=+!@#$%^&*()[]{}?~/\\<>:;"
const DIGITS: String = "0123456789"

View File

@ -0,0 +1 @@
uid://d2ro7q1ec5gk0

View File

@ -0,0 +1,722 @@
extends Control
const CommandLine = preload("res://addons/godot_vim/command_line.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 WordEdgeMode = Constants.WordEdgeMode
const KEYWORDS = Constants.KEYWORDS
const SPACES = Constants.SPACES
var code_edit: CodeEdit
var command_line: CommandLine
var status_bar: StatusBar
var mode: Mode = Mode.NORMAL
var caret: Vector2
var input_stream: String = ""
var selection_from: Vector2i = Vector2i() # For visual modes
var selection_to: Vector2i = Vector2i() # For visual modes
var globals: Dictionary = {}
func _init():
set_focus_mode(FOCUS_ALL)
func _ready():
code_edit.connect("focus_entered", focus_entered)
code_edit.connect("caret_changed", cursor_changed)
call_deferred('set_mode', Mode.NORMAL)
func cursor_changed():
draw_cursor()
func focus_entered():
if mode == Mode.NORMAL:
code_edit.release_focus()
self.grab_focus()
func reset_normal():
code_edit.cancel_code_completion()
input_stream = ''
set_mode(Mode.NORMAL)
selection_from = Vector2i.ZERO
selection_to = Vector2i.ZERO
set_column(code_edit.get_caret_column())
return
func back_to_normal_mode(event, m):
var old_caret_pos = code_edit.get_caret_column()
if Input.is_key_pressed(KEY_ESCAPE):
if m == Mode.INSERT:
handle_input_stream('l')
reset_normal()
return 1
if m == Mode.INSERT:
var old_time = Time.get_ticks_msec()
if Input.is_key_label_pressed(KEY_J):
old_caret_pos = code_edit.get_caret_column()
if Time.get_ticks_msec() - old_time < 700 and Input.is_key_label_pressed(KEY_K):
code_edit.backspace()
code_edit.cancel_code_completion()
reset_normal()
handle_input_stream('l')
return 1
return 0
func _input(event):
if back_to_normal_mode(event, mode): return
draw_cursor()
code_edit.cancel_code_completion()
if !has_focus(): return
if !event is InputEventKey: return
if !event.pressed: return
if mode == Mode.INSERT or mode == Mode.COMMAND: return
if event.keycode == KEY_ESCAPE:
input_stream = ''
return
var ch: String
if !event is InputEventMouseMotion and !event is InputEventMouseButton:
ch = char(event.unicode)
if Input.is_key_pressed(KEY_ENTER):
ch = '<CR>'
if Input.is_key_pressed(KEY_TAB):
ch = '<TAB>'
if Input.is_key_pressed(KEY_CTRL):
if OS.is_keycode_unicode(event.keycode):
var c: String = char(event.keycode)
if !Input.is_key_pressed(KEY_SHIFT):
c = c.to_lower()
ch = '<C-%s>' % c
input_stream += ch
status_bar.display_text(input_stream)
var s: int = globals.vim_plugin.get_first_non_digit_idx(input_stream)
if s == -1: return # All digits
var cmd: String = input_stream.substr(s)
var count: int = maxi( input_stream.left(s).to_int(), 1 )
for i in count:
input_stream = handle_input_stream(cmd)
func handle_input_stream(stream: String) -> String:
# BEHOLD, THE IF STATEMENT HELL!!! MUAHAHAHAHa
if stream == 'h':
move_column(-1)
return ''
if stream == 'j':
move_line(+1)
return ''
if stream == 'k':
move_line(-1)
return ''
if stream == 'l':
move_column(+1)
return ''
if stream.to_lower().begins_with('w'):
var p: Vector2i = get_word_edge_pos(get_line(), get_column(), '' if stream[0] == 'W' else KEYWORDS, WordEdgeMode.WORD)
set_caret_pos(p.y, p.x)
return ''
if stream.to_lower().begins_with('e'):
var p: Vector2i = get_word_edge_pos(get_line(), get_column(), '' if stream[0] == 'E' else KEYWORDS, WordEdgeMode.END)
set_caret_pos(p.y, p.x)
return ''
if stream.to_lower().begins_with('b'):
var p: Vector2i = get_word_edge_pos(get_line(), get_column(), '' if stream[0] == 'B' else KEYWORDS, WordEdgeMode.BEGINNING)
set_caret_pos(p.y, p.x)
return ''
if stream.to_lower() .begins_with('f') or stream.to_lower() .begins_with('t'):
if stream.length() == 1: return stream
var char: String = stream[1] # TODO check for <TAB>, <CR> and <Ctrl-somethign>
globals.last_search = stream.left(2) # First 2 in case it's longer
var col: int = find_char_motion(get_line(), get_column(), stream[0], char)
if col >= 0:
set_column(col)
return ''
if stream.begins_with(';') and globals.has('last_search'):
var cmd: String = globals.last_search[0]
var col: int = find_char_motion(get_line(), get_column(), cmd, globals.last_search[1])
if col >= 0:
set_column(col)
return ''
if stream.begins_with(',') and globals.has('last_search'):
var cmd: String = globals.last_search[0]
cmd = cmd.to_upper() if is_lowercase(cmd) else cmd.to_lower()
var col: int = find_char_motion(get_line(), get_column(), cmd, globals.last_search[1])
if col >= 0:
set_column(col)
return ''
if mode == Mode.VISUAL: # TODO make it work for visual line too
var range: Array = calc_double_motion_region(selection_to, stream)
if range.size() == 1: return stream
if range.size() == 2:
selection_from = range[0]
selection_to = range[1]
update_visual_selection()
if stream.begins_with('J') and mode == Mode.NORMAL:
code_edit.begin_complex_operation()
code_edit.select( get_line(), get_line_length(), get_line()+1, code_edit.get_first_non_whitespace_column(get_line()+1) )
code_edit.delete_selection()
code_edit.deselect()
code_edit.insert_text_at_caret(' ')
code_edit.end_complex_operation()
globals.last_command = stream
return ''
if stream.begins_with('d'):
if is_mode_visual(mode):
DisplayServer.clipboard_set( '\r' + code_edit.get_selected_text() )
code_edit.delete_selection()
move_line(+1)
set_mode(Mode.NORMAL)
return ''
if stream.begins_with('dd') and mode == Mode.NORMAL:
code_edit.select( get_line()-1, get_line_length(get_line()-1), get_line(), get_line_length() )
DisplayServer.clipboard_set( '\r' + code_edit.get_selected_text() )
code_edit.delete_selection()
move_line(+1)
globals.last_command = stream
return ''
var range: Array = calc_double_motion_region(get_caret_pos(), stream, 1)
if range.size() == 0: return ''
if range.size() == 1: return stream
if range.size() == 2:
code_edit.select(range[0].y, range[0].x, range[1].y, range[1].x + 1)
code_edit.cut()
globals.last_command = stream
return ''
if mode == Mode.NORMAL and stream.begins_with('D'):
code_edit.select( get_line(), code_edit.get_caret_column(), get_line(), get_line_length() )
code_edit.cut()
globals.last_command = stream
return ''
if stream.begins_with('p'):
code_edit.begin_complex_operation()
if is_mode_visual(mode):
code_edit.delete_selection()
if DisplayServer.clipboard_get().begins_with('\r\n'):
set_column(get_line_length())
else:
move_column(+1)
code_edit.deselect()
code_edit.paste()
move_column(-1)
code_edit.end_complex_operation()
set_mode(Mode.NORMAL)
globals.last_command = stream
return ''
if stream.begins_with('P'):
status_bar.display_error("Unimplemented command: P")
return ''
if stream.begins_with('$'):
set_column(get_line_length())
return ''
if stream.begins_with('^'):
set_column( code_edit.get_first_non_whitespace_column(get_line()) )
return ''
if stream == 'G':
set_line(code_edit.get_line_count())
return ''
if stream.begins_with('g'):
if stream.begins_with('gg'):
set_line(0)
return ''
if stream.begins_with('gc') and is_mode_visual(mode):
code_edit.begin_complex_operation()
for line in range( min(selection_from.y, selection_to.y), max(selection_from.y, selection_to.y)+1 ):
toggle_comment(line)
code_edit.end_complex_operation()
set_mode(Mode.NORMAL)
return ''
if stream.begins_with('gcc') and mode == Mode.NORMAL:
toggle_comment(get_line())
globals.last_command = stream
return ''
return stream
if stream == '0':
set_column(0)
return ''
if stream == 'i' and mode == Mode.NORMAL:
set_mode(Mode.INSERT)
return ''
if stream == 'a' and mode == Mode.NORMAL:
set_mode(Mode.INSERT)
move_column(+1)
return ''
if stream == 'I' and mode == Mode.NORMAL:
set_column(code_edit.get_first_non_whitespace_column(get_line()))
set_mode(Mode.INSERT)
return ''
if stream.begins_with('A') and mode == Mode.NORMAL:
set_mode(Mode.INSERT)
set_column(get_line_length())
return ''
if stream == 'v':
set_mode(Mode.VISUAL)
return ''
if stream == 'V':
set_mode(Mode.VISUAL_LINE)
return ''
if stream.begins_with('o'):
if is_mode_visual(mode):
var tmp: Vector2i = selection_from
selection_from = selection_to
selection_to = tmp
return ''
var ind: int = code_edit.get_first_non_whitespace_column(get_line())
if code_edit.get_line(get_line()).ends_with(':'):
ind += 1
var line: int = code_edit.get_caret_line()
code_edit.insert_line_at(line + int(line < code_edit.get_line_count() - 1), "\t".repeat(ind))
move_line(+1)
set_column(ind)
set_mode(Mode.INSERT)
globals.last_command = stream
return ''
if stream.begins_with('O') and mode == Mode.NORMAL:
var ind: int = code_edit.get_first_non_whitespace_column(get_line())
code_edit.insert_line_at(get_line(), "\t".repeat(ind))
move_line(-1)
set_column(ind)
set_mode(Mode.INSERT)
globals.last_command = stream
return ''
if stream == 'x':
code_edit.copy()
code_edit.delete_selection()
globals.last_command = stream
return ''
if stream.begins_with('s'):
code_edit.cut()
set_mode(Mode.INSERT)
return ''
if stream == 'u':
code_edit.undo()
set_mode(Mode.NORMAL)
return ''
if stream.begins_with('<C-r>'):
code_edit.redo()
return ''
if stream.begins_with('r') and mode == Mode.NORMAL:
if stream.length() < 2: return stream
code_edit.begin_complex_operation()
code_edit.delete_selection()
var ch: String = stream[1]
if stream.substr(1).begins_with('<CR>'):
ch = '\n'
elif stream.substr(1).begins_with('<TAB>'):
ch = '\t'
code_edit.insert_text_at_caret(ch)
move_column(-1)
code_edit.end_complex_operation()
globals.last_command = stream
return ''
if stream.begins_with('y'):
if is_mode_visual(mode):
code_edit.copy()
set_mode(Mode.NORMAL)
return ''
if stream.length() == 1: return stream
if stream.begins_with('yy') and mode == Mode.NORMAL:
code_edit.select(code_edit.get_caret_line(), 0, code_edit.get_caret_line(), get_line_length())
DisplayServer.clipboard_set( '\r\n' + code_edit.get_selected_text() )
move_column(0)
code_edit.deselect()
var range: Array = calc_double_motion_region(get_caret_pos(), stream, 1)
if range.size() == 0: return ''
if range.size() == 1: return stream
if range.size() == 2:
code_edit.select(range[0].y, range[0].x, range[1].y, range[1].x + 1)
code_edit.copy()
code_edit.deselect()
return ''
if stream == '.':
if globals.has('last_command'):
handle_input_stream(globals.last_command)
call_deferred(&'set_mode', Mode.NORMAL)
return ''
if stream.begins_with(':') and mode == Mode.NORMAL: # Could make this work with visual too ig
set_mode(Mode.COMMAND)
command_line.set_command(':')
return ''
if stream.begins_with('/') and mode == Mode.NORMAL:
set_mode(Mode.COMMAND)
command_line.set_command('/')
return ''
if stream.begins_with('n'):
var rmatch: RegExMatch = globals.vim_plugin.search_regex(
code_edit,
command_line.search_pattern,
get_caret_pos() + Vector2i.RIGHT
)
if rmatch != null:
var pos: Vector2i = globals.vim_plugin.idx_to_pos(code_edit,rmatch.get_start())
set_caret_pos(pos.y, pos.x)
return ''
if stream.begins_with('N'):
var rmatch: RegExMatch = globals.vim_plugin.search_regex_backwards(
code_edit,
command_line.search_pattern,
get_caret_pos() + Vector2i.LEFT
)
if rmatch != null:
var pos: Vector2i = globals.vim_plugin.idx_to_pos(code_edit,rmatch.get_start())
set_caret_pos(pos.y, pos.x)
return ''
if stream.begins_with('c'):
if mode == Mode.VISUAL:
code_edit.cut()
set_mode(Mode.INSERT)
return ''
if stream.begins_with('cc') and mode == Mode.NORMAL:
code_edit.begin_complex_operation()
var l: int = get_line()
var ind: int = code_edit.get_first_non_whitespace_column(l)
code_edit.select( l-1, get_line_length(l-1), l, get_line_length(l) )
code_edit.cut()
code_edit.insert_line_at(get_line()+1, "\t".repeat(ind))
code_edit.end_complex_operation()
move_line(+1)
set_mode(Mode.INSERT)
globals.last_command = stream
return ''
var range: Array = calc_double_motion_region(get_caret_pos(), stream, 1)
if range.size() == 0: return ''
if range.size() == 1: return stream
if range.size() == 2:
code_edit.select(range[0].y, range[0].x, range[1].y, range[1].x + 1)
code_edit.cut()
set_mode(Mode.INSERT)
globals.last_command = stream
return ''
if mode == Mode.NORMAL and stream.begins_with('C'):
code_edit.select( get_line(), code_edit.get_caret_column(), get_line(), get_line_length() )
code_edit.cut()
set_mode(Mode.INSERT)
globals.last_command = stream
return ''
if stream.begins_with('z'):
if stream.begins_with('zz') and mode == Mode.NORMAL:
code_edit.center_viewport_to_caret()
return ''
return stream
if stream.begins_with('>'):
if is_mode_visual(mode) and stream.length() == 1:
code_edit.indent_lines()
return ''
if stream.length() == 1: return stream
if stream.begins_with('>>') and mode == Mode.NORMAL:
code_edit.indent_lines()
globals.last_command = stream
return ''
if stream.begins_with('<'):
if is_mode_visual(mode) and stream.length() == 1:
code_edit.unindent_lines()
return ''
if stream.length() == 1: return stream
if stream.begins_with('<<') and mode == Mode.NORMAL:
code_edit.unindent_lines()
globals.last_command = stream
return ''
if stream.begins_with('}'):
var para_edge: Vector2i = get_paragraph_edge_pos( get_line(), 1 )
set_caret_pos(para_edge.y, para_edge.x)
return ''
if stream.begins_with('{'):
var para_edge: Vector2i = get_paragraph_edge_pos( get_line(), -1 )
set_caret_pos(para_edge.y, para_edge.x)
return ''
if stream.begins_with('m') and mode == Mode.NORMAL:
if stream.length() < 2: return stream
if !globals.has('marks'): globals.marks = {}
var m: String = stream[1]
var unicode: int = m.unicode_at(0)
if (unicode < 65 or unicode > 90) and (unicode < 97 or unicode > 122):
status_bar.display_error('Marks must be between a-z or A-Z')
return ''
globals.marks[m] = {
'file' : globals.script_editor.get_current_script().resource_path,
'pos' : Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line())
}
status_bar.display_text('Mark "%s" set' % m, TEXT_DIRECTION_LTR)
return ''
if stream.begins_with('`'):
if stream.length() < 2: return stream
if !globals.has('marks'): globals.marks = {}
if !globals.marks.has(stream[1]):
status_bar.display_error('Mark "%s" not set' % [ stream[1] ])
return ''
var mark: Dictionary = globals.marks[stream[1]]
globals.vim_plugin.edit_script(mark.file, mark.pos)
return ''
return ''
# Mostly used for commands like "w", "b", and "e"
# delims is the keywords / characters used as delimiters. Usually, it's the constant KEYWORDS
func get_word_edge_pos(from_line: int, from_col: int, delims: String, mode: WordEdgeMode) -> Vector2i:
var search_dir: int = -1 if mode == WordEdgeMode.BEGINNING else 1
var char_offset: int = 1 if mode == WordEdgeMode.END else -1
var line: int = from_line
var col: int = from_col + search_dir
var text: String = get_line_text(line)
while line >= 0 and line < code_edit.get_line_count():
while col >= 0 and col < text.length():
var char: String = text[col]
if SPACES.contains(char):
col += search_dir
continue
# Please don't question this lmao. It just works, alight?
var other_char: String = ' ' if col == (text.length()-1) * int(char_offset > 0) else text[col + char_offset]
if SPACES.contains(other_char):
return Vector2i(col, line)
if delims.contains(char) != delims.contains(other_char):
return Vector2i(col, line)
col += search_dir
line += search_dir
text = get_line_text(line)
col = (text.length() - 1) * int(search_dir < 0 and char_offset < 0)
return Vector2i(from_col, from_line)
func get_paragraph_edge_pos(from_line: int, search_dir: int):
var line: int = from_line
var prev_empty: bool = code_edit.get_line(line) .strip_edges().is_empty()
line += search_dir
while line >= 0 and line < code_edit.get_line_count():
var text: String = code_edit.get_line(line) .strip_edges()
if text.is_empty() and !prev_empty:
return Vector2i(text.length(), line)
prev_empty = text.is_empty()
line += search_dir
return Vector2i(0, line)
# motion: command like "f", "t", "F", or "T"
func find_char_motion(in_line: int, from_col: int, motion: String, char: String) -> int:
var search_dir: int = 1 if is_lowercase(motion) else -1
var offset: int = int(motion == 'T') - int(motion == 't') # 1 if T, -1 if t, 0 otherwise
var text: String = get_line_text(in_line)
var col: int = -1
if motion == 'f' or motion == 't':
col = text.find(char, from_col + search_dir)
elif motion == 'F' or motion == 'T':
col = text.rfind(char, from_col + search_dir)
if col == -1:
return -1
return col + offset
# returns: [ Vector2i from_pos, Vector2i to_pos ]
func calc_double_motion_region(from_pos: Vector2i, stream: String, from_char: int = 0) -> Array[Vector2i]:
var primary: String = get_stream_char(stream, from_char)
var secondary: String = get_stream_char(stream, from_char + 1)
if primary == '':
return [from_pos] # Incomplete
if primary.to_lower() == 'w':
var p1: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x, '' if primary == 'W' else KEYWORDS, WordEdgeMode.WORD)
return [from_pos, p1 + Vector2i.LEFT]
if primary.to_lower() == 'b':
var p0: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x, '' if primary == 'B' else KEYWORDS, WordEdgeMode.BEGINNING)
return [p0, from_pos + Vector2i.LEFT]
if primary.to_lower() == 'e':
var p1: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x, '' if primary == 'E' else KEYWORDS, WordEdgeMode.END)
return [from_pos, p1]
if primary == '$':
var p1: Vector2i = Vector2i(get_line_length(from_pos.y), from_pos.y)
return [from_pos, p1]
if primary == '^':
var p0: Vector2i = Vector2i(code_edit.get_first_non_whitespace_column(from_pos.y), from_pos.y)
return [p0, from_pos + Vector2i.LEFT]
if primary != 'i' and primary != 'a':
return [] # Invalid
if secondary == '':
return [from_pos] # Incomplete
if primary == 'i' and secondary.to_lower() == 'w':
var p0: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x + 1, '' if secondary == 'W' else KEYWORDS, WordEdgeMode.BEGINNING)
var p1: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x - 1, '' if secondary == 'W' else KEYWORDS, WordEdgeMode.END)
return [ p0, p1 ]
if primary == 'i' and secondary == 'p':
var p0: Vector2i = get_paragraph_edge_pos(from_pos.y + 1, -1) + Vector2i.DOWN
var p1: Vector2i = get_paragraph_edge_pos(from_pos.y - 1, 1)
return [ p0, p1 ]
return [] # Unknown combination
func toggle_comment(line: int):
var ind: int = code_edit.get_first_non_whitespace_column(line)
var text: String = get_line_text(line)
# Comment line
if text[ind] != '#':
code_edit.set_line(line, text.insert(ind, '# '))
return
# Uncomment line
var start_col: int = get_word_edge_pos(line, ind, KEYWORDS, WordEdgeMode.WORD).x
code_edit.select(line, ind, line, start_col)
code_edit.delete_selection()
func set_mode(m: int):
var old_mode: int = mode
mode = m
command_line.close()
match mode:
Mode.NORMAL:
code_edit.remove_secondary_carets()
code_edit.deselect()
code_edit.release_focus()
code_edit.deselect()
self.grab_focus()
status_bar.set_mode_text(Mode.NORMAL)
if old_mode == Mode.INSERT:
move_column(-1)
Mode.VISUAL:
if old_mode != Mode.VISUAL_LINE:
selection_from = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line())
selection_to = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line())
set_caret_pos(selection_to.y, selection_to.x)
status_bar.set_mode_text(Mode.VISUAL)
Mode.VISUAL_LINE:
if old_mode != Mode.VISUAL:
selection_from = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line())
selection_to = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line())
set_caret_pos(selection_to.y, selection_to.x)
status_bar.set_mode_text(Mode.VISUAL_LINE)
Mode.COMMAND:
command_line.show()
command_line.call_deferred("grab_focus")
status_bar.set_mode_text(Mode.COMMAND)
Mode.INSERT:
code_edit.call_deferred("grab_focus")
status_bar.set_mode_text(Mode.INSERT)
_:
pass
func move_line(offset:int):
set_line(get_line() + offset)
func get_line() -> int:
if is_mode_visual(mode):
return selection_to.y
return code_edit.get_caret_line()
func get_line_text(line: int = -1) -> String:
if line == -1:
return code_edit.get_line(get_line())
return code_edit.get_line(line)
func get_line_length(line: int = -1) -> int:
return get_line_text(line).length()
func set_caret_pos(line: int, column: int):
set_line(line) # line has to be set before column
set_column(column)
func get_caret_pos() -> Vector2i:
return Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line())
func set_line(position:int):
if !is_mode_visual(mode):
code_edit.set_caret_line(min(position, code_edit.get_line_count()-1))
return
selection_to = Vector2i( clampi(selection_to.x, 0, get_line_length(position)), clampi(position, 0, code_edit.get_line_count()) )
update_visual_selection()
func move_column(offset: int):
set_column(get_column()+offset)
func get_column():
if is_mode_visual(mode):
return selection_to.x
return code_edit.get_caret_column()
func set_column(position: int):
if !is_mode_visual(mode):
var line: String = code_edit.get_line(code_edit.get_caret_line())
code_edit.set_caret_column(min(line.length(), position))
return
selection_to = Vector2i( clampi(position, 0, get_line_length(selection_to.y)), clampi(selection_to.y, 0, code_edit.get_line_count()) )
update_visual_selection()
func update_visual_selection():
if mode == Mode.VISUAL:
var to_right: bool = selection_to.x >= selection_from.x or selection_to.y > selection_from.y
code_edit.select( selection_from.y, selection_from.x + int(!to_right), selection_to.y, selection_to.x + int(to_right) )
elif mode == Mode.VISUAL_LINE:
var f: int = mini(selection_from.y, selection_to.y) - 1
var t: int = maxi(selection_from.y, selection_to.y)
code_edit.select(f, get_line_length(f), t, get_line_length(t))
func is_mode_visual(m: int) -> bool:
return m == Mode.VISUAL or m == Mode.VISUAL_LINE
func is_lowercase(text: String) -> bool:
return text == text.to_lower()
func is_uppercase(text: String) -> bool:
return text == text.to_upper()
func get_stream_char(stream: String, idx: int) -> String:
return stream[idx] if stream.length() > idx else ''
func draw_cursor():
if code_edit.is_dragging_cursor():
selection_from = Vector2i(code_edit.get_selection_from_column(), code_edit.get_selection_from_line())
selection_to = Vector2i(code_edit.get_selection_to_column(), code_edit.get_selection_to_line())
if code_edit.get_selected_text(0).length() > 1 and !is_mode_visual(mode):
code_edit.release_focus()
self.grab_focus()
set_mode(Mode.VISUAL)
if mode == Mode.INSERT:
if code_edit.has_selection(0):
code_edit.deselect(0)
return
if mode != Mode.NORMAL:
return
var line: int = code_edit.get_caret_line()
var column: int = code_edit.get_caret_column()
if column >= code_edit.get_line(line).length():
column -= 1
code_edit.set_caret_column(column)
code_edit.select(line, column, line, column+1)

View File

@ -0,0 +1 @@
uid://bmkia3drpjivh

View File

@ -0,0 +1,22 @@
extends Object
var handlers = {
"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")
}
var globals
func dispatch(command : String):
var command_idx_end = command.find(' ', 1)
if command_idx_end == -1: command_idx_end = command.length()
var handler_name = command.substr(1, command_idx_end-1)
if not handlers.has(handler_name):
return ERR_DOES_NOT_EXIST
var handler = handlers.get(handler_name)
var handler_instance = handler.new()
var args = command.substr(command_idx_end, command.length())
handler_instance.execute(globals, args)
return OK

View File

@ -0,0 +1 @@
uid://dvfyfj3wtqfds

Binary file not shown.

View File

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

View File

@ -0,0 +1,164 @@
@tool
extends EditorPlugin
enum Mode { NORMAL, INSERT, VISUAL, VISUAL_LINE, COMMAND }
# Used for commands like "w" "b" and "e" respectively
enum WordEdgeMode { WORD, BEGINNING, END }
const SPACES: String = " \t"
const KEYWORDS: String = ".,\"'-=+!@#$%^&*()[]{}?~/\\<>:;"
const DIGITS: String = "0123456789"
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")
var cursor: Cursor
var command_line: CommandLine
var status_bar: StatusBar
var globals: Dictionary = {}
func _enter_tree():
globals = {}
if get_code_edit() != null:
_load()
get_editor_interface().get_script_editor().connect("editor_script_changed", _script_changed)
func _script_changed(script: Script):
# Add to recent files
var path: String = script.resource_path
var marks: Dictionary = globals.get('marks', {})
for i in range(9, -1, -1):
var m: String = str(i)
var pm: String = str(i - 1)
if !marks.has(pm):
continue
marks[m] = marks[pm]
marks['-1'] = { 'file' : path, 'pos' : Vector2i(-1, 0) }
_load()
func edit_script(path: String, pos: Vector2i):
var script = load(path)
var editor_interface: EditorInterface = globals.editor_interface
if script == null:
status_bar.display_error('Could not open file "%s"' % path)
return ''
editor_interface.edit_script(script)
cursor.call_deferred('set_caret_pos', pos.y, pos.x)
func _load():
if globals == null:
globals = {}
# Cursor
if cursor != null:
cursor.queue_free()
cursor = Cursor.new()
var code_edit = get_code_edit()
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.globals = globals
# Command line
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()
# 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
var editor_interface = get_editor_interface()
if editor_interface == null: return
var script_editor = editor_interface.get_script_editor()
if script_editor == null: return
var script_editor_base = script_editor.get_current_editor()
if script_editor_base == null: return
globals.editor_interface = editor_interface
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
script_editor_base.add_child(cursor)
script_editor_base.add_child(status_bar)
script_editor_base.add_child(command_line)
func get_code_edit():
var editor = get_editor_interface().get_script_editor().get_current_editor()
return _select(editor, ['VSplitContainer', 'CodeTextEditor', 'CodeEdit'])
func _select(obj: Node, types: Array[String]): # ???
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

View File

@ -0,0 +1 @@
uid://duaf0qcswso06

View File

@ -0,0 +1,68 @@
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
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.text_direction = Control.TEXT_DIRECTION_RTL
main_label.add_theme_font_override(&"normal_font", font)
add_child(main_label)
func display_text(text: String, text_direction: Control.TextDirection = TEXT_DIRECTION_RTL):
main_label.text = text
main_label.text_direction = text_direction
func display_error(text: String):
main_label.text = '[color=%s]%s' % [ERROR_COLOR, text]
main_label.text_direction = Control.TEXT_DIRECTION_LTR
func display_special(text: String):
main_label.text = '[color=%s]%s' % [SPECIAL_COLOR, text]
main_label.text_direction = Control.TEXT_DIRECTION_LTR
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

View File

@ -0,0 +1 @@
uid://bsixy362mx4r7

60
langtons-ant/ant.gd Normal file
View File

@ -0,0 +1,60 @@
extends Sprite2D
var pos: Vector2i = Vector2i(0,0)
var dir: TileSet.CellNeighbor = TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE
@onready var tiles: TileMapLayer = get_node("TileMapLayer")
var next_dir: Dictionary = {
0: TileSet.CELL_NEIGHBOR_RIGHT_SIDE,
1: TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE,
2: TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE,
3: TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE,
4: TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE,
}
var next_int: Dictionary = {
TileSet.CELL_NEIGHBOR_RIGHT_SIDE: 1,
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: 2,
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: 3,
TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE: 4,
TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE: 0,
}
func _ready() -> void:
set_frame(1)
func update() -> void:
var tile: int = tiles.get_tile_colour(pos)
print("tile %s" % tile)
var newFrame: int = next_int[dir]
if tile == 0:
newFrame = (newFrame + 1) % 5
dir = next_dir[newFrame]
else:
newFrame = (newFrame) % 5
dir = next_dir[newFrame]
#var tmpPos: Vector2i
#for i in range(0, 16):
#tmpPos = tiles.get_neighbor_cell(pos, i)
#print("%s: (%d %d)" % [i, tmpPos.x, tmpPos.y])
print("(%d %d)" % [pos.x, pos.y])
pos = tiles.get_neighbor_cell(pos, dir)
tile = tiles.get_tile_colour(pos)
print("(%d %d)" % [pos.x, pos.y])
tiles.set_tile(pos, (tile + 1) % 2) # TODO: should probably use another dictionary
set_frame(newFrame)
func _physics_process(delta: float) -> void:
var vel: Vector2 = Vector2(0,0)
match dir:
TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE:
vel = Vector2(1,1)
TileSet.CELL_NEIGHBOR_RIGHT_SIDE:
vel = Vector2(1,0)
_:
vel = Vector2(0,-1)
vel = vel.normalized() * 16
global_position += vel * delta

1
langtons-ant/ant.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://b6ll30b7xtwal

BIN
langtons-ant/assets/ant.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

18
langtons-ant/grid.gd Normal file
View File

@ -0,0 +1,18 @@
extends Node2D
var t: float = 0
@export var speed: float = 58
@export var ant: Sprite2D
@export var tiles: TileMapLayer
# 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:
t += delta
if (t > 60-speed):
t = 0
ant.update()

1
langtons-ant/grid.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://by3d3t5ymxl3m

43
langtons-ant/grid.tscn Normal file
View File

@ -0,0 +1,43 @@
[gd_scene load_steps=8 format=3 uid="uid://b6xi32r3co6md"]
[ext_resource type="Script" uid="uid://by3d3t5ymxl3m" path="res://grid.gd" id="1_bghhw"]
[ext_resource type="Script" uid="uid://b6ll30b7xtwal" path="res://ant.gd" id="1_ebq2e"]
[ext_resource type="Texture2D" uid="uid://bnfy5vx72ux33" path="res://assets/ant.png" id="3_sle3t"]
[ext_resource type="Texture2D" uid="uid://d0v1qlkltasln" path="res://assets/tiles.png" id="4_fqc2p"]
[ext_resource type="Script" uid="uid://cqxfmo3hixad1" path="res://tile_map_layer.gd" id="5_fqc2p"]
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_g2qvd"]
texture = ExtResource("4_fqc2p")
texture_region_size = Vector2i(32, 32)
0:0/0 = 0
1:0/0 = 0
0:1/0 = 0
1:1/0 = 0
0:2/0 = 0
1:2/0 = 0
0:3/0 = 0
1:3/0 = 0
[sub_resource type="TileSet" id="TileSet_05i0m"]
tile_shape = 3
tile_size = Vector2i(32, 32)
sources/0 = SubResource("TileSetAtlasSource_g2qvd")
[node name="Grid" type="Node2D" node_paths=PackedStringArray("ant", "tiles")]
script = ExtResource("1_bghhw")
speed = 60.0
ant = NodePath("ant")
tiles = NodePath("ant/TileMapLayer")
[node name="ant" type="Sprite2D" parent="."]
texture = ExtResource("3_sle3t")
vframes = 6
script = ExtResource("1_ebq2e")
[node name="Camera2D" type="Camera2D" parent="ant"]
[node name="TileMapLayer" type="TileMapLayer" parent="ant"]
position = Vector2(-74, -8)
tile_set = SubResource("TileSet_05i0m")
collision_enabled = false
script = ExtResource("5_fqc2p")

1
langtons-ant/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: 995 B

View File

@ -0,0 +1,61 @@
extends TileMapLayer
var tiles: Dictionary = {}
func get_tile_colour(pos: Vector2i) -> int:
return tiles.get_or_add(pos, 0)
# TODO: state sh1ould be an enum probably
func set_tile(pos: Vector2i, state: int) -> void:
print("setting cell (%s) at %d %d\n" % [state, pos.x, pos.y])
set_cell(pos, 0, Vector2i(0, state))
tiles.erase(pos)
tiles.get_or_add(pos, state)
#var direction = 0
#var antPos = Vector2i(0,0)
#var grid = {}
#var tileSize = 16
#
## Called when the node enters the scene tree for the first time.
#func _ready() -> void:
#set_process(true)
#grid[antPos] = 0
#updateTile(antPos, 0)
#
#func updateTile(pos: Vector2i, col: int):
#var tileCol = Color.WHITE if col == 0 else Color.BLACK
#var tileRect = ColorRect.new()
#tileRect.color = tileCol
#tileRect.size = Vector2(tileSize, tileSize)
#tileRect.position = Vector2(pos.x * tileSize, pos.y * tileSize)
#add_child(tileRect)
#
#
## Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta: float) -> void:
#moveAnt()
#
#func moveAnt():
#var curCol = grid.get(antPos, 0)
#
#if curCol == 0:
#direction = (direction + 1) % 4
#else:
#direction = (direction + 3) % 4
#
#grid[antPos] = 1 - curCol
#updateTile(antPos, grid[antPos])
#
#match direction:
#0:
#antPos.y -= 1
#1:
#antPos.x += 1
#2:
#antPos.y += 1
#3:
#antPos.x -= 1
#
#if not grid.has(antPos):
#grid[antPos] = 0

View File

@ -0,0 +1 @@
uid://cqxfmo3hixad1