diff --git a/.gitignore b/.gitignore index 62061e74..17d10047 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ te -ded SDL2 *.exe *.obj diff --git a/build.sh b/build.sh index 2c961d43..82f88045 100755 --- a/build.sh +++ b/build.sh @@ -1,15 +1,37 @@ -#!/bin/sh +#!/bin/bash + +askforoverwrite=true # Set to false to disable overwrite prompt + +if [ ! -d "$HOME/.config/ded" ]; then + cp -r ./config/ded "$HOME/.config/" +elif [ "$askforoverwrite" = true ]; then + echo "Config already exists. Overwrite? (y/n)" + read -r -n 1 overwrite_confirmation + echo # Move to a new line + + if [ "$overwrite_confirmation" = "y" ]; then + rm -rf "$HOME/.config/ded" + cp -r ./config/ded "$HOME/.config/" + echo "Config overwritten." + else + echo "Not overwriting the config." + fi +else + echo "Config already exists. Overwrite not allowed." +fi set -xe CC="${CXX:-cc}" PKGS="sdl2 glew freetype2" -CFLAGS="-Wall -Wextra -std=c11 -pedantic -ggdb" +CFLAGS="-Wall -Wextra -std=c11 -pedantic -ggdb -ljson-c -lpthread -D_DEFAULT_SOURCE" LIBS=-lm -SRC="src/main.c src/la.c src/editor.c src/file_browser.c src/free_glyph.c src/simple_renderer.c src/common.c src/lexer.c" +SRC="src/*.c" if [ `uname` = "Darwin" ]; then CFLAGS+=" -framework OpenGL" fi -$CC $CFLAGS `pkg-config --cflags $PKGS` -o ded $SRC $LIBS `pkg-config --libs $PKGS` +$CC $CFLAGS `pkg-config --cflags $PKGS` -o ded $SRC $LIBS `pkg-config --libs $PKGS ` + + diff --git a/clangd.log b/clangd.log new file mode 100644 index 00000000..e69de29b diff --git a/config/ded/fonts/FantasqueSansMNerdFont-Bold.ttf b/config/ded/fonts/FantasqueSansMNerdFont-Bold.ttf new file mode 100644 index 00000000..f4ead85d Binary files /dev/null and b/config/ded/fonts/FantasqueSansMNerdFont-Bold.ttf differ diff --git a/config/ded/fonts/FiraCodeNerdFontPropo-Bold.ttf b/config/ded/fonts/FiraCodeNerdFontPropo-Bold.ttf new file mode 100644 index 00000000..2fa0a1f5 Binary files /dev/null and b/config/ded/fonts/FiraCodeNerdFontPropo-Bold.ttf differ diff --git a/config/ded/fonts/MonoLisa-Bold.ttf b/config/ded/fonts/MonoLisa-Bold.ttf new file mode 100644 index 00000000..385908d7 Binary files /dev/null and b/config/ded/fonts/MonoLisa-Bold.ttf differ diff --git a/fonts/iosevka-regular.ttf b/config/ded/fonts/iosevka-regular.ttf similarity index 100% rename from fonts/iosevka-regular.ttf rename to config/ded/fonts/iosevka-regular.ttf diff --git a/config/ded/fonts/monocraft.ttf b/config/ded/fonts/monocraft.ttf new file mode 100644 index 00000000..4066b0a9 Binary files /dev/null and b/config/ded/fonts/monocraft.ttf differ diff --git a/config/ded/fonts/radon.otf b/config/ded/fonts/radon.otf new file mode 100644 index 00000000..27ba5653 Binary files /dev/null and b/config/ded/fonts/radon.otf differ diff --git a/config/ded/shaders/cursor.frag b/config/ded/shaders/cursor.frag new file mode 100644 index 00000000..84837b4a --- /dev/null +++ b/config/ded/shaders/cursor.frag @@ -0,0 +1,67 @@ +/* #version 330 core */ + +/* in vec4 out_color; */ + +/* void main() { */ +/* gl_FragColor = out_color; */ +/* } */ + + +#version 330 core + +in vec4 out_color; // Color input for the object. +in vec2 frag_coord; // The coordinate of the current fragment in screen space. + +uniform vec2 light_position; // The position of the light source in screen space. + +out vec4 frag_color; // Output color of the fragment. + +void main() { + // Calculate the distance between the fragment and the light source. + float distance_to_light = length(frag_coord - light_position); + + // Define a shadow radius and adjust the shadow intensity. + float shadow_radius = 100.0; + float shadow_intensity = 0.3; // Adjust this value to control the shadow intensity. + + // Calculate the shadow factor based on distance. + float shadow_factor = smoothstep(shadow_radius, shadow_radius + 1.0, distance_to_light); + + // Apply the shadow factor to darken the input color. + vec3 fragment_color = out_color.rgb * (1.0 - shadow_intensity * shadow_factor); + + frag_color = vec4(fragment_color, out_color.a); +} + + + + +// GRADIENT +/* #version 330 core */ + +/* in vec2 fragTexCoord; // Texture coordinate, if you're using textures */ +/* uniform float time; // Uniform variable for time */ +/* uniform vec2 cursorPos; // Cursor position in screen coordinates */ +/* uniform vec2 resolution; // Screen resolution */ + +/* out vec4 color; */ + +/* void main() { */ +/* // Calculate normalized coordinates */ +/* vec2 uv = fragTexCoord / resolution; */ + +/* // Create a dynamic gradient based on time */ +/* vec3 gradient = 0.5 + 0.9 * cos(time + uv.xyx + vec3(0, 2, 4)); */ + +/* // Create a glow effect */ +/* float dist = distance(uv, cursorPos / resolution); */ +/* float glow = exp(20.0 * dist); */ + +/* // Combine the gradient and glow */ +/* vec3 finalColor = gradient * glow; */ + +/* // Output the color */ +/* color = vec4(finalColor, 0.7); // Fully opaque */ +/* } */ + + diff --git a/config/ded/shaders/fixed.vert b/config/ded/shaders/fixed.vert new file mode 100644 index 00000000..f5b742f4 --- /dev/null +++ b/config/ded/shaders/fixed.vert @@ -0,0 +1,24 @@ +#version 330 core + +uniform vec2 resolution; +uniform float time; + +layout(location = 0) in vec2 position; +layout(location = 1) in vec4 color; +layout(location = 2) in vec2 uv; + +out vec4 out_color; +out vec2 out_uv; + +vec2 simple_project(vec2 point) +{ + // Project the point directly based on the resolution + return (2.0 * point / resolution) - vec2(1.0); +} + +void main() { + // Use the simple projection method without camera transformations + gl_Position = vec4(simple_project(position), 0.0, 1.0); + out_color = color; + out_uv = uv; +} diff --git a/config/ded/shaders/minibuffer.vert b/config/ded/shaders/minibuffer.vert new file mode 100644 index 00000000..43c693fa --- /dev/null +++ b/config/ded/shaders/minibuffer.vert @@ -0,0 +1,28 @@ +#version 330 core + +uniform vec2 resolution; +uniform float time; + +layout(location = 0) in vec2 position; +layout(location = 1) in vec4 color; +layout(location = 2) in vec2 uv; + +out vec4 out_color; +out vec2 out_uv; + +const float scaleFactor = 0.25; + +vec2 simple_project(vec2 point) { + // Project the point directly based on the resolution + return (2.0 * point / resolution) - vec2(1.0); +} + +void main() { + // Apply the scale factor to the position + vec2 scaledPosition = position * scaleFactor; + + // Use the simple projection method without camera transformations + gl_Position = vec4(simple_project(scaledPosition), 0.0, 1.0); + out_color = color; + out_uv = uv; +} diff --git a/shaders/simple.vert b/config/ded/shaders/simple.vert similarity index 100% rename from shaders/simple.vert rename to config/ded/shaders/simple.vert diff --git a/config/ded/shaders/simple_acid.frag b/config/ded/shaders/simple_acid.frag new file mode 100644 index 00000000..3752a132 --- /dev/null +++ b/config/ded/shaders/simple_acid.frag @@ -0,0 +1,36 @@ +#version 330 core + +uniform float time; +uniform vec2 resolution; +uniform sampler2D image; + +in vec2 out_uv; + +vec3 hsl2rgb(vec3 c) { + vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0); + return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0)); +} + +void main() { + vec4 tc = texture(image, out_uv); + float d = tc.r; + float aaf = fwidth(d); + float alpha = smoothstep(0.5 - aaf, 0.5 + aaf, d); + + vec2 frag_uv = gl_FragCoord.xy / resolution; + + // Firefly movement: This simulates the movement of 3 "fireflies" + float f1 = abs(sin(frag_uv.x * 10.0 + time)); + float f2 = abs(cos(frag_uv.y * 8.0 + time * 1.5)); + float f3 = abs(sin(frag_uv.x * 12.0 + frag_uv.y * 12.0 + time * 0.7)); + + // Combine fireflies' impact + float fireflyEffect = f1 + f2 + f3; + + // Translate that to a color-shifting effect + vec3 fireflyColor = hsl2rgb(vec3(fireflyEffect * 0.3, 0.6, 0.5)); + + vec3 finalColor = mix(tc.rgb, fireflyColor, d * fireflyEffect); + + gl_FragColor = vec4(finalColor, alpha); +} diff --git a/shaders/simple_color.frag b/config/ded/shaders/simple_color.frag similarity index 100% rename from shaders/simple_color.frag rename to config/ded/shaders/simple_color.frag diff --git a/config/ded/shaders/simple_epic.frag b/config/ded/shaders/simple_epic.frag new file mode 100644 index 00000000..919caefe --- /dev/null +++ b/config/ded/shaders/simple_epic.frag @@ -0,0 +1,31 @@ +#version 330 core + +uniform float time; +uniform vec2 resolution; +uniform sampler2D image; + +in vec2 out_uv; + +vec3 hsl2rgb(vec3 c) { + vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0); + return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0)); +} + +void main() { + vec4 tc = texture(image, out_uv); + float d = tc.r; + float aaf = fwidth(d); + float alpha = smoothstep(0.5 - aaf, 0.5 + aaf, d); + + vec2 frag_uv = gl_FragCoord.xy / resolution; + + // Dynamic color-shifting aura + vec3 auraColor = hsl2rgb(vec3(mod(time * 0.2 + frag_uv.y, 1.0), 0.5, 0.5)); + + // Shimmering gradient across the text + float shimmer = (sin(time * 3.0 + frag_uv.x * 10.0) + 1.0) * 0.5; + vec3 shimmerColor = mix(vec3(1.0, 0.8, 0.6), vec3(0.6, 0.8, 1.0), shimmer); + + vec3 finalColor = mix(auraColor, shimmerColor, d); + gl_FragColor = vec4(finalColor, alpha); +} diff --git a/config/ded/shaders/simple_glow.frag b/config/ded/shaders/simple_glow.frag new file mode 100644 index 00000000..fd823472 --- /dev/null +++ b/config/ded/shaders/simple_glow.frag @@ -0,0 +1,20 @@ +#version 330 core + +uniform sampler2D image; // The off-screen texture with the cursor +uniform float blurSize; // The size of the blur effect (experiment with this value) + +in vec2 out_uv; + +const float offset[5] = float[](0.0, 1.333, 2.666, 4.0, 5.333); // Offsets for Gaussian blur + +void main() { + vec4 sum = texture(image, out_uv) * 0.2941; // Central sample (weight is highest) + + sum += texture(image, out_uv + vec2(blurSize * offset[1], 0.0)) * 0.2353; + sum += texture(image, out_uv - vec2(blurSize * offset[1], 0.0)) * 0.2353; + sum += texture(image, out_uv + vec2(blurSize * offset[2], 0.0)) * 0.1176; + sum += texture(image, out_uv - vec2(blurSize * offset[2], 0.0)) * 0.1176; + // ... Add more samples if needed + + gl_FragColor = sum; +} diff --git a/shaders/simple_image.frag b/config/ded/shaders/simple_image.frag similarity index 100% rename from shaders/simple_image.frag rename to config/ded/shaders/simple_image.frag diff --git a/config/ded/shaders/simple_pony.frag b/config/ded/shaders/simple_pony.frag new file mode 100644 index 00000000..919caefe --- /dev/null +++ b/config/ded/shaders/simple_pony.frag @@ -0,0 +1,31 @@ +#version 330 core + +uniform float time; +uniform vec2 resolution; +uniform sampler2D image; + +in vec2 out_uv; + +vec3 hsl2rgb(vec3 c) { + vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0); + return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0)); +} + +void main() { + vec4 tc = texture(image, out_uv); + float d = tc.r; + float aaf = fwidth(d); + float alpha = smoothstep(0.5 - aaf, 0.5 + aaf, d); + + vec2 frag_uv = gl_FragCoord.xy / resolution; + + // Dynamic color-shifting aura + vec3 auraColor = hsl2rgb(vec3(mod(time * 0.2 + frag_uv.y, 1.0), 0.5, 0.5)); + + // Shimmering gradient across the text + float shimmer = (sin(time * 3.0 + frag_uv.x * 10.0) + 1.0) * 0.5; + vec3 shimmerColor = mix(vec3(1.0, 0.8, 0.6), vec3(0.6, 0.8, 1.0), shimmer); + + vec3 finalColor = mix(auraColor, shimmerColor, d); + gl_FragColor = vec4(finalColor, alpha); +} diff --git a/shaders/simple_epic.frag b/config/ded/shaders/simple_rainbow.frag similarity index 100% rename from shaders/simple_epic.frag rename to config/ded/shaders/simple_rainbow.frag diff --git a/config/ded/shaders/simple_text.frag b/config/ded/shaders/simple_text.frag new file mode 100644 index 00000000..e892529b --- /dev/null +++ b/config/ded/shaders/simple_text.frag @@ -0,0 +1,92 @@ +#version 330 core + +uniform sampler2D image; + +in vec4 out_color; +in vec2 out_uv; + +void main() { + float d = texture(image, out_uv).r; + float aaf = fwidth(d); + float alpha = smoothstep(0.5 - aaf, 0.5 + aaf, d); + gl_FragColor = vec4(out_color.rgb, alpha); +} + + + +// Dumb shadow +/* look good only with radon font */ +/* #version 330 core */ + +/* uniform sampler2D image; */ + +/* in vec4 out_color; */ +/* in vec2 out_uv; */ + +/* void main() { */ +/* // Shadow properties */ +/* vec2 shadowOffset = vec2(0.001, -0.001); // Very close offset for the shadow */ +/* vec4 shadowColor = vec4(0.0, 0.0, 0.0, 0.7); // Black shadow with decent opacity */ + +/* // Calculate distance for shadow and text */ +/* float d = texture(image, out_uv).r; */ + +/* float aaf = fwidth(d); */ +/* float alpha = smoothstep(0.5 - aaf, 0.5 + aaf, d); */ + +/* // Basic blur approximation for shadow */ +/* float blurRadius = 0.0005; // Adjust this value for more/less blur */ +/* float shadowAlpha = 0.0; */ +/* for (float x = -blurRadius; x <= blurRadius; x += 0.001) { */ +/* for (float y = -blurRadius; y <= blurRadius; y += 0.001) { */ +/* float dShadow = texture(image, out_uv + shadowOffset + vec2(x, y)).r; */ +/* shadowAlpha += smoothstep(0.5 - aaf, 0.5 + aaf, dShadow); */ +/* } */ +/* } */ +/* shadowAlpha = shadowAlpha / ((2.0 * blurRadius / 0.001 + 1.0) * (2.0 * blurRadius / 0.001 + 1.0)); */ +/* shadowAlpha *= shadowColor.a; */ + +/* // Mix shadow and text */ +/* vec4 textColor = vec4(out_color.rgb, alpha); */ +/* vec4 shadow = vec4(shadowColor.rgb, shadowAlpha); */ +/* gl_FragColor = mix(shadow, textColor, alpha); */ +/* } */ + + +// idk +/* #version 330 core */ + +/* uniform sampler2D image; */ +/* uniform float time; // Time variable for animation */ + +/* in vec4 out_color; // Base text color */ +/* in vec2 out_uv; // Texture coordinates */ + +/* void main() { */ +/* float d = texture(image, out_uv).r; // Distance field value */ +/* float aaf = fwidth(d); // Antialiasing factor */ + +/* // Outline settings */ +/* float outlineSize = 0.09; // Width of the outline */ +/* float movingSegmentLength = 0.8; // Length of the moving segment */ +/* float speed = 0.5; // Speed of the segment's movement */ + +/* // Calculate position of the moving segment */ +/* float segmentPosition = mod(time * speed, 1.0); */ + +/* // Calculate outline alpha based on SDF */ +/* float outlineAlpha = smoothstep(0.5 - outlineSize - aaf, 0.5 - outlineSize, d) - */ +/* smoothstep(0.5 + outlineSize, 0.5 + outlineSize + aaf, d); */ + +/* // Determine if we're within the moving segment */ +/* float sdfCoord = (atan(out_uv.y - 0.5, out_uv.x - 0.5) / 3.14159265 + 1.0) * 0.5; */ +/* bool inMovingSegment = abs(sdfCoord - segmentPosition) < movingSegmentLength * 0.5 || */ +/* abs(sdfCoord - segmentPosition - 1.0) < movingSegmentLength * 0.5; */ + +/* // Mix colors: text, outline, and moving segment */ +/* vec3 outlineColor = vec3(0, 1, 0); // Green for the moving segment */ +/* vec4 textColor = vec4(out_color.rgb, smoothstep(0.5 - aaf, 0.5 + aaf, d)); */ +/* vec4 segmentColor = mix(vec4(outlineColor, outlineAlpha), textColor, float(!inMovingSegment)); */ + +/* gl_FragColor = segmentColor; */ +/* } */ diff --git a/config/ded/shaders/wave.vert b/config/ded/shaders/wave.vert new file mode 100644 index 00000000..6e0b5267 --- /dev/null +++ b/config/ded/shaders/wave.vert @@ -0,0 +1,246 @@ +/* WAWE */ +/* #version 330 core */ + +/* uniform vec2 resolution; */ +/* uniform float time; */ +/* uniform float camera_scale; */ +/* uniform vec2 camera_pos; */ + +/* layout(location = 0) in vec2 position; */ +/* layout(location = 1) in vec4 color; */ +/* layout(location = 2) in vec2 uv; */ + +/* out vec4 out_color; */ +/* out vec2 out_uv; */ + +/* vec2 camera_project(vec2 point) */ +/* { */ +/* return 2.0 * (point - camera_pos) * camera_scale / resolution; */ +/* } */ + +/* void main() { */ +/* // Apply camera projection first */ +/* vec4 projected_position = vec4(camera_project(position), 0.0, 1.0); */ + +/* // Adding a displacement effect that varies with time in screen space */ +/* projected_position.x += sin(projected_position.y + time) * 0.22; // Horizontal wave */ +/* projected_position.y += cos(projected_position.x + time) * 0.02; // Vertical wave */ + +/* gl_Position = projected_position; */ + +/* out_color = color; */ +/* out_uv = uv; */ +/* } */ + + + +// GUITAR HERO +/* #version 330 core */ + +/* uniform vec2 resolution; */ +/* uniform float time; */ +/* uniform float camera_scale; */ +/* uniform vec2 camera_pos; */ + +/* layout(location = 0) in vec2 position; */ +/* layout(location = 1) in vec4 color; */ +/* layout(location = 2) in vec2 uv; */ + +/* out vec4 out_color; */ +/* out vec2 out_uv; */ + +/* vec2 camera_project(vec2 point) */ +/* { */ +/* return 2.0 * (point - camera_pos) * camera_scale / resolution; */ +/* } */ + +/* void main() { */ +/* // Apply camera projection first */ +/* vec2 projected_point = camera_project(position); */ + +/* // Apply a static perspective transformation */ +/* float perspectiveDepth = -0.45; // Adjust for more or less perspective */ +/* float depth = 1.0 / (1.0 - projected_point.y * perspectiveDepth); */ + +/* projected_point *= depth; */ + +/* // Convert back to vec4 */ +/* vec4 projected_position = vec4(projected_point, 0.0, 1.0); */ + +/* gl_Position = projected_position; */ + +/* out_color = color; */ +/* out_uv = uv; */ +/* } */ + + +// GUITAR HERO V2 +/* #version 330 core */ + +/* uniform vec2 resolution; */ +/* uniform float time; */ +/* uniform float camera_scale; */ +/* uniform vec2 camera_pos; */ + +/* layout(location = 0) in vec2 position; */ +/* layout(location = 1) in vec4 color; */ +/* layout(location = 2) in vec2 uv; */ + +/* out vec4 out_color; */ +/* out vec2 out_uv; */ + +/* vec2 camera_project(vec2 point) */ +/* { */ +/* return 2.0 * (point - camera_pos) * camera_scale / resolution; */ +/* } */ + +/* void main() { */ +/* // Apply camera projection first */ +/* vec2 projected_point = camera_project(position); */ + +/* // Adjust for more or less perspective, and limit the effect as y approaches the edges */ +/* float perspectiveDepth = -0.45; */ +/* float clamped_y = clamp(projected_point.y, -0.95, 0.95); // Clamping to avoid extreme values at the edges */ +/* float depth = 1.0 / (1.0 - clamped_y * perspectiveDepth); */ + +/* projected_point.x *= depth; */ +/* projected_point.y *= depth; // Apply depth scaling uniformly */ + +/* // Convert back to vec4 */ +/* vec4 projected_position = vec4(projected_point, 0.0, 1.0); */ + +/* gl_Position = projected_position; */ + +/* out_color = color; */ +/* out_uv = uv; */ +/* } */ + + + +// RIPPLE +/* #version 330 core */ + +/* uniform vec2 resolution; */ +/* uniform float time; */ +/* uniform float camera_scale; */ +/* uniform vec2 camera_pos; */ + +/* layout(location = 0) in vec2 position; */ +/* layout(location = 1) in vec4 color; */ +/* layout(location = 2) in vec2 uv; */ + +/* out vec4 out_color; */ +/* out vec2 out_uv; */ + +/* vec2 camera_project(vec2 point) { */ +/* return 2.0 * (point - camera_pos) * camera_scale / resolution; */ +/* } */ + +/* void main() { */ +/* // Apply camera projection */ +/* vec2 projected_point = camera_project(position); */ + +/* // Calculate the center point for the ripple effect (center of the screen) */ +/* vec2 center = vec2(0.0, 0.0); */ + +/* // Calculate the direction and distance from the current point to the ripple center */ +/* vec2 to_center = projected_point - center; */ +/* float distance = length(to_center); */ + +/* // Apply a ripple displacement based on distance and time */ +/* float ripple_frequency = 10.0; // Adjust for ripple tightness */ +/* float ripple_speed = 2.0; // Adjust for ripple speed */ +/* float ripple_amplitude = 0.05; // Adjust for ripple strength */ + +/* float ripple = sin(distance * ripple_frequency - time * ripple_speed) * ripple_amplitude; */ + +/* // Displace the projected point radially from the center */ +/* projected_point += normalize(to_center) * ripple; */ + +/* // Convert back to vec4 and set position */ +/* vec4 projected_position = vec4(projected_point, 0.0, 1.0); */ +/* gl_Position = projected_position; */ + +/* out_color = color; */ +/* out_uv = uv; */ +/* } */ + + + +//Jitter +/* #version 330 core */ + +/* uniform vec2 resolution; */ +/* uniform float time; */ +/* uniform float camera_scale; */ +/* uniform vec2 camera_pos; */ + +/* layout(location = 0) in vec2 position; */ +/* layout(location = 1) in vec4 color; */ +/* layout(location = 2) in vec2 uv; */ + +/* out vec4 out_color; */ +/* out vec2 out_uv; */ + +/* vec2 camera_project(vec2 point) { */ +/* return 2.0 * (point - camera_pos) * camera_scale / resolution; */ +/* } */ + +/* void main() { */ +/* // Apply camera projection */ +/* vec2 projected_point = camera_project(position); */ + +/* // Apply a jitter effect based on time to create flickering */ +/* float jitter_intensity = 0.02; // Adjust for desired flicker strength */ +/* projected_point.x += (sin(position.y * 10.0 + time) * jitter_intensity); */ +/* projected_point.y += (cos(position.x * 10.0 + time) * jitter_intensity); */ + +/* // Convert back to vec4 and set position */ +/* vec4 projected_position = vec4(projected_point, 0.0, 1.0); */ +/* gl_Position = projected_position; */ + +/* out_color = color; */ +/* out_uv = uv; */ +/* } */ + + +// WOBBLE +#version 330 core + +uniform vec2 resolution; +uniform float time; +uniform float camera_scale; +uniform vec2 camera_pos; + +layout(location = 0) in vec2 position; +layout(location = 1) in vec4 color; +layout(location = 2) in vec2 uv; + +out vec4 out_color; +out vec2 out_uv; + +vec2 camera_project(vec2 point) { + return 2.0 * (point - camera_pos) * camera_scale / resolution; +} + +void main() { + // Apply camera projection + vec2 projected_point = camera_project(position); + + // Apply a wobble effect using a circular pattern around the original point + float wobble_amplitude = 0.05; // Adjust for the wobble distance + float wobble_speed = 5.0; // Adjust for the wobble speed + + projected_point.x += sin(time * wobble_speed + projected_point.y) * wobble_amplitude; + projected_point.y += cos(time * wobble_speed + projected_point.x) * wobble_amplitude; + + // Convert back to vec4 and set position + vec4 projected_position = vec4(projected_point, 0.0, 1.0); + gl_Position = projected_position; + + out_color = color; + out_uv = uv; +} + + + diff --git a/config/ded/snippets/_s b/config/ded/snippets/_s new file mode 100644 index 00000000..0ddd2d83 --- /dev/null +++ b/config/ded/snippets/_s @@ -0,0 +1,3 @@ +#+begin_src $0 + +#+end_src diff --git a/config/ded/snippets/for b/config/ded/snippets/for new file mode 100644 index 00000000..ca4ef6e3 --- /dev/null +++ b/config/ded/snippets/for @@ -0,0 +1,3 @@ +for (int i = 0; i < $1; i++) { + $0 +} diff --git a/config/ded/snippets/glfw b/config/ded/snippets/glfw new file mode 100644 index 00000000..fa3b72fb --- /dev/null +++ b/config/ded/snippets/glfw @@ -0,0 +1,37 @@ +#include +$0 +int main(void) +{ + GLFWwindow* window; + + /* Initialize the library */ + if (!glfwInit()) + return -1; + + /* Create a windowed mode window and its OpenGL context */ + window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL); + if (!window) + { + glfwTerminate(); + return -1; + } + + /* Make the window's context current */ + glfwMakeContextCurrent(window); + + /* Loop until the user closes the window */ + while (!glfwWindowShouldClose(window)) + { + /* Render here */ + glClear(GL_COLOR_BUFFER_BIT); + + /* Swap front and back buffers */ + glfwSwapBuffers(window); + + /* Poll for and process events */ + glfwPollEvents(); + } + + glfwTerminate(); + return 0; +} diff --git a/config/ded/snippets/h b/config/ded/snippets/h new file mode 100644 index 00000000..9fe4bab0 --- /dev/null +++ b/config/ded/snippets/h @@ -0,0 +1,6 @@ +#ifndef ${1:HEADER_NAME}_H +#define ${1:HEADER_NAME}_H + +$0 + +#endif // ${1:HEADER_NAME}_H diff --git a/config/ded/snippets/if b/config/ded/snippets/if new file mode 100644 index 00000000..f84cf6ab --- /dev/null +++ b/config/ded/snippets/if @@ -0,0 +1,3 @@ +if (true) { + $0 +} diff --git a/config/ded/snippets/main b/config/ded/snippets/main new file mode 100644 index 00000000..b3db305e --- /dev/null +++ b/config/ded/snippets/main @@ -0,0 +1,5 @@ +int main() +{ + $0 + return 0; +} diff --git a/config/ded/snippets/print b/config/ded/snippets/print new file mode 100644 index 00000000..4be3ca8e --- /dev/null +++ b/config/ded/snippets/print @@ -0,0 +1 @@ +printf("$0"); diff --git a/config/ded/snippets/raylib b/config/ded/snippets/raylib new file mode 100644 index 00000000..7e9f9035 --- /dev/null +++ b/config/ded/snippets/raylib @@ -0,0 +1,18 @@ +#include "raylib.h" +$0 +int main(void) +{ + InitWindow(800, 450, "raylib [core] example - basic window"); + + while (!WindowShouldClose()) + { + BeginDrawing(); + ClearBackground(RAYWHITE); + DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY); + EndDrawing(); + } + + CloseWindow(); + + return 0; +} diff --git a/config/ded/snippets/struct b/config/ded/snippets/struct new file mode 100644 index 00000000..4e0df78d --- /dev/null +++ b/config/ded/snippets/struct @@ -0,0 +1,3 @@ +typedef struct { + $0 +} ${1:struct_name}; diff --git a/config/ded/snippets/switch b/config/ded/snippets/switch new file mode 100644 index 00000000..cff7d9e3 --- /dev/null +++ b/config/ded/snippets/switch @@ -0,0 +1,7 @@ +switch (${1:variable}) { + case ${2:case1}: + $0 + break; + default: + break; +} diff --git a/config/ded/snippets/while b/config/ded/snippets/while new file mode 100644 index 00000000..e1d658c2 --- /dev/null +++ b/config/ded/snippets/while @@ -0,0 +1,3 @@ +while (${1:condition}) { + $0 +} diff --git a/fonts/VictorMono-Regular.ttf b/fonts/VictorMono-Regular.ttf deleted file mode 100644 index aadcc88a..00000000 Binary files a/fonts/VictorMono-Regular.ttf and /dev/null differ diff --git a/shaders/simple_text.frag b/shaders/simple_text.frag deleted file mode 100644 index f0413756..00000000 --- a/shaders/simple_text.frag +++ /dev/null @@ -1,13 +0,0 @@ -#version 330 core - -uniform sampler2D image; - -in vec4 out_color; -in vec2 out_uv; - -void main() { - float d = texture(image, out_uv).r; - float aaf = fwidth(d); - float alpha = smoothstep(0.5 - aaf, 0.5 + aaf, d); - gl_FragColor = vec4(out_color.rgb, alpha); -} diff --git a/src/M-x.c b/src/M-x.c new file mode 100644 index 00000000..26184f24 --- /dev/null +++ b/src/M-x.c @@ -0,0 +1,149 @@ +#include "M-x.h" +#include "editor.h" +#include "hashmap.h" +#include "evil.h" +#include "helix.h" +#include "emacs.h" +#include "lsp.h" +#include "utilities.h" + +// TODO aliases (lua or lisp we will have an init file), +// history in program memory, when quitting save it in ~/.config/ded/M-x-history +// and load it when opening ded clamp it to max-M-x-history-size or something + + +void register_command(struct hashmap *command_map, const char *name, void (*execute)(Editor *, const char *params[]), int additional_params_count) { + Command *cmd = malloc(sizeof(Command)); + if (cmd) { + cmd->name = name; + cmd->execute = execute; + cmd->additional_params_count = additional_params_count; + hashmap_set(command_map, cmd); + } else { + // Handle allocation failure + } +} + +// TODO open-below && open-above && editor-enter behave weird +void initialize_commands(struct hashmap *command_map) { + /* register_command(command_map, "open", evil_open_below, 0); */ + /* register_command(command_map, "opena", evil_open_above, 0); */ + /* register_command(command_map, "drag-down", editor_drag_line_down, 0); */ + /* register_command(command_map, "drag-up", editor_drag_line_up, 0); */ + /* register_command(command_map, "editor-enter", editor_enter, 0); */ + /* register_command(command_map, "select", select_region_from_brace, 0); */ + /* register_command(command_map, "back", emacs_backward_kill_word, 0); */ + /* register_command(command_map, "evil-join", evil_join, 0); */ + /* register_command(command_map, "evil-yank-line", evil_yank_line, 0); */ + /* register_command(command_map, "open-include", editor_open_include, 0); */ + /* register_command(command_map, "toggle", toggle_bool, 0); // Wincompatible-function-pointer-types */ + /* register_command(command_map, "w", editor_save, 0); */ + register_command(command_map, "q", editor_quit, 0); + /* register_command(command_map, "wq", editor_save_and_quit, 0); */ + /* register_command(command_map, "go", editor_goto_line, 1); */ + /* register_command(command_map, "def", goto_definition, 0); */ + register_command(command_map, "helix", helix_mode, 0); + +} + +// TODO if you provide less arguments than needed warn the cursor +// TODO if the function fail print it and maybe with the actuall error code or string +void execute_command(struct hashmap *command_map, Editor *editor, const char *input) { + // First, check if the input is a number + if (is_number(input)) { + const char *params[2] = {input, NULL}; + editor_goto_line(editor, params); + return; + } + + char command_name[100]; + const char *params[10] = {0}; + int params_count = 0; + + // Duplicate the input string for safe tokenization + char *input_copy = strdup(input); + char *token = strtok(input_copy, " "); + + if (token != NULL) { + strncpy(command_name, token, sizeof(command_name) - 1); + command_name[sizeof(command_name) - 1] = '\0'; + + // Extract arguments + while ((token = strtok(NULL, " ")) != NULL && params_count < 10) { + params[params_count++] = token; + } + } + + Command tempCmd = {command_name, NULL, 0}; + Command *cmd = (Command *)hashmap_get(command_map, &tempCmd); + if (cmd) { + if (cmd->additional_params_count == params_count) { + cmd->execute(editor, params); + } else if (cmd->additional_params_count == 0 && params_count == 0) { + cmd->execute(editor, NULL); + } else { + // Handle incorrect number of arguments + } + } else { + // Command not found + } + + free(input_copy); +} + +int command_compare(const void *a, const void *b, void *udata) { + const Command *cmd_a = a; + const Command *cmd_b = b; + return strcmp(cmd_a->name, cmd_b->name); +} + +uint64_t simple_string_hash(const void *item, uint64_t seed0, uint64_t seed1) { + const Command *cmd = item; + const char *str = cmd->name; + uint64_t hash = seed0; + while (*str) { + hash = 31 * hash + (*str++); + } + return hash ^ seed1; +} + + +// HISTORY + + +/* #define MAX_HISTORY 500 */ + +/* typedef struct { */ +/* char **items; */ +/* size_t count; */ +/* size_t capacity; */ +/* } History; */ + +/* History command_history = {NULL, 0, 0}; */ + +/* void add_to_history(const char *command) { */ +/* if (command_history.count >= MAX_HISTORY) { */ +/* // Remove the oldest command */ +/* free(command_history.items[0]); */ +/* memmove(command_history.items, command_history.items + 1, (MAX_HISTORY - 1) * sizeof(char*)); */ +/* command_history.count--; */ +/* } */ + +/* if (command_history.count == command_history.capacity) { */ +/* // Increase the capacity */ +/* size_t new_capacity = command_history.capacity == 0 ? 16 : command_history.capacity * 2; */ +/* char **new_items = realloc(command_history.items, new_capacity * sizeof(char*)); */ +/* if (!new_items) { */ +/* // Handle allocation failure */ +/* return; */ +/* } */ +/* command_history.items = new_items; */ +/* command_history.capacity = new_capacity; */ +/* } */ + +/* command_history.items[command_history.count++] = strdup(command); */ +/* } */ + +/* // Call this function in execute_command after successfully executing a command */ +/* add_to_history(input); */ + diff --git a/src/M-x.h b/src/M-x.h new file mode 100644 index 00000000..7e3fb1d2 --- /dev/null +++ b/src/M-x.h @@ -0,0 +1,19 @@ +#ifndef META_H_ +#define META_H_ + +#include // for uint64_t +#include "editor.h" + +typedef struct { + const char *name; + void (*execute)(Editor *, const char *params[]); // Updated to take an array of strings as additional parameters + int additional_params_count; // Number of additional parameters needed +} Command; + +void register_command(struct hashmap *command_map, const char *name, void (*execute)(Editor *, const char *params[]), int additional_params_count); +void initialize_commands(struct hashmap *command_map); +void execute_command(struct hashmap *command_map, Editor *editor, const char *command_name); +int command_compare(const void *a, const void *b, void *udata); +uint64_t simple_string_hash(const void *item, uint64_t seed0, uint64_t seed1); + +#endif // META_H__ diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 00000000..db190ad9 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,96 @@ +#include "buffer.h" + + + +// BUFFER +// TODO switching buffers delete unsaved changes +// TODO save cursor position on each buffer + +void editor_add_to_buffer_history(Editor *e, const char *file_path) { + if (e->buffer_history_count < MAX_BUFFER_HISTORY) { + free(e->buffer_history[e->buffer_history_count]); // Free existing string if any + e->buffer_history[e->buffer_history_count] = strdup(file_path); + } + e->buffer_index = e->buffer_history_count; // Update buffer index + e->buffer_history_count++; +} + + +void editor_remove_from_buffer_history(Editor *e) { + if (e->buffer_history_count > 0) { + free(e->buffer_history[--e->buffer_history_count]); // Free the last string + } +} + + +Errno editor_open_buffer(Editor *e, const char *file_path) { + printf("Opening buffer: %s\n", file_path); + + e->data.count = 0; + Errno err = read_entire_file(file_path, &e->data); + if (err != 0) return err; + + e->cursor = 0; + editor_retokenize(e); + + e->file_path.count = 0; + sb_append_cstr(&e->file_path, file_path); + sb_append_null(&e->file_path); + + return 0; +} + +void editor_kill_buffer(Editor *e) { + if (e->buffer_history_count > 0) { + // Free the current buffer path and remove it from the history + free(e->buffer_history[e->buffer_index]); + e->buffer_history[e->buffer_index] = NULL; + + // Shift all elements after the current index down + for (int i = e->buffer_index; i < e->buffer_history_count - 1; i++) { + e->buffer_history[i] = e->buffer_history[i + 1]; + } + + // Decrease the count of buffers in the history + e->buffer_history_count--; + + // Update the buffer index to point to the previous buffer, if possible + + + + if (e->buffer_index > 0) { + } + + // If there are still buffers in the history, load the previous one + if (e->buffer_history_count > 0) { + const char *prev_file_path = e->buffer_history[e->buffer_index]; + editor_open_buffer(e, prev_file_path); // Open the previous buffer without adding to history + } else { + // Handle the case when there are no more buffers in the history + // For example open a scratch buffer + } + } +} + + +void editor_previous_buffer(Editor *e) { + if (e->buffer_index > 0) { + e->buffer_index--; // Move to the previous buffer in history + const char *prev_file_path = e->buffer_history[e->buffer_index]; + editor_open_buffer(e, prev_file_path); // Open the previous buffer + } else { + // Handle case when there's no previous buffer + printf("No previous buffer available.\n"); + } +} + +void editor_next_buffer(Editor *e) { + if (e->buffer_index < e->buffer_history_count - 1) { + e->buffer_index++; // Move to the next buffer in history + const char *next_file_path = e->buffer_history[e->buffer_index]; + editor_open_buffer(e, next_file_path); // Open the next buffer + } else { + // Handle case when there's no next buffer + printf("No next buffer available.\n"); + } +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 00000000..9cea9e8f --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,16 @@ +#ifndef BUFFER_H +#define BUFFER_H + +#include "editor.h" + +// BUFFER +void editor_add_to_buffer_history(Editor *e, const char *file_path); +void editor_remove_from_buffer_history(Editor *e); +Errno editor_open_buffer(Editor *e, const char *file_path); +Errno editor_open_buffer(Editor *e, const char *file_path); +void editor_kill_buffer(Editor *e); +void editor_previous_buffer(Editor *e); +void editor_next_buffer(Editor *e); + + +#endif // BUFFER_H diff --git a/src/clock.c b/src/clock.c new file mode 100644 index 00000000..e4731d4a --- /dev/null +++ b/src/clock.c @@ -0,0 +1,189 @@ +// Clock +#include +#include "theme.h" +#include "clock.h" + +const bool digit_patterns[10][5][3] = { + // 0 + { + {1, 1, 1}, + {1, 0, 1}, + {1, 0, 1}, + {1, 0, 1}, + {1, 1, 1} + }, + // 1 + { + {0, 0, 1}, + {0, 0, 1}, + {0, 0, 1}, + {0, 0, 1}, + {0, 0, 1} + }, + // 2 + { + {1, 1, 1}, + {0, 0, 1}, + {1, 1, 1}, + {1, 0, 0}, + {1, 1, 1} + }, + // 3 + { + {1, 1, 1}, + {0, 0, 1}, + {1, 1, 1}, + {0, 0, 1}, + {1, 1, 1} + }, + // 4 + { + {1, 0, 1}, + {1, 0, 1}, + {1, 1, 1}, + {0, 0, 1}, + {0, 0, 1} + }, + // 5 + { + {1, 1, 1}, + {1, 0, 0}, + {1, 1, 1}, + {0, 0, 1}, + {1, 1, 1} + }, + // 6 + { + {1, 1, 1}, + {1, 0, 0}, + {1, 1, 1}, + {1, 0, 1}, + {1, 1, 1} + }, + // 7 + { + {1, 1, 1}, + {0, 0, 1}, + {0, 0, 1}, + {0, 0, 1}, + {0, 0, 1} + }, + // 8 + { + {1, 1, 1}, + {1, 0, 1}, + {1, 1, 1}, + {1, 0, 1}, + {1, 1, 1} + }, + // 9 + { + {1, 1, 1}, + {1, 0, 1}, + {1, 1, 1}, + {0, 0, 1}, + {1, 1, 1} + } +}; + + +Vec2f clockPosition; +float clockScale; +Vec4f clockColor; + +void init_clock() { + clockPosition = vec2f(1860, 31); + clockScale = 3; + clockColor = themes[currentThemeIndex].cursor; +} + + +void render_digit(Simple_Renderer *sr, int digit, Vec2f position, float squareSize, Vec4f color) { + for (int y = 0; y < 5; ++y) { + for (int x = 0; x < 3; ++x) { + if (digit_patterns[digit][y][x]) { + Vec2f segment_position = { + position.x + x * squareSize, + position.y + (4 - y) * squareSize // Flipping the Y-coordinate + }; + simple_renderer_solid_rect(sr, segment_position, vec2f(squareSize, squareSize), color); + } + } + } +} + +void render_colon(Simple_Renderer *sr, Vec2f position, float squareSize, Vec4f color) { + Vec2f top_dot_position = {position.x, position.y + 3.5 * squareSize}; // Flipped Y-coordinate + Vec2f bottom_dot_position = {position.x, position.y + 0.5 * squareSize}; // Flipped Y-coordinate + simple_renderer_solid_rect(sr, top_dot_position, vec2f(squareSize, squareSize), color); // Top dot + simple_renderer_solid_rect(sr, bottom_dot_position, vec2f(squareSize, squareSize), color); // Bottom dot +} + + +/* void render_clock(Simple_Renderer *sr, int hours, int minutes) { */ +/* simple_renderer_set_shader(sr, VERTEX_SHADER_FIXED, SHADER_FOR_CURSOR); */ + +/* Vec4f currentClockColor = themes[currentThemeIndex].cursor; */ +/* float digitWidth = 3 * clockScale; // Width of each digit, assuming 3 units wide */ +/* float colonWidth = clockScale; // Assuming colon is 1 unit wide */ +/* float spacing = clockScale; // Spacing between elements */ + +/* // Calculate total width for hours, colon, and minutes */ +/* float totalWidth = (digitWidth * 4) + colonWidth + (spacing * 3); // 4 digits, 1 colon, 3 spaces */ + +/* // Calculate starting X position based on total width to center the clock */ +/* float startX = clockPosition.x - (totalWidth / 2.0); */ + +/* // Adjust positions based on startX */ +/* Vec2f firstHourDigitPos = vec2f(startX, clockPosition.y); */ +/* Vec2f secondHourDigitPos = vec2f(startX + digitWidth + spacing, clockPosition.y); */ +/* Vec2f colonPosition = vec2f(startX + (digitWidth * 2) + (spacing * 2), clockPosition.y); */ +/* Vec2f firstMinuteDigitPos = vec2f(colonPosition.x + colonWidth + spacing, clockPosition.y); */ +/* Vec2f secondMinuteDigitPos = vec2f(firstMinuteDigitPos.x + digitWidth + spacing, clockPosition.y); */ + +/* // Render the digits and colon based on adjusted positions */ +/* render_digit(sr, hours / 10, firstHourDigitPos, clockScale, currentClockColor); */ +/* render_digit(sr, hours % 10, secondHourDigitPos, clockScale, currentClockColor); */ +/* render_colon(sr, colonPosition, clockScale, currentClockColor); */ +/* render_digit(sr, minutes / 10, firstMinuteDigitPos, clockScale, currentClockColor); */ +/* render_digit(sr, minutes % 10, secondMinuteDigitPos, clockScale, currentClockColor); */ + +/* simple_renderer_flush(sr); */ +/* } */ + + +void render_clock(Simple_Renderer *sr, int hours, int minutes) { + simple_renderer_set_shader(sr, VERTEX_SHADER_FIXED, SHADER_FOR_CURSOR); + + Vec4f currentClockColor = themes[currentThemeIndex].cursor; + float digitWidth = 3 * clockScale; // Width of each digit, assuming 3 units wide + float colonWidth = clockScale; // Assuming colon is 1 unit wide + float spacing = clockScale; // Spacing between elements + float blockMove = 2 * clockScale; // Define the block move distance + + // Adjust starting positions based on the presence of "1" + float hourAdjust = (hours % 10 == 1) ? blockMove : 0; // Adjust if second hour digit is "1" + float minuteAdjust = (minutes / 10 == 1) ? -blockMove : 0; // Adjust if first minute digit is "1" + float secondMinuteAdjust = (minutes % 10 == 1 && minutes / 10 != 1) ? -blockMove : 0; // Adjust if second minute digit is "1" and first is not "1" + + // Calculate total width and starting X position to center the clock + float totalWidth = (digitWidth * 4) + colonWidth + (spacing * 3) + hourAdjust + minuteAdjust; + float startX = clockPosition.x - (totalWidth / 2.0); + + Vec2f firstHourDigitPos = vec2f(startX + hourAdjust, clockPosition.y); // Move first hour digit if needed + Vec2f secondHourDigitPos = vec2f(startX + digitWidth + spacing + hourAdjust, clockPosition.y); + Vec2f colonPosition = vec2f(startX + (digitWidth * 2) + (spacing * 2) + hourAdjust, clockPosition.y); + Vec2f firstMinuteDigitPos = vec2f(colonPosition.x + colonWidth + spacing + minuteAdjust, clockPosition.y); + Vec2f secondMinuteDigitPos = vec2f(firstMinuteDigitPos.x + digitWidth + spacing + secondMinuteAdjust, clockPosition.y); + + // Render the digits and colon based on adjusted positions + render_digit(sr, hours / 10, firstHourDigitPos, clockScale, currentClockColor); + render_digit(sr, hours % 10, secondHourDigitPos, clockScale, currentClockColor); + render_colon(sr, colonPosition, clockScale, currentClockColor); + render_digit(sr, minutes / 10, firstMinuteDigitPos, clockScale, currentClockColor); + render_digit(sr, minutes % 10, secondMinuteDigitPos, clockScale, currentClockColor); + + simple_renderer_flush(sr); +} + + diff --git a/src/clock.h b/src/clock.h new file mode 100644 index 00000000..15151d43 --- /dev/null +++ b/src/clock.h @@ -0,0 +1,16 @@ +#ifndef CLOCK_H +#define CLOCK_H + +#include "common.h" +#include "simple_renderer.h" + +extern Vec2f clockPosition; +extern float clockScale; +extern Vec4f clockColor; + +void render_clock(Simple_Renderer *sr, int hours, int minutes); +void render_digit(Simple_Renderer *sr, int digit, Vec2f position, float squareSize, Vec4f color); +void render_colon(Simple_Renderer *sr, Vec2f position, float squareSize, Vec4f color); +void init_clock(); + +#endif // CLOCK_H diff --git a/src/common.c b/src/common.c index 46bcfc50..975b1d5a 100644 --- a/src/common.c +++ b/src/common.c @@ -2,6 +2,7 @@ #include #include #include +#include "editor.h" #ifdef _WIN32 # define MINIRENT_IMPLEMENTATION @@ -35,32 +36,104 @@ void temp_reset(void) arena_reset(&temporary_arena); } -Errno read_entire_dir(const char *dir_path, Files *files) -{ - Errno result = 0; - DIR *dir = NULL; - dir = opendir(dir_path); - if (dir == NULL) { - return_defer(errno); +#include +#include +#include + +void get_file_info(const char *dir, const char *filename, FileInfo *info) { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", dir, filename); + + struct stat statbuf; + if (stat(path, &statbuf) != 0) { + perror("Failed to get file stats"); + memset(info, 0, sizeof(FileInfo)); // Clear the info struct on error + return; } - errno = 0; - struct dirent *ent = readdir(dir); - while (ent != NULL) { - da_append(files, temp_strdup(ent->d_name)); - ent = readdir(dir); + info->name = strdup(filename); + + // Permissions + char permissions[11]; + sprintf(permissions, "%c%c%c%c%c%c%c%c%c%c", + (S_ISDIR(statbuf.st_mode)) ? 'd' : '-', + (statbuf.st_mode & S_IRUSR) ? 'r' : '-', + (statbuf.st_mode & S_IWUSR) ? 'w' : '-', + (statbuf.st_mode & S_IXUSR) ? 'x' : '-', + (statbuf.st_mode & S_IRGRP) ? 'r' : '-', + (statbuf.st_mode & S_IWGRP) ? 'w' : '-', + (statbuf.st_mode & S_IXGRP) ? 'x' : '-', + (statbuf.st_mode & S_IROTH) ? 'r' : '-', + (statbuf.st_mode & S_IWOTH) ? 'w' : '-', + (statbuf.st_mode & S_IXOTH) ? 'x' : '-'); + info->permissions = strdup(permissions); + + // File size + info->size = statbuf.st_size; + + // Modification time + char mod_time[20]; + strftime(mod_time, sizeof(mod_time), "%Y-%m-%d %H:%M", localtime(&statbuf.st_mtime)); + info->mod_time = strdup(mod_time); + + // Owner and group + struct passwd *pwd = getpwuid(statbuf.st_uid); + struct group *grp = getgrgid(statbuf.st_gid); + info->owner = strdup(pwd ? pwd->pw_name : "unknown"); + info->group = strdup(grp ? grp->gr_name : "unknown"); +} + + +/* Errno read_entire_dir(const char *dir_path, Files *files) */ +/* { */ +/* Errno result = 0; */ +/* DIR *dir = NULL; */ + +/* dir = opendir(dir_path); */ +/* if (dir == NULL) { */ +/* return_defer(errno); */ +/* } */ + +/* errno = 0; */ +/* struct dirent *ent = readdir(dir); */ +/* while (ent != NULL) { */ +/* da_append(files, temp_strdup(ent->d_name)); */ +/* ent = readdir(dir); */ +/* } */ + +/* if (errno != 0) { */ +/* return_defer(errno); */ +/* } */ + +/* defer: */ +/* if (dir) closedir(dir); */ +/* return result; */ +/* } */ + + +Errno read_entire_dir(const char *dir_path, Files *files) { + DIR *dir = opendir(dir_path); + if (dir == NULL) { + perror("Failed to open directory"); + return errno; } - if (errno != 0) { - return_defer(errno); + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + + FileInfo info; + get_file_info(dir_path, ent->d_name, &info); + da_append(files, info); } -defer: - if (dir) closedir(dir); - return result; + closedir(dir); + return 0; } + Errno write_entire_file(const char *file_path, const char *buf, size_t buf_size) { Errno result = 0; @@ -89,33 +162,113 @@ static Errno file_size(FILE *file, size_t *size) return 0; } -Errno read_entire_file(const char *file_path, String_Builder *sb) +Errno create_new_file_here(const char *file_name) { Errno result = 0; - FILE *f = NULL; - f = fopen(file_path, "r"); - if (f == NULL) return_defer(errno); + FILE *f = fopen(file_name, "wb"); + if (f == NULL) + return errno; + + fclose(f); + return result; +} + + +/* Errno read_entire_file(const char *file_path, String_Builder *sb) */ +/* { */ +/* Errno result = 0; */ +/* FILE *f = NULL; */ + +/* f = fopen(file_path, "r"); */ +/* if (f == NULL) return_defer(errno); */ + +/* size_t size; */ +/* Errno err = file_size(f, &size); */ +/* if (err != 0) return_defer(err); */ + +/* if (sb->capacity < size) { */ +/* sb->capacity = size; */ +/* sb->items = realloc(sb->items, sb->capacity*sizeof(*sb->items)); */ +/* assert(sb->items != NULL && "Buy more RAM lol"); */ +/* } */ + +/* fread(sb->items, size, 1, f); */ +/* if (ferror(f)) return_defer(errno); */ +/* sb->count = size; */ + +/* defer: */ +/* if (f) fclose(f); */ +/* return result; */ +/* } */ + + +// Convert tabs into 4 spaces +// and ignore carriage returns +/* Errno read_entire_file(const char *file_path, String_Builder *sb) { */ +/* FILE *f = fopen(file_path, "r"); */ +/* if (f == NULL) */ +/* return 1; */ + +/* int c; */ +/* while ((c = fgetc(f)) != EOF && c != '\0') { */ +/* if (c == '\t') { */ +/* da_append(sb, ' '); */ +/* da_append(sb, ' '); */ +/* da_append(sb, ' '); */ +/* da_append(sb, ' '); */ +/* } else if (c == '\r') { */ +/* continue; */ +/* } else { */ +/* da_append(sb, (char) c); */ +/* } */ +/* } */ + +/* fclose(f); */ +/* return 0; */ +/* } */ - size_t size; - Errno err = file_size(f, &size); - if (err != 0) return_defer(err); - if (sb->capacity < size) { - sb->capacity = size; - sb->items = realloc(sb->items, sb->capacity*sizeof(*sb->items)); - assert(sb->items != NULL && "Buy more RAM lol"); +// Convert tabs into 4 spaces +// and ignore carriage returns +Errno read_entire_file(const char *file_path, String_Builder *sb) { + FILE *f = fopen(file_path, "r+"); + if (f == NULL) { + if (errno == EACCES) { + // Retry opening in read-only mode + f = fopen(file_path, "r"); + if (f == NULL) return 1; // If opening still fails, return error + readonly = true; + } else { + return 1; // Other errors, return error + } } - fread(sb->items, size, 1, f); - if (ferror(f)) return_defer(errno); - sb->count = size; + int c; + while ((c = fgetc(f)) != EOF && c != '\0') { + if (c == '\t') { + for (size_t i = 0; i < indentation; i++) { + da_append(sb, ' '); + } + } else if (c == '\r') { + continue; // Ignore carriage returns + } else { + da_append(sb, (char)c); + } + } -defer: - if (f) fclose(f); - return result; + fclose(f); + return 0; } + + + +bool is_hex_digit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + + Vec4f hex_to_vec4f(uint32_t color) { Vec4f result; @@ -147,3 +300,4 @@ Errno type_of_file(const char *file_path, File_Type *ft) #endif return 0; } + diff --git a/src/common.h b/src/common.h index 761b093f..2e190cc9 100644 --- a/src/common.h +++ b/src/common.h @@ -4,11 +4,12 @@ #include #include #include +#include #include "./la.h" #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 600 -#define FPS 60 +#define FPS 144 #define DELTA_TIME (1.0f / FPS) #define CURSOR_OFFSET 0.13f @@ -92,8 +93,18 @@ typedef struct { #define sb_to_sv(sb) sv_from_parts((sb).items, (sb).count) +// ADDED typedef struct { - const char **items; + char *name; + char *permissions; + long size; + char *mod_time; + char *owner; + char *group; +} FileInfo; + +typedef struct { + FileInfo *items; size_t count; size_t capacity; } Files; @@ -109,6 +120,12 @@ Errno read_entire_file(const char *file_path, String_Builder *sb); Errno write_entire_file(const char *file_path, const char *buf, size_t buf_size); Errno read_entire_dir(const char *dir_path, Files *files); +void get_file_info(const char *dir, const char *filename, FileInfo *info); + + + +bool is_hex_digit(char c); Vec4f hex_to_vec4f(uint32_t color); + #endif // COMMON_H_ diff --git a/src/editor.c b/src/editor.c index 1aaeee76..6ee1839b 100644 --- a/src/editor.c +++ b/src/editor.c @@ -1,34 +1,243 @@ #include #include +#include #include #include #include +#include #include "./editor.h" #include "./common.h" +/* #include "./free_glyph.h" */ +#include "./file_browser.h" +#include "emacs.h" +#include "lexer.h" +#include "simple_renderer.h" +#include // For isalnum +#include "evil.h" +#include "theme.h" +#include "M-x.h" +#include "utilities.h" -void editor_backspace(Editor *e) -{ + +// TODO attach documentation to each variable +// and make an interface to modify variables at runtime à la emacs + +// TODO show lock logo in the modeline when the file is in read only mode + +Editor editor = {0}; + +bool quit = false; +float zoom_factor = 3.0f; +float min_zoom_factor = 1.0; +float max_zoom_factor = 100.0; + +bool isWave = false; +size_t indentation = 4; + +bool showLineNumbers = false; +bool showLineNumbersBackground = false; +bool highlightCurrentLineNumber = true; +bool relativeLineNumbers = false; + +bool showWhitespaces = false; +bool copiedLine = false; +bool matchParenthesis = true; + +bool zoomInInsertMode = false; +bool instantCamera = false; +bool followCursor = true; +bool centeredText = true; + + +bool hl_line = false; +bool showIndentationLines = true; + +bool showMinibuffer = true; +bool showModeline = true; +float minibufferHeight = 21.0f; +float modelineHeight = 35.0f; +float modelineAccentWidth = 5.0f; +bool fzy = false; +bool M_x_active = false; +bool evil_command_active = false; + +bool BlockInsertCursor = true; +bool highlightCurrentLineNumberOnInsertMode = true; // the loong way + +bool helix = false; +bool emacs = false; +bool automatic_zoom = true; + +float fringeWidth = 8.0f; +bool showFringe = true; + +size_t fillColumn = 80; +float fillColumnThickness = 5.0; // if 0, it default to the width of one character +bool smartFillColumn = true; +bool showFillColumn = true; + +bool readonly = false; // TODO actually use this, +//like don't save if its readonly and show a lock in the modeline + +bool electric_mode = true; // whether to indent automatically when typing ";"" or "}" + +// When enabled, typing an open parenthesis automatically inserts the corresponding +// closing parenthesis, and vice versa. (Likewise for brackets, etc.). +// If the region is active, the parentheses (brackets, etc.) are +// inserted around the region instead. TODO +bool electric_pair_mode = true; + +// When Delete Selection mode is enabled, typed text replaces the selection +// if the selection is active. Otherwise, typed text is just inserted at +// point regardless of any selection. +bool delete_selection_mode = true; + +// How many lines a file should have +// to be considered a long file +size_t long_file_lines = 100; + +bool show_line_numbers_opening_long_files = true; +bool decenter_text_opening_long_files = true; + +bool hide_line_numbers_opening_small_files = true; +bool center_text_opening_small_files = true; + +bool diredfl_mode = true; + + + +bool ctrl_x_pressed = false; + +void reset_keychords() { + ctrl_x_pressed = false; +} + +void set_current_mode() { + if (emacs) { + current_mode = EMACS; + } else if (helix) { + current_mode = HELIX; + } else { + current_mode = NORMAL; + } +} + +EvilMode current_mode = NORMAL; + + +void move_camera(Simple_Renderer *sr, const char* direction, float amount) { + if(strcmp(direction, "up") == 0) { + sr->camera_pos.y -= amount; + } else if(strcmp(direction, "down") == 0) { + sr->camera_pos.y += amount; + } else if(strcmp(direction, "left") == 0) { + sr->camera_pos.x -= amount; + } else if(strcmp(direction, "right") == 0) { + sr->camera_pos.x += amount; + } else { + printf("Invalid direction '%s'\n", direction); + } +} + +// TODO if we are on a multiple of indentation delete the correct number of indentations +void editor_backspace(Editor *e) { + // If in search mode, reduce the search query length if (e->searching) { if (e->search.count > 0) { e->search.count -= 1; } + } else if (e->minibuffer_active) { + if (e->minibuffer_text.count > 0) { + e->minibuffer_text.count -= 1; + } } else { - if (e->cursor > e->data.count) { - e->cursor = e->data.count; + // Check if the cursor is at the beginning or at the beginning of a line + if (e->cursor == 0) return; // Cursor at the beginning, nothing to delete + + size_t cursor_pos = e->cursor; + size_t row = editor_cursor_row(e); + + if (cursor_pos > e->data.count) { + cursor_pos = e->data.count; } - if (e->cursor == 0) return; - memmove( - &e->data.items[e->cursor - 1], - &e->data.items[e->cursor], - e->data.count - e->cursor - ); - e->cursor -= 1; - e->data.count -= 1; + // Determine the characters before and after the cursor + char char_before_cursor = (cursor_pos > 0) ? e->data.items[cursor_pos - 1] : '\0'; + char char_after_cursor = (cursor_pos < e->data.count) ? e->data.items[cursor_pos] : '\0'; + + // Smart parentheses: delete both characters if they match + if ((char_before_cursor == '(' && char_after_cursor == ')') || + (char_before_cursor == '[' && char_after_cursor == ']') || + (char_before_cursor == '{' && char_after_cursor == '}') || + (char_before_cursor == '\'' && char_after_cursor == '\'') || + (char_before_cursor == '"' && char_after_cursor == '"')) { + memmove(&e->data.items[cursor_pos - 1], &e->data.items[cursor_pos + 1], e->data.count - cursor_pos); + e->cursor -= 1; + e->data.count -= 2; + } else if (editor_is_line_empty(e, row)) { + if (row > 0) { + // If it's not the first line, delete the newline character from the previous line + size_t newline_pos = e->lines.items[row - 1].end; // Position of newline character + memmove(&e->data.items[newline_pos], &e->data.items[newline_pos + 1], e->data.count - newline_pos - 1); + e->cursor = newline_pos; // Move cursor to the end of the previous line + e->data.count -= 1; + } else if (e->lines.count > 1) { + // If it's the first line but there are more lines, delete the newline character at the end of this line + size_t newline_pos = e->lines.items[row].end; // Position of newline character + memmove(&e->data.items[newline_pos], &e->data.items[newline_pos + 1], e->data.count - newline_pos - 1); + e->data.count -= 1; + // Cursor stays at the beginning of the next line (which is now the first line) + } + } else if (editor_is_line_whitespaced(e, row)) { + /* // If the line is only whitespaces */ + /* size_t line_begin = e->lines.items[row].begin; */ + /* size_t delete_length = (cursor_pos - line_begin >= indentation) ? indentation : cursor_pos - line_begin; */ + + /* memmove(&e->data.items[cursor_pos - delete_length], &e->data.items[cursor_pos], e->data.count - cursor_pos); */ + /* e->cursor -= delete_length; */ + /* e->data.count -= delete_length; */ + + // If the line is only whitespaces + size_t line_begin = e->lines.items[row].begin; + size_t line_end = e->lines.items[row].end; + size_t whitespace_length = cursor_pos - line_begin; + + if (whitespace_length == indentation) { + // If the number of whitespaces matches indentation exactly, remove the entire line + if (row < e->lines.count - 1) { + memmove(&e->data.items[line_begin], &e->data.items[line_end + 1], e->data.count - line_end - 1); + e->data.count -= (line_end - line_begin + 1); + e->cursor = line_begin; // Update cursor position to the beginning of the next line + } else if (row > 0 && e->data.items[line_begin - 1] == '\n') { + // If it's the last line, remove the preceding newline character + e->data.count -= 1; + memmove(&e->data.items[line_begin - 1], &e->data.items[line_end], e->data.count - line_end); + e->cursor = (line_begin > 1) ? line_begin - 1 : 0; // Move cursor to the end of the previous line, plus one character + } + // Update the cursor position if it's not the first line + if (row > 0) { + e->cursor = e->lines.items[row - 1].end; // Move cursor to one character right of the end of the previous line + if (e->cursor > e->data.count) e->cursor = e->data.count; // Bound check + } + } else { + // Original behavior for deleting whitespaces + size_t delete_length = (whitespace_length >= indentation) ? indentation : whitespace_length; + memmove(&e->data.items[cursor_pos - delete_length], &e->data.items[cursor_pos], e->data.count - cursor_pos); + e->cursor -= delete_length; + e->data.count -= delete_length; + } + } else { + // Delete only the character before the cursor + memmove(&e->data.items[cursor_pos - 1], &e->data.items[cursor_pos], e->data.count - cursor_pos); + e->cursor -= 1; + e->data.count -= 1; + } editor_retokenize(e); } } + +// Unused ? void editor_delete(Editor *e) { if (e->searching) return; @@ -43,6 +252,36 @@ void editor_delete(Editor *e) editor_retokenize(e); } +void editor_delete_selection(Editor *e) +{ + assert(e->selection); + + size_t begin = e->select_begin; + size_t end = e->cursor; + if (begin > end) { + SWAP(size_t, begin, end); + } + + if (end >= e->data.count) { + end = e->data.count - 1; + } + if (begin == e->data.count) return; + + size_t nchars = end - begin + 1; // Correct calculation to include the end character + + memmove( + &e->data.items[begin], + &e->data.items[end + 1], + e->data.count - end - 1 + ); + + e->data.count -= nchars; + e->cursor = begin; // Set cursor to the beginning of the deleted range + + editor_retokenize(e); +} + + // TODO: make sure that you always have new line at the end of the file while saving // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206 @@ -64,22 +303,99 @@ Errno editor_save(const Editor *e) return write_entire_file(e->file_path.items, e->data.items, e->data.count); } -Errno editor_load_from_file(Editor *e, const char *file_path) -{ - printf("Loading %s\n", file_path); +/* Errno editor_load_from_file(Editor *e, const char *file_path) */ +/* { */ +/* printf("Loading %s\n", file_path); */ + +/* e->data.count = 0; */ +/* Errno err = read_entire_file(file_path, &e->data); */ +/* if (err != 0) return err; */ + +/* e->cursor = 0; */ + +/* editor_retokenize(e); */ + +/* e->file_path.count = 0; */ +/* sb_append_cstr(&e->file_path, file_path); */ +/* sb_append_null(&e->file_path); */ + +/* // Add file path to buffer history */ +/* if (e->buffer_history_count < MAX_BUFFER_HISTORY) { */ +/* e->buffer_history[e->buffer_history_count++] = strdup(file_path); */ +/* } */ + +/* return 0; */ +/* } */ + + +size_t get_position_from_line_column(Editor *e, size_t line, size_t column) { + size_t pos = 0; + size_t current_line = 0; + + while (pos < e->data.count && current_line < line) { + if (e->data.items[pos] == '\n') { + current_line++; + } + pos++; + } + + // Adjust column position + size_t line_start = pos; + size_t current_column = 0; + while (pos < e->data.count && current_column < column) { + if (e->data.items[pos] == '\n') { + break; // Prevent going to next line + } + current_column++; + pos++; + } + + return line_start + current_column; +} + +Errno find_file(Editor *e, const char *file_path, size_t line, size_t column) { + char expanded_file_path[PATH_MAX]; + expand_path(file_path, expanded_file_path, sizeof(expanded_file_path)); + + printf("[find_file] Requested File: %s\n", file_path); + printf("[find_file] Expanded File Path: %s\n", expanded_file_path); + printf("[find_file] Line: %zu, Column: %zu\n", line, column); e->data.count = 0; - Errno err = read_entire_file(file_path, &e->data); - if (err != 0) return err; + Errno err = read_entire_file(expanded_file_path, &e->data); + if (err != 0) { + printf("[find_file] Error reading file: %d\n", err); + return err; + } - e->cursor = 0; + size_t line_count = 0; + for (size_t i = 0; i < e->data.count && line_count <= long_file_lines; ++i) { + if (e->data.items[i] == '\n') { + line_count++; + } + } + + if (line_count > long_file_lines) { + if (show_line_numbers_opening_long_files) showLineNumbers = true; + if (decenter_text_opening_long_files) centeredText = false; + } else { + if (hide_line_numbers_opening_small_files) showLineNumbers = false; + if (center_text_opening_small_files) centeredText = true; + } + e->cursor = get_position_from_line_column(e, line, column); editor_retokenize(e); e->file_path.count = 0; - sb_append_cstr(&e->file_path, file_path); + sb_append_cstr(&e->file_path, expanded_file_path); sb_append_null(&e->file_path); + if (e->buffer_history_count < MAX_BUFFER_HISTORY) { + e->buffer_history[e->buffer_history_count++] = strdup(expanded_file_path); + } + + printf("[find_file] File loaded and cursor set.\n"); + printf("[find_file] Read only: %d\n", readonly); return 0; } @@ -95,6 +411,23 @@ size_t editor_cursor_row(const Editor *e) return e->lines.count - 1; } + +void get_cursor_position(const Editor *e) { + /* assert(e != NULL && line != NULL && character != NULL); */ + + // Get the line number + size_t line = editor_cursor_row(e); + + // Find the start of the current line + size_t line_start = 0; + if (line > 0 && line < e->lines.count) { + line_start = e->lines.items[line].begin; + } + + // Calculate the column number (character position) + int character = e->cursor - line_start; +} + void editor_move_line_up(Editor *e) { editor_stop_search(e); @@ -162,6 +495,12 @@ void editor_insert_char(Editor *e, char x) editor_insert_buf(e, &x, 1); } +void editor_insert_char_at(Editor *e, char c, size_t pos) { + editor_insert_buf_at(e, &c, 1, pos); +} + + + void editor_insert_buf(Editor *e, char *buf, size_t buf_len) { if (e->searching) { @@ -175,6 +514,9 @@ void editor_insert_buf(Editor *e, char *buf, size_t buf_len) } } if (!matched) e->search.count -= buf_len; + } else if (e->minibuffer_active) { + sb_append_buf(&e->minibuffer_text, buf, buf_len); + /* printf("Minibuffer: "SB_Fmt"\n", SB_Arg(e->minibuffer_text)); */ } else { if (e->cursor > e->data.count) { e->cursor = e->data.count; @@ -191,7 +533,31 @@ void editor_insert_buf(Editor *e, char *buf, size_t buf_len) memcpy(&e->data.items[e->cursor], buf, buf_len); e->cursor += buf_len; editor_retokenize(e); + /* printf("%.*s", (int)buf_len, buf); */ + } +} + + +void editor_insert_buf_at(Editor *e, char *buf, size_t buf_len, size_t pos) { + // Ensure the position is within bounds + if (pos > e->data.count) { + pos = e->data.count; + } + + // Expand the buffer to accommodate the new text + for (size_t i = 0; i < buf_len; ++i) { + da_append(&e->data, '\0'); } + + // Shift existing text to make room for the new text + memmove(&e->data.items[pos + buf_len], &e->data.items[pos], e->data.count - pos); + + // Copy the new text into the buffer at the specified position + memcpy(&e->data.items[pos], buf, buf_len); + + // Update the cursor position and retokenize + e->cursor = pos + buf_len; + editor_retokenize(e); } void editor_retokenize(Editor *e) @@ -219,6 +585,7 @@ void editor_retokenize(Editor *e) { e->tokens.count = 0; Lexer l = lexer_new(e->atlas, e->data.items, e->data.count); + /* Lexer l = lexer_new(e->atlas, e->data.items, e->data.count, e->file_path); */ Token t = lexer_next(&l); while (t.kind != TOKEN_END) { da_append(&e->tokens, t); @@ -255,176 +622,6 @@ const char *editor_line_starts_with_one_of(Editor *e, size_t row, size_t col, co return NULL; } -void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor) -{ - int w, h; - SDL_GetWindowSize(window, &w, &h); - - float max_line_len = 0.0f; - - sr->resolution = vec2f(w, h); - sr->time = (float) SDL_GetTicks() / 1000.0f; - - // Render selection - { - simple_renderer_set_shader(sr, SHADER_FOR_COLOR); - if (editor->selection) { - for (size_t row = 0; row < editor->lines.count; ++row) { - size_t select_begin_chr = editor->select_begin; - size_t select_end_chr = editor->cursor; - if (select_begin_chr > select_end_chr) { - SWAP(size_t, select_begin_chr, select_end_chr); - } - - Line line_chr = editor->lines.items[row]; - - if (select_begin_chr < line_chr.begin) { - select_begin_chr = line_chr.begin; - } - - if (select_end_chr > line_chr.end) { - select_end_chr = line_chr.end; - } - - if (select_begin_chr <= select_end_chr) { - Vec2f select_begin_scr = vec2f(0, -((float)row + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE); - free_glyph_atlas_measure_line_sized( - atlas, editor->data.items + line_chr.begin, select_begin_chr - line_chr.begin, - &select_begin_scr); - - Vec2f select_end_scr = select_begin_scr; - free_glyph_atlas_measure_line_sized( - atlas, editor->data.items + select_begin_chr, select_end_chr - select_begin_chr, - &select_end_scr); - - Vec4f selection_color = vec4f(.25, .25, .25, 1); - simple_renderer_solid_rect(sr, select_begin_scr, vec2f(select_end_scr.x - select_begin_scr.x, FREE_GLYPH_FONT_SIZE), selection_color); - } - } - } - simple_renderer_flush(sr); - } - - Vec2f cursor_pos = vec2fs(0.0f); - { - size_t cursor_row = editor_cursor_row(editor); - Line line = editor->lines.items[cursor_row]; - size_t cursor_col = editor->cursor - line.begin; - cursor_pos.y = -((float)cursor_row + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE; - cursor_pos.x = free_glyph_atlas_cursor_pos( - atlas, - editor->data.items + line.begin, line.end - line.begin, - vec2f(0.0, cursor_pos.y), - cursor_col - ); - } - - // Render search - { - if (editor->searching) { - simple_renderer_set_shader(sr, SHADER_FOR_COLOR); - Vec4f selection_color = vec4f(.10, .10, .25, 1); - Vec2f p1 = cursor_pos; - Vec2f p2 = p1; - free_glyph_atlas_measure_line_sized(editor->atlas, editor->search.items, editor->search.count, &p2); - simple_renderer_solid_rect(sr, p1, vec2f(p2.x - p1.x, FREE_GLYPH_FONT_SIZE), selection_color); - simple_renderer_flush(sr); - } - } - - // Render text - { - simple_renderer_set_shader(sr, SHADER_FOR_TEXT); - for (size_t i = 0; i < editor->tokens.count; ++i) { - Token token = editor->tokens.items[i]; - Vec2f pos = token.position; - Vec4f color = vec4fs(1); - switch (token.kind) { - case TOKEN_PREPROC: - color = hex_to_vec4f(0x95A99FFF); - break; - case TOKEN_KEYWORD: - color = hex_to_vec4f(0xFFDD33FF); - break; - case TOKEN_COMMENT: - color = hex_to_vec4f(0xCC8C3CFF); - break; - case TOKEN_STRING: - color = hex_to_vec4f(0x73c936ff); - break; - default: - {} - } - free_glyph_atlas_render_line_sized(atlas, sr, token.text, token.text_len, &pos, color); - // TODO: the max_line_len should be calculated based on what's visible on the screen right now - if (max_line_len < pos.x) max_line_len = pos.x; - } - simple_renderer_flush(sr); - } - - // Render cursor - simple_renderer_set_shader(sr, SHADER_FOR_COLOR); - { - float CURSOR_WIDTH = 5.0f; - Uint32 CURSOR_BLINK_THRESHOLD = 500; - Uint32 CURSOR_BLINK_PERIOD = 1000; - Uint32 t = SDL_GetTicks() - editor->last_stroke; - - sr->verticies_count = 0; - if (t < CURSOR_BLINK_THRESHOLD || t/CURSOR_BLINK_PERIOD%2 != 0) { - simple_renderer_solid_rect( - sr, - cursor_pos, vec2f(CURSOR_WIDTH, FREE_GLYPH_FONT_SIZE), - vec4fs(1)); - } - - simple_renderer_flush(sr); - } - - // Update camera - { - if (max_line_len > 1000.0f) { - max_line_len = 1000.0f; - } - - float target_scale = w/3/(max_line_len*0.75); // TODO: division by 0 - - Vec2f target = cursor_pos; - float offset = 0.0f; - - if (target_scale > 3.0f) { - target_scale = 3.0f; - } else { - offset = cursor_pos.x - w/3/sr->camera_scale; - if (offset < 0.0f) offset = 0.0f; - target = vec2f(w/3/sr->camera_scale + offset, cursor_pos.y); - } - - sr->camera_vel = vec2f_mul( - vec2f_sub(target, sr->camera_pos), - vec2fs(2.0f)); - sr->camera_scale_vel = (target_scale - sr->camera_scale) * 2.0f; - - sr->camera_pos = vec2f_add(sr->camera_pos, vec2f_mul(sr->camera_vel, vec2fs(DELTA_TIME))); - sr->camera_scale = sr->camera_scale + sr->camera_scale_vel * DELTA_TIME; - } -} - -void editor_update_selection(Editor *e, bool shift) -{ - if (e->searching) return; - if (shift) { - if (!e->selection) { - e->selection = true; - e->select_begin = e->cursor; - } - } else { - if (e->selection) { - e->selection = false; - } - } -} - void editor_clipboard_copy(Editor *e) { if (e->searching) return; @@ -441,6 +638,7 @@ void editor_clipboard_copy(Editor *e) fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); } } + copiedLine = false; } void editor_clipboard_paste(Editor *e) @@ -455,6 +653,25 @@ void editor_clipboard_paste(Editor *e) SDL_free(text); } +void editor_update_selection(Editor *e, bool shift) { + if (e->searching) return; + + if (shift) { + if (!e->selection) { + e->selection = true; + e->select_begin = e->cursor; + } + } else if (current_mode == VISUAL_LINE) { + + } else if (current_mode == VISUAL) { + + } else { + e->selection = false; + } +} + + +// search void editor_start_search(Editor *e) { if (e->searching) { @@ -480,6 +697,20 @@ void editor_stop_search(Editor *e) e->searching = false; } +void editor_stop_search_and_mark(Editor *e) { + e->searching = false; + + e->has_mark = true; // Mark the search result. + e->mark_start = e->cursor; + e->mark_end = e->cursor + e->search.count; +} + +void editor_clear_mark(Editor *editor) { + editor->has_mark = false; + editor->mark_start = 0; // or some other appropriate default value + editor->mark_end = 0; // or some other appropriate default value +} + bool editor_search_matches_at(Editor *e, size_t pos) { if (e->data.count - pos < e->search.count) return false; @@ -542,3 +773,876 @@ void editor_move_paragraph_down(Editor *e) } e->cursor = e->lines.items[row].begin; } + +ssize_t find_matching_parenthesis(Editor *editor, size_t cursor_pos) { + // Ensure the cursor position is within the valid range + if (cursor_pos >= editor->data.count) return -1; + if (matchParenthesis){ + char current_char = editor->data.items[cursor_pos]; + char matching_char; + int direction; + + // Check if the character at cursor is a parenthesis + switch (current_char) { + case '(': matching_char = ')'; direction = 1; break; + case ')': matching_char = '('; direction = -1; break; + case '[': matching_char = ']'; direction = 1; break; + case ']': matching_char = '['; direction = -1; break; + case '{': matching_char = '}'; direction = 1; break; + case '}': matching_char = '{'; direction = -1; break; + default: return -1; // Not on a parenthesis character + } + + int balance = 1; + size_t pos = cursor_pos; + + while ((direction > 0 && pos < editor->data.count - 1) || (direction < 0 && pos > 0)) { + pos += direction; + + if (editor->data.items[pos] == current_char) { + balance++; + } else if (editor->data.items[pos] == matching_char) { + balance--; + if (balance == 0) { + return pos; // Found the matching parenthesis + } + } + } + return -1; // No matching parenthesis found + } + // if matchParenthesis is false or any other condition not covered above + return -1; +} + + +void editor_enter(Editor *e) { + if (e->searching) { + editor_stop_search_and_mark(e); + current_mode = NORMAL; + return; + } else if ((M_x_active || evil_command_active) && e->minibuffer_active) { + sb_append_null(&e->minibuffer_text); // null termination + execute_command(e->commands, e, e->minibuffer_text.items); + e->minibuffer_text.count = 0; + e->minibuffer_active = false; + M_x_active = false; + current_mode = NORMAL; + } else { + size_t row = editor_cursor_row(e); + size_t line_end = e->lines.items[row].end; + + editor_insert_char(e, '\n'); + size_t line_begin = e->lines.items[row].begin; + bool inside_braces = false; + + // Check if the line contains an opening brace '{' + for (size_t i = line_begin; i < line_end; ++i) { + char c = e->data.items[i]; + if (c == '{') { + inside_braces = true; + break; + } + } + + // Insert the same whitespace character + for (size_t i = line_begin; i < line_end; ++i) { + char c = e->data.items[i]; + if (c == ' ' || c == '\t') { + editor_insert_char(e, c); + } else { + break; + } + } + + // If inside braces, perform additional steps + if (inside_braces) { + editor_move_line_up(e); + editor_move_to_line_end(e); + editor_insert_char(e, '\n'); + + // Add indentation + for (size_t i = 0; i < indentation; ++i) { + editor_insert_char(e, ' '); + } + } + + indent(e); + + e->last_stroke = SDL_GetTicks(); + } +} + + +// Anchor Implementation: Initially, the anchor used a single index from the +// start of the buffer, requiring updates on text changes. To simplify, we now +// track two indices (start and end of buffer). The anchor position self-adjusts +// based on cursor's relative position, ensuring correct placement without +// modifying all text-manipulating functions still a dumb implementation. + +void editor_set_anchor(Editor *editor) { + if (editor->cursor < editor->data.count) { + editor->has_anchor = true; + editor->anchor_pos_from_start = editor->cursor; + editor->anchor_pos_from_end = editor->data.count - editor->cursor; + } +} + +void editor_goto_anchor_and_clear(Editor *editor) { + if (editor->has_anchor) { + if (editor->cursor > editor->anchor_pos_from_start) { + editor->cursor = editor->anchor_pos_from_start; + } else { + editor->cursor = editor->data.count - editor->anchor_pos_from_end; + } + editor->has_anchor = false; + } +} + +void editor_update_anchor(Editor *editor) { + if (!editor->has_anchor) return; + + if (editor->cursor > editor->anchor_pos_from_start) { + // Cursor is after the anchor, use position from the start + editor->anchor_pos = editor->anchor_pos_from_start; + } else { + // Cursor is before the anchor, use position from the end + editor->anchor_pos = editor->data.count - editor->anchor_pos_from_end; + } +} + + + +// TODO refactor and implement drag region (selection) +void editor_drag_line_down(Editor *editor) { + size_t row = editor_cursor_row(editor); + if (row >= editor->lines.count - 1) return; // Can't move the last line down + + Line current_line = editor->lines.items[row]; + Line next_line = editor->lines.items[row + 1]; + + // Calculate lengths including the newline character + size_t current_line_length = current_line.end - current_line.begin + 1; + size_t next_line_length = next_line.end - next_line.begin + 1; + + // Allocate temporary buffer to hold the lines + char *temp = malloc(current_line_length + next_line_length); + if (!temp) { + // Handle memory allocation error + fprintf(stderr, "ERROR: Unable to allocate memory for line swapping.\n"); + return; + } + + // Copy current and next lines into temp + memcpy(temp, &editor->data.items[current_line.begin], current_line_length); + memcpy(temp + current_line_length, &editor->data.items[next_line.begin], next_line_length); + + // Swap lines in editor's data + memcpy(&editor->data.items[current_line.begin], temp + current_line_length, next_line_length); + memcpy(&editor->data.items[current_line.begin + next_line_length], temp, current_line_length); + + // Free the temporary buffer + free(temp); + + // Update cursor position + if (editor->cursor >= current_line.begin && editor->cursor < current_line.end) { + // The cursor is on the current line, move it down with the line + editor->cursor += next_line_length; + } else if (editor->cursor >= next_line.begin && editor->cursor <= next_line.end) { + // The cursor is on the next line, move it up to the start of the current line + editor->cursor = current_line.begin + (editor->cursor - next_line.begin); + } + + // Update line positions in the Lines struct + editor->lines.items[row].begin = current_line.begin; + editor->lines.items[row].end = current_line.begin + next_line_length - 1; + editor->lines.items[row + 1].begin = current_line.begin + next_line_length; + editor->lines.items[row + 1].end = editor->lines.items[row + 1].begin + current_line_length - 1; + + // Retokenize + editor_retokenize(editor); +} + +void editor_drag_line_up(Editor *editor) { + size_t row = editor_cursor_row(editor); + if (row == 0) return; // Can't move the first line up + + Line current_line = editor->lines.items[row]; + Line previous_line = editor->lines.items[row - 1]; + + // Calculate lengths including the newline character + size_t current_line_length = current_line.end - current_line.begin + 1; + size_t previous_line_length = previous_line.end - previous_line.begin + 1; + + // Allocate temporary buffer to hold the lines + char *temp = malloc(current_line_length + previous_line_length); + if (!temp) { + // Handle memory allocation error + fprintf(stderr, "ERROR: Unable to allocate memory for line swapping.\n"); + return; + } + + // Copy current and previous lines into temp + memcpy(temp, &editor->data.items[previous_line.begin], previous_line_length); + memcpy(temp + previous_line_length, &editor->data.items[current_line.begin], current_line_length); + + // Swap lines in editor's data + memcpy(&editor->data.items[previous_line.begin], temp + previous_line_length, current_line_length); + memcpy(&editor->data.items[previous_line.begin + current_line_length], temp, previous_line_length); + + // Free the temporary buffer + free(temp); + + // Update cursor position + editor->cursor = previous_line.begin + (editor->cursor - current_line.begin); + + // Update line positions in the Lines struct + editor->lines.items[row - 1].begin = previous_line.begin; + editor->lines.items[row - 1].end = previous_line.begin + current_line_length - 1; + editor->lines.items[row].begin = previous_line.begin + current_line_length; + editor->lines.items[row].end = editor->lines.items[row].begin + previous_line_length - 1; + + // Retokenize + editor_retokenize(editor); +} + + + + +void add_one_indentation_here(Editor *editor) { + for (size_t i = 0; i < indentation; ++i) { + editor_insert_char(editor, ' '); + } +} + +void remove_one_indentation_here(Editor *editor) { + for (size_t i = 0; i < indentation; ++i) { + editor_delete(editor); + } +} + +void add_one_indentation(Editor *editor) { + size_t cursor_row = editor_cursor_row(editor); + Line currentLineData = editor->lines.items[cursor_row]; + size_t originalCursorPosition = editor->cursor; + + // Calculate current indentation of the line + size_t currentIndentation = 0; + for (size_t i = currentLineData.begin; i < currentLineData.end && isspace(editor->data.items[i]); ++i) { + currentIndentation++; + } + + // Move cursor to the beginning of the current line + editor->cursor = currentLineData.begin; + + // Add one level of indentation at the beginning of the line + for (size_t i = 0; i < indentation; ++i) { + editor_insert_char(editor, ' '); + } + + // Adjust cursor position + if (originalCursorPosition <= currentLineData.begin + currentIndentation) { + // If the cursor was at or before the first non-whitespace character, move it right after the added indentation + editor->cursor = currentLineData.begin + currentIndentation + indentation; + } else { + // If the cursor was on a non-whitespace character, maintain relative position + editor->cursor = originalCursorPosition + indentation; + } +} + +void remove_one_indentation(Editor *editor) { + size_t cursor_row = editor_cursor_row(editor); + Line currentLineData = editor->lines.items[cursor_row]; + + // Save the current cursor position + size_t originalCursorPosition = editor->cursor; + + // Calculate current indentation of the line + size_t currentIndentation = 0; + size_t firstNonWhitespace = currentLineData.begin; + while (firstNonWhitespace < currentLineData.end && isspace(editor->data.items[firstNonWhitespace])) { + currentIndentation++; + firstNonWhitespace++; + } + + // Check if there's at least one indentation level to remove + if (currentIndentation >= indentation) { + // Move cursor to the beginning of the current line + editor->cursor = currentLineData.begin; + + // Remove one level of indentation from the beginning of the line + for (size_t i = 0; i < indentation; ++i) { + editor_delete(editor); // Assuming delete removes one character. + } + + // Adjust cursor position + if (originalCursorPosition <= currentLineData.begin + currentIndentation) { + // If the cursor was within the leading whitespace, move it to the first non-whitespace character + editor->cursor = firstNonWhitespace - indentation; + } else { + // If the cursor was on a non-whitespace character, maintain relative position + editor->cursor = originalCursorPosition - indentation; + } + } +} + + + +// TODO slow calculation on whitespaces +void indent(Editor *editor) { + size_t cursor_row = editor_cursor_row(editor); + int braceLevel = 0; + + // Calculate brace level up to the current line + for (size_t i = 0; i < cursor_row; ++i) { + Line line = editor->lines.items[i]; + for (size_t j = line.begin; j < line.end; ++j) { + char c = editor->data.items[j]; + if (c == '{') { + braceLevel++; + } else if (c == '}') { + braceLevel = (braceLevel > 0) ? braceLevel - 1 : 0; + } + } + } + + Line currentLineData = editor->lines.items[cursor_row]; + bool decreaseIndentation = false; + size_t firstNonWhitespace = currentLineData.begin; + bool isLineEmpty = true; + for (size_t j = currentLineData.begin; j < currentLineData.end; ++j) { + char c = editor->data.items[j]; + if (!isspace(c)) { + firstNonWhitespace = j; + isLineEmpty = false; + if (c == '}') { + decreaseIndentation = true; + } + break; + } + } + + // Adjust brace level for current line if it starts with a closing brace + if (decreaseIndentation) { + braceLevel = (braceLevel > 0) ? braceLevel - 1 : 0; + } + + // Calculate required and current indentation + size_t requiredIndentation = braceLevel * indentation; + size_t currentIndentation = 0; + for (size_t i = currentLineData.begin; i < currentLineData.end && (editor->data.items[i] == ' ' || editor->data.items[i] == '\t'); ++i) { + currentIndentation++; + } + + // Save the current cursor position + size_t originalCursorPosition = editor->cursor; + + // Adjust indentation + editor->cursor = currentLineData.begin; + while (currentIndentation < requiredIndentation) { + editor_insert_char(editor, ' '); + currentIndentation++; + } + + while (currentIndentation > requiredIndentation && currentIndentation > 0) { + editor_delete(editor); // or evil_delete_char(editor); + currentIndentation--; + } + + // Adjust cursor position based on initial condition + if (isLineEmpty || originalCursorPosition <= firstNonWhitespace) { + // If the line is empty or the cursor was on or before the first non-whitespace character + editor->cursor = currentLineData.begin + requiredIndentation; + } else { + // If the cursor was on a non-whitespace character, maintain relative position + size_t characterOffset = originalCursorPosition - firstNonWhitespace; + editor->cursor = currentLineData.begin + requiredIndentation + characterOffset; + } +} + + + +bool extractLocalIncludePath(Editor *editor, char *includePath) { + char line[512]; // Adjust size as needed + if (!extractLine(editor, editor->cursor, line, sizeof(line))) { + return false; + } + + if (strncmp(line, "#include \"", 10) == 0) { + char *start = strchr(line, '\"') + 1; + char *end = strrchr(line, '\"'); + if (start && end && start < end) { + size_t length = end - start; + strncpy(includePath, start, length); + includePath[length] = '\0'; + return true; + } + } + + return false; +} + +void getDirectoryFromFilePath(const char *filePath, char *directory) { + strcpy(directory, filePath); + char *lastSlash = strrchr(directory, '/'); + if (lastSlash != NULL) { + *lastSlash = '\0'; // Null-terminate at the last slash + } else { + // Handle the case where there is no slash in the path + strcpy(directory, "."); + } +} + +Errno openLocalIncludeFile(Editor *editor, const char *includePath) { + char fullPath[512]; // Buffer for the full path + char directory[256]; // Buffer for the directory + + // Get the directory of the current file + getDirectoryFromFilePath(editor->file_path.items, directory); + + // Construct the full path + snprintf(fullPath, sizeof(fullPath), "%s/%s", directory, includePath); + + // Load the file using the full path + Errno load_err = find_file(editor, fullPath, 10, 10); + if (load_err != 0) { + fprintf(stderr, "Error loading file %s: %s\n", fullPath, strerror(load_err)); + return load_err; + } + + printf("Opened file: %s\n", fullPath); + return 0; +} + +bool extractGlobalIncludePath(Editor *editor, char *includePath) { + char line[512]; + if (!extractLine(editor, editor->cursor, line, sizeof(line))) { + return false; + } + + if (strncmp(line, "#include <", 10) == 0) { + char *start = strchr(line, '<') + 1; + char *end = strrchr(line, '>'); + if (start && end && start < end) { + size_t length = end - start; + strncpy(includePath, start, length); + includePath[length] = '\0'; + return true; + } + } + + return false; +} + +#include "unistd.h" // for F_OK +Errno openGlobalIncludeFile(Editor *editor, const char *includePath) { + char fullPath[512]; // Buffer for the full path + + // List of standard directories (expandable) + const char *standardDirs[] = {"/usr/include", NULL}; // NULL terminated array + + for (int i = 0; standardDirs[i] != NULL; i++) { + snprintf(fullPath, sizeof(fullPath), "%s/%s", standardDirs[i], includePath); + + // Check if the file exists and is accessible + if (access(fullPath, F_OK) != -1) { + // Try to load the file using the constructed full path + Errno load_err = find_file(editor, fullPath, 0, 0); + if (load_err == 0) { + printf("Opened file: %s\n", fullPath); + return 0; // File opened successfully + } + } + } + + // Print the error message only if the file is not found in /usr/include + fprintf(stderr, "Error: File %s not found in /usr/include\n", includePath); + return -1; // File not found in /usr/include +} + +void editor_open_include(Editor *editor) { + char includePath[256]; + + if (extractLocalIncludePath(editor, includePath)) { + openLocalIncludeFile(editor, includePath); + } else if (extractGlobalIncludePath(editor, includePath)) { + openGlobalIncludeFile(editor, includePath); + } +} + + + + + + + + + + +// CLANG FORMAT TODO +#include + +int is_clang_format_installed() { + if (system("clang-format --version") != 0) { + return 0; + } + return 1; +} + +void clang_format(const char *filename, const char *arguments) { + if (!is_clang_format_installed()) { + printf("bruh clang-format is not installed.\n"); + return; + } + + char command[1024]; + snprintf(command, sizeof(command), "clang-format %s %s", arguments, filename); + + // Execute the command + int result = system(command); + if (result != 0) { + printf("Error executing clang-format.\n"); + } +} + + +// TODO select more after end brace +void select_region_from_inside_braces(Editor *editor) { + if (editor->cursor >= editor->data.count) return; + + size_t row = editor_cursor_row(editor); + size_t start = row; + size_t end = row; + + // Find the start of the function + while (start > 0) { + start--; + size_t line_end = editor->lines.items[start].end; + + // Simple heuristic: a line ending with '{' might be the start of a function + if (editor->data.items[line_end - 1] == '{') { + break; + } + } + + // Find the end of the function + int brace_count = 1; // Start after finding the opening brace + while (end < editor->lines.count - 1) { + end++; + size_t line_begin = editor->lines.items[end].begin; + size_t line_end = editor->lines.items[end].end; + + for (size_t i = line_begin; i < line_end; i++) { + if (editor->data.items[i] == '{') { + brace_count++; + } else if (editor->data.items[i] == '}') { + brace_count--; + if (brace_count == 0) { + // Found the matching closing brace + goto found_end; + } + } + } + } +found_end: + + // Update the selection + editor->selection = true; + editor->select_begin = editor->lines.items[start].begin; + editor->cursor = editor->lines.items[end].end; +} + + +// TODO should not run from anywhere just curly braces +// TODO dont move the cursor on open brace like it does for closing brace +void select_region_from_brace(Editor *editor) { + if (editor->cursor >= editor->data.count) return; + + char current_char = editor->data.items[editor->cursor]; + + if (strchr("})", current_char)) { + // Called from a closing brace + editor->select_begin = editor->cursor; + evil_jump_item(editor); + size_t row = editor_cursor_row(editor); + editor->cursor = editor->lines.items[row].begin; // Extend to the beginning of the line + } else if (strchr("({", current_char)) { + // Called from an opening brace + size_t row = editor_cursor_row(editor); + editor->select_begin = editor->lines.items[row].begin; // Start from the beginning of the line + evil_jump_item(editor); + row = editor_cursor_row(editor); + editor->cursor = editor->lines.items[row].end; // Extend to the end of the line with the closing brace + } + + // Update the selection + editor->selection = true; + if (editor->select_begin > editor->cursor) { + // Ensure select_begin is always before the cursor + size_t temp = editor->select_begin; + editor->select_begin = editor->cursor; + editor->cursor = temp; + } +} + + +// TODO select_function + + +bool toggle_bool(Editor *editor) { + char word[256]; + if (!extract_word_under_cursor(editor, word)) { + return false; + } + + const char *replacement = NULL; + int difference = 0; + if (strcmp(word, "true") == 0) { + replacement = "false"; + difference = 1; // "false" is 1 character longer than "true" + } else if (strcmp(word, "false") == 0) { + replacement = "true"; + difference = -1; // "true" is 1 character shorter than "false" + } else { + return false; + } + + // Find the start position of the word + size_t word_start = editor->cursor; + while (word_start > 0 && isalnum(editor->data.items[word_start - 1])) { + word_start--; + } + + // Shift the buffer contents if necessary + if (difference != 0) { + memmove(&editor->data.items[word_start + strlen(replacement)], + &editor->data.items[word_start + strlen(word)], + editor->data.count - word_start - strlen(word)); + editor->data.count += difference; + } + + // Replace the word directly in the buffer + memcpy(&editor->data.items[word_start], replacement, strlen(replacement)); + + editor_retokenize(editor); + return true; // Successfully toggled +} + +void editor_quit() { + quit = true; +} + +void editor_save_and_quit(Editor *e) { + editor_save(e); + quit = true; +} + + +#define MAX_MATCHES 1024 +#define MAX_WORD_LENGTH 256 + +// TODO cycle + +void evil_complete_next(Editor *e) { + static char last_word[MAX_WORD_LENGTH] = {0}; + static size_t last_match_index = 0; + char current_word[MAX_WORD_LENGTH]; + + if (!extract_word_left_of_cursor(e, current_word, sizeof(current_word))) { + printf("No word to complete.\n"); + return; + } + + if (strcmp(last_word, current_word) != 0) { + strcpy(last_word, current_word); + last_match_index = 0; + } + + char *matches[MAX_MATCHES]; + size_t matches_count = 0; + find_matches_in_editor_data(e, current_word, matches, &matches_count); + + if (matches_count == 0) { + printf("Pattern not found.\n"); + return; + } + + const char *next_match = matches[last_match_index % matches_count]; + size_t next_match_length = strlen(next_match); + size_t current_word_length = strlen(current_word); + + // Adjust the buffer size and content + if (next_match_length != current_word_length) { + memmove(&e->data.items[e->cursor + next_match_length], + &e->data.items[e->cursor + current_word_length], + e->data.count - e->cursor); + e->data.count += next_match_length - current_word_length; + } + + // Replace the current word with the match + memcpy(&e->data.items[e->cursor], next_match, next_match_length); + + // Update the cursor position to the end of the new word + e->cursor += next_match_length; + + last_match_index++; + + // Clean up + for (size_t i = 0; i < matches_count; i++) { + free(matches[i]); + } + editor_retokenize(e); +} + +void find_matches_in_editor_data(Editor *e, const char *word, char **matches, size_t *matches_count) { + size_t word_length = strlen(word); + *matches_count = 0; + char *data = e->data.items; + size_t data_length = e->data.count; + + for (size_t i = 0; i < data_length; i++) { + if (isalnum(data[i]) && (i == 0 || !isalnum(data[i - 1]))) { + // Found the start of a word + if (strncmp(&data[i], word, word_length) == 0) { + // Found a matching word, now find the end of the word + size_t word_end = i + 1; + while (word_end < data_length && isalnum(data[word_end])) { + word_end++; + } + + size_t match_length = word_end - i; + if (*matches_count < MAX_MATCHES) { + matches[*matches_count] = malloc(match_length + 1); + strncpy(matches[*matches_count], &data[i], match_length); + matches[*matches_count][match_length] = '\0'; + (*matches_count)++; + } + } + } + } +} + +Errno editor_goto_line(Editor *editor, const char *params[]) { + if (!params || !params[0]) { + // Handle error: No line number provided + return -1; + } + + size_t line_number = atoi(params[0]); + if (line_number == 0 || line_number > editor->lines.count) { + // Line number is out of range + return -1; + } + + // Adjust line_number to zero-based index + line_number -= 1; + + // Set the cursor to the beginning of the specified line + editor->cursor = editor->lines.items[line_number].begin; + + return 0; +} + + + + + + + + +// ANIMATIONS + +float easeOutCubic(float x) { + return 1 - pow(1 - x, 3); +} + + +float targetModelineHeight; +bool isModelineAnimating = false; +void update_modeline_animation() { + if (!isModelineAnimating) { + return; + } + + float animationSpeed = 1.50f; + + if (modelineHeight < targetModelineHeight) { + modelineHeight += animationSpeed; + if (modelineHeight > targetModelineHeight) { + modelineHeight = targetModelineHeight; + } + } else if (modelineHeight > targetModelineHeight) { + modelineHeight -= animationSpeed; + if (modelineHeight < targetModelineHeight) { + modelineHeight = targetModelineHeight; + } + } + + if (modelineHeight == targetModelineHeight) { + isModelineAnimating = false; + } +} + + +float targetMinibufferHeight; +bool isMinibufferAnimating = false; +float minibufferAnimationProgress = 0.0f; // Normalized progress of the animation +float minibufferAnimationDuration = 1.0f; // Duration of the animation in seconds + + +void update_minibuffer_animation(float deltaTime) { + if (!isMinibufferAnimating) { + return; + } + + minibufferAnimationProgress += deltaTime / minibufferAnimationDuration; + + if (minibufferAnimationProgress > 1.0f) { + minibufferAnimationProgress = 1.0f; + isMinibufferAnimating = false; + } + + float easedProgress = easeOutCubic(minibufferAnimationProgress); + minibufferHeight = easedProgress * (targetMinibufferHeight - minibufferHeight) + minibufferHeight; + + if (minibufferHeight == targetMinibufferHeight || minibufferAnimationProgress >= 1.0f) { + isMinibufferAnimating = false; + } +} + + +size_t calculate_max_line_length(const Editor *editor) { + size_t max_len = 0; + for (size_t i = 0; i < editor->lines.count; ++i) { + Line line = editor->lines.items[i]; + size_t line_length = line.end - line.begin; + if (line_length > max_len) { + max_len = line_length; + } + } + return max_len; +} + +float column_to_x(Free_Glyph_Atlas *atlas, int column) { + float whitespace_width = measure_whitespace_width(atlas); + return column * whitespace_width; +} + + +void editor_color_text_range(Editor *editor, size_t start, size_t end, Vec4f new_color) { + for (size_t i = 0; i < editor->tokens.count; ++i) { + Token *token = &editor->tokens.items[i]; + size_t token_start = token->position.x; // Make sure this is the correct way to calculate the start position + size_t token_end = token_start + token->text_len; + + // Check if the token is within the specified range + if (token_start < end && token_end > start) { + token->color = new_color; + } + } +} + + + + + + diff --git a/src/editor.h b/src/editor.h index 4b5ddd58..cad8b387 100644 --- a/src/editor.h +++ b/src/editor.h @@ -1,14 +1,84 @@ #ifndef EDITOR_H_ #define EDITOR_H_ +#include #include #include "common.h" -#include "free_glyph.h" +/* #include "free_glyph.h" */ #include "simple_renderer.h" #include "lexer.h" - +#include #include +#include "hashmap.h" + + +extern bool copiedLine; +extern bool matchParenthesis; + +extern bool followCursor; +extern size_t indentation; +extern float zoom_factor; +extern float min_zoom_factor; +extern float max_zoom_factor; +extern bool showLineNumbers; +extern bool showLineNumbersBackground; +extern bool isWave; +extern bool showWhitespaces; +extern bool hl_line; +extern bool relativeLineNumbers; +extern bool highlightCurrentLineNumber; +extern bool showIndentationLines; +extern bool zoomInInsertMode; +extern bool centeredText; +extern bool showMinibuffer; +extern bool showModeline; +extern float minibufferHeight; +extern float modelineHeight; +extern float modelineAccentWidth; +extern bool fzy; +extern bool M_x_active; +extern bool evil_command_active; +extern bool quit; + +extern bool BlockInsertCursor; +extern bool highlightCurrentLineNumberOnInsertMode; +extern bool instantCamera; + + +extern bool helix; +extern bool emacs; +extern bool automatic_zoom; + +extern size_t fillColumn; +extern float fillColumnThickness; +extern bool smartFillColumn; +extern bool showFillColumn; + +extern bool readonly; +extern bool electric_mode; +extern bool electric_pair_mode; +extern bool delete_selection_mode; + + +extern size_t long_file_lines; +extern bool show_line_numbers_opening_long_files; +extern bool decenter_text_opening_long_files; +extern bool hide_line_numbers_opening_small_files; +extern bool center_text_opening_small_files; + +extern bool diredfl_mode; + + +// Simple Emacs Style Key Chords +// TODO this is the simplest dumbest implementation +extern bool ctrl_x_pressed; + +void reset_keychords(); + + +extern float fringeWidth; +extern bool showFringe; typedef struct { size_t begin; size_t end; @@ -26,6 +96,24 @@ typedef struct { size_t capacity; } Tokens; + + +//TODO replace, replace char +typedef enum { + EMACS, + HELIX, + NORMAL, + INSERT, + VISUAL, + VISUAL_LINE, + MINIBUFFER, +} EvilMode; + +extern EvilMode current_mode; + + +#define MAX_BUFFER_HISTORY 100 + typedef struct { Free_Glyph_Atlas *atlas; @@ -37,21 +125,51 @@ typedef struct { bool searching; String_Builder search; + bool minibuffer_active; + String_Builder minibuffer_text; + + struct hashmap *commands; + bool selection; size_t select_begin; + size_t select_end; // Needed for VISUAL_LINE selection size_t cursor; + bool has_mark; // Indicates if there's a marked search result. + size_t mark_start; + size_t mark_end; + Uint32 last_stroke; String_Builder clipboard; + + bool has_anchor; + size_t anchor_pos_from_start; + size_t anchor_pos_from_end; + size_t anchor_pos; + + + char *buffer_history[MAX_BUFFER_HISTORY]; + int buffer_history_count; + int buffer_index; + + // lsp + int to_clangd_fd; + int from_clangd_fd; + } Editor; +extern Editor editor; + Errno editor_save_as(Editor *editor, const char *file_path); Errno editor_save(const Editor *editor); -Errno editor_load_from_file(Editor *editor, const char *file_path); +/* Errno editor_load_from_file(Editor *editor, const char *file_path); */ +Errno find_file(Editor *e, const char *file_path, size_t line, size_t column); +size_t get_position_from_line_column(Editor *e, size_t line, size_t column); void editor_backspace(Editor *editor); void editor_delete(Editor *editor); +void editor_delete_selection(Editor *editor); size_t editor_cursor_row(const Editor *e); void editor_move_line_up(Editor *e); @@ -72,12 +190,83 @@ void editor_move_paragraph_down(Editor *e); void editor_insert_char(Editor *e, char x); void editor_insert_buf(Editor *e, char *buf, size_t buf_len); void editor_retokenize(Editor *e); -void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor); void editor_update_selection(Editor *e, bool shift); void editor_clipboard_copy(Editor *e); void editor_clipboard_paste(Editor *e); + + + void editor_start_search(Editor *e); void editor_stop_search(Editor *e); bool editor_search_matches_at(Editor *e, size_t pos); + +// ADDED +void editor_stop_search_and_mark(Editor *e); +void editor_clear_mark(Editor *editor); +void move_camera(Simple_Renderer *sr, const char* direction, float amount); +void editor_insert_buf_at(Editor *e, char *buf, size_t buf_len, size_t pos); +void editor_insert_char_at(Editor *e, char c, size_t pos); +ssize_t find_matching_parenthesis(Editor *editor, size_t cursor_pos); +void editor_enter(Editor *e); +void editor_set_anchor(Editor *editor); +void editor_goto_anchor_and_clear(Editor *editor); +void editor_update_anchor(Editor *editor); +void editor_drag_line_down(Editor *editor); +void editor_drag_line_up(Editor *editor); +void add_one_indentation_here(Editor *editor); +void add_one_indentation(Editor *editor); +void remove_one_indentation(Editor *editor); +void indent(Editor *editor); +void select_region_from_brace(Editor *editor); +void select_region_from_inside_braces(Editor *editor); + +bool extractLocalIncludePath(Editor *editor, char *includePath); +void getDirectoryFromFilePath(const char *filePath, char *directory); +Errno openLocalIncludeFile(Editor *editor, const char *includePath); +bool extractGlobalIncludePath(Editor *editor, char *includePath); +Errno openGlobalIncludeFile(Editor *editor, const char *includePath); +void editor_open_include(Editor *editor); +bool toggle_bool(Editor *editor); + +void editor_quit(); +void editor_save_and_quit(Editor *e); + +void find_matches_in_editor_data(Editor *e, const char *word, char **matches, size_t *matches_count); +void evil_complete_next(Editor *e); +Errno editor_goto_line(Editor *editor, const char *params[]); + +/* void get_cursor_position(const Editor *e, size_t *line, int *character); */ +void get_cursor_position(const Editor *e); + + +void set_current_mode(); +size_t calculate_max_line_length(const Editor *editor); + + +Vec4f get_color_for_token_kind(Token_Kind kind); +void update_cursor_color(Editor * editor); +/* void update_cursor_color(Editor *editor, Free_Glyph_Atlas *atlas); */ + + +// animation +extern float targetModelineHeight; +extern bool isModelineAnimating; +extern void update_modeline_animation(); + +extern float targetMinibufferHeight; +extern bool isMinibufferAnimating; + +extern float minibufferAnimationProgress; +extern float minibufferAnimationDuration; +void update_minibuffer_animation(float deltaTime); + +float easeOutCubic(float x); + + + +void editor_color_text_range(Editor *editor, size_t start, size_t end, Vec4f new_color); +void adjust_line_number_width(Editor *editor, float *lineNumberWidth); + #endif // EDITOR_H_ + diff --git a/src/emacs.c b/src/emacs.c new file mode 100644 index 00000000..632b2f53 --- /dev/null +++ b/src/emacs.c @@ -0,0 +1,263 @@ +#include "emacs.h" +#include "editor.h" +#include "utilities.h" + +// Features i borrowed from emacs + +void emacs_mwim_beginning(Editor *e) { + if (e->cursor >= e->data.count) return; + size_t row = editor_cursor_row(e); + size_t line_begin = e->lines.items[row].begin; + size_t first_non_whitespace = find_first_non_whitespace(e->data.items, line_begin, e->lines.items[row].end); + + if (e->cursor != first_non_whitespace) { + e->cursor = first_non_whitespace; + } else { + e->cursor = line_begin; + } +} + +void emacs_mwim_end(Editor *e) { + if (e->cursor >= e->data.count) return; + + size_t row = editor_cursor_row(e); + size_t line_end = e->lines.items[row].end; + size_t last_non_whitespace = find_last_non_whitespace(e->data.items, e->lines.items[row].begin, line_end); + + if (e->cursor != last_non_whitespace) { + e->cursor = last_non_whitespace; + } else { + e->cursor = line_end; + } +} + +void emacs_delete_char(Editor *e) { + memmove( + &e->data.items[e->cursor], + &e->data.items[e->cursor + 1], + (e->data.count - e->cursor - 1) * sizeof(e->data.items[0]) + ); + e->data.count -= 1; + editor_retokenize(e); +} + + +// TODO it should not move the cursor at the start of the line +void emacs_open_line(Editor *e) { + editor_insert_char(e, '\n'); + editor_move_line_up(e); + indent(e); + e->last_stroke = SDL_GetTicks(); +} + +void emacs_ungry_delete_backwards(Editor *e) { + if (e->searching || e->cursor == 0) return; + + size_t original_cursor = e->cursor; + size_t start_pos = e->cursor; + size_t last_newline_pos = 0; + bool found_non_newline_whitespace = false; + int newline_count = 0; + bool should_delete_single_character = true; // Assume we'll delete a single character by default + + // Move left to the start of contiguous whitespace or to the start of the file + while (start_pos > 0 && isspace(e->data.items[start_pos - 1])) { + should_delete_single_character = false; // Found whitespace, so we won't be deleting just a single character + if (e->data.items[start_pos - 1] == '\n') { + newline_count++; + last_newline_pos = start_pos - 1; + } else { + found_non_newline_whitespace = true; + } + start_pos--; + } + + // If spanning multiple lines, delete but preserve one newline character. + if ((newline_count > 1 || (newline_count == 1 && found_non_newline_whitespace)) && !should_delete_single_character) { + start_pos = last_newline_pos + 1; + } else if (should_delete_single_character) { + // If no preceding whitespace was found, adjust start_pos to delete just the last character + start_pos = original_cursor - 1; + } + + size_t length_to_delete = original_cursor - start_pos; + + if (length_to_delete > 0) { + // Perform the deletion + memmove(&e->data.items[start_pos], &e->data.items[original_cursor], e->data.count - original_cursor); + e->data.count -= length_to_delete; + e->cursor = start_pos; + + indent(e); + editor_retokenize(e); + } +} + + +// TODO it delete the line if it is on whitespaces even if there is text +void emacs_kill_line(Editor *e) { + if (e->searching || e->cursor >= e->data.count) return; + + size_t row = editor_cursor_row(e); + size_t line_begin = e->lines.items[row].begin; + size_t line_end = e->lines.items[row].end; + + // Check if the line is empty or if the cursor is at the end of the line + if (line_begin == line_end || e->cursor == line_end) { + // If the line is empty or the cursor is at the end of the line + // Remove the newline character if it's not the first line + if (row < e->lines.count - 1) { + memmove(&e->data.items[line_begin], &e->data.items[line_end + 1], e->data.count - line_end - 1); + e->data.count -= (line_end - line_begin + 1); + } else if (row > 0 && e->data.items[line_begin - 1] == '\n') { + // If it's the last line, remove the preceding newline character + e->data.count -= 1; + memmove(&e->data.items[line_begin - 1], &e->data.items[line_end], e->data.count - line_end); + } + } else if (isspace(e->data.items[e->cursor])) { + // If the cursor is on a whitespace character within the line, delete the entire line + memmove(&e->data.items[line_begin], &e->data.items[line_end + 1], e->data.count - line_end - 1); + e->data.count -= (line_end - line_begin + 1); + } else { + // If the line is not empty and the cursor is not on a whitespace character, kill the text from the cursor to the end of the line + size_t length = line_end - e->cursor; + + // Copy the text to be killed to the clipboard + e->clipboard.count = 0; + sb_append_buf(&e->clipboard, &e->data.items[e->cursor], length); + sb_append_null(&e->clipboard); + if (SDL_SetClipboardText(e->clipboard.items) < 0) { + fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); + } + + // Delete the range from the editor + memmove(&e->data.items[e->cursor], &e->data.items[line_end], e->data.count - line_end); + e->data.count -= length; + } + + editor_retokenize(e); +} + +// TODO make this work also on search and minibuffer +void emacs_backward_kill_word(Editor *e) { + editor_stop_search(e); + + size_t start_pos = e->cursor; + + // Move cursor left to the start of the previous word or to the first capital letter + if (e->cursor > 0 && isalnum(e->data.items[e->cursor - 1])) { + // Move left until a non-alphanumeric character or the start of the camelCase word + while (e->cursor > 0 && isalnum(e->data.items[e->cursor - 1])) { + e->cursor -= 1; + if (isupper(e->data.items[e->cursor]) && e->cursor != start_pos - 1) { + break; // Break if it's an uppercase letter and not the first letter of the word + } + } + } else { + // If the character left of the cursor is not alphanumeric, move until an alphanumeric is found + while (e->cursor > 0 && !isalnum(e->data.items[e->cursor - 1])) { + e->cursor -= 1; + } + } + + size_t end_pos = e->cursor; + + if (start_pos > end_pos) { + // Copy the deleted text to clipboard + size_t length = start_pos - end_pos; + e->clipboard.count = 0; + sb_append_buf(&e->clipboard, &e->data.items[end_pos], length); + sb_append_null(&e->clipboard); + if (SDL_SetClipboardText(e->clipboard.items) < 0) { + fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); + } + + // Perform the deletion + memmove(&e->data.items[end_pos], &e->data.items[start_pos], e->data.count - start_pos); + e->data.count -= length; + } + + editor_retokenize(e); +} + + +int killed_word_times = 0; + +void emacs_kill_word(Editor *e) { + editor_stop_search(e); + + size_t start_pos = e->cursor; + size_t end_pos = e->cursor; + + while (end_pos < e->data.count && !isalnum(e->data.items[end_pos])) { + end_pos++; + } + + while (end_pos < e->data.count && isalnum(e->data.items[end_pos])) { + end_pos++; + if (isupper(e->data.items[end_pos]) && islower(e->data.items[end_pos - 1])) { + break; + } + } + + if (start_pos < end_pos) { + size_t length = end_pos - start_pos; + + if (killed_word_times == 0) { + e->clipboard.count = 0; + } else { + // Remove the existing null terminator before appending new content + if (e->clipboard.count > 0 && e->clipboard.items[e->clipboard.count - 1] == '\0') { + e->clipboard.count--; // Decrement to remove the null terminator + } + } + + sb_append_buf(&e->clipboard, &e->data.items[start_pos], length); + sb_append_null(&e->clipboard); // Reapply the null terminator after appending + + // Update the SDL clipboard content + if (SDL_SetClipboardText(e->clipboard.items) < 0) { + fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); + } + + // Perform the deletion + memmove(&e->data.items[start_pos], &e->data.items[end_pos], e->data.count - end_pos); + e->data.count -= length; + e->cursor = start_pos; + + killed_word_times++; + } else { + killed_word_times = 0; + } + + editor_retokenize(e); +} + + + +void emacs_back_to_indentation(Editor *e) { + if (e->cursor >= e->data.count) return; + size_t row = editor_cursor_row(e); + size_t line_begin = e->lines.items[row].begin; + size_t line_end = e->lines.items[row].end; + size_t first_non_whitespace = find_first_non_whitespace(e->data.items, line_begin, line_end); + e->cursor = first_non_whitespace; +} + +void emacs_mark_paragraph(Editor *e) { + if (!e->selection) { + // Find the first empty line above + size_t row = editor_cursor_row(e); + while (row > 0 && !editor_is_line_empty(e, row - 1)) { + row--; + } + + // Set the selection start to the beginning of the line after the empty line + e->select_begin = e->lines.items[row].begin; + e->cursor = e->select_begin; + e->selection = true; + } + + // Extend the selection downwards to the end of the paragraph + editor_move_paragraph_down(e); +} diff --git a/src/emacs.h b/src/emacs.h new file mode 100644 index 00000000..4507f829 --- /dev/null +++ b/src/emacs.h @@ -0,0 +1,20 @@ +#ifndef EMACS_H +#define EMACS_H + +#include "editor.h" + +extern int killed_word_times; +void emacs_kill_word(Editor *e); + +void emacs_kill_line(Editor *e); +void emacs_backward_kill_word(Editor *e); +void emacs_back_to_indentation(Editor *e); +void emacs_mark_paragraph(Editor *e); +void emacs_ungry_delete_backwards(Editor *e); +void emacs_open_line(Editor *e); +void emacs_mwim_beginning(Editor *e); +void emacs_mwim_end(Editor *e); +void emacs_delete_char(Editor *e); + + +#endif // EMACS_H diff --git a/src/evil.c b/src/evil.c new file mode 100644 index 00000000..1a378fd7 --- /dev/null +++ b/src/evil.c @@ -0,0 +1,541 @@ +#include +#include "evil.h" +#include "editor.h" +#include "utilities.h" + +void evil_open_below(Editor *editor) { + size_t row = editor_cursor_row(editor); + size_t line_begin = editor->lines.items[row].begin; + size_t line_end = editor->lines.items[row].end; + + editor_move_to_line_end(editor); + editor_insert_char(editor, '\n'); + + // Copy indentation + for (size_t i = line_begin; i < line_end; ++i) { + char c = editor->data.items[i]; + if (c == ' ' || c == '\t') { + editor_insert_char(editor, c); + } else { + break; + } + } +} + +void evil_open_above(Editor *editor) { + size_t row = editor_cursor_row(editor); + + // Determine the current line's start and end for capturing indentation + size_t line_begin = editor->lines.items[row].begin; + size_t line_end = editor->lines.items[row].end; + + // Capture the indentation of the current line in a local array + char indentation[128]; // Assuming 128 characters is enough for indentation + size_t indentIndex = 0; + for (size_t i = line_begin; i < line_end && indentIndex < sizeof(indentation) - 1; ++i) { + char c = editor->data.items[i]; + if (c == ' ' || c == '\t') { + indentation[indentIndex++] = c; + } else { + break; + } + } + indentation[indentIndex] = '\0'; // Null-terminate the string + + // Insert a newline at the beginning of the current line + editor_move_to_line_begin(editor); + editor_insert_char(editor, '\n'); + editor_move_line_up(editor); + + // Apply the captured indentation + for (size_t i = 0; i < indentIndex; ++i) { + editor_insert_char(editor, indentation[i]); + } +} + +void evil_jump_item(Editor *editor) { + if (editor->cursor >= editor->data.count) return; + + char current_char = editor->data.items[editor->cursor]; + ssize_t matching_pos = -1; + + // Check if the current cursor position is a parenthesis + if (strchr("()[]{}", current_char)) { + matching_pos = find_matching_parenthesis(editor, editor->cursor); + } else { + // If not, search for a parenthesis on the current line + size_t row = editor_cursor_row(editor); + size_t line_begin = editor->lines.items[row].begin; + size_t line_end = editor->lines.items[row].end; + + for (size_t pos = line_begin; pos < line_end; ++pos) { + current_char = editor->data.items[pos]; + if (strchr("()[]{}", current_char)) { + matching_pos = find_matching_parenthesis(editor, pos); + if (matching_pos != -1) { + break; + } + } + } + } + + // Move the cursor to the matching parenthesis + if (matching_pos != -1) { + editor->cursor = matching_pos; + } +} + +// TODO when there is a {} dont add the space +// TODO when animatins are off +// move the cursor to the added whitespace +void evil_join(Editor *e) { + size_t row = editor_cursor_row(e); + if (row >= e->lines.count - 1) return; + + // Get the current line and the next line + size_t current_line_end = e->lines.items[row].end; + size_t next_line_begin = e->lines.items[row + 1].begin; + size_t next_line_end = e->lines.items[row + 1].end; + + + // Check if the current line is empty or only has whitespaces + bool only_whitespaces = true; + for (size_t i = e->lines.items[row].begin; i < current_line_end; ++i) { + if (!isspace(e->data.items[i])) { + only_whitespaces = false; + break; + } + } + + if (only_whitespaces) { + // Current line is empty or has only whitespaces, delete the line + size_t length_to_move = e->data.count - current_line_end; + memmove(&e->data.items[e->lines.items[row].begin], + &e->data.items[next_line_begin], + length_to_move); + e->data.count -= (next_line_begin - e->lines.items[row].begin); + editor_retokenize(e); + return; + } + + // Check if the current line ends in a newline character + if (e->data.items[current_line_end] == '\n') { + // Skip leading spaces on the next line + while (next_line_begin < next_line_end && + isspace(e->data.items[next_line_begin])) { + next_line_begin++; + } + + // Calculate the length to move in memmove + size_t length_to_move = e->data.count - next_line_begin; + + // Move the data from the next line start to the current line end + memmove(&e->data.items[current_line_end + 1], + &e->data.items[next_line_begin], + length_to_move); + + // Adjust the total count of characters in the buffer + e->data.count -= (next_line_begin - current_line_end - 1); + + // Insert a single space to separate the lines + e->data.items[current_line_end] = ' '; + } + + editor_retokenize(e); +} + +void evil_yank_line(Editor* editor) { + size_t start = editor->cursor; + while (start > 0 && editor->data.items[start - 1] != '\n') { + start--; + } + + size_t end = start; + while (end < editor->data.count && editor->data.items[end] != '\n') { + end++; + } + + if (start < end) { + editor->clipboard.count = 0; + sb_append_buf(&editor->clipboard, &editor->data.items[start], end - start); + sb_append_null(&editor->clipboard); + + if (SDL_SetClipboardText(editor->clipboard.items) < 0) { + fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); + } + } + copiedLine = true; +} + + +// TODO handle !copiedline not in the keybind and behave like vim +void evil_paste_after(Editor* editor) { + if (!copiedLine) { + return; // Do nothing if no line has been copied + } + + char *text = SDL_GetClipboardText(); + if (!text) { + fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); + return; + } + + size_t text_len = strlen(text); + if (text_len > 0) { + // Find the end of the current line + size_t end = editor->cursor; + while (end < editor->data.count && editor->data.items[end] != '\n') { + end++; + } + + // If not at the end of the file, move to the start of the next line + if (end < editor->data.count) { + end++; + } + + // Insert the text from the clipboard + editor_insert_buf_at(editor, text, text_len, end); + + // If the pasted text does not end with a newline, add one + if (text[text_len - 1] != '\n') { + editor_insert_buf_at(editor, "\n", 1, end + text_len); + } + + // Move cursor to the first non-space character of the pasted line + editor->cursor = end; + while (editor->cursor < editor->data.count && editor->data.items[editor->cursor] == ' ') { + editor->cursor++; + } + } + + SDL_free(text); +} + +// TODO handle !copiedline not in the keybind and behave like vim +void evil_paste_before(Editor* editor) { + if (!copiedLine) { + return; // Do nothing if no line has been copied + } + + char *text = SDL_GetClipboardText(); + if (!text) { + fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); + return; + } + + size_t text_len = strlen(text); + if (text_len > 0) { + // Find the start of the current line + size_t start = editor->cursor; + while (start > 0 && editor->data.items[start - 1] != '\n') { + start--; + } + + // Insert the text from the clipboard at the start of the line + editor_insert_buf_at(editor, text, text_len, start); + + // Optionally, insert a newline after pasting if the text doesn't end with one + if (text[text_len - 1] != '\n') { + editor_insert_buf_at(editor, "\n", 1, start + text_len); + } + + // Move cursor to the first non-space character of the pasted line + editor->cursor = start; + while (editor->cursor < editor->data.count && editor->data.items[editor->cursor] == ' ') { + editor->cursor++; + } + } + + SDL_free(text); +} + +void evil_visual_char(Editor *e) { + e->selection = true; + e->select_begin = e->cursor; +} + +// TODO doesn't work +void evil_visual_line(Editor *e) { + e->selection = true; + + // Identify the current line the cursor is on + size_t cursor_row = editor_cursor_row(e); + Line current_line = e->lines.items[cursor_row]; + + // Set the beginning and end of the selection to span the entire line + e->select_begin = current_line.begin; + e->cursor = current_line.end; +} + +void evil_delete_char(Editor *e) { + if (e->searching) return; + + if (e->cursor >= e->data.count) return; + + // Copy the character to clipboard. + e->clipboard.count = 0; + sb_append_buf(&e->clipboard, &e->data.items[e->cursor], 1); + sb_append_null(&e->clipboard); + if (SDL_SetClipboardText(e->clipboard.items) < 0) { + fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); + } + + // Delete the character from the editor. + memmove( + &e->data.items[e->cursor], + &e->data.items[e->cursor + 1], + (e->data.count - e->cursor - 1) * sizeof(e->data.items[0]) + ); + e->data.count -= 1; + editor_retokenize(e); +} + +void evil_delete_backward_char(Editor *e) { + // If in search mode or at the start of the data, return. + if (e->searching || e->cursor == 0) return; + + // Adjust the cursor to point to the previous character. + e->cursor -= 1; + + // 1. Copy the character to clipboard. + e->clipboard.count = 0; + sb_append_buf(&e->clipboard, &e->data.items[e->cursor], 1); + sb_append_null(&e->clipboard); + if (SDL_SetClipboardText(e->clipboard.items) < 0) { + fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); + } + + // 2. Delete the character from the editor. + memmove( + &e->data.items[e->cursor], + &e->data.items[e->cursor + 1], + (e->data.count - e->cursor - 1) * sizeof(e->data.items[0]) + ); + e->data.count -= 1; + editor_retokenize(e); +} + + + +void evil_search_next(Editor *e) { + size_t startPos = e->cursor + 1; + for (size_t pos = startPos; pos < e->data.count; ++pos) { + if (editor_search_matches_at(e, pos)) { + e->cursor = pos; + editor_stop_search_and_mark(e); + return; // Exit after finding a match + } + } + + // If not found in the remainder of the text, wrap around to the beginning + for (size_t pos = 0; pos < startPos; ++pos) { + if (editor_search_matches_at(e, pos)) { + e->cursor = pos; + editor_stop_search_and_mark(e); + return; // Exit after finding a match + } + } +} + +void evil_search_previous(Editor *e) { + if (e->cursor == 0) { + // If we are at the beginning of the file, wrap around immediately + for (size_t pos = e->data.count - 1; pos != SIZE_MAX; --pos) { // Note the loop condition + if (editor_search_matches_at(e, pos)) { + e->cursor = pos; + editor_stop_search_and_mark(e); + return; // Exit after finding a match + } + } + } else { + for (size_t pos = e->cursor - 1; pos != SIZE_MAX; --pos) { // Note the loop condition + if (editor_search_matches_at(e, pos)) { + e->cursor = pos; + editor_stop_search_and_mark(e); + return; // Exit after finding a match + } + } + + // If not found in the preceding text, wrap around to the end + for (size_t pos = e->data.count - 1; pos > e->cursor; --pos) { + if (editor_search_matches_at(e, pos)) { + e->cursor = pos; + editor_stop_search_and_mark(e); + return; // Exit after finding a match + } + } + } +} + +void evil_search_word_forward(Editor *e) { + char word[256]; + + e->searching = true; + e->search.count = 0; + + // Extract the word under the cursor. + if (extract_word_under_cursor(e, word)) { + sb_append_buf(&e->search, word, strlen(word)); + editor_stop_search_and_mark(e); + evil_search_next(e); + } else { + // If no word is extracted, exit search mode + e->searching = false; + } +} + +void evil_change_line(Editor *e) { + if (e->searching || e->cursor >= e->data.count) return; + + size_t row = editor_cursor_row(e); + size_t line_begin = e->lines.items[row].begin; + size_t line_end = e->lines.items[row].end; + + // Calculate the position of the first non-whitespace character + size_t first_non_whitespace = line_begin; + while (first_non_whitespace < line_end && + (e->data.items[first_non_whitespace] == ' ' || e->data.items[first_non_whitespace] == '\t')) { + first_non_whitespace++; + } + + // Adjust line_end to stop at the semicolon, if it's the last character + if (line_end > first_non_whitespace && e->data.items[line_end - 1] == ';') { + line_end--; + } + + // Determine the start position for deletion + size_t delete_from = e->cursor < first_non_whitespace ? first_non_whitespace : e->cursor; + + // Calculate the length from the deletion start position to the end of the line + size_t length = line_end - delete_from; + + // Copy the text to be deleted to the clipboard + e->clipboard.count = 0; + sb_append_buf(&e->clipboard, &e->data.items[delete_from], length); + sb_append_null(&e->clipboard); + if (SDL_SetClipboardText(e->clipboard.items) < 0) { + fprintf(stderr, "ERROR: SDL ERROR: %s\n", SDL_GetError()); + } + + // Delete the text from the deletion start position to the end of the line + memmove(&e->data.items[delete_from], &e->data.items[line_end], e->data.count - line_end); + e->data.count -= length; + + // Set the cursor position to the first non-whitespace character if the cursor was on the whitespace + e->cursor = e->cursor < first_non_whitespace ? first_non_whitespace : e->cursor; + + current_mode = INSERT; + + editor_retokenize(e); +} + +// TODO can't find Capital chars and chars that require holding shift +void evil_find_char(Editor *e, char target) { + if (e->searching || e->cursor >= e->data.count) return; + + size_t row = editor_cursor_row(e); + size_t line_end = e->lines.items[row].end; + + // Start searching from the character right after the cursor position + size_t search_position = e->cursor + 1; + + while (search_position < line_end) { + if (e->data.items[search_position] == target) { + // If the target character is found, move the cursor to its position + e->cursor = search_position; + break; + } + search_position++; + } +} + +bool handle_evil_find_char(Editor *editor, SDL_Event *event) { + static bool waitingForFindChar = false; // Static variable inside the function + + if (waitingForFindChar) { + // Call evil_find_char with the pressed key + evil_find_char(editor, event->key.keysym.sym); + waitingForFindChar = false; + editor->last_stroke = SDL_GetTicks(); + return true; // The key event has been handled + } else if (event->key.keysym.sym == SDLK_f && !(SDL_GetModState() & KMOD_CTRL)) { + waitingForFindChar = true; + editor->last_stroke = SDL_GetTicks(); + return false; // The key event has not been fully handled yet + } + return false; // The key event has not been fully handled +} + + + +void evil_substitute(Editor *e) { + if (e->searching) return; // Check if editor is in search mode + + if (e->selection) { + // If there is an active selection, delete the selected text + editor_delete_selection(e); + } else if (e->cursor < e->data.count) { + // If no selection and cursor is within bounds, delete the character at cursor + memmove(&e->data.items[e->cursor], + &e->data.items[e->cursor + 1], + (e->data.count - e->cursor - 1) * sizeof(e->data.items[0])); + e->data.count -= 1; + } + + // Switch to insert mode + current_mode = INSERT; + + // Re-tokenize if needed + editor_retokenize(e); +} + + + +void evil_change_whole_line(Editor *e) { + if (e->searching || e->cursor >= e->data.count) return; + + size_t row = editor_cursor_row(e); + size_t line_begin = e->lines.items[row].begin; + size_t line_end = e->lines.items[row].end; + + // Find the first non-whitespace character + size_t first_non_whitespace = line_begin; + while (first_non_whitespace < line_end && + (e->data.items[first_non_whitespace] == ' ' || e->data.items[first_non_whitespace] == '\t')) { + first_non_whitespace++; + } + + // If entire line is whitespace, first_non_whitespace will be line_end + if (first_non_whitespace < line_end) { + // Delete from the first non-whitespace character to the end of the line + size_t length = line_end - first_non_whitespace; + memmove(&e->data.items[first_non_whitespace], + &e->data.items[line_end], + e->data.count - line_end); + e->data.count -= length; + + // Set cursor to the first non-whitespace character + e->cursor = first_non_whitespace; + } else { + // If the line is all whitespace, just place the cursor at the end + e->cursor = line_end; + } + + // Switch to insert mode + current_mode = INSERT; + + // Re-tokenize if needed + editor_retokenize(e); +} + + + + +void evil_insert_line(Editor *e) { + size_t row = editor_cursor_row(e); + size_t line_begin = e->lines.items[row].begin; + size_t line_end = e->lines.items[row].end; + size_t first_non_whitespace = find_first_non_whitespace(e->data.items, line_begin, line_end); + e->cursor = first_non_whitespace; + current_mode = INSERT; +} diff --git a/src/evil.h b/src/evil.h new file mode 100644 index 00000000..d0fddd48 --- /dev/null +++ b/src/evil.h @@ -0,0 +1,28 @@ +#ifndef EVIL_H +#define EVIL_H + +#include "editor.h" + + +void evil_open_below(Editor *editor); +void evil_open_above(Editor *editor); +void evil_jump_item(Editor *editor); +void evil_join(Editor *e); +void evil_yank_line(Editor *editor); +void evil_paste_after(Editor *editor); +void evil_paste_before(Editor *editor); +void evil_visual_char(Editor *e); +void evil_visual_line(Editor *e); +void evil_delete_char(Editor *e); +void evil_delete_backward_char(Editor *e); +void evil_search_next(Editor *e); +void evil_search_previous(Editor *e); +void evil_search_word_forward(Editor *e); +void evil_change_line(Editor *e); +void evil_find_char(Editor *e, char target); +bool handle_evil_find_char(Editor *editor, SDL_Event *event); +void evil_substitute(Editor *e); +void evil_change_whole_line(Editor *e); +void evil_insert_line(Editor *e); + +#endif // EVIL_H diff --git a/src/file_browser.c b/src/file_browser.c index d4afb45e..aac268b8 100644 --- a/src/file_browser.c +++ b/src/file_browser.c @@ -1,6 +1,17 @@ #include #include "file_browser.h" +#include "la.h" +#include "simple_renderer.h" #include "sv.h" +#include "editor.h" // only for zoom_factor maybe im bad at programming +#include "theme.h" +#include "utilities.h" +#include +#include + + +bool file_browser = false; + static int file_cmp(const void *ap, const void *bp) { @@ -9,23 +20,28 @@ static int file_cmp(const void *ap, const void *bp) return strcmp(a, b); } + Errno fb_open_dir(File_Browser *fb, const char *dir_path) { + char resolved_path[PATH_MAX]; + expand_path(dir_path, resolved_path, sizeof(resolved_path)); + fb->files.count = 0; fb->cursor = 0; - Errno err = read_entire_dir(dir_path, &fb->files); + Errno err = read_entire_dir(resolved_path, &fb->files); if (err != 0) { return err; } qsort(fb->files.items, fb->files.count, sizeof(*fb->files.items), file_cmp); fb->dir_path.count = 0; - sb_append_cstr(&fb->dir_path, dir_path); + sb_append_cstr(&fb->dir_path, resolved_path); sb_append_null(&fb->dir_path); - + printf("Opened directory: %s\n", fb->dir_path.items); return 0; } + #define PATH_SEP "/" #define PATH_EMPTY "" #define PATH_DOT "." @@ -103,102 +119,185 @@ Errno fb_change_dir(File_Browser *fb) if (fb->cursor >= fb->files.count) return 0; - const char *dir_name = fb->files.items[fb->cursor]; - - fb->dir_path.count -= 1; + /* const char *dir_name = fb->files.items[fb->cursor]; */ // ORIGINAL + const char *dir_name = fb->files.items[fb->cursor].name; + char new_path[PATH_MAX]; + snprintf(new_path, sizeof(new_path), "%s/%s", fb->dir_path.items, dir_name); - // TODO: fb->dir_path grows indefinitely if we hit the root - sb_append_cstr(&fb->dir_path, "/"); - sb_append_cstr(&fb->dir_path, dir_name); + char resolved_path[PATH_MAX]; + expand_path(new_path, resolved_path, sizeof(resolved_path)); - String_Builder result = {0}; - normpath(sb_to_sv(fb->dir_path), &result); - da_move(&fb->dir_path, result); + fb->dir_path.count = 0; + sb_append_cstr(&fb->dir_path, resolved_path); sb_append_null(&fb->dir_path); - printf("Changed dir to %s\n", fb->dir_path.items); - fb->files.count = 0; fb->cursor = 0; - Errno err = read_entire_dir(fb->dir_path.items, &fb->files); - + Errno err = read_entire_dir(resolved_path, &fb->files); if (err != 0) { return err; } qsort(fb->files.items, fb->files.count, sizeof(*fb->files.items), file_cmp); - + printf("Changed directory to: %s\n", fb->dir_path.items); return 0; } -void fb_render(const File_Browser *fb, SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr) -{ + +// TODO move some stuff out it doesnt need to run in a while +// TODO dired functionality +// TODO it crash if the directory contain a symlink + +void fb_render(const File_Browser *fb, SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr) { + // Calculate cursor position based on the selected item Vec2f cursor_pos = vec2f(0, -(float)fb->cursor * FREE_GLYPH_FONT_SIZE); + // Get the window dimensions int w, h; SDL_GetWindowSize(window, &w, &h); float max_line_len = 0.0f; + size_t max_size_length = 0; + + // Measure whitespace width for consistent spacing + int space = measure_whitespace_width(atlas); + + // Pre-calculate maximum file size length + char size_buffer[32]; + for (size_t i = 0; i < fb->files.count; ++i) { + snprintf(size_buffer, sizeof(size_buffer), "%ld", fb->files.items[i].size); + size_t current_length = strlen(size_buffer); + if (current_length > max_size_length) { + max_size_length = current_length; + } + } + // Set the renderer resolution and current time sr->resolution = vec2f(w, h); - sr->time = (float) SDL_GetTicks() / 1000.0f; + sr->time = (float)SDL_GetTicks() / 1000.0f; + + // Highlight the selected file + if (isWave) { + simple_renderer_set_shader(sr, VERTEX_SHADER_WAVE, SHADER_FOR_COLOR); + } else { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + } - simple_renderer_set_shader(sr, SHADER_FOR_COLOR); if (fb->cursor < fb->files.count) { - const Vec2f begin = vec2f(0, -((float)fb->cursor + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE); + FileInfo highlighted_file = fb->files.items[fb->cursor]; + char highlighted_line[1024]; + snprintf(highlighted_line, sizeof(highlighted_line), "%s %s %s %*ld %s %s", + highlighted_file.permissions, highlighted_file.owner, highlighted_file.group, + (int)max_size_length, highlighted_file.size, highlighted_file.mod_time, highlighted_file.name); + + Vec2f begin = vec2f(0, -((float)fb->cursor) * FREE_GLYPH_FONT_SIZE); Vec2f end = begin; - free_glyph_atlas_measure_line_sized( - atlas, fb->files.items[fb->cursor], strlen(fb->files.items[fb->cursor]), - &end); - simple_renderer_solid_rect(sr, begin, vec2f(end.x - begin.x, FREE_GLYPH_FONT_SIZE), vec4f(.25, .25, .25, 1)); + free_glyph_atlas_measure_line_sized(atlas, highlighted_line, strlen(highlighted_line), &end); + + // Draw background for the highlighted file + simple_renderer_solid_rect(sr, begin, vec2f(end.x - begin.x, FREE_GLYPH_FONT_SIZE), vec4f(0.25f, 0.25f, 0.25f, 1)); } + + // Flush color rendering simple_renderer_flush(sr); - simple_renderer_set_shader(sr, SHADER_FOR_EPICNESS); + // Render file attributes and names + if (isWave) { + simple_renderer_set_shader(sr, VERTEX_SHADER_WAVE, SHADER_FOR_TEXT); + } else { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_TEXT); + } for (size_t row = 0; row < fb->files.count; ++row) { - const Vec2f begin = vec2f(0, -(float)row * FREE_GLYPH_FONT_SIZE); + FileInfo file = fb->files.items[row]; + Vec2f begin = vec2f(0, -(float)row * FREE_GLYPH_FONT_SIZE); Vec2f end = begin; - free_glyph_atlas_render_line_sized( - atlas, sr, fb->files.items[row], strlen(fb->files.items[row]), - &end, - vec4fs(0)); - // TODO: the max_line_len should be calculated based on what's visible on the screen right now + + // Render permissions individually + Vec4f permission_color; + for (size_t i = 0; i < strlen(file.permissions); ++i) { + char perm_char[2] = {file.permissions[i], '\0'}; + if (diredfl_mode) { + if (perm_char[0] == '-') { + permission_color = CURRENT_THEME.fb_no_priv; + } else if (perm_char[0] == 'd') { + permission_color = CURRENT_THEME.fb_dir_priv; + } else if (perm_char[0] == 'r') { + permission_color = CURRENT_THEME.fb_read_priv; + } else if (perm_char[0] == 'w') { + permission_color = CURRENT_THEME.fb_write_priv; + } else if (perm_char[0] == 'x') { + permission_color = CURRENT_THEME.fb_exec_priv; + } else { + permission_color = CURRENT_THEME.text; + } + } else { + permission_color = CURRENT_THEME.text; + } + free_glyph_atlas_render_line_sized(atlas, sr, perm_char, 1, &end, permission_color); + } + end.x += space; + + // Render owner and group + char owner_group[1024]; + snprintf(owner_group, sizeof(owner_group), "%s %s", file.owner, file.group); + free_glyph_atlas_render_line_sized(atlas, sr, owner_group, strlen(owner_group), &end, CURRENT_THEME.text); + + // Render file size, conditionally color if `diredfl_mode` + snprintf(size_buffer, sizeof(size_buffer), "%*ld", (int)max_size_length, file.size); + end.x += space; + Vec4f size_color = diredfl_mode ? CURRENT_THEME.fb_size : CURRENT_THEME.text; + free_glyph_atlas_render_line_sized(atlas, sr, size_buffer, strlen(size_buffer), &end, size_color); + + // Render the file's modification time with conditional coloring + snprintf(owner_group, sizeof(owner_group), "%s", file.mod_time); + end.x += space; + Vec4f time_color = diredfl_mode ? CURRENT_THEME.fb_date_time : CURRENT_THEME.text; + free_glyph_atlas_render_line_sized(atlas, sr, owner_group, strlen(owner_group), &end, time_color); + + // Render the file name (special color for directories) + Vec4f name_color = (file.permissions[0] == 'd') ? CURRENT_THEME.fb_dir_name : CURRENT_THEME.text; + end.x += space; + free_glyph_atlas_render_line_sized(atlas, sr, file.name, strlen(file.name), &end, name_color); + + // Track the maximum line length float line_len = fabsf(end.x - begin.x); if (line_len > max_line_len) { max_line_len = line_len; } } + // Flush text rendering simple_renderer_flush(sr); - // Update camera - { + /* Adjust the camera to follow the cursor or ensure the selected file is visible */ + if (followCursor) { if (max_line_len > 1000.0f) { max_line_len = 1000.0f; } - float target_scale = w/3/(max_line_len*0.75); // TODO: division by 0 - - Vec2f target = cursor_pos; - float offset = 0.0f; - + float target_scale = w / zoom_factor / (max_line_len * 0.75f); if (target_scale > 3.0f) { target_scale = 3.0f; - } else { - offset = cursor_pos.x - w/3/sr->camera_scale; - if (offset < 0.0f) offset = 0.0f; - target = vec2f(w/3/sr->camera_scale + offset, cursor_pos.y); } - sr->camera_vel = vec2f_mul( - vec2f_sub(target, sr->camera_pos), - vec2fs(2.0f)); + float offset = cursor_pos.x - w / sr->camera_scale; + if (offset < 0.0f) { + offset = 0.0f; + } + + Vec2f target = vec2f(w / 2.1 / sr->camera_scale + offset, cursor_pos.y); + + // Adjust camera velocity and scaling + sr->camera_vel = vec2f_mul(vec2f_sub(target, sr->camera_pos), vec2fs(2.0f)); sr->camera_scale_vel = (target_scale - sr->camera_scale) * 2.0f; sr->camera_pos = vec2f_add(sr->camera_pos, vec2f_mul(sr->camera_vel, vec2fs(DELTA_TIME))); - sr->camera_scale = sr->camera_scale + sr->camera_scale_vel * DELTA_TIME; + sr->camera_scale += sr->camera_scale_vel * DELTA_TIME; } + } + + const char *fb_file_path(File_Browser *fb) { assert(fb->dir_path.count > 0 && "You need to call fb_open_dir() before fb_file_path()"); @@ -209,8 +308,71 @@ const char *fb_file_path(File_Browser *fb) fb->file_path.count = 0; sb_append_buf(&fb->file_path, fb->dir_path.items, fb->dir_path.count - 1); sb_append_buf(&fb->file_path, "/", 1); - sb_append_cstr(&fb->file_path, fb->files.items[fb->cursor]); + /* sb_append_cstr(&fb->file_path, fb->files.items[fb->cursor]); */ + sb_append_cstr(&fb->file_path, fb->files.items[fb->cursor].name); sb_append_null(&fb->file_path); + /* extract_file_extension(fb->files.items[fb->cursor], &fb->file_extension); */ + extract_file_extension(fb->files.items[fb->cursor].name, &fb->file_extension); + printf("File path: %s\n", fb->file_path.items); // Print file path + printf("File extension: %s\n", fb->file_extension.items); // Print file extension + return fb->file_path.items; } + +// ADDED +void extract_file_extension(const char *filename, String_Builder *ext) { + const char *dot = strrchr(filename, '.'); + if (!dot || dot == filename) { + // No extension found or the dot is the first character (hidden files in Unix) + // Clear the String_Builder manually + ext->count = 0; + sb_append_null(ext); + return; + } + // Clear the String_Builder manually before appending new content + ext->count = 0; + sb_append_cstr(ext, dot + 1); // Skip the dot + sb_append_null(ext); // Ensure null termination +} + +void expand_path(const char *original_path, char *expanded_path, size_t expanded_path_size) { + if (original_path[0] == '~') { + const char *home = getenv("HOME"); + if (home) { + snprintf(expanded_path, expanded_path_size, "%s%s", home, original_path + 1); + } else { + strncpy(expanded_path, original_path, expanded_path_size); + } + } else { + char resolved_path[PATH_MAX]; + if (realpath(original_path, resolved_path) != NULL) { + strncpy(expanded_path, resolved_path, expanded_path_size); + } else { + strncpy(expanded_path, original_path, expanded_path_size); + } + } + expanded_path[expanded_path_size - 1] = '\0'; +} + +/* static int is_directory(const char* base_path, const char* file) { */ +/* char full_path[PATH_MAX]; */ +/* snprintf(full_path, PATH_MAX, "%s/%s", base_path, file); */ + +/* struct stat statbuf; */ +/* if (stat(full_path, &statbuf) != 0) { */ +/* return 0; // In case of error, assume it's not a directory */ +/* } */ + +/* return S_ISDIR(statbuf.st_mode); */ +/* } */ + +void toggle_file_browser(){ + file_browser = !file_browser; +} + + + + + + diff --git a/src/file_browser.h b/src/file_browser.h index f83497c8..10d38037 100644 --- a/src/file_browser.h +++ b/src/file_browser.h @@ -3,19 +3,33 @@ #include "common.h" #include "free_glyph.h" - #include +#include +#include typedef struct { Files files; size_t cursor; String_Builder dir_path; String_Builder file_path; + String_Builder file_extension; + // for file creation mode + bool is_in_file_creation_mode; + char tmp_filename[PATH_MAX]; + size_t tmp_filename_len; } File_Browser; +const char *fb_file_path(File_Browser *fb); Errno fb_open_dir(File_Browser *fb, const char *dir_path); Errno fb_change_dir(File_Browser *fb); void fb_render(const File_Browser *fb, SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr); -const char *fb_file_path(File_Browser *fb); +Errno fb_go_to_parent(File_Browser *fb); + +// ADDED +extern bool file_browser; + +void extract_file_extension(const char *filename, String_Builder *ext); +void expand_path(const char *original_path, char *expanded_path, size_t expanded_path_size); +void toggle_file_browser(); #endif // FILE_BROWSER_H_ diff --git a/src/free_glyph.c b/src/free_glyph.c index fe2b55b3..82714a4c 100644 --- a/src/free_glyph.c +++ b/src/free_glyph.c @@ -1,4 +1,6 @@ #include +#include "editor.h" +#include "theme.h" #include #include "./free_glyph.h" @@ -95,8 +97,7 @@ float free_glyph_atlas_cursor_pos(const Free_Glyph_Atlas *atlas, const char *tex return pos.x; } -void free_glyph_atlas_measure_line_sized(Free_Glyph_Atlas *atlas, const char *text, size_t text_size, Vec2f *pos) -{ +void free_glyph_atlas_measure_line_sized(Free_Glyph_Atlas *atlas, const char *text, size_t text_size, Vec2f *pos) { for (size_t i = 0; i < text_size; ++i) { size_t glyph_index = text[i]; // TODO: support for glyphs outside of ASCII range @@ -110,8 +111,103 @@ void free_glyph_atlas_measure_line_sized(Free_Glyph_Atlas *atlas, const char *te } } -void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos, Vec4f color) -{ + +// ALMOST TODO multiple lines +void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, + Simple_Renderer *sr, const char *text, + size_t text_size, Vec2f *pos, + Vec4f color) { + // Determine the line and character position of the cursor using the global + // editor + size_t cursor_line = 0; + size_t cursor_character = 0; + + // Find the line that contains the cursor + for (size_t row = 0; row < editor.lines.count; ++row) { + Line line = editor.lines.items[row]; + if (line.begin <= editor.cursor && editor.cursor <= line.end) { + cursor_line = row; + cursor_character = editor.cursor - line.begin; + break; + } + } + + size_t current_pos = 0; + + for (size_t i = 0; i < text_size; ++i) { + size_t glyph_index = text[i]; + if (glyph_index >= GLYPH_METRICS_CAPACITY) { + glyph_index = '?'; // Fallback for unsupported glyphs + } + Glyph_Metric metric = atlas->metrics[glyph_index]; + float x2 = pos->x + metric.bl; + float y2 = -pos->y - metric.bt; + float w = metric.bw; + float h = metric.bh; + + // Use the background color if this character is at the cursor position and + // on the correct line + Vec4f use_color = color; + if (cursor_line * FREE_GLYPH_FONT_SIZE <= pos->y && + pos->y < (cursor_line + 1) * FREE_GLYPH_FONT_SIZE && + current_pos == cursor_character) { + use_color = CURRENT_THEME.background; + } + + simple_renderer_image_rect(sr, vec2f(x2, -y2), vec2f(w, -h), + vec2f(metric.tx, 0.0f), + vec2f(metric.bw / (float)atlas->atlas_width, + metric.bh / (float)atlas->atlas_height), + use_color); + + // Advance the cursor after rendering the glyph + pos->x += metric.ax; + pos->y += metric.ay; + + // Handle newlines + if (text[i] == '\n') { + pos->y -= FREE_GLYPH_FONT_SIZE; + pos->x = 0; + } + + current_pos++; + } +} + + +/* ORIGINAL */ +/* void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos, Vec4f color) */ +/* { */ +/* for (size_t i = 0; i < text_size; ++i) { */ +/* size_t glyph_index = text[i]; */ +/* // TODO: support for glyphs outside of ASCII range */ +/* if (glyph_index >= GLYPH_METRICS_CAPACITY) { */ +/* glyph_index = '?'; */ +/* } */ +/* Glyph_Metric metric = atlas->metrics[glyph_index]; */ +/* float x2 = pos->x + metric.bl; */ +/* float y2 = -pos->y - metric.bt; */ +/* float w = metric.bw; */ +/* float h = metric.bh; */ + +/* pos->x += metric.ax; */ +/* pos->y += metric.ay; */ + +/* simple_renderer_image_rect( */ +/* sr, */ +/* vec2f(x2, -y2), */ +/* vec2f(w, -h), */ +/* vec2f(metric.tx, 0.0f), */ +/* vec2f(metric.bw / (float) atlas->atlas_width, metric.bh / (float) atlas->atlas_height), */ +/* color); */ +/* } */ +/* } */ + + +// ADDED + +float free_glyph_atlas_measure_line_width(Free_Glyph_Atlas *atlas, const char *text, size_t text_size) { + Vec2f pos = {0.0f, 0.0f}; for (size_t i = 0; i < text_size; ++i) { size_t glyph_index = text[i]; // TODO: support for glyphs outside of ASCII range @@ -119,20 +215,7 @@ void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer glyph_index = '?'; } Glyph_Metric metric = atlas->metrics[glyph_index]; - float x2 = pos->x + metric.bl; - float y2 = -pos->y - metric.bt; - float w = metric.bw; - float h = metric.bh; - - pos->x += metric.ax; - pos->y += metric.ay; - - simple_renderer_image_rect( - sr, - vec2f(x2, -y2), - vec2f(w, -h), - vec2f(metric.tx, 0.0f), - vec2f(metric.bw / (float) atlas->atlas_width, metric.bh / (float) atlas->atlas_height), - color); + pos.x += metric.ax; } + return pos.x; } diff --git a/src/free_glyph.h b/src/free_glyph.h index cae2246d..7562dc99 100644 --- a/src/free_glyph.h +++ b/src/free_glyph.h @@ -1,6 +1,7 @@ #ifndef FREE_GLYPH_H_ #define FREE_GLYPH_H_ + #include #include "./la.h" @@ -10,7 +11,7 @@ #define GL_GLEXT_PROTOTYPES #include -#include +#include "ft2build.h" #include FT_FREETYPE_H #include "simple_renderer.h" @@ -32,7 +33,8 @@ typedef struct { float tx; // x offset of glyph in texture coordinates } Glyph_Metric; -#define GLYPH_METRICS_CAPACITY 128 +/* #define GLYPH_METRICS_CAPACITY 128 */ +#define GLYPH_METRICS_CAPACITY 6400 typedef struct { FT_UInt atlas_width; @@ -44,6 +46,18 @@ typedef struct { void free_glyph_atlas_init(Free_Glyph_Atlas *atlas, FT_Face face); float free_glyph_atlas_cursor_pos(const Free_Glyph_Atlas *atlas, const char *text, size_t text_size, Vec2f pos, size_t col); void free_glyph_atlas_measure_line_sized(Free_Glyph_Atlas *atlas, const char *text, size_t text_size, Vec2f *pos); -void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos, Vec4f color); + +/* void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos, Vec4f color); */ +void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, + Simple_Renderer *sr, const char *text, + size_t text_size, Vec2f *pos, + Vec4f color); + + + +// ADDED +float free_glyph_atlas_measure_line_width(Free_Glyph_Atlas *atlas, + const char *text, + size_t text_size); #endif // FREE_GLYPH_H_ diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 00000000..769452e1 --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,1149 @@ +#include +#include +#include +#include +#include +#include "hashmap.h" + +#define GROW_AT 0.60 /* 60% */ +#define SHRINK_AT 0.10 /* 10% */ + +#ifndef HASHMAP_LOAD_FACTOR +#define HASHMAP_LOAD_FACTOR GROW_AT +#endif + +static void *(*__malloc)(size_t) = NULL; +static void *(*__realloc)(void *, size_t) = NULL; +static void (*__free)(void *) = NULL; + +// hashmap_set_allocator allows for configuring a custom allocator for +// all hashmap library operations. This function, if needed, should be called +// only once at startup and a prior to calling hashmap_new(). +void hashmap_set_allocator(void *(*malloc)(size_t), void (*free)(void*)) { + __malloc = malloc; + __free = free; +} + +struct bucket { + uint64_t hash:48; + uint64_t dib:16; +}; + +// hashmap is an open addressed hash map using robinhood hashing. +struct hashmap { + void *(*malloc)(size_t); + void *(*realloc)(void *, size_t); + void (*free)(void *); + size_t elsize; + size_t cap; + uint64_t seed0; + uint64_t seed1; + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1); + int (*compare)(const void *a, const void *b, void *udata); + void (*elfree)(void *item); + void *udata; + size_t bucketsz; + size_t nbuckets; + size_t count; + size_t mask; + size_t growat; + size_t shrinkat; + uint8_t loadfactor; + uint8_t growpower; + bool oom; + void *buckets; + void *spare; + void *edata; +}; + +void hashmap_set_grow_by_power(struct hashmap *map, size_t power) { + map->growpower = power < 1 ? 1 : power > 16 ? 16 : power; +} + +static double clamp_load_factor(double factor, double default_factor) { + // Check for NaN and clamp between 50% and 90% + return factor != factor ? default_factor : + factor < 0.50 ? 0.50 : + factor > 0.95 ? 0.95 : + factor; +} + +void hashmap_set_load_factor(struct hashmap *map, double factor) { + factor = clamp_load_factor(factor, map->loadfactor / 100.0); + map->loadfactor = factor * 100; + map->growat = map->nbuckets * (map->loadfactor / 100.0); +} + +static struct bucket *bucket_at0(void *buckets, size_t bucketsz, size_t i) { + return (struct bucket*)(((char*)buckets)+(bucketsz*i)); +} + +static struct bucket *bucket_at(struct hashmap *map, size_t index) { + return bucket_at0(map->buckets, map->bucketsz, index); +} + +static void *bucket_item(struct bucket *entry) { + return ((char*)entry)+sizeof(struct bucket); +} + +static uint64_t clip_hash(uint64_t hash) { + return hash & 0xFFFFFFFFFFFF; +} + +static uint64_t get_hash(struct hashmap *map, const void *key) { + return clip_hash(map->hash(key, map->seed0, map->seed1)); +} + + +// hashmap_new_with_allocator returns a new hash map using a custom allocator. +// See hashmap_new for more information information +struct hashmap *hashmap_new_with_allocator(void *(*_malloc)(size_t), + void *(*_realloc)(void*, size_t), void (*_free)(void*), + size_t elsize, size_t cap, uint64_t seed0, uint64_t seed1, + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1), + int (*compare)(const void *a, const void *b, void *udata), + void (*elfree)(void *item), + void *udata) +{ + _malloc = _malloc ? _malloc : __malloc ? __malloc : malloc; + _realloc = _realloc ? _realloc : __realloc ? __realloc : realloc; + _free = _free ? _free : __free ? __free : free; + size_t ncap = 16; + if (cap < ncap) { + cap = ncap; + } else { + while (ncap < cap) { + ncap *= 2; + } + cap = ncap; + } + size_t bucketsz = sizeof(struct bucket) + elsize; + while (bucketsz & (sizeof(uintptr_t)-1)) { + bucketsz++; + } + // hashmap + spare + edata + size_t size = sizeof(struct hashmap)+bucketsz*2; + struct hashmap *map = _malloc(size); + if (!map) { + return NULL; + } + memset(map, 0, sizeof(struct hashmap)); + map->elsize = elsize; + map->bucketsz = bucketsz; + map->seed0 = seed0; + map->seed1 = seed1; + map->hash = hash; + map->compare = compare; + map->elfree = elfree; + map->udata = udata; + map->spare = ((char*)map)+sizeof(struct hashmap); + map->edata = (char*)map->spare+bucketsz; + map->cap = cap; + map->nbuckets = cap; + map->mask = map->nbuckets-1; + map->buckets = _malloc(map->bucketsz*map->nbuckets); + if (!map->buckets) { + _free(map); + return NULL; + } + memset(map->buckets, 0, map->bucketsz*map->nbuckets); + map->growpower = 1; + map->loadfactor = clamp_load_factor(HASHMAP_LOAD_FACTOR, GROW_AT) * 100; + map->growat = map->nbuckets * (map->loadfactor / 100.0); + map->shrinkat = map->nbuckets * SHRINK_AT; + map->malloc = _malloc; + map->realloc = _realloc; + map->free = _free; + return map; +} + +// hashmap_new returns a new hash map. +// Param `elsize` is the size of each element in the tree. Every element that +// is inserted, deleted, or retrieved will be this size. +// Param `cap` is the default lower capacity of the hashmap. Setting this to +// zero will default to 16. +// Params `seed0` and `seed1` are optional seed values that are passed to the +// following `hash` function. These can be any value you wish but it's often +// best to use randomly generated values. +// Param `hash` is a function that generates a hash value for an item. It's +// important that you provide a good hash function, otherwise it will perform +// poorly or be vulnerable to Denial-of-service attacks. This implementation +// comes with two helper functions `hashmap_sip()` and `hashmap_murmur()`. +// Param `compare` is a function that compares items in the tree. See the +// qsort stdlib function for an example of how this function works. +// The hashmap must be freed with hashmap_free(). +// Param `elfree` is a function that frees a specific item. This should be NULL +// unless you're storing some kind of reference data in the hash. +struct hashmap *hashmap_new(size_t elsize, size_t cap, uint64_t seed0, + uint64_t seed1, + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1), + int (*compare)(const void *a, const void *b, void *udata), + void (*elfree)(void *item), + void *udata) +{ + return hashmap_new_with_allocator(NULL, NULL, NULL, elsize, cap, seed0, + seed1, hash, compare, elfree, udata); +} + +static void free_elements(struct hashmap *map) { + if (map->elfree) { + for (size_t i = 0; i < map->nbuckets; i++) { + struct bucket *bucket = bucket_at(map, i); + if (bucket->dib) map->elfree(bucket_item(bucket)); + } + } +} + +// hashmap_clear quickly clears the map. +// Every item is called with the element-freeing function given in hashmap_new, +// if present, to free any data referenced in the elements of the hashmap. +// When the update_cap is provided, the map's capacity will be updated to match +// the currently number of allocated buckets. This is an optimization to ensure +// that this operation does not perform any allocations. +void hashmap_clear(struct hashmap *map, bool update_cap) { + map->count = 0; + free_elements(map); + if (update_cap) { + map->cap = map->nbuckets; + } else if (map->nbuckets != map->cap) { + void *new_buckets = map->malloc(map->bucketsz*map->cap); + if (new_buckets) { + map->free(map->buckets); + map->buckets = new_buckets; + } + map->nbuckets = map->cap; + } + memset(map->buckets, 0, map->bucketsz*map->nbuckets); + map->mask = map->nbuckets-1; + map->growat = map->nbuckets * (map->loadfactor / 100.0) ; + map->shrinkat = map->nbuckets * SHRINK_AT; +} + +static bool resize0(struct hashmap *map, size_t new_cap) { + struct hashmap *map2 = hashmap_new_with_allocator(map->malloc, map->realloc, + map->free, map->elsize, new_cap, map->seed0, map->seed1, map->hash, + map->compare, map->elfree, map->udata); + if (!map2) return false; + for (size_t i = 0; i < map->nbuckets; i++) { + struct bucket *entry = bucket_at(map, i); + if (!entry->dib) { + continue; + } + entry->dib = 1; + size_t j = entry->hash & map2->mask; + while(1) { + struct bucket *bucket = bucket_at(map2, j); + if (bucket->dib == 0) { + memcpy(bucket, entry, map->bucketsz); + break; + } + if (bucket->dib < entry->dib) { + memcpy(map2->spare, bucket, map->bucketsz); + memcpy(bucket, entry, map->bucketsz); + memcpy(entry, map2->spare, map->bucketsz); + } + j = (j + 1) & map2->mask; + entry->dib += 1; + } + } + map->free(map->buckets); + map->buckets = map2->buckets; + map->nbuckets = map2->nbuckets; + map->mask = map2->mask; + map->growat = map2->growat; + map->shrinkat = map2->shrinkat; + map->free(map2); + return true; +} + +static bool resize(struct hashmap *map, size_t new_cap) { + return resize0(map, new_cap); +} + +// hashmap_set_with_hash works like hashmap_set but you provide your +// own hash. The 'hash' callback provided to the hashmap_new function +// will not be called +const void *hashmap_set_with_hash(struct hashmap *map, const void *item, + uint64_t hash) +{ + hash = clip_hash(hash); + map->oom = false; + if (map->count >= map->growat) { + if (!resize(map, map->nbuckets*(1<growpower))) { + map->oom = true; + return NULL; + } + } + + struct bucket *entry = map->edata; + entry->hash = hash; + entry->dib = 1; + void *eitem = bucket_item(entry); + memcpy(eitem, item, map->elsize); + + void *bitem; + size_t i = entry->hash & map->mask; + while(1) { + struct bucket *bucket = bucket_at(map, i); + if (bucket->dib == 0) { + memcpy(bucket, entry, map->bucketsz); + map->count++; + return NULL; + } + bitem = bucket_item(bucket); + if (entry->hash == bucket->hash && (!map->compare || + map->compare(eitem, bitem, map->udata) == 0)) + { + memcpy(map->spare, bitem, map->elsize); + memcpy(bitem, eitem, map->elsize); + return map->spare; + } + if (bucket->dib < entry->dib) { + memcpy(map->spare, bucket, map->bucketsz); + memcpy(bucket, entry, map->bucketsz); + memcpy(entry, map->spare, map->bucketsz); + eitem = bucket_item(entry); + } + i = (i + 1) & map->mask; + entry->dib += 1; + } +} + +// hashmap_set inserts or replaces an item in the hash map. If an item is +// replaced then it is returned otherwise NULL is returned. This operation +// may allocate memory. If the system is unable to allocate additional +// memory then NULL is returned and hashmap_oom() returns true. +const void *hashmap_set(struct hashmap *map, const void *item) { + return hashmap_set_with_hash(map, item, get_hash(map, item)); +} + +// hashmap_get_with_hash works like hashmap_get but you provide your +// own hash. The 'hash' callback provided to the hashmap_new function +// will not be called +const void *hashmap_get_with_hash(struct hashmap *map, const void *key, + uint64_t hash) +{ + hash = clip_hash(hash); + size_t i = hash & map->mask; + while(1) { + struct bucket *bucket = bucket_at(map, i); + if (!bucket->dib) return NULL; + if (bucket->hash == hash) { + void *bitem = bucket_item(bucket); + if (!map->compare || map->compare(key, bitem, map->udata) == 0) { + return bitem; + } + } + i = (i + 1) & map->mask; + } +} + +// hashmap_get returns the item based on the provided key. If the item is not +// found then NULL is returned. +const void *hashmap_get(struct hashmap *map, const void *key) { + return hashmap_get_with_hash(map, key, get_hash(map, key)); +} + +// hashmap_probe returns the item in the bucket at position or NULL if an item +// is not set for that bucket. The position is 'moduloed' by the number of +// buckets in the hashmap. +const void *hashmap_probe(struct hashmap *map, uint64_t position) { + size_t i = position & map->mask; + struct bucket *bucket = bucket_at(map, i); + if (!bucket->dib) { + return NULL; + } + return bucket_item(bucket); +} + +// hashmap_delete_with_hash works like hashmap_delete but you provide your +// own hash. The 'hash' callback provided to the hashmap_new function +// will not be called +const void *hashmap_delete_with_hash(struct hashmap *map, const void *key, + uint64_t hash) +{ + hash = clip_hash(hash); + map->oom = false; + size_t i = hash & map->mask; + while(1) { + struct bucket *bucket = bucket_at(map, i); + if (!bucket->dib) { + return NULL; + } + void *bitem = bucket_item(bucket); + if (bucket->hash == hash && (!map->compare || + map->compare(key, bitem, map->udata) == 0)) + { + memcpy(map->spare, bitem, map->elsize); + bucket->dib = 0; + while(1) { + struct bucket *prev = bucket; + i = (i + 1) & map->mask; + bucket = bucket_at(map, i); + if (bucket->dib <= 1) { + prev->dib = 0; + break; + } + memcpy(prev, bucket, map->bucketsz); + prev->dib--; + } + map->count--; + if (map->nbuckets > map->cap && map->count <= map->shrinkat) { + // Ignore the return value. It's ok for the resize operation to + // fail to allocate enough memory because a shrink operation + // does not change the integrity of the data. + resize(map, map->nbuckets/2); + } + return map->spare; + } + i = (i + 1) & map->mask; + } +} + +// hashmap_delete removes an item from the hash map and returns it. If the +// item is not found then NULL is returned. +const void *hashmap_delete(struct hashmap *map, const void *key) { + return hashmap_delete_with_hash(map, key, get_hash(map, key)); +} + +// hashmap_count returns the number of items in the hash map. +size_t hashmap_count(struct hashmap *map) { + return map->count; +} + +// hashmap_free frees the hash map +// Every item is called with the element-freeing function given in hashmap_new, +// if present, to free any data referenced in the elements of the hashmap. +void hashmap_free(struct hashmap *map) { + if (!map) return; + free_elements(map); + map->free(map->buckets); + map->free(map); +} + +// hashmap_oom returns true if the last hashmap_set() call failed due to the +// system being out of memory. +bool hashmap_oom(struct hashmap *map) { + return map->oom; +} + +// hashmap_scan iterates over all items in the hash map +// Param `iter` can return false to stop iteration early. +// Returns false if the iteration has been stopped early. +bool hashmap_scan(struct hashmap *map, + bool (*iter)(const void *item, void *udata), void *udata) +{ + for (size_t i = 0; i < map->nbuckets; i++) { + struct bucket *bucket = bucket_at(map, i); + if (bucket->dib && !iter(bucket_item(bucket), udata)) { + return false; + } + } + return true; +} + +// hashmap_iter iterates one key at a time yielding a reference to an +// entry at each iteration. Useful to write simple loops and avoid writing +// dedicated callbacks and udata structures, as in hashmap_scan. +// +// map is a hash map handle. i is a pointer to a size_t cursor that +// should be initialized to 0 at the beginning of the loop. item is a void +// pointer pointer that is populated with the retrieved item. Note that this +// is NOT a copy of the item stored in the hash map and can be directly +// modified. +// +// Note that if hashmap_delete() is called on the hashmap being iterated, +// the buckets are rearranged and the iterator must be reset to 0, otherwise +// unexpected results may be returned after deletion. +// +// This function has not been tested for thread safety. +// +// The function returns true if an item was retrieved; false if the end of the +// iteration has been reached. +bool hashmap_iter(struct hashmap *map, size_t *i, void **item) { + struct bucket *bucket; + do { + if (*i >= map->nbuckets) return false; + bucket = bucket_at(map, *i); + (*i)++; + } while (!bucket->dib); + *item = bucket_item(bucket); + return true; +} + + +//----------------------------------------------------------------------------- +// SipHash reference C implementation +// +// Copyright (c) 2012-2016 Jean-Philippe Aumasson +// +// Copyright (c) 2012-2014 Daniel J. Bernstein +// +// To the extent possible under law, the author(s) have dedicated all copyright +// and related and neighboring rights to this software to the public domain +// worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this software. If not, see +// . +// +// default: SipHash-2-4 +//----------------------------------------------------------------------------- +static uint64_t SIP64(const uint8_t *in, const size_t inlen, uint64_t seed0, + uint64_t seed1) +{ +#define U8TO64_LE(p) \ + { (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \ + ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \ + ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \ + ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) } +#define U64TO8_LE(p, v) \ + { U32TO8_LE((p), (uint32_t)((v))); \ + U32TO8_LE((p) + 4, (uint32_t)((v) >> 32)); } +#define U32TO8_LE(p, v) \ + { (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); } +#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) +#define SIPROUND \ + { v0 += v1; v1 = ROTL(v1, 13); \ + v1 ^= v0; v0 = ROTL(v0, 32); \ + v2 += v3; v3 = ROTL(v3, 16); \ + v3 ^= v2; \ + v0 += v3; v3 = ROTL(v3, 21); \ + v3 ^= v0; \ + v2 += v1; v1 = ROTL(v1, 17); \ + v1 ^= v2; v2 = ROTL(v2, 32); } + uint64_t k0 = U8TO64_LE((uint8_t*)&seed0); + uint64_t k1 = U8TO64_LE((uint8_t*)&seed1); + uint64_t v3 = UINT64_C(0x7465646279746573) ^ k1; + uint64_t v2 = UINT64_C(0x6c7967656e657261) ^ k0; + uint64_t v1 = UINT64_C(0x646f72616e646f6d) ^ k1; + uint64_t v0 = UINT64_C(0x736f6d6570736575) ^ k0; + const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); + for (; in != end; in += 8) { + uint64_t m = U8TO64_LE(in); + v3 ^= m; + SIPROUND; SIPROUND; + v0 ^= m; + } + const int left = inlen & 7; + uint64_t b = ((uint64_t)inlen) << 56; + switch (left) { + case 7: b |= ((uint64_t)in[6]) << 48; /* fall through */ + case 6: b |= ((uint64_t)in[5]) << 40; /* fall through */ + case 5: b |= ((uint64_t)in[4]) << 32; /* fall through */ + case 4: b |= ((uint64_t)in[3]) << 24; /* fall through */ + case 3: b |= ((uint64_t)in[2]) << 16; /* fall through */ + case 2: b |= ((uint64_t)in[1]) << 8; /* fall through */ + case 1: b |= ((uint64_t)in[0]); break; + case 0: break; + } + v3 ^= b; + SIPROUND; SIPROUND; + v0 ^= b; + v2 ^= 0xff; + SIPROUND; SIPROUND; SIPROUND; SIPROUND; + b = v0 ^ v1 ^ v2 ^ v3; + uint64_t out = 0; + U64TO8_LE((uint8_t*)&out, b); + return out; +} + +//----------------------------------------------------------------------------- +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. +// +// Murmur3_86_128 +//----------------------------------------------------------------------------- +static uint64_t MM86128(const void *key, const int len, uint32_t seed) { +#define ROTL32(x, r) ((x << r) | (x >> (32 - r))) +#define FMIX32(h) h^=h>>16; h*=0x85ebca6b; h^=h>>13; h*=0xc2b2ae35; h^=h>>16; + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 16; + uint32_t h1 = seed; + uint32_t h2 = seed; + uint32_t h3 = seed; + uint32_t h4 = seed; + uint32_t c1 = 0x239b961b; + uint32_t c2 = 0xab0e9789; + uint32_t c3 = 0x38b34ae5; + uint32_t c4 = 0xa1e38b93; + const uint32_t * blocks = (const uint32_t *)(data + nblocks*16); + for (int i = -nblocks; i; i++) { + uint32_t k1 = blocks[i*4+0]; + uint32_t k2 = blocks[i*4+1]; + uint32_t k3 = blocks[i*4+2]; + uint32_t k4 = blocks[i*4+3]; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; + } + const uint8_t * tail = (const uint8_t*)(data + nblocks*16); + uint32_t k1 = 0; + uint32_t k2 = 0; + uint32_t k3 = 0; + uint32_t k4 = 0; + switch(len & 15) { + case 15: k4 ^= tail[14] << 16; /* fall through */ + case 14: k4 ^= tail[13] << 8; /* fall through */ + case 13: k4 ^= tail[12] << 0; + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + /* fall through */ + case 12: k3 ^= tail[11] << 24; /* fall through */ + case 11: k3 ^= tail[10] << 16; /* fall through */ + case 10: k3 ^= tail[ 9] << 8; /* fall through */ + case 9: k3 ^= tail[ 8] << 0; + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + /* fall through */ + case 8: k2 ^= tail[ 7] << 24; /* fall through */ + case 7: k2 ^= tail[ 6] << 16; /* fall through */ + case 6: k2 ^= tail[ 5] << 8; /* fall through */ + case 5: k2 ^= tail[ 4] << 0; + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + /* fall through */ + case 4: k1 ^= tail[ 3] << 24; /* fall through */ + case 3: k1 ^= tail[ 2] << 16; /* fall through */ + case 2: k1 ^= tail[ 1] << 8; /* fall through */ + case 1: k1 ^= tail[ 0] << 0; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + /* fall through */ + }; + h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + FMIX32(h1); FMIX32(h2); FMIX32(h3); FMIX32(h4); + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + return (((uint64_t)h2)<<32)|h1; +} + +//----------------------------------------------------------------------------- +// xxHash Library +// Copyright (c) 2012-2021 Yann Collet +// All rights reserved. +// +// BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php) +// +// xxHash3 +//----------------------------------------------------------------------------- +#define XXH_PRIME_1 11400714785074694791ULL +#define XXH_PRIME_2 14029467366897019727ULL +#define XXH_PRIME_3 1609587929392839161ULL +#define XXH_PRIME_4 9650029242287828579ULL +#define XXH_PRIME_5 2870177450012600261ULL + +static uint64_t XXH_read64(const void* memptr) { + uint64_t val; + memcpy(&val, memptr, sizeof(val)); + return val; +} + +static uint32_t XXH_read32(const void* memptr) { + uint32_t val; + memcpy(&val, memptr, sizeof(val)); + return val; +} + +static uint64_t XXH_rotl64(uint64_t x, int r) { + return (x << r) | (x >> (64 - r)); +} + +static uint64_t xxh3(const void* data, size_t len, uint64_t seed) { + const uint8_t* p = (const uint8_t*)data; + const uint8_t* const end = p + len; + uint64_t h64; + + if (len >= 32) { + const uint8_t* const limit = end - 32; + uint64_t v1 = seed + XXH_PRIME_1 + XXH_PRIME_2; + uint64_t v2 = seed + XXH_PRIME_2; + uint64_t v3 = seed + 0; + uint64_t v4 = seed - XXH_PRIME_1; + + do { + v1 += XXH_read64(p) * XXH_PRIME_2; + v1 = XXH_rotl64(v1, 31); + v1 *= XXH_PRIME_1; + + v2 += XXH_read64(p + 8) * XXH_PRIME_2; + v2 = XXH_rotl64(v2, 31); + v2 *= XXH_PRIME_1; + + v3 += XXH_read64(p + 16) * XXH_PRIME_2; + v3 = XXH_rotl64(v3, 31); + v3 *= XXH_PRIME_1; + + v4 += XXH_read64(p + 24) * XXH_PRIME_2; + v4 = XXH_rotl64(v4, 31); + v4 *= XXH_PRIME_1; + + p += 32; + } while (p <= limit); + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + + XXH_rotl64(v4, 18); + + v1 *= XXH_PRIME_2; + v1 = XXH_rotl64(v1, 31); + v1 *= XXH_PRIME_1; + h64 ^= v1; + h64 = h64 * XXH_PRIME_1 + XXH_PRIME_4; + + v2 *= XXH_PRIME_2; + v2 = XXH_rotl64(v2, 31); + v2 *= XXH_PRIME_1; + h64 ^= v2; + h64 = h64 * XXH_PRIME_1 + XXH_PRIME_4; + + v3 *= XXH_PRIME_2; + v3 = XXH_rotl64(v3, 31); + v3 *= XXH_PRIME_1; + h64 ^= v3; + h64 = h64 * XXH_PRIME_1 + XXH_PRIME_4; + + v4 *= XXH_PRIME_2; + v4 = XXH_rotl64(v4, 31); + v4 *= XXH_PRIME_1; + h64 ^= v4; + h64 = h64 * XXH_PRIME_1 + XXH_PRIME_4; + } + else { + h64 = seed + XXH_PRIME_5; + } + + h64 += (uint64_t)len; + + while (p + 8 <= end) { + uint64_t k1 = XXH_read64(p); + k1 *= XXH_PRIME_2; + k1 = XXH_rotl64(k1, 31); + k1 *= XXH_PRIME_1; + h64 ^= k1; + h64 = XXH_rotl64(h64, 27) * XXH_PRIME_1 + XXH_PRIME_4; + p += 8; + } + + if (p + 4 <= end) { + h64 ^= (uint64_t)(XXH_read32(p)) * XXH_PRIME_1; + h64 = XXH_rotl64(h64, 23) * XXH_PRIME_2 + XXH_PRIME_3; + p += 4; + } + + while (p < end) { + h64 ^= (*p) * XXH_PRIME_5; + h64 = XXH_rotl64(h64, 11) * XXH_PRIME_1; + p++; + } + + h64 ^= h64 >> 33; + h64 *= XXH_PRIME_2; + h64 ^= h64 >> 29; + h64 *= XXH_PRIME_3; + h64 ^= h64 >> 32; + + return h64; +} + +// hashmap_sip returns a hash value for `data` using SipHash-2-4. +uint64_t hashmap_sip(const void *data, size_t len, uint64_t seed0, + uint64_t seed1) +{ + return SIP64((uint8_t*)data, len, seed0, seed1); +} + +// hashmap_murmur returns a hash value for `data` using Murmur3_86_128. +uint64_t hashmap_murmur(const void *data, size_t len, uint64_t seed0, + uint64_t seed1) +{ + (void)seed1; + return MM86128(data, len, seed0); +} + +uint64_t hashmap_xxhash3(const void *data, size_t len, uint64_t seed0, + uint64_t seed1) +{ + (void)seed1; + return xxh3(data, len ,seed0); +} + +//============================================================================== +// TESTS AND BENCHMARKS +// $ cc -DHASHMAP_TEST hashmap.c && ./a.out # run tests +// $ cc -DHASHMAP_TEST -O3 hashmap.c && BENCH=1 ./a.out # run benchmarks +//============================================================================== +#ifdef HASHMAP_TEST + +static size_t deepcount(struct hashmap *map) { + size_t count = 0; + for (size_t i = 0; i < map->nbuckets; i++) { + if (bucket_at(map, i)->dib) { + count++; + } + } + return count; +} + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wpedantic" +#endif +#ifdef __clang__ +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wcompound-token-split-by-macro" +#pragma GCC diagnostic ignored "-Wgnu-statement-expression-from-macro-expansion" +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#include +#include +#include +#include +#include +#include "hashmap.h" + +static bool rand_alloc_fail = false; +static int rand_alloc_fail_odds = 3; // 1 in 3 chance malloc will fail. +static uintptr_t total_allocs = 0; +static uintptr_t total_mem = 0; + +static void *xmalloc(size_t size) { + if (rand_alloc_fail && rand()%rand_alloc_fail_odds == 0) { + return NULL; + } + void *mem = malloc(sizeof(uintptr_t)+size); + assert(mem); + *(uintptr_t*)mem = size; + total_allocs++; + total_mem += size; + return (char*)mem+sizeof(uintptr_t); +} + +static void xfree(void *ptr) { + if (ptr) { + total_mem -= *(uintptr_t*)((char*)ptr-sizeof(uintptr_t)); + free((char*)ptr-sizeof(uintptr_t)); + total_allocs--; + } +} + +static void shuffle(void *array, size_t numels, size_t elsize) { + char tmp[elsize]; + char *arr = array; + for (size_t i = 0; i < numels - 1; i++) { + int j = i + rand() / (RAND_MAX / (numels - i) + 1); + memcpy(tmp, arr + j * elsize, elsize); + memcpy(arr + j * elsize, arr + i * elsize, elsize); + memcpy(arr + i * elsize, tmp, elsize); + } +} + +static bool iter_ints(const void *item, void *udata) { + int *vals = *(int**)udata; + vals[*(int*)item] = 1; + return true; +} + +static int compare_ints_udata(const void *a, const void *b, void *udata) { + return *(int*)a - *(int*)b; +} + +static int compare_strs(const void *a, const void *b, void *udata) { + return strcmp(*(char**)a, *(char**)b); +} + +static uint64_t hash_int(const void *item, uint64_t seed0, uint64_t seed1) { + return hashmap_xxhash3(item, sizeof(int), seed0, seed1); + // return hashmap_sip(item, sizeof(int), seed0, seed1); + // return hashmap_murmur(item, sizeof(int), seed0, seed1); +} + +static uint64_t hash_str(const void *item, uint64_t seed0, uint64_t seed1) { + return hashmap_xxhash3(*(char**)item, strlen(*(char**)item), seed0, seed1); + // return hashmap_sip(*(char**)item, strlen(*(char**)item), seed0, seed1); + // return hashmap_murmur(*(char**)item, strlen(*(char**)item), seed0, seed1); +} + +static void free_str(void *item) { + xfree(*(char**)item); +} + +static void all(void) { + int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL); + int N = getenv("N")?atoi(getenv("N")):2000; + printf("seed=%d, count=%d, item_size=%zu\n", seed, N, sizeof(int)); + srand(seed); + + rand_alloc_fail = true; + + // test sip and murmur hashes + assert(hashmap_sip("hello", 5, 1, 2) == 2957200328589801622); + assert(hashmap_murmur("hello", 5, 1, 2) == 1682575153221130884); + assert(hashmap_xxhash3("hello", 5, 1, 2) == 2584346877953614258); + + int *vals; + while (!(vals = xmalloc(N * sizeof(int)))) {} + for (int i = 0; i < N; i++) { + vals[i] = i; + } + + struct hashmap *map; + + while (!(map = hashmap_new(sizeof(int), 0, seed, seed, + hash_int, compare_ints_udata, NULL, NULL))) {} + shuffle(vals, N, sizeof(int)); + for (int i = 0; i < N; i++) { + // // printf("== %d ==\n", vals[i]); + assert(map->count == (size_t)i); + assert(map->count == hashmap_count(map)); + assert(map->count == deepcount(map)); + const int *v; + assert(!hashmap_get(map, &vals[i])); + assert(!hashmap_delete(map, &vals[i])); + while (true) { + assert(!hashmap_set(map, &vals[i])); + if (!hashmap_oom(map)) { + break; + } + } + + for (int j = 0; j < i; j++) { + v = hashmap_get(map, &vals[j]); + assert(v && *v == vals[j]); + } + while (true) { + v = hashmap_set(map, &vals[i]); + if (!v) { + assert(hashmap_oom(map)); + continue; + } else { + assert(!hashmap_oom(map)); + assert(v && *v == vals[i]); + break; + } + } + v = hashmap_get(map, &vals[i]); + assert(v && *v == vals[i]); + v = hashmap_delete(map, &vals[i]); + assert(v && *v == vals[i]); + assert(!hashmap_get(map, &vals[i])); + assert(!hashmap_delete(map, &vals[i])); + assert(!hashmap_set(map, &vals[i])); + assert(map->count == (size_t)(i+1)); + assert(map->count == hashmap_count(map)); + assert(map->count == deepcount(map)); + } + + int *vals2; + while (!(vals2 = xmalloc(N * sizeof(int)))) {} + memset(vals2, 0, N * sizeof(int)); + assert(hashmap_scan(map, iter_ints, &vals2)); + + // Test hashmap_iter. This does the same as hashmap_scan above. + size_t iter = 0; + void *iter_val; + while (hashmap_iter (map, &iter, &iter_val)) { + assert (iter_ints(iter_val, &vals2)); + } + for (int i = 0; i < N; i++) { + assert(vals2[i] == 1); + } + xfree(vals2); + + shuffle(vals, N, sizeof(int)); + for (int i = 0; i < N; i++) { + const int *v; + v = hashmap_delete(map, &vals[i]); + assert(v && *v == vals[i]); + assert(!hashmap_get(map, &vals[i])); + assert(map->count == (size_t)(N-i-1)); + assert(map->count == hashmap_count(map)); + assert(map->count == deepcount(map)); + for (int j = N-1; j > i; j--) { + v = hashmap_get(map, &vals[j]); + assert(v && *v == vals[j]); + } + } + + for (int i = 0; i < N; i++) { + while (true) { + assert(!hashmap_set(map, &vals[i])); + if (!hashmap_oom(map)) { + break; + } + } + } + + assert(map->count != 0); + size_t prev_cap = map->cap; + hashmap_clear(map, true); + assert(prev_cap < map->cap); + assert(map->count == 0); + + + for (int i = 0; i < N; i++) { + while (true) { + assert(!hashmap_set(map, &vals[i])); + if (!hashmap_oom(map)) { + break; + } + } + } + + prev_cap = map->cap; + hashmap_clear(map, false); + assert(prev_cap == map->cap); + + hashmap_free(map); + + xfree(vals); + + + while (!(map = hashmap_new(sizeof(char*), 0, seed, seed, + hash_str, compare_strs, free_str, NULL))); + + for (int i = 0; i < N; i++) { + char *str; + while (!(str = xmalloc(16))); + snprintf(str, 16, "s%i", i); + while(!hashmap_set(map, &str)); + } + + hashmap_clear(map, false); + assert(hashmap_count(map) == 0); + + for (int i = 0; i < N; i++) { + char *str; + while (!(str = xmalloc(16))); + snprintf(str, 16, "s%i", i); + while(!hashmap_set(map, &str)); + } + + hashmap_free(map); + + if (total_allocs != 0) { + fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs); + exit(1); + } +} + +#define bench(name, N, code) {{ \ + if (strlen(name) > 0) { \ + printf("%-14s ", name); \ + } \ + size_t tmem = total_mem; \ + size_t tallocs = total_allocs; \ + uint64_t bytes = 0; \ + clock_t begin = clock(); \ + for (int i = 0; i < N; i++) { \ + (code); \ + } \ + clock_t end = clock(); \ + double elapsed_secs = (double)(end - begin) / CLOCKS_PER_SEC; \ + double bytes_sec = (double)bytes/elapsed_secs; \ + printf("%d ops in %.3f secs, %.0f ns/op, %.0f op/sec", \ + N, elapsed_secs, \ + elapsed_secs/(double)N*1e9, \ + (double)N/elapsed_secs \ + ); \ + if (bytes > 0) { \ + printf(", %.1f GB/sec", bytes_sec/1024/1024/1024); \ + } \ + if (total_mem > tmem) { \ + size_t used_mem = total_mem-tmem; \ + printf(", %.2f bytes/op", (double)used_mem/N); \ + } \ + if (total_allocs > tallocs) { \ + size_t used_allocs = total_allocs-tallocs; \ + printf(", %.2f allocs/op", (double)used_allocs/N); \ + } \ + printf("\n"); \ +}} + +static void benchmarks(void) { + int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL); + int N = getenv("N")?atoi(getenv("N")):5000000; + printf("seed=%d, count=%d, item_size=%zu\n", seed, N, sizeof(int)); + srand(seed); + + + int *vals = xmalloc(N * sizeof(int)); + for (int i = 0; i < N; i++) { + vals[i] = i; + } + + shuffle(vals, N, sizeof(int)); + + struct hashmap *map; + shuffle(vals, N, sizeof(int)); + + map = hashmap_new(sizeof(int), 0, seed, seed, hash_int, compare_ints_udata, + NULL, NULL); + bench("set", N, { + const int *v = hashmap_set(map, &vals[i]); + assert(!v); + }) + shuffle(vals, N, sizeof(int)); + bench("get", N, { + const int *v = hashmap_get(map, &vals[i]); + assert(v && *v == vals[i]); + }) + shuffle(vals, N, sizeof(int)); + bench("delete", N, { + const int *v = hashmap_delete(map, &vals[i]); + assert(v && *v == vals[i]); + }) + hashmap_free(map); + + map = hashmap_new(sizeof(int), N, seed, seed, hash_int, compare_ints_udata, + NULL, NULL); + bench("set (cap)", N, { + const int *v = hashmap_set(map, &vals[i]); + assert(!v); + }) + shuffle(vals, N, sizeof(int)); + bench("get (cap)", N, { + const int *v = hashmap_get(map, &vals[i]); + assert(v && *v == vals[i]); + }) + shuffle(vals, N, sizeof(int)); + bench("delete (cap)" , N, { + const int *v = hashmap_delete(map, &vals[i]); + assert(v && *v == vals[i]); + }) + + hashmap_free(map); + + + xfree(vals); + + if (total_allocs != 0) { + fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs); + exit(1); + } +} + +int main(void) { + hashmap_set_allocator(xmalloc, xfree); + + if (getenv("BENCH")) { + printf("Running hashmap.c benchmarks...\n"); + benchmarks(); + } else { + printf("Running hashmap.c tests...\n"); + all(); + printf("PASSED\n"); + } +} + + +#endif + + diff --git a/src/hashmap.h b/src/hashmap.h new file mode 100644 index 00000000..3ae8b519 --- /dev/null +++ b/src/hashmap.h @@ -0,0 +1,50 @@ +#ifndef HASHMAP_H +#define HASHMAP_H + +#include +#include +#include + +struct hashmap; + +struct hashmap *hashmap_new(size_t elsize, size_t cap, uint64_t seed0, + uint64_t seed1, + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1), + int (*compare)(const void *a, const void *b, void *udata), + void (*elfree)(void *item), + void *udata); + +struct hashmap *hashmap_new_with_allocator(void *(*malloc)(size_t), + void *(*realloc)(void *, size_t), void (*free)(void*), size_t elsize, + size_t cap, uint64_t seed0, uint64_t seed1, + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1), + int (*compare)(const void *a, const void *b, void *udata), + void (*elfree)(void *item), + void *udata); + +void hashmap_free(struct hashmap *map); +void hashmap_clear(struct hashmap *map, bool update_cap); +size_t hashmap_count(struct hashmap *map); +bool hashmap_oom(struct hashmap *map); +const void *hashmap_get(struct hashmap *map, const void *item); +const void *hashmap_set(struct hashmap *map, const void *item); +const void *hashmap_delete(struct hashmap *map, const void *item); +const void *hashmap_probe(struct hashmap *map, uint64_t position); +bool hashmap_scan(struct hashmap *map, bool (*iter)(const void *item, void *udata), void *udata); +bool hashmap_iter(struct hashmap *map, size_t *i, void **item); + +uint64_t hashmap_sip(const void *data, size_t len, uint64_t seed0, uint64_t seed1); +uint64_t hashmap_murmur(const void *data, size_t len, uint64_t seed0, uint64_t seed1); +uint64_t hashmap_xxhash3(const void *data, size_t len, uint64_t seed0, uint64_t seed1); + +const void *hashmap_get_with_hash(struct hashmap *map, const void *key, uint64_t hash); +const void *hashmap_delete_with_hash(struct hashmap *map, const void *key, uint64_t hash); +const void *hashmap_set_with_hash(struct hashmap *map, const void *item, uint64_t hash); +void hashmap_set_grow_by_power(struct hashmap *map, size_t power); +void hashmap_set_load_factor(struct hashmap *map, double load_factor); + + +// DEPRECATED: use `hashmap_new_with_allocator` +void hashmap_set_allocator(void *(*malloc)(size_t), void (*free)(void*)); + +#endif diff --git a/src/helix.c b/src/helix.c new file mode 100644 index 00000000..f61f8fea --- /dev/null +++ b/src/helix.c @@ -0,0 +1,109 @@ +#include +#include +#include "helix.h" +#include "editor.h" +#include "lexer.h" +#include "theme.h" + +void helix_mode() { + if (current_mode != HELIX) { + current_mode = HELIX; + switch_to_theme(¤tThemeIndex, 7); + targetModelineHeight = 21.0f; + targetMinibufferHeight = 0.0f; + } else { + current_mode = NORMAL; + targetModelineHeight = 35.0f; + targetMinibufferHeight = 21.0f; + srand(time(NULL)); + + int randomThemeIndex; + do { + randomThemeIndex = rand() % 8; + } while (randomThemeIndex == 7); // Ensure the random theme is not Helix + + switch_to_theme(¤tThemeIndex, randomThemeIndex); + } + + minibufferAnimationProgress = 0.0f; + isModelineAnimating = true; + isMinibufferAnimating = true; +} + +void update_cursor_color(Editor *editor) { + // Check for no text + if (editor == NULL || editor->data.items == NULL || editor->data.count == 0) { + currentTheme.cursor = themes[currentThemeIndex].notext_cursor; + return; + } + + size_t cursor_pos = editor->cursor; + + // check if cursor is at EOF + if (cursor_pos >= editor->data.count) { + currentTheme.cursor = themes[currentThemeIndex].EOF_cursor; + return; + } + + // Check if the cursor is on a whitespace + if (isspace(editor->data.items[cursor_pos])) { + currentTheme.cursor = themes[currentThemeIndex].cursor; + return; + } + + size_t current_pos = 0; + size_t token_index = 0; // Current token being processed + + while (current_pos < editor->data.count && token_index < editor->tokens.count) { + Token token = editor->tokens.items[token_index]; + size_t token_end = current_pos + token.text_len; + + // Check if the cursor is within the current token + if (cursor_pos >= current_pos && cursor_pos < token_end) { + Vec4f color = get_color_for_token_kind(token.kind); + currentTheme.cursor = color; + return; + } else { + currentTheme.cursor = currentTheme.text; + } + + // Advance to the next token or character + if (cursor_pos < token_end || strncmp(&editor->data.items[current_pos], token.text, token.text_len) == 0) { + current_pos = token_end; // Skip over the token + token_index++; // Move to the next token + } else { + current_pos++; // Move to the next character + } + } +} + + + + +Vec4f get_color_for_token_kind(Token_Kind kind) { + switch (kind) { + case TOKEN_KEYWORD: return currentTheme.logic; + case TOKEN_STRING: return currentTheme.string; + case TOKEN_TYPE: return currentTheme.type; + case TOKEN_PIPE: return currentTheme.pipe; + case TOKEN_TRUE: return currentTheme.truee; + case TOKEN_FALSE: return currentTheme.falsee; + case TOKEN_NULL: return currentTheme.null; + case TOKEN_PREPROC: return currentTheme.hashtag; + case TOKEN_POINTER: return currentTheme.pointer; + case TOKEN_EQUALS: return currentTheme.equals; + case TOKEN_GREATER_THAN: return currentTheme.greater_than; + case TOKEN_LESS_THAN: return currentTheme.less_than; + case TOKEN_EQUALS_EQUALS: return currentTheme.equals_equals; + case TOKEN_COMMENT: return currentTheme.comment; + case TOKEN_ARROW: return currentTheme.arrow; + case TOKEN_FUNCTION_DEFINITION: return currentTheme.function_definition; + case TOKEN_ARRAY_CONTENT: return currentTheme.array_content; + case TOKEN_OPEN_SQUARE: return currentTheme.open_square; + case TOKEN_CLOSE_SQUARE: return currentTheme.close_square; + case TOKEN_OPEN_CURLY: return currentTheme.open_curly; + case TOKEN_CLOSE_CURLY: return currentTheme.close_curly; + default: return currentTheme.cursor; + } +} + diff --git a/src/helix.h b/src/helix.h new file mode 100644 index 00000000..24d0cd57 --- /dev/null +++ b/src/helix.h @@ -0,0 +1,10 @@ +#ifndef HELIX_H +#define HELIX_H + +#include "editor.h" + +void helix_mode(); +void update_cursor_color(Editor *editor); +Vec4f get_color_for_token_kind(Token_Kind kind); + +#endif // HELIX_H diff --git a/src/la.c b/src/la.c index 2d736c7d..501a1c4c 100644 --- a/src/la.c +++ b/src/la.c @@ -1,5 +1,4 @@ #include "./la.h" - Vec2f vec2f(float x, float y) { return (Vec2f) { diff --git a/src/lexer.c b/src/lexer.c index cfd9022c..4a5ba84a 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -2,39 +2,60 @@ #include #include #include +#include #include "common.h" #include "lexer.h" +#include "theme.h" typedef struct { Token_Kind kind; const char *text; } Literal_Token; + Literal_Token literal_tokens[] = { {.text = "(", .kind = TOKEN_OPEN_PAREN}, {.text = ")", .kind = TOKEN_CLOSE_PAREN}, {.text = "{", .kind = TOKEN_OPEN_CURLY}, {.text = "}", .kind = TOKEN_CLOSE_CURLY}, {.text = ";", .kind = TOKEN_SEMICOLON}, + {.text = "=", .kind = TOKEN_EQUALS}, + {.text = "!=", .kind = TOKEN_NOT_EQUALS}, + {.text = "==", .kind = TOKEN_EQUALS_EQUALS}, + {.text = "!", .kind = TOKEN_EXCLAMATION}, }; #define literal_tokens_count (sizeof(literal_tokens)/sizeof(literal_tokens[0])) -const char *keywords[] = { - "auto", "break", "case", "char", "const", "continue", "default", "do", "double", - "else", "enum", "extern", "float", "for", "goto", "if", "int", "long", "register", - "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", - "union", "unsigned", "void", "volatile", "while", "alignas", "alignof", "and", +const char *cKeywords[] = { + "auto", "break", "case", "const", "continue", "default", "do", + "else", "enum", "extern", "for", "goto", "if", "int", "register", + "return", "sizeof", "static", "struct", "switch", "typedef", + "union", "volatile", "while", "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "bitand", - "bitor", "bool", "catch", "char16_t", "char32_t", "char8_t", "class", "co_await", + "bitor", "catch", "char16_t", "char32_t", "char8_t", "class", "co_await", "co_return", "co_yield", "compl", "concept", "const_cast", "consteval", "constexpr", - "constinit", "decltype", "delete", "dynamic_cast", "explicit", "export", "false", + "constinit", "decltype", "delete", "dynamic_cast", "explicit", "export", "friend", "inline", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", "reflexpr", "reinterpret_cast", "requires", "static_assert", "static_cast", "synchronized", - "template", "this", "thread_local", "throw", "true", "try", "typeid", "typename", + "template", "this", "thread_local", "throw", "try", "typeid", "typename", "using", "virtual", "wchar_t", "xor", "xor_eq", + "let", "fn", "type", "defer", "println", "use", "impl", "loop", "match" }; -#define keywords_count (sizeof(keywords)/sizeof(keywords[0])) + + +/* #define keywords_count (sizeof(keywords)/sizeof(keywords[0])) */ +#define cKeywords_count (sizeof(cKeywords)/sizeof(cKeywords[0])) + + +const char *cTypeKeywords[] = { + "char", "double", "float", "int", "long", "short", "signed", "unsigned", "void", + "_Bool", "_Complex", "_Imaginary", "bool", "Vec4f", + "i128", "i32", "i16", "i8", "u128", "u32", "u16", "u8", "str", "f32", "f16", "f8", +}; + +#define cTypeKeywords_count (sizeof(cTypeKeywords) / sizeof(cTypeKeywords[0])) + const char *token_kind_name(Token_Kind kind) { @@ -53,12 +74,54 @@ const char *token_kind_name(Token_Kind kind) return "close paren"; case TOKEN_OPEN_CURLY: return "open curly"; + case TOKEN_COLOR: + return "color"; case TOKEN_CLOSE_CURLY: return "close curly"; case TOKEN_SEMICOLON: return "semicolon"; case TOKEN_KEYWORD: return "keyword"; + case TOKEN_EQUALS: + return "="; + case TOKEN_NOT_EQUALS: + return "!="; + case TOKEN_EQUALS_EQUALS: + return "=="; + case TOKEN_EXCLAMATION: + return "!"; + case TOKEN_ARROW: + return "->"; + case TOKEN_MINUS: + return "-"; + case TOKEN_PLUS: + return "+"; + case TOKEN_TRUE: + return "true"; + case TOKEN_FALSE: + return "false"; + case TOKEN_ARRAY_CONTENT: + return "array_content"; + case TOKEN_OPEN_SQUARE: + return "open_square"; + case TOKEN_CLOSE_SQUARE: + return "close_square"; + case TOKEN_LINK: + return "link"; + case TOKEN_OR: + return "logic_or"; + case TOKEN_PIPE: + return "pipe"; + case TOKEN_AND: + return "logic_and"; + case TOKEN_AMPERSAND: + return "ampersand"; + case TOKEN_MULTIPLICATION: + return "multiplication"; + case TOKEN_POINTER: + return "pointer"; + case TOKEN_BAD_SPELLCHECK: + return "bad_spellcheck"; default: UNREACHABLE("token_kind_name"); } @@ -146,6 +209,211 @@ Token lexer_next(Lexer *l) if (l->cursor >= l->content_len) return token; + // Check for specific operators (e.g., "=", "!", "!=", "==", "<", ">", "<=", ">=") + if (l->cursor < l->content_len) { + char current_char = l->content[l->cursor]; + char next_char = (l->cursor + 1 < l->content_len) ? l->content[l->cursor + 1] : '\0'; + char prev_char = (l->cursor > 0) ? l->content[l->cursor - 1] : '\0'; // added for * + + switch (current_char) { + case '=': + if (next_char == '=') { + token.kind = TOKEN_EQUALS_EQUALS; + token.text_len = 2; + lexer_chop_char(l, 2); + } else { + token.kind = TOKEN_EQUALS; + token.text_len = 1; + lexer_chop_char(l, 1); + } + return token; + + case '!': + if (next_char == '=') { + token.kind = TOKEN_NOT_EQUALS; + token.text_len = 2; + lexer_chop_char(l, 2); + } else { + token.kind = TOKEN_EXCLAMATION; + token.text_len = 1; + lexer_chop_char(l, 1); + } + return token; + + case '<': + token.kind = TOKEN_LESS_THAN; + token.text_len = 1; + lexer_chop_char(l, 1); + return token; + + case '>': + token.kind = TOKEN_GREATER_THAN; + token.text_len = 1; + lexer_chop_char(l, 1); + return token; + + case '-': + if (next_char == '>') { + token.kind = TOKEN_ARROW; + token.text_len = 2; + lexer_chop_char(l, 2); + } else { + token.kind = TOKEN_MINUS; + token.text_len = 1; + lexer_chop_char(l, 1); + } + return token; + + case '+': + token.kind = TOKEN_PLUS; + token.text_len = 1; + lexer_chop_char(l, 1); + return token; + + case '|': + if (next_char == '|') { + token.kind = TOKEN_OR; + token.text_len = 2; + lexer_chop_char(l, 2); + } else { + token.kind = TOKEN_PIPE; + token.text_len = 1; + lexer_chop_char(l, 1); + } + return token; + + case '&': + if (next_char == '&') { + token.kind = TOKEN_AND; + token.text_len = 2; + lexer_chop_char(l, 2); + } else { + token.kind = TOKEN_AMPERSAND; + token.text_len = 1; + lexer_chop_char(l, 1); + } + return token; + + case '*': + // If there's a space both before and after '*', treat it as + // multiplication. In all other cases, treat it as a pointer. + if (isspace(prev_char) && isspace(next_char)) { + token.kind = TOKEN_MULTIPLICATION; + } else { + token.kind = TOKEN_POINTER; + } + token.text_len = 1; + lexer_chop_char(l, 1); + return token; + } + } + + // Check for links + if ((l->cursor + 6 < l->content_len && + strncmp(&l->content[l->cursor], "http://", 7) == 0) || + (l->cursor + 7 < l->content_len && + strncmp(&l->content[l->cursor], "https://", 8) == 0) || + (l->cursor + 3 < l->content_len && + strncmp(&l->content[l->cursor], "www.", 4) == 0)) { + + size_t potential_length = 0; + while (l->cursor + potential_length < l->content_len && + !isspace(l->content[l->cursor + potential_length]) && + l->content[l->cursor + potential_length] != '\n' && + l->content[l->cursor + potential_length] != + ')') { // Exclude closing parenthesis + potential_length++; + } + + if (potential_length > 0) { + token.kind = TOKEN_LINK; + token.text_len = potential_length; + lexer_chop_char(l, potential_length); + return token; + } + } + + // Check for arrays + if (l->cursor < l->content_len) { + char current_char = l->content[l->cursor]; + + // If the current character is the start of an array + if (current_char == '[') { + token.kind = TOKEN_OPEN_SQUARE; + token.text_len = 1; + lexer_chop_char(l, 1); + l->in_array = true; // Set the flag indicating we are inside an array + return token; + } + else if (current_char == ']' && l->in_array) { + token.kind = TOKEN_CLOSE_SQUARE; + token.text_len = 1; + lexer_chop_char(l, 1); + l->in_array = false; // Reset the flag indicating we are no longer inside an array + return token; + } + } + + // Check for array content, but only if we are inside an array + if (l->in_array && l->cursor < l->content_len) { + size_t potential_length = 0; + + while (l->cursor + potential_length < l->content_len && l->content[l->cursor + potential_length] != ']') { + potential_length++; + } + + // If potential array content was detected and not empty + if (potential_length > 0) { + token.kind = TOKEN_ARRAY_CONTENT; + token.text_len = potential_length; + lexer_chop_char(l, potential_length); + return token; + } + } + + // Check for boolean literals "true" and "false" + if ((l->cursor + 3 < l->content_len) && + (strncmp(&l->content[l->cursor], "true", 4) == 0) && + ((l->cursor + 4 == l->content_len) || !isalnum(l->content[l->cursor + 4]))) { + + lexer_chop_char(l, 4); // Skip the entire "true" token + token.kind = TOKEN_TRUE; + token.text_len = 4; + return token; + + } else if ((l->cursor + 4 < l->content_len) && + (strncmp(&l->content[l->cursor], "false", 5) == 0) && + ((l->cursor + 5 == l->content_len) || !isalnum(l->content[l->cursor + 5]))) { + + lexer_chop_char(l, 5); // Skip the entire "false" token + token.kind = TOKEN_FALSE; + token.text_len = 5; + return token; + } + + + // Check for color-like format (e.g., 0xf38ba8FF) + if (l->content[l->cursor] == '0' && + (l->cursor + 1 < l->content_len) && + (l->content[l->cursor + 1] == 'x' || l->content[l->cursor + 1] == 'X')) { + + size_t start_cursor = l->cursor; + size_t potential_length = 0; + + // Count the potential hex digits + while ((start_cursor + 2 + potential_length) < l->content_len && isxdigit(l->content[start_cursor + 2 + potential_length])) { + potential_length++; + } + + // Check if the length is 8, meaning it's a full color + if (potential_length == 8) { + lexer_chop_char(l, 10); // Skip the entire color token including '0x' + token.kind = TOKEN_COLOR; + token.text_len = 10; // Including the '0x' prefix + return token; + } + } + if (l->content[l->cursor] == '"') { // TODO: TOKEN_STRING should also handle escape sequences token.kind = TOKEN_STRING; @@ -160,10 +428,13 @@ Token lexer_next(Lexer *l) return token; } - if (l->content[l->cursor] == '#') { - // TODO: preproc should also handle newlines - token.kind = TOKEN_PREPROC; - while (l->cursor < l->content_len && l->content[l->cursor] != '\n') { + // single quote + // TODO if there is only one quote, + // the text on the right should not be colored. + if (l->content[l->cursor] == '\'') { + token.kind = TOKEN_STRING; + lexer_chop_char(l, 1); + while (l->cursor < l->content_len && l->content[l->cursor] != '\'' && l->content[l->cursor] != '\n') { lexer_chop_char(l, 1); } if (l->cursor < l->content_len) { @@ -173,6 +444,44 @@ Token lexer_next(Lexer *l) return token; } + + // "NULL" + if ((l->cursor + 3 < l->content_len) && + (strncmp(&l->content[l->cursor], "NULL", 4) == 0) && + ((l->cursor + 4 == l->content_len) || !isalnum(l->content[l->cursor + 4]))) { + + lexer_chop_char(l, 4); // Skip the entire "NULL" token + token.kind = TOKEN_NULL; + token.text_len = 4; + return token; + } + + + if (l->content[l->cursor] == '#') { + if (l->cursor + 6 < l->content_len && is_hex_digit(l->content[l->cursor + 1]) + && is_hex_digit(l->content[l->cursor + 2]) + && is_hex_digit(l->content[l->cursor + 3]) + && is_hex_digit(l->content[l->cursor + 4]) + && is_hex_digit(l->content[l->cursor + 5]) + && is_hex_digit(l->content[l->cursor + 6])) { + token.kind = TOKEN_PREPROC; + lexer_chop_char(l, 7); // Chop # and the 6 characters + token.text_len = &l->content[l->cursor] - token.text; + return token; + } else { + // # as a preprocessor directive + token.kind = TOKEN_PREPROC; + while (l->cursor < l->content_len && l->content[l->cursor] != '\n') { + lexer_chop_char(l, 1); + } + if (l->cursor < l->content_len) { + lexer_chop_char(l, 1); + } + token.text_len = &l->content[l->cursor] - token.text; + return token; + } + } + if (lexer_starts_with(l, "//")) { token.kind = TOKEN_COMMENT; while (l->cursor < l->content_len && l->content[l->cursor] != '\n') { @@ -184,6 +493,91 @@ Token lexer_next(Lexer *l) token.text_len = &l->content[l->cursor] - token.text; return token; } + + // TODO + // multi-line comments + if (lexer_starts_with(l, "/*")) { + token.kind = TOKEN_COMMENT; + lexer_chop_char(l, 2); // Skip the "/*" + + while (l->cursor + 1 < l->content_len) { + if (l->content[l->cursor] == '*' && l->content[l->cursor + 1] == '/') { + lexer_chop_char(l, 2); // Skip the "*/" + break; + } + lexer_chop_char(l, 1); + } + + token.text_len = &l->content[l->cursor] - token.text; + return token; + } + + // FUNCTION DEFINITION + if (l->cursor < l->content_len && is_symbol_start(l->content[l->cursor])) { + // Save the start position of the potential function name + size_t symbolStart = l->cursor; + + // Skip over the potential function name + while (l->cursor < l->content_len && is_symbol(l->content[l->cursor])) { + l->cursor++; + } + + size_t symbolEnd = l->cursor; + + // Look to the left for a type keyword + bool precededByTypeKeyword = false; + size_t leftCursor = symbolStart; + while (leftCursor > 0 && isspace(l->content[leftCursor - 1])) { + leftCursor--; // Skip whitespace + } + if (leftCursor > 0) { + for (size_t i = 0; i < cTypeKeywords_count; ++i) { + size_t keyword_len = strlen(cTypeKeywords[i]); + if (leftCursor >= keyword_len && + strncmp(cTypeKeywords[i], &l->content[leftCursor - keyword_len], keyword_len) == 0 && + (leftCursor == keyword_len || isspace(l->content[leftCursor - keyword_len - 1]))) { + precededByTypeKeyword = true; + break; + } + } + } + + // Look to the right for parentheses + bool followedByParentheses = false; + size_t rightCursor = symbolEnd; + while (rightCursor < l->content_len && isspace(l->content[rightCursor])) { + rightCursor++; // Skip whitespace + } + if (l->content_len - rightCursor >= 1 && l->content[rightCursor] == '(') { + followedByParentheses = true; + } + + // Mark as a function definition if conditions are met + if (precededByTypeKeyword && followedByParentheses) { + token.kind = TOKEN_FUNCTION_DEFINITION; + token.text_len = symbolEnd - symbolStart; + token.text = &l->content[symbolStart]; + + // IMPORTANT: Adjust the position offset for the next token + token.position.x = l->x; + for (size_t i = symbolStart; i < rightCursor; i++) { + char c = l->content[i]; + size_t glyph_index = c; + if (glyph_index >= GLYPH_METRICS_CAPACITY) { + glyph_index = '?'; + } + Glyph_Metric metric = l->atlas->metrics[glyph_index]; + l->x += metric.ax; + } + + l->cursor = rightCursor; // Set cursor to the start of the parentheses + return token; + } else { + // Reset cursor position to start of symbol for further processing + l->cursor = symbolStart; + } + } + for (size_t i = 0; i < literal_tokens_count; ++i) { if (lexer_starts_with(l, literal_tokens[i].text)) { @@ -196,16 +590,27 @@ Token lexer_next(Lexer *l) } } + if (is_symbol_start(l->content[l->cursor])) { token.kind = TOKEN_SYMBOL; while (l->cursor < l->content_len && is_symbol(l->content[l->cursor])) { lexer_chop_char(l, 1); token.text_len += 1; } + + // First, check if the token is a type + for (size_t i = 0; i < cTypeKeywords_count; ++i) { + size_t keyword_len = strlen(cTypeKeywords[i]); + if (keyword_len == token.text_len && memcmp(cTypeKeywords[i], token.text, keyword_len) == 0) { + token.kind = TOKEN_TYPE; + return token; + } + } - for (size_t i = 0; i < keywords_count; ++i) { - size_t keyword_len = strlen(keywords[i]); - if (keyword_len == token.text_len && memcmp(keywords[i], token.text, keyword_len) == 0) { + // If not a type, check if it's a general keyword + for (size_t i = 0; i < cKeywords_count; ++i) { + size_t keyword_len = strlen(cKeywords[i]); + if (keyword_len == token.text_len && memcmp(cKeywords[i], token.text, keyword_len) == 0) { token.kind = TOKEN_KEYWORD; break; } @@ -219,3 +624,4 @@ Token lexer_next(Lexer *l) token.text_len = 1; return token; } + diff --git a/src/lexer.h b/src/lexer.h index 6ee721ed..bf16f9fa 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -4,6 +4,7 @@ #include #include "./la.h" #include "./free_glyph.h" +#include "./common.h" typedef enum { TOKEN_END = 0, @@ -18,15 +19,45 @@ typedef enum { TOKEN_KEYWORD, TOKEN_COMMENT, TOKEN_STRING, + TOKEN_COLOR, + TOKEN_EQUALS, + TOKEN_NOT_EQUALS, + TOKEN_EQUALS_EQUALS, + TOKEN_EXCLAMATION, + TOKEN_LESS_THAN, + TOKEN_GREATER_THAN, + TOKEN_ARROW, + TOKEN_MINUS, + TOKEN_PLUS, + TOKEN_TRUE, + TOKEN_FALSE, + TOKEN_OPEN_SQUARE, + TOKEN_CLOSE_SQUARE, + TOKEN_ARRAY_CONTENT, + TOKEN_BAD_SPELLCHECK, + TOKEN_LINK, + TOKEN_OR, + TOKEN_PIPE, + TOKEN_AND, + TOKEN_AMPERSAND, + TOKEN_MULTIPLICATION, + TOKEN_POINTER, + TOKEN_TYPE, + TOKEN_FUNCTION_DEFINITION, + TOKEN_NULL, } Token_Kind; const char *token_kind_name(Token_Kind kind); + +// TODO add a size_t position typedef struct { Token_Kind kind; const char *text; size_t text_len; Vec2f position; + int nesting_level; // TODO + Vec4f color; // <-- New attribute } Token; typedef struct { @@ -37,9 +68,12 @@ typedef struct { size_t line; size_t bol; float x; + String_Builder file_path; + bool in_array; // to remember if we are inside an array } Lexer; Lexer lexer_new(Free_Glyph_Atlas *atlas, const char *content, size_t content_len); +/* Lexer lexer_new(Free_Glyph_Atlas *atlas, const char *content, size_t content_len, String_Builder file_path); */ Token lexer_next(Lexer *l); #endif // LEXER_H_ diff --git a/src/lsp.c b/src/lsp.c new file mode 100644 index 00000000..7ac328d6 --- /dev/null +++ b/src/lsp.c @@ -0,0 +1,301 @@ +#include "lsp.h" +#include "common.h" +#include "editor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int to_clangd[2]; +int from_clangd[2]; +pthread_t receive_thread; +int current_request_id = 1; + +void handle_error(const char *message) { + perror(message); + exit(EXIT_FAILURE); +} + +void start_clangd(Editor *e) { + printf("Starting clangd...\n"); + if (pipe(to_clangd) == -1 || pipe(from_clangd) == -1) { + handle_error("Failed to create pipes"); + } + + pid_t pid = fork(); + if (pid == -1) { + handle_error("Failed to fork"); + } else if (pid == 0) { // Child process + // Close unused pipe ends + close(to_clangd[1]); + close(from_clangd[0]); + + // Redirect stdin and stdout + dup2(to_clangd[0], STDIN_FILENO); + dup2(from_clangd[1], STDOUT_FILENO); + + execlp("clangd", "clangd", NULL); + handle_error("Failed to start clangd"); + } else { // Parent process + // Close unused pipe ends + close(to_clangd[0]); + close(from_clangd[1]); + + e->to_clangd_fd = to_clangd[1]; + e->from_clangd_fd = from_clangd[0]; + if (pthread_create(&receive_thread, NULL, receive_json_rpc, e) != 0) { + handle_error("Failed to create thread for receive_json_rpc"); + } + } + send_initialize_request(e); +} + +void shutdown_clangd(Editor *e) { + send_json_rpc(e->to_clangd_fd, "shutdown", "{}", current_request_id++); + send_json_rpc(e->to_clangd_fd, "exit", "{}", current_request_id++); + close(e->to_clangd_fd); + pthread_join(receive_thread, NULL); + close(e->from_clangd_fd); + wait(NULL); // Wait for clangd to terminate +} + +void send_json_rpc(int fd, const char* method, const char* params, int request_id) { + char message[4096]; + size_t message_length = snprintf(message, sizeof(message), "{\"jsonrpc\": \"2.0\", \"id\": %d, \"method\": \"%s\", \"params\": %s}\n", + request_id, method, params); + + if (message_length >= sizeof(message)) { + fprintf(stderr, "[send_json_rpc] Error: JSON-RPC message is too long.\n"); + return; + } + + ssize_t bytes_written = write(fd, message, message_length); + if (bytes_written == -1) { + perror("[send_json_rpc] Error sending JSON-RPC"); + } else if ((size_t)bytes_written != message_length) { + fprintf(stderr, "[send_json_rpc] Error: Partial JSON-RPC message sent. Expected %zu bytes, sent %zu bytes.\n", message_length, (size_t)bytes_written); + } else { + printf("[send_json_rpc] Sent %zu bytes: %s\n", (size_t)bytes_written, message); + } +} + +void parse_lsp_response(const char *response_json, LSPResponse *response) { + json_object *parsed_json = json_tokener_parse(response_json); + json_object *id, *method, *params; + + if (json_object_object_get_ex(parsed_json, "id", &id)) { + response->id = json_object_get_int(id); + } + + if (json_object_object_get_ex(parsed_json, "method", &method)) { + response->method = strdup(json_object_get_string(method)); + } + + if (json_object_object_get_ex(parsed_json, "params", ¶ms)) { + response->params = strdup(json_object_to_json_string(params)); + } + + json_object_put(parsed_json); +} + +void handle_lsp_response(LSPResponse *response, Editor *e) { + printf("[handle_lsp_response] Received response with method: %s\n", response->method); + + if (strcmp(response->method, "textDocument/definition") == 0) { + json_object *parsed_response = json_tokener_parse(response->params); + json_object *locations_array; + + if (json_object_object_get_ex(parsed_response, "result", &locations_array)) { + // Assuming the result is an array of locations (as per LSP specification) + json_object *location = json_object_array_get_idx(locations_array, 0); // Get the first location + if (location) { + json_object *uri_obj, *range_obj, *start_obj, *line_obj, *char_obj; + if (json_object_object_get_ex(location, "uri", &uri_obj) && + json_object_object_get_ex(location, "range", &range_obj) && + json_object_object_get_ex(range_obj, "start", &start_obj) && + json_object_object_get_ex(start_obj, "line", &line_obj) && + json_object_object_get_ex(start_obj, "character", &char_obj)) { + + const char *file_uri = json_object_get_string(uri_obj); + int line = json_object_get_int(line_obj); + int character = json_object_get_int(char_obj); + + char file_path[PATH_MAX]; + convert_uri_to_file_path(file_uri, file_path, sizeof(file_path)); + printf("[handle_lsp_response] Definition found at file: %s, line: %d, character: %d\n", file_path, line, character); + find_file(e, file_path, line, character); + } + } + } + json_object_put(parsed_response); + } else { + printf("[handle_lsp_response] Received non-definition response or method not recognized\n"); + } +} + +void* receive_json_rpc(void* arg) { + Editor *e = (Editor *)arg; + if (e == NULL) { + fprintf(stderr, "[receive_json_rpc] Editor instance is NULL\n"); + return NULL; + } + + char buffer[4096]; + ssize_t nbytes; + + printf("[receive_json_rpc] Thread started, waiting for responses from clangd...\n"); + + while (1) { + nbytes = read(e->from_clangd_fd, buffer, sizeof(buffer) - 1); + + if (nbytes > 0) { + buffer[nbytes] = '\0'; + printf("[receive_json_rpc] Received %zd bytes from clangd: %s\n", nbytes, buffer); + + LSPResponse response; + parse_lsp_response(buffer, &response); + + if (response.method) { + printf("[receive_json_rpc] Handling response for method: %s\n", response.method); + handle_lsp_response(&response, e); + free(response.method); + free(response.params); + } else { + printf("[receive_json_rpc] No valid method found in response or response parsing failed\n"); + } + } else if (nbytes == 0) { + printf("[receive_json_rpc] EOF reached, clangd might have closed the connection.\n"); + break; + } else { + perror("[receive_json_rpc] Error reading from clangd"); + break; + } + } + + printf("[receive_json_rpc] Thread is exiting.\n"); + return NULL; +} + + +void convert_uri_to_file_path(const char *uri, char *file_path, size_t file_path_size) { + if (strncmp(uri, "file://", 7) == 0) { + uri += 7; // Skip the "file://" part + char *decoded_uri = url_decode(uri); // Implement url_decode to handle percent-encoding + snprintf(file_path, file_path_size, "%s", decoded_uri); + free(decoded_uri); // Assuming url_decode dynamically allocates memory + } else { + fprintf(stderr, "Invalid URI format\n"); + strncpy(file_path, "", file_path_size); + } +} + +// Example implementation of url_decode (simplified) +char *url_decode(const char *str) { + char *decoded = malloc(strlen(str) + 1); + char *d = decoded; + while (*str) { + if (*str == '%' && *(str + 1) && *(str + 2)) { + char hex[3] = { str[1], str[2], '\0' }; + *d++ = (char)strtol(hex, NULL, 16); + str += 3; + } else { + *d++ = *str++; + } + } + *d = '\0'; + return decoded; +} + +void goto_definition(Editor *e, File_Browser *fb) { + if (!e || !fb) { + fprintf(stderr, "[goto_definition] Error: Editor or File_Browser is NULL\n"); + return; + } + + char file_uri[256]; + get_current_file_uri(e, fb, file_uri, sizeof(file_uri)); + int character; + size_t line; + /* get_cursor_position(e, &line, &character); */ + get_cursor_position(e); + + char params[512]; + int params_length = snprintf(params, sizeof(params), + "{\"textDocument\": {\"uri\": \"%s\"}, \"position\": {\"line\": %zu, \"character\": %d}}", + file_uri, line, character); + + // Check for snprintf error + if (params_length < 0) { + fprintf(stderr, "[goto_definition] Error: Encoding error in snprintf.\n"); + return; + } + + // Now safe to compare, with casting to match types + if (params_length >= (int)sizeof(params)) { + fprintf(stderr, "[goto_definition] Error: Params string is too long.\n"); + return; + } + + send_json_rpc(e->to_clangd_fd, "textDocument/definition", params, current_request_id++); + printf("[goto_definition] Requested definition at URI: %s, Line: %zu, Character: %d\n", file_uri, line, character); +} + +void get_current_file_uri(Editor *e, File_Browser *fb, char *file_uri, size_t uri_size) { + if (!e || !fb || !file_uri) { + fprintf(stderr, "Error: Invalid arguments in get_current_file_uri\n"); + return; + } + + // Directly access the items of the String_Builder + /* char *path = fb->dir_path.items; */ + char *path = fb->file_path.items; + /* char *path = "/home/l/Desktop/test/ded"; */ + + if (path && path[0] != '\0') { // Check if the path is not empty + snprintf(file_uri, uri_size, "file://%s", path); + } else { + fprintf(stderr, "Error: File path is empty in File_Browser\n"); + strncpy(file_uri, "", uri_size); + } +} + +void send_initialize_request(Editor *e) { + const char *params = "{" + "\"processId\": null," + "\"rootUri\": \"file:///home/l/Desktop/test/ded\"," + "\"capabilities\": {" + " \"textDocument\": {" + " \"definition\": {" + " \"dynamicRegistration\": true" + " }" + " }" + "}" + "}"; + send_json_rpc(e->to_clangd_fd, "initialize", params, current_request_id++); +} + + + +void send_initialized_notification(Editor *e) { + send_json_rpc(e->to_clangd_fd, "initialized", "{}", current_request_id++); +} + +void send_did_open_notification(Editor *e, const char *file_uri, const char *file_content) { + char params[1024]; + snprintf(params, sizeof(params), + "{\"textDocument\": {" + "\"uri\": \"%s\"," + "\"languageId\": \"c\"," + "\"version\": 1," + "\"text\": \"%s\"" + "}}", file_uri, file_content); + send_json_rpc(e->to_clangd_fd, "textDocument/didOpen", params, current_request_id++); +} + + diff --git a/src/lsp.h b/src/lsp.h new file mode 100644 index 00000000..cb1efebe --- /dev/null +++ b/src/lsp.h @@ -0,0 +1,39 @@ +#ifndef LSP_H_ +#define LSP_H_ + +#include "file_browser.h" +#include "editor.h" +#include + +// Struct to store LSP response +typedef struct { + int id; + char *method; + char *params; +} LSPResponse; + +extern pthread_t receive_thread; + + +/* void start_clangd(); */ +void start_clangd(Editor *e); +/* void shutdown_clangd(); */ +void shutdown_clangd(Editor *e); +/* void send_json_rpc(const char* method, const char* params, int request_id); */ +void send_json_rpc(int fd, const char* method, const char* params, int request_id); +void* receive_json_rpc(void* arg); +void goto_definition(Editor *e, File_Browser *fb); +void handle_lsp_response(LSPResponse *response, Editor *e); +void convert_uri_to_file_path(const char *uri, char *file_path, size_t file_path_size); +char *url_decode(const char *str); +void get_current_file_uri(Editor *e, File_Browser *fb, char *file_uri, size_t uri_size); +void parse_lsp_response(const char *response_json, LSPResponse *response); + + + + +void send_did_open_notification(Editor *e, const char *file_uri, const char *file_content); +void send_initialize_request(Editor *e); +void send_initialized_notification(Editor * e); + +#endif // LSP_H_ diff --git a/src/main.c b/src/main.c index eac9ac5f..63b16d09 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,14 @@ +#include +#include #include #include #include #include #include +#include "common.h" +#include "helix.h" + +#include #include #define GLEW_STATIC @@ -22,14 +28,43 @@ #include "./lexer.h" #include "./sv.h" +// added +#include +#include +#include +#include "yasnippet.h" +#include "render.h" +#include "evil.h" +#include "emacs.h" +#include "buffer.h" +#include "theme.h" +#include "unistd.h" +#include "M-x.h" +#include "lsp.h" +#include "clock.h" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#define FONT_DIR "~/.config/ded/fonts/" +/* #define DEFAULT_FONT "jet-extra-bold.ttf" */ +#define DEFAULT_FONT "radon.otf" +/* #define DEFAULT_FONT "minecraft.ttf" */ +/* #define DEFAULT_FONT "iosevka-regular.ttf" */ +#define MAX_FONTS 20 +#define MAX_PATH_SIZE 1024 + +char *fonts[MAX_FONTS]; +int font_count = 0; +int current_font_index = 0; + + // TODO: Save file dialog // Needed when ded is ran without any file so it does not know where to save. // TODO: An ability to create a new file -// TODO: Delete a word -// TODO: Delete selection // TODO: Undo/redo system - +// DONE: Delete a word +// DONE: Delete selection void MessageCallback(GLenum source, GLenum type, GLuint id, @@ -49,15 +84,138 @@ void MessageCallback(GLenum source, static Free_Glyph_Atlas atlas = {0}; static Simple_Renderer sr = {0}; -static Editor editor = {0}; +/* static Editor editor = {0}; */ static File_Browser fb = {0}; + +FT_Face load_font_face(FT_Library library, const char *font_name, FT_UInt pixel_size) { + printf("Loading font: %s at index: %d\n", font_name, current_font_index); + char font_path[MAX_PATH_SIZE]; + const char *homeDir = getenv("HOME"); + snprintf(font_path, sizeof(font_path), "%s/.config/ded/fonts/%s", homeDir, font_name); + + FT_Face face; + FT_Error error = FT_New_Face(library, font_path, 0, &face); + if (error == FT_Err_Unknown_File_Format) { + fprintf(stderr, "ERROR: `%s` has an unknown format\n", font_path); + exit(1); + } else if (error) { + fprintf(stderr, "ERROR: Could not load file `%s`\n", font_path); + exit(1); + } + + error = FT_Set_Pixel_Sizes(face, 0, pixel_size); // Set pixel size for the loaded font face + if (error) { + fprintf(stderr, "ERROR: Could not set pixel size to %u\n", pixel_size); + return NULL; // or handle the error in a different way + } + + return face; +} + +void prev_font() { + if (current_font_index == 0) { + // Already at the first font, don't do anything. + return; + } + current_font_index--; +} + +void next_font() { + if (current_font_index == font_count - 1) { + // Already at the last font, don't do anything. + return; + } + current_font_index++; +} + + +void populate_font_list() { + char path[MAX_PATH_SIZE]; + const char *homeDir = getenv("HOME"); + if (!homeDir) { + fprintf(stderr, "ERROR: Could not get HOME directory\n"); + exit(1); + } + + snprintf(path, sizeof(path), "%s/.config/ded/fonts/", homeDir); + + DIR *dir = opendir(path); + if (!dir) { + fprintf(stderr, "ERROR: Could not open directory `%s`\n", path); + exit(1); + } + + struct dirent *entry; + while ((entry = readdir(dir)) && font_count < MAX_FONTS) { + if (entry->d_type == DT_REG) { // If the entry is a regular file + fonts[font_count] = strdup(entry->d_name); + font_count++; + } + } + closedir(dir); +} + +void switch_to_font(FT_Library library, FT_Face *currentFace, Free_Glyph_Atlas *atlas, int direction) { + if (direction > 0) { + next_font(); + } else { + prev_font(); + } + /* *currentFace = load_font_face(library, fonts[current_font_index]); */ + *currentFace = load_font_face(library, fonts[current_font_index], FREE_GLYPH_FONT_SIZE); + + + // Dispose the old texture + /* glDeleteTextures(1, &atlas->glyphs_texture); */ + + // Reinitialize the atlas with the new font face + free_glyph_atlas_init(atlas, *currentFace); +} + // TODO: display errors reported via flash_error right in the text editor window somehow #define flash_error(...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while(0) +#include int main(int argc, char **argv) { + set_current_mode(); + initialize_themes(); + initialize_shader_paths(); + load_snippets_from_directory(); + + init_clock(); + + // lsp + start_clangd(&editor); + /* pthread_create(&receive_thread, NULL, receive_json_rpc, NULL); */ + + + // Define hash seeds (these could be randomly generated for more robustness) + uint64_t seed0 = 0x12345678; + uint64_t seed1 = 0x9ABCDEF0; + + // Allocate and initialize the commands hashmap + editor.commands = hashmap_new( + sizeof(Command), // Size of each element + 16, // Initial capacity + seed0, seed1, // Hash seeds + simple_string_hash, // Hash function + command_compare, // Compare function + NULL, // Element free function (NULL if not needed) + NULL // User data for compare function (NULL if not needed) + ); + + if (!editor.commands) { + // Handle allocation failure + fprintf(stderr, "Failed to initialize command map\n"); + return -1; + } + + initialize_commands(editor.commands); + + Errno err; FT_Library library = {0}; @@ -69,19 +227,55 @@ int main(int argc, char **argv) } // TODO: users should be able to customize the font - // const char *const font_file_path = "./fonts/VictorMono-Regular.ttf"; - const char *const font_file_path = "./fonts/iosevka-regular.ttf"; + /* const char *const font_file_path = "./fonts/VictorMono-Regular.ttf"; */ + /* const char *const font_file_path = "./fonts/jet-bold.ttf"; */ + /* const char *const font_file_path = "~/.config/ded/fonts/jet-extra-bold.ttf"; */ + /* const char *const font_file_path = "./fonts/iosevka-regular.ttf"; */ - FT_Face face; - error = FT_New_Face(library, font_file_path, 0, &face); - if (error == FT_Err_Unknown_File_Format) { - fprintf(stderr, "ERROR: `%s` has an unknown format\n", font_file_path); - return 1; - } else if (error) { - fprintf(stderr, "ERROR: Could not load file `%s`\n", font_file_path); - return 1; + + /* char font_file_path_buffer[1024]; */ + /* const char *homeDir = getenv("HOME"); */ + /* if (homeDir) { */ + /* snprintf(font_file_path_buffer, sizeof(font_file_path_buffer), "%s/.config/ded/fonts/minecraft_font.ttf", homeDir); */ + /* } else { */ + /* // handle the error, for now, we'll just set it to the original value as a fallback */ + /* strncpy(font_file_path_buffer, "~/.config/ded/fonts/jet-extra-bold.ttf", sizeof(font_file_path_buffer)); */ + /* } */ + /* const char *const font_file_path = font_file_path_buffer; */ + + + + populate_font_list(); + + if (font_count == 0) { + fprintf(stderr, "ERROR: No fonts found in `%s`\n", FONT_DIR); + return 1; } + // Start with the default font + for (int i = 0; i < font_count; i++) { + if (strcmp(fonts[i], DEFAULT_FONT) == 0) { + current_font_index = i; + break; + } + } + + /* FT_Face face = load_font_face(library, fonts[current_font_index]); */ + FT_Face face = load_font_face(library, fonts[current_font_index], FREE_GLYPH_FONT_SIZE); + + + /* original */ + /* FT_Face face; */ + /* error = FT_New_Face(library, font_file_path, 0, &face); */ + /* if (error == FT_Err_Unknown_File_Format) { */ + /* fprintf(stderr, "ERROR: `%s` has an unknown format\n", font_file_path); */ + /* return 1; */ + /* } else if (error) { */ + /* fprintf(stderr, "ERROR: Could not load file `%s`\n", font_file_path); */ + /* return 1; */ + /* } */ + + FT_UInt pixel_size = FREE_GLYPH_FONT_SIZE; error = FT_Set_Pixel_Sizes(face, 0, pixel_size); if (error) { @@ -91,7 +285,7 @@ int main(int argc, char **argv) if (argc > 1) { const char *file_path = argv[1]; - err = editor_load_from_file(&editor, file_path); + err = find_file(&editor, file_path, 0, 0); if (err != 0) { fprintf(stderr, "ERROR: Could not read file %s: %s\n", file_path, strerror(err)); return 1; @@ -154,62 +348,60 @@ int main(int argc, char **argv) } simple_renderer_init(&sr); + + free_glyph_atlas_init(&atlas, face); editor.atlas = &atlas; editor_retokenize(&editor); - bool quit = false; + + /* bool quit = false; */ bool file_browser = false; + while (!quit) { const Uint32 start = SDL_GetTicks(); SDL_Event event = {0}; while (SDL_PollEvent(&event)) { switch (event.type) { - case SDL_QUIT: { - quit = true; - } - break; - - case SDL_KEYDOWN: { - if (file_browser) { - switch (event.key.keysym.sym) { - case SDLK_F3: { - file_browser = false; - } - break; + case SDL_QUIT: + quit = true; + break; - case SDLK_UP: { - if (fb.cursor > 0) fb.cursor -= 1; + case SDL_KEYDOWN: + if (current_mode == NORMAL) { + if (handle_evil_find_char(&editor, &event)) { + break; // Skip further processing if the key event was handled } - break; + } - case SDLK_DOWN: { - if (fb.cursor + 1 < fb.files.count) fb.cursor += 1; - } - break; + if (file_browser) { + switch (event.key.keysym.sym) { + case SDLK_F3: { + file_browser = false; + } break; - case SDLK_RETURN: { - const char *file_path = fb_file_path(&fb); - if (file_path) { - File_Type ft; - err = type_of_file(file_path, &ft); - if (err != 0) { - flash_error("Could not determine type of file %s: %s", file_path, strerror(err)); - } else { - switch (ft) { - case FT_DIRECTORY: { - err = fb_change_dir(&fb); - if (err != 0) { - flash_error("Could not change directory to %s: %s", file_path, strerror(err)); - } + case SDLK_RETURN: { + const char *file_path = fb_file_path(&fb); + if (file_path) { + File_Type ft; + err = type_of_file(file_path, &ft); + if (err != 0) { + flash_error("Could not determine type of file %s: %s", file_path, strerror(err)); + } else { + switch (ft) { + case FT_DIRECTORY: { + err = fb_change_dir(&fb); + if (err != 0) { + flash_error("Could not change directory to %s: %s", file_path, strerror(err)); } - break; + } + break; case FT_REGULAR: { // TODO: before opening a new file make sure you don't have unsaved changes // And if you do, annoy the user about it. (just like all the other editors do) - err = editor_load_from_file(&editor, file_path); + err = find_file(&editor, file_path, 0, 0); if (err != 0) { flash_error("Could not open file %s: %s", file_path, strerror(err)); } else { @@ -230,208 +422,2194 @@ int main(int argc, char **argv) } } break; + + case SDLK_EQUALS: { + if (SDL_GetModState() & KMOD_ALT) { + theme_next(¤tThemeIndex); + printf("Changed theme to %d\n", currentThemeIndex); + } else if (SDL_GetModState() & KMOD_CTRL) { + zoom_factor -= 0.8f; + if (zoom_factor < min_zoom_factor) { + zoom_factor = min_zoom_factor; } - } else { + } + } break; + + case SDLK_MINUS: { + if (SDL_GetModState() & KMOD_ALT) { + theme_previous(¤tThemeIndex); + printf("Changed theme back to %d\n", currentThemeIndex); + } else if (SDL_GetModState() & KMOD_CTRL) { + zoom_factor += 0.8f; + if (zoom_factor > max_zoom_factor) { + zoom_factor = max_zoom_factor; + } + } + } break; + + case SDLK_d: { + if (SDL_GetModState() & KMOD_CTRL) { + diredfl_mode = !diredfl_mode; + } + } break; + + + case SDLK_q: + case SDLK_ESCAPE: { + file_browser = false; + } break; + + + case SDLK_r: + if (event.key.keysym.mod & KMOD_CTRL) { + file_browser = false; + } + break; + + + case SDLK_F5: { + simple_renderer_reload_shaders(&sr); + } + break; + + case SDLK_t: { + if (SDL_GetModState() & KMOD_CTRL) { + followCursor = !followCursor; // Toggle the state + } + } + break; + + case SDLK_UP: + case SDLK_k: + case SDLK_p: + if (fb.cursor > 0) fb.cursor -= 1; + break; + + case SDLK_DOWN: + case SDLK_j: + case SDLK_n: + if (fb.cursor + 1 < fb.files.count) fb.cursor += 1; + break; + + case SDLK_LEFT: + case SDLK_b: + case SDLK_h: { + // Copy current directory path + char current_dir[PATH_MAX]; + strncpy(current_dir, fb.dir_path.items, fb.dir_path.count); + current_dir[fb.dir_path.count - 1] = '\0'; // Ensure null-termination + + // Get parent directory + char *parent = dirname(current_dir); + + // Open parent directory + Errno err = fb_open_dir(&fb, parent); + if (err != 0) { + // Handle error, for example, print out an error message. + } else { + fb.cursor = 0; // Reset cursor position in the new directory + } + } break; + + case SDLK_RIGHT: + case SDLK_f: + case SDLK_l: { + const char *file_path = fb_file_path(&fb); + if (file_path) { + File_Type ft; + err = type_of_file(file_path, &ft); + if (err != 0) { + flash_error("Could not determine type of file %s: %s", + file_path, strerror(err)); + } else { + switch (ft) { + case FT_DIRECTORY: { + err = fb_change_dir(&fb); + if (err != 0) { + flash_error("Could not change directory to %s: %s", + file_path, strerror(err)); + } + } break; + + case FT_REGULAR: { + // TODO: before opening a new file make sure you don't + // have unsaved changes And if you do, annoy the user + // about it. (just like all the other editors do) + err = find_file(&editor, file_path, 0, 0); + if (err != 0) { + flash_error("Could not open file %s: %s", file_path, + strerror(err)); + } else { + file_browser = false; + } + } break; + + case FT_OTHER: { + flash_error("%s is neither a regular file nor a " + "directory. We can't open it.", + file_path); + } break; + + default: + UNREACHABLE("unknown File_Type"); + } + } + } + } + break; + } + } else { + switch (current_mode) { + case EMACS: switch (event.key.keysym.sym) { - case SDLK_HOME: { + + case SDLK_l: editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); if (event.key.keysym.mod & KMOD_CTRL) { - editor_move_to_begin(&editor); + if (event.key.keysym.mod & KMOD_SHIFT) { + relativeLineNumbers = !relativeLineNumbers; + } else { + showLineNumbers = !showLineNumbers; + } + } else if (event.key.keysym.mod & KMOD_ALT) { + select_region_from_inside_braces(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_DOWN: + if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_down(&editor); } else { - editor_move_to_line_begin(&editor); + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_down(&editor); + } else { + editor_move_line_down(&editor); + } } editor.last_stroke = SDL_GetTicks(); - } break; + break; - case SDLK_END: { - editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); - if (event.key.keysym.mod & KMOD_CTRL) { - editor_move_to_end(&editor); + + case SDLK_UP: + if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_up(&editor); } else { - editor_move_to_line_end(&editor); + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_line_up(&editor); + + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_up(&editor); + } + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_RIGHT: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_char_right(&editor); + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_LEFT: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_char_left(&editor); + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_n: { + if (SDL_GetModState() & KMOD_CTRL) { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_line_down(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_paragraph_down(&editor); + + // Consume the next SDL_TEXTINPUT event for 'n' and 'N' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'n') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'N') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } editor.last_stroke = SDL_GetTicks(); } break; - case SDLK_BACKSPACE: { - editor_backspace(&editor); + + case SDLK_p: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (SDL_GetModState() & KMOD_CTRL){ + editor_move_line_up(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_move_paragraph_up(&editor); + + // Consume the next SDL_TEXTINPUT event for 'p' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'p') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_f: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (SDL_GetModState() & KMOD_CTRL) { + editor_move_char_right(&editor); + } editor.last_stroke = SDL_GetTicks(); - } break; - case SDLK_F2: { - if (editor.file_path.count > 0) { - err = editor_save(&editor); - if (err != 0) { - flash_error("Could not save currently edited file: %s", strerror(err)); - } - } else { - // TODO: ask the user for the path to save to in this situation - flash_error("Nowhere to save the text"); + case SDLK_b: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (SDL_GetModState() & KMOD_CTRL){ + editor_move_char_left(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_move_word_left(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_e: { + if (SDL_GetModState() & KMOD_CTRL) { + emacs_mwim_end(&editor); + editor.last_stroke = SDL_GetTicks(); + } } break; - case SDLK_F3: { - file_browser = true; + case SDLK_a: { + if (SDL_GetModState() & KMOD_CTRL) { + emacs_mwim_beginning(&editor); + editor.last_stroke = SDL_GetTicks(); + } } break; - case SDLK_F5: { - simple_renderer_reload_shaders(&sr); + case SDLK_y: { + if (SDL_GetModState() & KMOD_CTRL) { + editor_clipboard_paste(&editor); + killed_word_times = 0; + + editor.last_stroke = SDL_GetTicks(); + } } break; - case SDLK_RETURN: { - if (editor.searching) { - editor_stop_search(&editor); - } else { - editor_insert_char(&editor, '\n'); + case SDLK_g: { + if (SDL_GetModState() & KMOD_CTRL) { + if (editor.searching) { + editor_clear_mark(&editor); + editor_stop_search(&editor); + } editor.last_stroke = SDL_GetTicks(); + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); } } break; - case SDLK_DELETE: { - editor_delete(&editor); - editor.last_stroke = SDL_GetTicks(); + case SDLK_x: + if (event.key.keysym.mod & KMOD_ALT) { + if (!M_x_active) { + current_mode = MINIBUFFER; + M_x_active = true; + editor.minibuffer_active = true; + + // Consume the next SDL_TEXTINPUT event for 'x' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'x') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } + } + break; + + + case SDLK_RETURN: { + editor_enter(&editor); } break; - case SDLK_f: { - if (event.key.keysym.mod & KMOD_CTRL) { - editor_start_search(&editor); + case SDLK_j: { + if (SDL_GetModState() & KMOD_CTRL) { + editor_enter(&editor); } } break; - case SDLK_ESCAPE: { - editor_stop_search(&editor); - editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + case SDLK_d: { + if (SDL_GetModState() & KMOD_CTRL) { + if (SDL_GetModState() & KMOD_SHIFT) { + emacs_kill_word(&editor); + } else { + emacs_delete_char(&editor); + } + editor.last_stroke = SDL_GetTicks(); + } } break; - - case SDLK_a: { + + case SDLK_BACKSPACE: if (event.key.keysym.mod & KMOD_CTRL) { - editor.selection = true; - editor.select_begin = 0; - editor.cursor = editor.data.count; - } + emacs_backward_kill_word(&editor); + editor.last_stroke = SDL_GetTicks(); + }else{ + editor_backspace(&editor); + editor.last_stroke = SDL_GetTicks(); } break; - case SDLK_TAB: { - // TODO: indent on Tab instead of just inserting 4 spaces at the cursor - // That is insert the spaces at the beginning of the line. Shift+TAB should - // do unindent, that is remove 4 spaces from the beginning of the line. - // TODO: customizable indentation style - // - tabs/spaces - // - tab width - // - etc. - for (size_t i = 0; i < 4; ++i) { - editor_insert_char(&editor, ' '); + + case SDLK_k: { + if (SDL_GetModState() & KMOD_CTRL) { + emacs_kill_line(&editor); } } break; - case SDLK_c: { - if (event.key.keysym.mod & KMOD_CTRL) { - editor_clipboard_copy(&editor); + case SDLK_o: { + if (SDL_GetModState() & KMOD_CTRL) { + emacs_open_line(&editor); + } } break; - case SDLK_v: { - if (event.key.keysym.mod & KMOD_CTRL) { - editor_clipboard_paste(&editor); + case SDLK_t: { + if (SDL_GetModState() & KMOD_CTRL) { + followCursor = !followCursor; } } break; - case SDLK_UP: { - editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); - if (event.key.keysym.mod & KMOD_CTRL) { - editor_move_paragraph_up(&editor); - } else { - editor_move_line_up(&editor); - } + case SDLK_TAB: { + activate_snippet(&editor); editor.last_stroke = SDL_GetTicks(); } break; + + case SDLK_EQUALS: { + if (SDL_GetModState() & KMOD_ALT) { // Check if ALT is pressed + theme_next(¤tThemeIndex); + printf("Changed theme to %d\n", currentThemeIndex); // Logging the theme change for debugging + } else if (SDL_GetModState() & KMOD_CTRL) { // Check if CTRL is pressed + zoom_factor -= 1.0f; + if (zoom_factor < min_zoom_factor) { + zoom_factor = min_zoom_factor; + } + printf("zoom_factor: %.6f", zoom_factor); + } + } break; - case SDLK_DOWN: { - editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); - if (event.key.keysym.mod & KMOD_CTRL) { - editor_move_paragraph_down(&editor); - } else { - editor_move_line_down(&editor); + case SDLK_MINUS: { + if (SDL_GetModState() & KMOD_ALT) { // Check if ALT is pressed + theme_previous(¤tThemeIndex); + printf("Changed theme back to %d\n", currentThemeIndex); // Logging the theme change for debugging + } else if (SDL_GetModState() & KMOD_CTRL) { // Check if CTRL is pressed + zoom_factor += 1.0f; + if (zoom_factor > max_zoom_factor) { + zoom_factor = max_zoom_factor; + } + printf("zoom_factor: %.6f", zoom_factor); } - editor.last_stroke = SDL_GetTicks(); - } - break; + } break; - case SDLK_LEFT: { - editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); - if (event.key.keysym.mod & KMOD_CTRL) { - editor_move_word_left(&editor); - } else { - editor_move_char_left(&editor); + case SDLK_z: { + if (SDL_GetModState() & KMOD_CTRL) { + current_mode = NORMAL; + editor.last_stroke = SDL_GetTicks(); } - editor.last_stroke = SDL_GetTicks(); } break; - case SDLK_RIGHT: { - editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + case SDLK_s: { if (event.key.keysym.mod & KMOD_CTRL) { - editor_move_word_right(&editor); - } else { - editor_move_char_right(&editor); + editor_start_search(&editor); } - editor.last_stroke = SDL_GetTicks(); - } + }} break; - } - } - } - break; - case SDL_TEXTINPUT: { + case NORMAL: + switch (event.key.keysym.sym) { + SDL_Event tmpEvent; // Declare once at the beginning of the switch block + + case SDLK_RETURN: { + if (!toggle_bool(&editor)) { + editor_open_include(&editor); + } + } break; + + + case SDLK_SEMICOLON: + if (event.key.keysym.mod & KMOD_SHIFT) { + current_mode = MINIBUFFER; + evil_command_active = true; + editor.minibuffer_active = true; + + // Consume the next SDL_TEXTINPUT event for ':' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != ':') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } + break; + + case SDLK_d: + if (event.key.keysym.mod & KMOD_SHIFT) { + emacs_kill_line(&editor); + } else if (editor.selection) { + editor_clipboard_copy(&editor); + editor_delete_selection(&editor); + editor.selection = false; + } else if (event.key.keysym.mod & KMOD_CTRL) { + evil_delete_char(&editor); + } else { + emacs_kill_line(&editor); + } + break; + + case SDLK_c: + if (event.key.keysym.mod & KMOD_CTRL) { + centeredText = !centeredText; + } else if (event.key.keysym.mod & KMOD_ALT) { + instantCamera = !instantCamera; + } else if (event.key.keysym.mod & KMOD_SHIFT) { + evil_change_line(&editor); + + // Eat up the next SDL_TEXTINPUT event for 'C' + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || + (tmpEvent.text.text[0] != 'C')) { + SDL_PushEvent(&tmpEvent); // Push it back to the event queue if it's not + } + } + break; + + case SDLK_m: + if (event.key.keysym.mod & KMOD_ALT) { + emacs_back_to_indentation(&editor); + } + break; + + case SDLK_ESCAPE: { + if (fzy) { + minibufferHeight -= 189; + fzy = false; + } + + mixSelectionColor = false ; + editor_clear_mark(&editor); + editor_stop_search(&editor); + + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + } + break; + + case SDLK_SPACE: { + if (SDL_GetModState() & KMOD_CTRL) { + if (!editor.has_anchor){ + editor_set_anchor(&editor); + } else { + editor_goto_anchor_and_clear(&editor); + } + } else if (!fzy) { + // TODO time delay whichkey + minibufferHeight += 189; + fzy = true; + } + } + break; + + case SDLK_5: { + if (SDL_GetModState() & KMOD_SHIFT) { + evil_jump_item(&editor); + } + } + break; + + case SDLK_8: { + if (SDL_GetModState() & KMOD_SHIFT) { + evil_search_word_forward(&editor); + } + } + break; + + + case SDLK_1: { + if (SDL_GetModState() & KMOD_CTRL) { + hl_line = !hl_line; + } + } + break; + + + case SDLK_o: + if (SDL_GetModState() & KMOD_SHIFT) { + evil_open_above(&editor); + } else { + evil_open_below(&editor); + } + + current_mode = INSERT; + editor.last_stroke = SDL_GetTicks(); + + // Eat up the next SDL_TEXTINPUT event for 'o' or 'O' + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || + (tmpEvent.text.text[0] != 'o' && tmpEvent.text.text[0] != 'O')) { + SDL_PushEvent(&tmpEvent); // Push it back to the event queue if it's not + } + break; + + case SDLK_LEFTBRACKET: + if (SDL_GetModState() & KMOD_ALT) { + switch_to_font(library, &face, &atlas, -1); + printf("Switched to previous font: %s\n", fonts[current_font_index]); + /* redraw_screen(); */ + } + break; + + case SDLK_RIGHTBRACKET: + if (SDL_GetModState() & KMOD_ALT) { + switch_to_font(library, &face, &atlas, 1); + printf("Switched to next font: %s\n", fonts[current_font_index]); + /* redraw_screen(); */ + } + break; + + + case SDLK_TAB: { + indent(&editor); + } + break; + + case SDLK_z: { + if (SDL_GetModState() & KMOD_CTRL) { + /* helix_mode(); */ + current_mode = EMACS; + editor.last_stroke = SDL_GetTicks(); + } + } + break; + + case SDLK_t: { + if (SDL_GetModState() & KMOD_CTRL) { + followCursor = !followCursor; + } + } + break; + + + case SDLK_F5: { + simple_renderer_reload_shaders(&sr); + } + break; + + case SDLK_y: + if (editor.selection) { + editor_clipboard_copy(&editor); + } else { + evil_yank_line(&editor); + } + break; + + + case SDLK_g: { + if (SDL_GetModState() & KMOD_SHIFT) { + editor_move_to_end(&editor); + } else { + editor_move_to_begin(&editor); + } + } break; + + case SDLK_SLASH: { + current_mode = MINIBUFFER; + editor.last_stroke = SDL_GetTicks(); + editor_start_search(&editor); + + // Consume the next SDL_TEXTINPUT event for '/' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != '/') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } break; + + case SDLK_n: { + if (SDL_GetModState() & KMOD_CTRL) { + editor_move_line_down(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_next_buffer(&editor); + } else if (SDL_GetModState() & KMOD_SHIFT) { + evil_search_previous(&editor); + } else { + evil_search_next(&editor); + } + } break; + + case SDLK_p: + if (SDL_GetModState() & KMOD_CTRL){ + editor_move_line_up(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_previous_buffer(&editor); + } else if (copiedLine) { + if (SDL_GetModState() & KMOD_SHIFT) { + evil_paste_before(&editor); + } else { + evil_paste_after(&editor); + } + } else { + editor_clipboard_paste(&editor); + } + break; + + case SDLK_b: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (SDL_GetModState() & KMOD_CTRL){ + editor_move_char_left(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_kill_buffer(&editor); + } else { + editor_move_word_left(&editor); + } + break; + + case SDLK_f: + if (SDL_GetModState() & KMOD_CTRL) { + /* followCursor = !followCursor; */ + /* editor_move_char_right(&editor); */ + showFillColumn = !showFillColumn; + } + break; + + case SDLK_s: { + if (event.key.keysym.mod & KMOD_CTRL) { + if (ctrl_x_pressed) { + editor_save(&editor); + } else { + editor_start_search(&editor); + current_mode = MINIBUFFER; + } + } else { + // Either S or Shift+S is pressed + if (event.key.keysym.mod & KMOD_SHIFT) { + evil_change_whole_line(&editor); + } else { + evil_substitute(&editor); + } + editor.selection = false; + // Eat up the next SDL_TEXTINPUT event for 's' or 'S' + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || + (tmpEvent.text.text[0] != 's' && tmpEvent.text.text[0] != 'S')) { + SDL_PushEvent(&tmpEvent); // Push it back to the event queue if it's not + } + editor.last_stroke = SDL_GetTicks(); + } + break; + } + + case SDLK_EQUALS: { + if (SDL_GetModState() & KMOD_ALT) { // Check if ALT is pressed + theme_next(¤tThemeIndex); + printf("Changed theme to %d\n", currentThemeIndex); // Logging the theme change for debugging + } else if (SDL_GetModState() & KMOD_CTRL) { // Check if CTRL is pressed + zoom_factor -= 1.0f; + if (zoom_factor < min_zoom_factor) { + zoom_factor = min_zoom_factor; + } + printf("zoom_factor: %.6f", zoom_factor); + } + } break; + + case SDLK_MINUS: { + if (SDL_GetModState() & KMOD_ALT) { // Check if ALT is pressed + theme_previous(¤tThemeIndex); + printf("Changed theme back to %d\n", currentThemeIndex); // Logging the theme change for debugging + } else if (SDL_GetModState() & KMOD_CTRL) { // Check if CTRL is pressed + zoom_factor += 1.0f; + if (zoom_factor > max_zoom_factor) { + zoom_factor = max_zoom_factor; + } + printf("zoom_factor: %.6f", zoom_factor); + } + } break; + + case SDLK_i: + if (SDL_GetModState() & KMOD_CTRL) { + showIndentationLines = !showIndentationLines; + } else if (SDL_GetModState() & KMOD_ALT) { + if (SDL_GetModState() & KMOD_SHIFT) { + remove_one_indentation(&editor); + } else { + add_one_indentation(&editor); + } + } else { + if (SDL_GetModState() & KMOD_SHIFT) { + evil_insert_line(&editor); + } else { + current_mode = INSERT; + } + + editor.last_stroke = SDL_GetTicks(); + + // Eat up the next SDL_TEXTINPUT event for 'i' or 'I' + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || + (tmpEvent.text.text[0] != 'i' && tmpEvent.text.text[0] != 'I')) { + SDL_PushEvent(&tmpEvent); // Push it back to the event queue if it's not + } + } + break; + + case SDLK_v: { + if (SDL_GetModState() & KMOD_SHIFT) { + current_mode = VISUAL_LINE; + evil_visual_line(&editor); + } else { + current_mode = VISUAL; + evil_visual_char(&editor); + } + } break; + + case SDLK_4: { + if (SDL_GetModState() & KMOD_SHIFT) { + editor_move_to_line_end(&editor); + } + } break; + + case SDLK_a: + editor.last_stroke = SDL_GetTicks(); + if (SDL_GetModState() & KMOD_CTRL) { + automatic_zoom = !automatic_zoom; + break; + } else if (SDL_GetModState() & KMOD_SHIFT) { + editor_move_to_line_end(&editor); + } else { + editor_move_char_right(&editor); + } + + current_mode = INSERT; + + // Eat up the next SDL_TEXTINPUT event for 'a' or 'A' + SDL_PollEvent(&tmpEvent); // This will typically be the SDL_TEXTINPUT event for 'a' or 'A' + if (tmpEvent.type != SDL_TEXTINPUT || (tmpEvent.text.text[0] != 'a' && tmpEvent.text.text[0] != 'A')) { + SDL_PushEvent(&tmpEvent); // If it's not, push it back to the event queue + } + break; + + case SDLK_x: + if (editor.selection) { + editor_clipboard_copy(&editor); + editor_delete_selection(&editor); + editor.selection = false; + } else if (event.key.keysym.mod & KMOD_ALT) { + if (!M_x_active) { + current_mode = MINIBUFFER; + M_x_active = true; + editor.minibuffer_active = true; + + // Consume the next SDL_TEXTINPUT event for 'x' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'x') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } + + // TODO fzy + /* if (!fzy) { */ + /* minibufferHeight += 189; */ + /* fzy = true; */ + /* } */ + } else if (event.key.keysym.mod & KMOD_SHIFT) { + evil_delete_backward_char(&editor); + } else if (event.key.keysym.mod & KMOD_CTRL) { + ctrl_x_pressed = true; + } else { + editor_clipboard_copy(&editor); + evil_delete_char(&editor); + } + break; + + case SDLK_0: + editor_move_to_line_begin(&editor); + break; + + case SDLK_F3: + file_browser = true; + break; + + case SDLK_r: + if (event.key.keysym.mod & KMOD_CTRL) { + file_browser = true; + } + break; + + case SDLK_BACKSPACE: // yes you can delete in normal mode + if (editor.selection) { + editor_clipboard_copy(&editor); + editor_delete_selection(&editor); + editor.selection = false; + } else if (event.key.keysym.mod & KMOD_CTRL) { + emacs_backward_kill_word(&editor); + } else { + emacs_ungry_delete_backwards(&editor); + /* editor_backspace(&editor); */ + } + break; + + + case SDLK_j: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if ((event.key.keysym.mod & KMOD_CTRL) && (event.key.keysym.mod & KMOD_ALT)) { + evil_open_above(&editor); + } else if (event.key.keysym.mod & KMOD_CTRL) { + if (ctrl_x_pressed) { + file_browser = true; + ctrl_x_pressed = false; + } else { + editor_enter(&editor); + } + } else if ((event.key.keysym.mod & KMOD_ALT) && !followCursor) { + move_camera(&sr, "down", 50.0f); + } else if ((event.key.keysym.mod & KMOD_SHIFT) && !(event.key.keysym.mod & KMOD_ALT)) { + evil_join(&editor); + } else if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_down(&editor); + } else { + editor_move_line_down(&editor); + } + mixSelectionColor = false; + editor.last_stroke = SDL_GetTicks(); + break; + + + + case SDLK_k: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if ((event.key.keysym.mod & KMOD_ALT) && !followCursor) { + move_camera(&sr, "up", 50.0f); + } else if (event.key.keysym.mod & KMOD_CTRL) { + emacs_kill_line(&editor); + } else if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_up(&editor); + } else if (event.key.keysym.mod & KMOD_SHIFT) { + goto_definition(&editor, &fb); + } else { + editor_move_line_up(&editor); + } + mixSelectionColor = false; + editor.last_stroke = SDL_GetTicks(); + break; + + + case SDLK_h: + if (event.key.keysym.mod & KMOD_ALT) { + // If Alt is held, check if char under cursor is { or } and not editor->selection + emacs_mark_paragraph(&editor); + } else { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_backspace(&editor); + } else { + editor_move_char_left(&editor); + } + // Toggle mixSelectionColor when Shift is pressed without Ctrl or Alt + mixSelectionColor = (event.key.keysym.mod & KMOD_SHIFT) && !(event.key.keysym.mod & KMOD_CTRL) && !(event.key.keysym.mod & KMOD_ALT); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_l: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + if (event.key.keysym.mod & KMOD_SHIFT) { + relativeLineNumbers = !relativeLineNumbers; + } else if (event.key.keysym.mod & KMOD_ALT) { + showLineNumbersBackground = !showLineNumbersBackground; + } else { + showLineNumbers = !showLineNumbers; + } + + } else if (event.key.keysym.mod & KMOD_ALT) { + select_region_from_inside_braces(&editor); + } else { + editor_move_char_right(&editor); + // Toggle mixSelectionColor when Shift is pressed + // without Ctrl or Alt + mixSelectionColor = + (event.key.keysym.mod & KMOD_SHIFT) && + !(event.key.keysym.mod & KMOD_CTRL) && + !(event.key.keysym.mod & KMOD_ALT); + } + editor.last_stroke = SDL_GetTicks(); + break; + + + + case SDLK_DOWN: + if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_down(&editor); + } else { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_down(&editor); + } else { + editor_move_line_down(&editor); + } + } + break; + + + case SDLK_UP: + if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_up(&editor); + } else { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_line_up(&editor); + + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_up(&editor); + } + } + break; + + + + case SDLK_RIGHT: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_char_right(&editor); + break; + + case SDLK_LEFT: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_char_left(&editor); + break; + + + case SDLK_w: + if (event.key.keysym.mod & KMOD_CTRL) { + showWhitespaces = !showWhitespaces; + }else{ + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_word_right(&editor); + } + break; + + case SDLK_e: + if (event.key.keysym.mod & KMOD_CTRL) { + isWave = !isWave; + /* current_mode = EMACS; */ + /* editor.last_stroke = SDL_GetTicks(); */ + } + break; + + // additional NORMAL mode keybinds here... + } break; + + + case INSERT: + switch (event.key.keysym.sym) { + SDL_Event tmpEvent; + + + case SDLK_DOWN: + if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_down(&editor); + } else { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_down(&editor); + } else { + editor_move_line_down(&editor); + } + } + editor.last_stroke = SDL_GetTicks(); + break; + + + case SDLK_UP: + if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_up(&editor); + } else { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_line_up(&editor); + + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_up(&editor); + } + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_RIGHT: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_char_right(&editor); + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_LEFT: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_char_left(&editor); + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_n: { + if (SDL_GetModState() & KMOD_CTRL) { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_line_down(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_paragraph_down(&editor); + + // Consume the next SDL_TEXTINPUT event for 'n' and 'N' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'n') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'N') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + + } + editor.last_stroke = SDL_GetTicks(); + } break; + + + case SDLK_p: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (SDL_GetModState() & KMOD_CTRL){ + editor_move_line_up(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_move_paragraph_up(&editor); + + // Consume the next SDL_TEXTINPUT event for 'p' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'p') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_f: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (SDL_GetModState() & KMOD_CTRL) { + editor_move_char_right(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_b: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (SDL_GetModState() & KMOD_CTRL){ + editor_move_char_left(&editor); + } else { + editor_move_word_left(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_e: { + if (SDL_GetModState() & KMOD_CTRL) { + emacs_mwim_end(&editor); + editor.last_stroke = SDL_GetTicks(); + + } + } + break; + + case SDLK_a: { + if (SDL_GetModState() & KMOD_CTRL) { + emacs_mwim_beginning(&editor); + editor.last_stroke = SDL_GetTicks(); + } + } + break; + + case SDLK_y: { + if (SDL_GetModState() & KMOD_CTRL) { + editor_clipboard_paste(&editor); + killed_word_times = 0; + + editor.last_stroke = SDL_GetTicks(); + } + } + break; + + case SDLK_g: { + if (SDL_GetModState() & KMOD_CTRL) { + if (editor.searching) { + editor_clear_mark(&editor); + editor_stop_search(&editor); + } + editor.last_stroke = SDL_GetTicks(); + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + } + } + break; + + case SDLK_x: + if (event.key.keysym.mod & KMOD_ALT) { + if (!M_x_active) { + current_mode = MINIBUFFER; + M_x_active = true; + editor.minibuffer_active = true; + + // Consume the next SDL_TEXTINPUT event for 'x' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'x') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } + } + break; + + + + case SDLK_c: { + if (SDL_GetModState() & KMOD_CTRL) { + evil_complete_next(&editor); + editor.last_stroke = SDL_GetTicks(); + } + } + break; + + + case SDLK_SPACE: { + if (SDL_GetModState() & KMOD_CTRL) { + if (!editor.has_anchor){ + editor_set_anchor(&editor); + } else { + editor_goto_anchor_and_clear(&editor); + } + } + } + break; + + case SDLK_i: + if (SDL_GetModState() & KMOD_ALT) { + if (SDL_GetModState() & KMOD_SHIFT) { + remove_one_indentation(&editor); + } else { + add_one_indentation(&editor); + } + + editor.last_stroke = SDL_GetTicks(); + // Eat up the next SDL_TEXTINPUT event for 'i' or 'I' + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || + (tmpEvent.text.text[0] != 'i' && tmpEvent.text.text[0] != 'I')) { + SDL_PushEvent(&tmpEvent); // Push it back to the event queue if it's not + } + } + break; + + case SDLK_d: { + if (SDL_GetModState() & KMOD_CTRL) { + if (SDL_GetModState() & KMOD_SHIFT) { + emacs_kill_word(&editor); + } else { + emacs_delete_char(&editor); + } + editor.last_stroke = SDL_GetTicks(); + } + } + break; + + case SDLK_k: { + if (SDL_GetModState() & KMOD_CTRL) { + emacs_kill_line(&editor); + } + } + break; + + case SDLK_o: { + if (SDL_GetModState() & KMOD_CTRL) { + emacs_open_line(&editor); + + } + } + break; + + case SDLK_t: { + if (SDL_GetModState() & KMOD_CTRL) { + followCursor = !followCursor; + } + } + break; + + case SDLK_TAB: { + activate_snippet(&editor); + break; + } + + case SDLK_F3: + file_browser = true; + break; + + case SDLK_MINUS: + if (SDL_GetModState() & KMOD_CTRL) { + zoom_factor += 1.0f; + + + if (zoom_factor > max_zoom_factor) { + zoom_factor = max_zoom_factor; + } + + printf("zoom_factor = %f\n", zoom_factor); + // Consume the next SDL_TEXTINPUT event for '-' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (!(tmpEvent.type == SDL_TEXTINPUT && tmpEvent.text.text[0] == '-')) { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } + break; + + case SDLK_EQUALS: + if (SDL_GetModState() & KMOD_CTRL) { + zoom_factor -= 1.0f; + + printf("zoom_factor = %f\n", zoom_factor); + + if (zoom_factor < min_zoom_factor) { + zoom_factor = min_zoom_factor; + } + + // Consume the next SDL_TEXTINPUT event for '=' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (!(tmpEvent.type == SDL_TEXTINPUT && tmpEvent.text.text[0] == '=')) { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } + break; + + case SDLK_BACKSPACE: + if (editor.selection) { + editor_clipboard_copy(&editor); + editor_delete_selection(&editor); + editor.selection = false; + } else if (event.key.keysym.mod & KMOD_CTRL) { + emacs_backward_kill_word(&editor); + editor.last_stroke = SDL_GetTicks(); + }else if (event.key.keysym.mod & KMOD_ALT) { + emacs_ungry_delete_backwards(&editor); + } else { + editor_backspace(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_RETURN: + editor_enter(&editor); + break; + + case SDLK_s: { + if (event.key.keysym.mod & KMOD_CTRL) { + editor_start_search(&editor); + } + } + break; + + case SDLK_ESCAPE: { + if (editor.searching) { + editor_clear_mark(&editor); + editor_stop_search(&editor); + } else if (editor.minibuffer_active) { + editor.minibuffer_text.count = 0; + M_x_active = false; + editor.minibuffer_active = false; + } + + current_mode = NORMAL; + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + } + break; + + + case SDLK_F5: { + simple_renderer_reload_shaders(&sr); + } + break; + + case SDLK_v: + if (event.key.keysym.mod & KMOD_CTRL) { + editor_clipboard_paste(&editor); + } + break; + + + } + break; + + case VISUAL: + switch (event.key.keysym.sym) { + + case SDLK_y: + if (editor.selection) { + editor_clipboard_copy(&editor); + } + break; + + case SDLK_x: + if (editor.selection) { + editor_clipboard_copy(&editor); + editor_delete_selection(&editor); + editor.selection = false; + current_mode = NORMAL; + + } + break; + + case SDLK_j: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_down(&editor); + } else { + editor_move_line_down(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_h: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_word_left(&editor); + } else { + editor_move_char_left(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_k: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_up(&editor); + } else { + editor_move_line_up(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_l: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_word_right(&editor); + } else { + editor_move_char_right(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_ESCAPE: + editor.selection = false; + current_mode = NORMAL; + break; + } + break; + + // additional VISUAL mode keybinds here... + + case VISUAL_LINE: + switch (event.key.keysym.sym) { + + case SDLK_j: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_down(&editor); + } else { + editor_move_line_down(&editor); + + size_t cursor_row = editor_cursor_row(&editor); + if (cursor_row < editor.lines.count) { + // Set cursor to the end of this new line + Line current_line = editor.lines.items[cursor_row]; + editor.cursor = current_line.end; + } + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_h: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_word_left(&editor); + } else { + editor_move_char_left(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_k: // Up + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_up(&editor); + } else { + editor_move_line_up(&editor); + size_t cursor_row = editor_cursor_row(&editor); + if (cursor_row < editor.lines.count) { + // Set cursor to the end of this new line + Line current_line = editor.lines.items[cursor_row]; + editor.cursor = current_line.begin; + } + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_l: // Right + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_word_right(&editor); + } else { + editor_move_char_right(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + // Transition back to NORMAL mode + case SDLK_ESCAPE: + current_mode = NORMAL; + break; + + // Add additional VISUAL_LINE mode keybinds here... + } + break; + + // TODO + case HELIX: + switch (event.key.keysym.sym) { + SDL_Event tmpEvent; // Declare once at the beginning of the switch block + + case SDLK_RETURN: { + if (!toggle_bool(&editor)) { + editor_open_include(&editor); + } + } break; + + case SDLK_SEMICOLON: + if (event.key.keysym.mod & KMOD_SHIFT) { + current_mode = MINIBUFFER; + evil_command_active = true; + editor.minibuffer_active = true; + + // Consume the next SDL_TEXTINPUT event for ':' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != ':') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + + // TODO fzy + /* if (!fzy) { */ + /* minibufferHeight += 189; */ + /* fzy = true; */ + /* } */ + } + break; + + + + case SDLK_d: + if (event.key.keysym.mod & KMOD_SHIFT) { + emacs_kill_line(&editor); + } else if (editor.selection) { + editor_clipboard_copy(&editor); + editor_delete_selection(&editor); + editor.selection = false; + } else { + emacs_kill_line(&editor); + } + break; + + case SDLK_c: + if (event.key.keysym.mod & KMOD_SHIFT) { + evil_change_line(&editor); + } + + // Eat up the next SDL_TEXTINPUT event for 'C' + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || + (tmpEvent.text.text[0] != 'C')) { + SDL_PushEvent(&tmpEvent); // Push it back to the event queue if it's not + } + break; + + case SDLK_m: + if (event.key.keysym.mod & KMOD_ALT) { + emacs_back_to_indentation(&editor); + } + break; + + + + case SDLK_ESCAPE: { + if (fzy) { + minibufferHeight -= 189; + fzy = false; + } + + if (editor.minibuffer_active) { + M_x_active = false; + editor.minibuffer_active = false; + } + + editor_clear_mark(&editor); + editor_stop_search(&editor); + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + } + break; + + + case SDLK_SPACE: { + if (SDL_GetModState() & KMOD_CTRL) { + if (!editor.has_anchor){ + editor_set_anchor(&editor); + } else { + editor_goto_anchor_and_clear(&editor); + } + } else if (!fzy) { + // TODO time delay whichkey + minibufferHeight += 189; + fzy = true; + } + } + break; + + case SDLK_5: { + if (SDL_GetModState() & KMOD_SHIFT) { + evil_jump_item(&editor); + } + } + break; + + case SDLK_8: { + if (SDL_GetModState() & KMOD_SHIFT) { + evil_search_word_forward(&editor); + } + } + break; + + + case SDLK_1: { + if (SDL_GetModState() & KMOD_CTRL) { + hl_line = !hl_line; + } + } + break; + + + case SDLK_o: + if (SDL_GetModState() & KMOD_SHIFT) { + evil_open_above(&editor); + } else { + evil_open_below(&editor); + } + + current_mode = INSERT; + editor.last_stroke = SDL_GetTicks(); + + // Eat up the next SDL_TEXTINPUT event for 'o' or 'O' + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || + (tmpEvent.text.text[0] != 'o' && tmpEvent.text.text[0] != 'O')) { + SDL_PushEvent(&tmpEvent); // Push it back to the event queue if it's not + } + break; + + case SDLK_LEFTBRACKET: + if (SDL_GetModState() & KMOD_ALT) { + switch_to_font(library, &face, &atlas, -1); + printf("Switched to previous font: %s\n", fonts[current_font_index]); + /* redraw_screen(); */ + } + break; + + case SDLK_RIGHTBRACKET: + if (SDL_GetModState() & KMOD_ALT) { + switch_to_font(library, &face, &atlas, 1); + printf("Switched to next font: %s\n", fonts[current_font_index]); + /* redraw_screen(); */ + } + break; + + + case SDLK_TAB: { + indent(&editor); + } + break; + + case SDLK_z: { + if (SDL_GetModState() & KMOD_CTRL) { + /* current_mode = EMACS; */ + /* current_mode = NORMAL; */ + helix_mode(); + } + } + break; + + case SDLK_t: { + if (SDL_GetModState() & KMOD_CTRL) { + followCursor = !followCursor; // Toggle the state + } + } + break; + + + case SDLK_F5: { + simple_renderer_reload_shaders(&sr); + } + break; + + case SDLK_y: + if (editor.selection) { + editor_clipboard_copy(&editor); + } else { + evil_yank_line(&editor); + } + break; + + + case SDLK_g: { + if (SDL_GetModState() & KMOD_SHIFT) { + editor_move_to_end(&editor); + } else { + editor_move_to_begin(&editor); + } + } break; + + case SDLK_SLASH: { + current_mode = MINIBUFFER; + editor.last_stroke = SDL_GetTicks(); + editor_start_search(&editor); + + // Consume the next SDL_TEXTINPUT event for '/' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != '/') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } break; + + case SDLK_n: { + if (SDL_GetModState() & KMOD_CTRL) { + editor_move_line_down(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_next_buffer(&editor); + } else if (SDL_GetModState() & KMOD_SHIFT) { + evil_search_previous(&editor); + } else { + evil_search_next(&editor); + } + } break; + + case SDLK_p: + if (SDL_GetModState() & KMOD_CTRL){ + editor_move_line_up(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_previous_buffer(&editor); + } else if (copiedLine) { + if (SDL_GetModState() & KMOD_SHIFT) { + evil_paste_before(&editor); + } else { + evil_paste_after(&editor); + } + } else { + editor_clipboard_paste(&editor); + } + break; + + case SDLK_b: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (SDL_GetModState() & KMOD_CTRL){ + editor_move_char_left(&editor); + } else if (SDL_GetModState() & KMOD_ALT) { + editor_kill_buffer(&editor); + } else { + editor_move_word_left(&editor); + } + break; + + case SDLK_f: + if (SDL_GetModState() & KMOD_CTRL){ + editor_move_char_right(&editor); + } + break; + + case SDLK_s: { + if (event.key.keysym.mod & KMOD_CTRL) { + // Ctrl+S is pressed + editor_start_search(&editor); + current_mode = MINIBUFFER; + } else { + // Either S or Shift+S is pressed + if (event.key.keysym.mod & KMOD_SHIFT) { + evil_change_whole_line(&editor); + } else { + evil_substitute(&editor); + } + editor.selection = false; + // Eat up the next SDL_TEXTINPUT event for 's' or 'S' + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || + (tmpEvent.text.text[0] != 's' && tmpEvent.text.text[0] != 'S')) { + SDL_PushEvent(&tmpEvent); // Push it back to the event queue if it's not + } + editor.last_stroke = SDL_GetTicks(); + } + break; + } + + case SDLK_EQUALS: { + if (SDL_GetModState() & KMOD_ALT) { // Check if ALT is pressed + theme_next(¤tThemeIndex); + printf("Changed theme to %d\n", currentThemeIndex); // Logging the theme change for debugging + } else if (SDL_GetModState() & KMOD_CTRL) { // Check if CTRL is pressed + zoom_factor -= 1.0f; + if (zoom_factor < min_zoom_factor) { + zoom_factor = min_zoom_factor; + } + } + } break; + + case SDLK_MINUS: { + if (SDL_GetModState() & KMOD_ALT) { // Check if ALT is pressed + theme_previous(¤tThemeIndex); + printf("Changed theme back to %d\n", currentThemeIndex); // Logging the theme change for debugging + } else if (SDL_GetModState() & KMOD_CTRL) { // Check if CTRL is pressed + zoom_factor += 1.0f; + if (zoom_factor > max_zoom_factor) { + zoom_factor = max_zoom_factor; + } + } + } break; + + case SDLK_i: + if (SDL_GetModState() & KMOD_CTRL) { + showIndentationLines = !showIndentationLines; + } else if (SDL_GetModState() & KMOD_ALT) { + if (SDL_GetModState() & KMOD_SHIFT) { + remove_one_indentation(&editor); + } else { + add_one_indentation(&editor); + } + } else { + if (SDL_GetModState() & KMOD_SHIFT) { + evil_insert_line(&editor); + } else { + current_mode = INSERT; + } + + editor.last_stroke = SDL_GetTicks(); + + // Eat up the next SDL_TEXTINPUT event for 'i' or 'I' + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || + (tmpEvent.text.text[0] != 'i' && tmpEvent.text.text[0] != 'I')) { + SDL_PushEvent(&tmpEvent); // Push it back to the event queue if it's not + } + } + break; + + case SDLK_v: { + if (SDL_GetModState() & KMOD_SHIFT) { + current_mode = VISUAL_LINE; + evil_visual_line(&editor); + } else { + current_mode = VISUAL; + evil_visual_char(&editor); + } + } break; + + case SDLK_4: { + if (SDL_GetModState() & KMOD_SHIFT) { + editor_move_to_line_end(&editor); + } + } break; + + case SDLK_a: + editor.last_stroke = SDL_GetTicks(); + if (SDL_GetModState() & KMOD_SHIFT) { // Check if shift is being held + editor_move_to_line_end(&editor); + } else { + // Move the cursor one position to the right + editor_move_char_right(&editor); + } + + current_mode = INSERT; + + // Eat up the next SDL_TEXTINPUT event for 'a' or 'A' + SDL_PollEvent(&tmpEvent); // This will typically be the SDL_TEXTINPUT event for 'a' or 'A' + if (tmpEvent.type != SDL_TEXTINPUT || (tmpEvent.text.text[0] != 'a' && tmpEvent.text.text[0] != 'A')) { + SDL_PushEvent(&tmpEvent); // If it's not, push it back to the event queue + } + break; + + case SDLK_x: + if (editor.selection) { + editor_clipboard_copy(&editor); + editor_delete_selection(&editor); + editor.selection = false; + } else if (event.key.keysym.mod & KMOD_ALT) { + if (!M_x_active) { + current_mode = MINIBUFFER; + M_x_active = true; + editor.minibuffer_active = true; + + // Consume the next SDL_TEXTINPUT event for 'x' + SDL_Event tmpEvent; + SDL_PollEvent(&tmpEvent); + if (tmpEvent.type != SDL_TEXTINPUT || tmpEvent.text.text[0] != 'x') { + SDL_PushEvent(&tmpEvent); // Push the event back if it's not the one we're trying to consume + } + } + + // TODO fzy + /* if (!fzy) { */ + /* minibufferHeight += 189; */ + /* fzy = true; */ + /* } */ + } else if (event.key.keysym.mod & KMOD_SHIFT) { + evil_delete_backward_char(&editor); + } else { + editor_clipboard_copy(&editor); + evil_delete_char(&editor); + } + break; + + case SDLK_0: + editor_move_to_line_begin(&editor); + break; + + case SDLK_F3: + file_browser = true; + break; + + case SDLK_r: + if (event.key.keysym.mod & KMOD_CTRL) { + file_browser = true; + } + break; + + case SDLK_BACKSPACE: // yes you can delete in normal mode + if (editor.selection) { + editor_clipboard_copy(&editor); + editor_delete_selection(&editor); + editor.selection = false; + } else if (event.key.keysym.mod & KMOD_CTRL) { + emacs_backward_kill_word(&editor); + } else { + editor_backspace(&editor); + } + break; + + case SDLK_h: + if (event.key.keysym.mod & KMOD_ALT) { + emacs_mark_paragraph(&editor); + } else { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_word_left(&editor); + } else { + editor_move_char_left(&editor); + } + } + editor.last_stroke = SDL_GetTicks(); + break; + + + case SDLK_j: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if ((event.key.keysym.mod & KMOD_ALT) && !followCursor) { + move_camera(&sr, "down", 50.0f); + } else if (event.key.keysym.mod & KMOD_CTRL) { + evil_open_above(&editor); + } else if ((event.key.keysym.mod & KMOD_SHIFT) && !(event.key.keysym.mod & KMOD_ALT)) { + evil_join(&editor); + } else if (event.key.keysym.mod & KMOD_ALT) { + editor_move_paragraph_down(&editor); + } else { + editor_move_line_down(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_k: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if ((event.key.keysym.mod & KMOD_ALT) && !followCursor) { + move_camera(&sr, "up", 50.0f); + } else if (event.key.keysym.mod & KMOD_CTRL) { + emacs_kill_line(&editor); + } else if (event.key.keysym.mod & KMOD_ALT) { + editor_move_paragraph_up(&editor); + } else if (event.key.keysym.mod & KMOD_SHIFT) { + goto_definition(&editor, &fb); + } else { + editor_move_line_up(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_l: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + showLineNumbers = !showLineNumbers; + } else if (event.key.keysym.mod & KMOD_ALT) { + select_region_from_inside_braces(&editor); + } else { + editor_move_char_right(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + + case SDLK_DOWN: + if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_down(&editor); + } else { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_down(&editor); + } else { + editor_move_line_down(&editor); + } + } + break; + + + case SDLK_UP: + if (event.key.keysym.mod & KMOD_ALT) { + editor_drag_line_up(&editor); + } else { + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_line_up(&editor); + + if (event.key.keysym.mod & KMOD_CTRL) { + editor_move_paragraph_up(&editor); + } + } + break; + + + + case SDLK_RIGHT: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_char_right(&editor); + break; + + case SDLK_LEFT: + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_char_left(&editor); + break; + + + case SDLK_w: + if (event.key.keysym.mod & KMOD_CTRL) { + showWhitespaces = !showWhitespaces; + }else{ + editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); + editor_move_word_right(&editor); + } + break; + + case SDLK_e: + if (event.key.keysym.mod & KMOD_CTRL) { + isWave = !isWave; + /* current_mode = EMACS; */ + /* editor.last_stroke = SDL_GetTicks(); */ + } + break; + + // additional NORMAL mode keybinds here... + } break; + + case MINIBUFFER: + switch (event.key.keysym.sym) { + + case SDLK_ESCAPE: { + if (fzy) { + minibufferHeight -= 189; + fzy = false; + } + + if (editor.searching) { + editor_clear_mark(&editor); + editor_stop_search(&editor); + } else if (editor.minibuffer_active) { + editor.minibuffer_text.count = 0; + M_x_active = false; + evil_command_active = false; + editor.minibuffer_active = false; + } + current_mode = NORMAL; + } + break; + + case SDLK_g: { + if (fzy) { + minibufferHeight -= 189; + fzy = false; + } + + if (editor.searching) { + editor_clear_mark(&editor); + editor_stop_search(&editor); + } else if (editor.minibuffer_active) { + editor.minibuffer_text.count = 0; + M_x_active = false; + editor.minibuffer_active = false; + } + current_mode = EMACS; // TODO store the mode we were in + editor.last_stroke = SDL_GetTicks(); + } + break; + + + case SDLK_BACKSPACE: + if (editor.selection) { + // TODO once we have selection in the minibuffer + /* editor_clipboard_copy(&editor); */ + /* editor_delete_selection(&editor); */ + /* editor.selection = false; */ + } else if (event.key.keysym.mod & KMOD_CTRL) { + emacs_backward_kill_word(&editor); + editor.last_stroke = SDL_GetTicks(); + }else{ + editor_backspace(&editor); + } + editor.last_stroke = SDL_GetTicks(); + break; + + case SDLK_RETURN: { + editor_enter(&editor); + } + break; + } + break; + + // More cases for other modes can follow here... + // ... + } + break; + } + break; + + + case SDL_TEXTINPUT: if (file_browser) { - // Nothing for now // Once we have incremental search in the file browser this may become useful - } else { + // or to edit file names or create files/directory + } else if (current_mode == INSERT || current_mode == EMACS || current_mode == MINIBUFFER) { // Process text input + + if (delete_selection_mode) { + if (editor.selection) { + editor_delete_selection(&editor); + } + } + const char *text = event.text.text; size_t text_len = strlen(text); + for (size_t i = 0; i < text_len; ++i) { editor_insert_char(&editor, text[i]); + if (electric_mode) { // TODO maybe to it only in c files + if (text[i] == '}' || text[i] == ';') { + indent(&editor); + } + } + + if (electric_pair_mode) { + switch (text[i]) { + case '(': + editor_insert_char(&editor, ')'); + editor_move_char_left(&editor); + break; + case '{': + editor_insert_char(&editor, '}'); + editor_move_char_left(&editor); + break; + case '[': + editor_insert_char(&editor, ']'); + editor_move_char_left(&editor); + break; + case '\"': // Double quotes + editor_insert_char(&editor, '\"'); + editor_move_char_left(&editor); + break; + case '\'': // Single quotes + editor_insert_char(&editor, '\''); + editor_move_char_left(&editor); + break; + } + } } + + editor.selection = false; editor.last_stroke = SDL_GetTicks(); } - } - break; - } - } + reset_keychords(); + break; - { - int w, h; - SDL_GetWindowSize(window, &w, &h); - // TODO(#19): update the viewport and the resolution only on actual window change - glViewport(0, 0, w, h); + } } - Vec4f bg = hex_to_vec4f(0x181818FF); + int w, h; + SDL_GetWindowSize(window, &w, &h); + glViewport(0, 0, w, h); + /* Vec4f bg = themes[currentThemeIndex].background; */ + Vec4f bg = currentTheme.background; + bg.w = 0.0f; glClearColor(bg.x, bg.y, bg.z, bg.w); glClear(GL_COLOR_BUFFER_BIT); + if (theme_lerp){ + update_theme_interpolation(); + } + if (file_browser) { - fb_render(&fb, window, &atlas, &sr); + fb_render(&fb, window, &atlas, &sr); } else { + update_modeline_animation(); + update_minibuffer_animation(DELTA_TIME); + editor_render(window, &atlas, &sr, &editor); + update_cursor_color(&editor); + + if (fb.file_extension.items != NULL && strcmp(fb.file_extension.items, "md") == 0) { + render_markdown(&atlas, &sr, &editor, &fb); + } + + + if (M_x_active){ + render_minibuffer_content(&atlas, &sr, &editor, "M-x"); + } else if (evil_command_active) { + render_minibuffer_content(&atlas, &sr, &editor, ":"); + } else if (editor.searching) { + render_minibuffer_content(&atlas, &sr, &editor, "/"); + } else { + /* render_minibuffer_content(&atlas, &sr, &editor, "MESSAGES: "); */ + } } SDL_GL_SwapWindow(window); - const Uint32 duration = SDL_GetTicks() - start; const Uint32 delta_time_ms = 1000 / FPS; if (duration < delta_time_ms) { @@ -439,6 +2617,8 @@ int main(int argc, char **argv) } } + shutdown_clangd(&editor); + free_snippet_array(&snippets); return 0; } diff --git a/src/render.c b/src/render.c new file mode 100644 index 00000000..ebf17cf4 --- /dev/null +++ b/src/render.c @@ -0,0 +1,1240 @@ +#include "render.h" +#include "editor.h" +#include "file_browser.h" +#include "free_glyph.h" +#include "la.h" +#include "lexer.h" +#include "simple_renderer.h" +#include "theme.h" +#include +#include +#include "clock.h" +#include "utilities.h" + +float lineNumberWidth = FREE_GLYPH_FONT_SIZE * 1; +bool render_whitespaces_on_select = false; +bool lerpTokens = true; // TODO +bool mixSelectionColor = false; + + +Vec4f blend_color(Vec4f color1, Vec4f color2, float blendFactor) { + Vec4f result; + result.x = color1.x * (1 - blendFactor) + color2.x * blendFactor; + result.y = color1.y * (1 - blendFactor) + color2.y * blendFactor; + result.z = color1.z * (1 - blendFactor) + color2.z * blendFactor; + result.w = color1.w * (1 - blendFactor) + color2.w * blendFactor; + return result; +} + + +void render_fill_column(Simple_Renderer *sr, Free_Glyph_Atlas *atlas, Editor *editor) { + + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + + float len = calculate_max_line_length(editor); + if (smartFillColumn && len < fillColumn + 1 || showFillColumn == false) { + return; + } + + float characterWidth = measure_whitespace_width(atlas); + float columnPosition = characterWidth * fillColumn; + + if (showLineNumbers) { + columnPosition += lineNumberWidth; + } + + Vec2f startPos = {columnPosition, sr->camera_pos.y - (sr->resolution.y / 2) / sr->camera_scale}; + Vec2f endPos = {columnPosition, sr->camera_pos.y + (sr->resolution.y / 2) / sr->camera_scale}; + + float COLUMN_THICKNESS; + if (fillColumnThickness == 0) { + COLUMN_THICKNESS = characterWidth; + } else { + COLUMN_THICKNESS = fillColumnThickness; + } + + Vec4f COLUMN_COLOR = CURRENT_THEME.fill_column; + simple_renderer_solid_rect(sr, startPos, vec2f(COLUMN_THICKNESS, endPos.y - startPos.y), COLUMN_COLOR); + + simple_renderer_flush(sr); +} + + +void render_minibuffer_content(Free_Glyph_Atlas *atlas, + Simple_Renderer *sr, + Editor *editor, + const char *prefixText) +{ + if (editor->minibuffer_active || editor->searching) { + Vec4f cursorColor = CURRENT_THEME.cursor; + Vec4f textColor = CURRENT_THEME.text; + Vec2f textPos = {30.0f, 20.0f}; // Initial position + float prefixRightPadding = 0.0f; + float minibufferCursorOffset = 5.0f; + + if (M_x_active) { + prefixRightPadding = 50.0f; + } else if (evil_command_active) { + prefixRightPadding = 0.0f; + } else if (editor->searching) { + prefixRightPadding = 50.0f; + } + + // Render the prefix + free_glyph_atlas_render_line_sized(atlas, sr, prefixText, strlen(prefixText), &textPos, cursorColor); + float prefixWidth = free_glyph_atlas_measure_line_width(atlas, prefixText, strlen(prefixText)); + + // Adjust the position according to the prefix width and padding + if (!editor->searching) { + textPos.x += prefixRightPadding; + } + + // Select which text to render (minibuffer or search) + const char *textToRender = editor->searching ? editor->search.items : editor->minibuffer_text.items; + size_t textLength = editor->searching ? editor->search.count : editor->minibuffer_text.count; + + // Render the search or minibuffer text + simple_renderer_set_shader(sr, VERTEX_SHADER_MINIBUFFER, SHADER_FOR_TEXT); + free_glyph_atlas_render_line_sized(atlas, sr, textToRender, textLength, &textPos, textColor); + + // Adjust the cursor position for the cursor offset + textPos.x += minibufferCursorOffset; // Adjust x for the cursor + textPos.y = 0.0f; // Reset y for the cursor + Vec2f cursorPos = textPos; + + // Set cursor size + float cursor_width = measure_whitespace_width(atlas); + float minibufferHeight = 21.0f; // Set based on your minibuffer height + Vec2f cursorSize = {cursor_width, minibufferHeight * 4.0f}; + + // Render the cursor + simple_renderer_flush(sr); + simple_renderer_set_shader(sr, VERTEX_SHADER_MINIBUFFER, SHADER_FOR_COLOR); + simple_renderer_solid_rect(sr, cursorPos, cursorSize, cursorColor); + + // Flush to ensure rendering is complete + simple_renderer_flush(sr); + } +} + + + + +void render_selection(Editor *editor, Simple_Renderer *sr, Free_Glyph_Atlas *atlas) { + if (isWave) { + simple_renderer_set_shader(sr, VERTEX_SHADER_WAVE, SHADER_FOR_COLOR); + } else if (editor->selection) { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_CURSOR); + } else { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + } + + if (editor->selection) { + Vec4f selection_color; + if (mixSelectionColor) { + selection_color = blend_color(currentTheme.cursor, currentTheme.selection, 0.5); + } else { + selection_color = themes[currentThemeIndex].selection; + } + + for (size_t row = 0; row < editor->lines.count; ++row) { + size_t select_begin_chr = editor->select_begin; + size_t select_end_chr = editor->cursor; + if (select_begin_chr > select_end_chr) { + SWAP(size_t, select_begin_chr, select_end_chr); + } + + Line line_chr = editor->lines.items[row]; + + if (select_begin_chr < line_chr.begin) { + select_begin_chr = line_chr.begin; + } + + if (select_end_chr > line_chr.end) { + select_end_chr = line_chr.end; + } + + if (select_begin_chr <= select_end_chr) { + Vec2f select_begin_scr = vec2f(0, -((float)row + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE); + free_glyph_atlas_measure_line_sized(atlas, editor->data.items + line_chr.begin, select_begin_chr - line_chr.begin, &select_begin_scr); + + Vec2f select_end_scr = select_begin_scr; + // Adjust the range to include the end character + free_glyph_atlas_measure_line_sized(atlas, editor->data.items + select_begin_chr, select_end_chr - select_begin_chr + 1, &select_end_scr); + + // Adjust selection for line numbers if displayed + if (showLineNumbers) { + select_begin_scr.x += lineNumberWidth; + select_end_scr.x += lineNumberWidth; + } + + simple_renderer_solid_rect(sr, select_begin_scr, vec2f(select_end_scr.x - select_begin_scr.x, FREE_GLYPH_FONT_SIZE), selection_color); + } + } + } + simple_renderer_flush(sr); +} + + + + +#include // Include string.h for strcmp + +typedef struct { + size_t startLine; + size_t endLine; + float startX; +} MarkdownCodeBlockInfo; + + +// TODO allign codeblock with cursor and make them expandable adding chars +void render_markdown(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor, File_Browser *fb) { + const float LINE_HEIGHT = FREE_GLYPH_FONT_SIZE; + MarkdownCodeBlockInfo codeBlockStack[500]; // Assuming a max of 500 code blocks + int codeBlockCount = 0; + + for (size_t i = 0; i < editor->lines.count; ++i) { + Line line = editor->lines.items[i]; + float lineStartX = 0; // Start of the line + + // Check for code block start or end + if (line.end - line.begin >= 3 && strncmp(editor->data.items + line.begin, "```", 3) == 0) { + if (codeBlockCount > 0 && codeBlockStack[codeBlockCount - 1].endLine == 0) { + // Closing code block + codeBlockStack[codeBlockCount - 1].endLine = i; + } else { + // Starting new code block + codeBlockStack[codeBlockCount++] = (MarkdownCodeBlockInfo){i, 0, lineStartX}; + } + } + } + + // Draw rectangles for each code block + for (int k = 0; k < codeBlockCount; k++) { + if (codeBlockStack[k].endLine > 0) { // Only if code block is closed + // Start one line before + Vec2f startPos = {codeBlockStack[k].startX, -((float)codeBlockStack[k].startLine + CURSOR_OFFSET - 1) * LINE_HEIGHT}; + // End one line later + Vec2f endPos = {1000.0f, -((float)codeBlockStack[k].endLine + CURSOR_OFFSET) * LINE_HEIGHT}; // Removed the -1 + + + if (showLineNumbers) { + startPos.x += lineNumberWidth; + endPos.x += lineNumberWidth; + } + + Vec4f codeBlockColor = CURRENT_THEME.code_block; + Vec2f rectSize = {/* endPos.x - */ startPos.x + 2000.0f, endPos.y - startPos.y}; // TODO use w + + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + simple_renderer_solid_rect(sr, startPos, rectSize, codeBlockColor); + } + } +} + +void adjust_line_number_width(Editor *editor, float *lineNumberWidth) { + size_t lineCount = editor->lines.count; + + if (lineCount < 10) { + *lineNumberWidth = FREE_GLYPH_FONT_SIZE * 3; + } else if (lineCount < 100) { + *lineNumberWidth = FREE_GLYPH_FONT_SIZE * 3; + } else if (lineCount < 1000) { + *lineNumberWidth = FREE_GLYPH_FONT_SIZE * 3; + } else { + *lineNumberWidth = FREE_GLYPH_FONT_SIZE * 4; + } +} + +void render_line_numbers(Simple_Renderer *sr, Free_Glyph_Atlas *atlas, Editor *editor) { + if (showLineNumbers) { + simple_renderer_set_shader(sr, isWave ? VERTEX_SHADER_WAVE : VERTEX_SHADER_SIMPLE, SHADER_FOR_TEXT); + + adjust_line_number_width(editor, &lineNumberWidth); + + size_t currentLineNumber = editor_cursor_row(editor); + + Vec4f defaultColor = CURRENT_THEME.line_numbers; + Vec4f currentLineColor = CURRENT_THEME.current_line_number; + + if (highlightCurrentLineNumberOnInsertMode) { + currentLineColor = (current_mode == INSERT) ? CURRENT_THEME.insert_cursor : + CURRENT_THEME.current_line_number; + } + + int lineNumberFieldWidth; + size_t lineCount = editor->lines.count; + + // Adjust line number width based on followCursor and line count + if (followCursor || lineCount >= 1000) { + // Adjust width based on current line count + lineNumberFieldWidth = snprintf(NULL, 0, "%zu", lineCount); + } else { + // Fixed width for up to three digits + lineNumberFieldWidth = snprintf(NULL, 0, "%d", 999); + } + + for (size_t i = 0; i < lineCount; ++i) { + char lineNumberStr[12]; + + size_t displayLineNumber = relativeLineNumbers ? + (i == currentLineNumber) ? currentLineNumber + 1 : abs((int)i - (int)currentLineNumber) : + i + 1; + + snprintf(lineNumberStr, sizeof(lineNumberStr), "%*zu", lineNumberFieldWidth, displayLineNumber); + + int whitespace = measure_whitespace_width(atlas); + Vec2f pos = {whitespace, -((float)i + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE}; + + Vec4f colorToUse = (highlightCurrentLineNumber && i == currentLineNumber) ? currentLineColor : defaultColor; + + free_glyph_atlas_render_line_sized(atlas, sr, lineNumberStr, strlen(lineNumberStr), &pos, colorToUse); + } + + simple_renderer_flush(sr); + } +} + +void render_line_numbers_background(Simple_Renderer *sr, Free_Glyph_Atlas *atlas, Editor *editor) { + if (!showLineNumbers ||!showLineNumbersBackground) return; + + Vec4f backgroundColor = CURRENT_THEME.line_numbers_background; + float viewportHeight = sr->resolution.y / sr->camera_scale; + + adjust_line_number_width(editor, &lineNumberWidth); + + float characterWidth = measure_whitespace_width(atlas); + float lineNumberAreaWidth = lineNumberWidth + characterWidth; + Vec2f pos = {-characterWidth, sr->camera_pos.y - (viewportHeight / 2)}; + Vec2f size = {lineNumberAreaWidth, viewportHeight}; + + /* simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); */ + simple_renderer_set_shader( + sr, isWave ? VERTEX_SHADER_WAVE : VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + + simple_renderer_solid_rect(sr, pos, size, backgroundColor); + simple_renderer_flush(sr); +} + +void render_whitespaces(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor) { + float circleRadius = FREE_GLYPH_FONT_SIZE * 0.1; + Vec4f whitespaceColor = CURRENT_THEME.whitespace; + int circleSegments = 20; + + for (size_t i = 0; i < editor->lines.count; ++i) { + Line line = editor->lines.items[i]; + Vec2f pos = {0, -((float)i + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE}; + + if (showLineNumbers) { + pos.x += lineNumberWidth; + } + + // Manually calculate the selection start and end + size_t selectionStart = editor->select_begin; + size_t selectionEnd = editor->cursor; + if (selectionStart > selectionEnd) { + size_t temp = selectionStart; + selectionStart = selectionEnd; + selectionEnd = temp; + } + + for (size_t j = line.begin; j < line.end; ++j) { + bool isWhitespace = editor->data.items[j] == ' ' || editor->data.items[j] == '\t'; + bool isInSelection = editor->selection && j >= selectionStart && j < selectionEnd; + bool shouldRenderAll = showWhitespaces && isWhitespace; + bool shouldRenderInSelection = render_whitespaces_on_select && isInSelection && isWhitespace; + + if (shouldRenderAll || shouldRenderInSelection) { + Vec2f char_pos = pos; + char_pos.x += (j - line.begin) * circleRadius * 2; + free_glyph_atlas_measure_line_sized(atlas, editor->data.items + j, 1, &char_pos); + float char_width = char_pos.x - pos.x - (j - line.begin) * circleRadius * 2; + + Vec2f circleCenter = {pos.x + (j - line.begin) * char_width + char_width / 2, pos.y + FREE_GLYPH_FONT_SIZE / 2}; + + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + simple_renderer_circle(sr, circleCenter, circleRadius, whitespaceColor, circleSegments); + } + } + } + + simple_renderer_flush(sr); +} + + +void render_indentation_lines(Simple_Renderer *sr, Free_Glyph_Atlas *atlas, Editor *editor) { + if (!showIndentationLines) return; + + if (isWave) { + simple_renderer_set_shader(sr, VERTEX_SHADER_WAVE, SHADER_FOR_COLOR); + } else { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + } + + + float COLUMN_THICKNESS = 5.0f; + float CHARACTER_WIDTH = measure_whitespace_width(atlas); + // This structure keeps track of open braces and their line start positions. + // TODO this should be done in the lexer + struct LineSpan { + size_t startLine; + size_t endLine; + float startX; + } lineSpans[5000]; + int spanCount = 0; + + // Iterate through each line in the editor + for (size_t i = 0; i < editor->lines.count; ++i) { + Line line = editor->lines.items[i]; + + // Iterate through each character in the line + for (size_t j = line.begin; j < line.end; ++j) { + char c = editor->data.items[j]; + if (c == '{') { + ssize_t matchingPos = find_matching_parenthesis(editor, j); + if (matchingPos != -1) { + size_t matchingLine = editor_row_from_pos(editor, matchingPos); + + // Calculate the position of the first non-whitespace character + size_t firstNonWhitespace = find_first_non_whitespace(editor->data.items, line.begin, j); + + // Calculate the X position where the line should start + float lineStartX = (firstNonWhitespace - line.begin) * CHARACTER_WIDTH; + if (showLineNumbers) { + lineStartX += lineNumberWidth; + } + + // Add a new line span for the opening brace + lineSpans[spanCount++] = (struct LineSpan){i, matchingLine, lineStartX}; + } + } + } + } + + // Draw lines for each collected brace pair + for (int k = 0; k < spanCount; k++) { + Vec2f startPos = {lineSpans[k].startX, -((float)lineSpans[k].startLine + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE}; + // Adjust endPos to make the line one line shorter, so it doesn't extend to the closing brace line + Vec2f endPos = {startPos.x, -((float)lineSpans[k].endLine - 1 + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE}; + + simple_renderer_solid_rect(sr, startPos, vec2f(COLUMN_THICKNESS, endPos.y - startPos.y), CURRENT_THEME.indentation_line); + } +} + + + + + +void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor) +{ + int w, h; + SDL_GetWindowSize(window, &w, &h); + + float max_line_len = 0.0f; + + sr->resolution = vec2f(w, h); + sr->time = (float) SDL_GetTicks() / 1000.0f; + + float whitespace_width = measure_whitespace_width(atlas); + + + // Render hl_line + { + if (hl_line){ + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + + size_t currentLine = editor_cursor_row(editor); + Vec2f highlightPos = {0.0f, -((float)currentLine + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE}; + + float highlightWidth = 8000; // Default width for the highlight + + // If showing line numbers, adjust the position and width of the highlight + if (showLineNumbers) { + highlightPos.x -= lineNumberWidth - 260; // Move highlight to the left to cover line numbers + highlightWidth += lineNumberWidth; // Increase width to include line numbers area + } + + simple_renderer_solid_rect(sr, highlightPos, vec2f(highlightWidth, FREE_GLYPH_FONT_SIZE), CURRENT_THEME.hl_line); + + simple_renderer_flush(sr); + } + } + + // Render anchor + if (editor->has_anchor) { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + + // Update the anchor position before rendering + editor_update_anchor(editor); + + size_t anchor_row = editor_row_from_pos(editor, editor->anchor_pos); + Line anchor_line = editor->lines.items[anchor_row]; + size_t anchor_col = editor->anchor_pos - anchor_line.begin; + + Vec2f anchor_pos_vec = vec2fs(0.0f); + anchor_pos_vec.y = -((float)anchor_row + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE; + anchor_pos_vec.x = free_glyph_atlas_cursor_pos( + atlas, + editor->data.items + anchor_line.begin, anchor_line.end - anchor_line.begin, + vec2f(0.0, anchor_pos_vec.y), + anchor_col + ); + + // Adjust anchor position if line numbers are shown + if (showLineNumbers) { + anchor_pos_vec.x += lineNumberWidth; + } + + Vec4f ANCHOR_COLOR = CURRENT_THEME.anchor; + + simple_renderer_solid_rect( + sr, anchor_pos_vec, vec2f(whitespace_width, FREE_GLYPH_FONT_SIZE), + ANCHOR_COLOR); + + + simple_renderer_flush(sr); + } + + + // TODO shader switch + render_selection(editor, sr, atlas); + render_whitespaces(atlas, sr, editor); + + + Vec2f cursor_pos = vec2fs(0.0f); + { + size_t cursor_row = editor_cursor_row(editor); + Line line = editor->lines.items[cursor_row]; + size_t cursor_col = editor->cursor - line.begin; + cursor_pos.y = -((float)cursor_row + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE; + cursor_pos.x = free_glyph_atlas_cursor_pos( + atlas, + editor->data.items + line.begin, line.end - line.begin, + vec2f(0.0, cursor_pos.y), + cursor_col + ); + } + + + // Render search + { + if (editor->searching) { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + Vec4f selection_color = CURRENT_THEME.search; // or .selection_color if that's what you named it in the struct. + + Vec2f p1 = cursor_pos; + Vec2f p2 = p1; + + free_glyph_atlas_measure_line_sized(editor->atlas, editor->search.items, editor->search.count, &p2); + + // Adjust for line numbers width if they are displayed + if (showLineNumbers) { + p1.x += lineNumberWidth; + p2.x += lineNumberWidth; + } + + simple_renderer_solid_rect(sr, p1, vec2f(p2.x - p1.x, FREE_GLYPH_FONT_SIZE), selection_color); + simple_renderer_flush(sr); + } + } + + render_line_numbers_background(sr, atlas, editor); + render_line_numbers(sr, atlas, editor); + + render_indentation_lines(sr, atlas, editor); + + // Render matching parenthesis + { + if (current_mode == NORMAL || current_mode == EMACS) { + if (matchParenthesis) { + if (isWave) { + simple_renderer_set_shader(sr, VERTEX_SHADER_WAVE, SHADER_FOR_COLOR); + } else { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + } + + ssize_t matching_pos = find_matching_parenthesis(editor, editor->cursor); + if (matching_pos != -1) { + size_t matching_row = editor_row_from_pos(editor, matching_pos); + + Vec2f match_pos_screen = vec2fs(0.0f); // Initialize to zero + match_pos_screen.y = -((float)matching_row + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE; + + Line line = editor->lines.items[matching_row]; + if (matching_pos >= line.begin && matching_pos < line.end) { + // Measure the position up to the matching character + free_glyph_atlas_measure_line_sized(atlas, editor->data.items + line.begin, matching_pos - line.begin, &match_pos_screen); + + // Measure the width of the actual character at the matching position + Vec2f char_end_pos = match_pos_screen; + free_glyph_atlas_measure_line_sized(atlas, editor->data.items + matching_pos, 1, &char_end_pos); + float char_width = char_end_pos.x - match_pos_screen.x; + + // Adjust for line numbers if displayed + if (showLineNumbers) { + match_pos_screen.x += lineNumberWidth; + } + + // Define the size of the highlight rectangle to match character size + Vec2f rect_size = vec2f(char_width, FREE_GLYPH_FONT_SIZE); + + simple_renderer_solid_rect(sr, match_pos_screen, rect_size, CURRENT_THEME.matching_parenthesis); + } + } + } + simple_renderer_flush(sr); + } + } + + render_fill_column(sr, atlas, editor); + + // Render cursor + if (isWave) { + simple_renderer_set_shader(sr, VERTEX_SHADER_WAVE, SHADER_FOR_COLOR); + } else if (editor->selection) { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_CURSOR); + } else { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + } + + { + if (showLineNumbers) { + cursor_pos.x += lineNumberWidth; + } + + // Constants and Default Settings + float CURSOR_WIDTH; + const Uint32 CURSOR_BLINK_THRESHOLD = 500; + const Uint32 CURSOR_BLINK_PERIOD = 1000; + const Uint32 t = SDL_GetTicks() - editor->last_stroke; + Vec4f CURSOR_COLOR = CURRENT_THEME.cursor; + float BORDER_THICKNESS = 3.0f; + Vec4f INNER_COLOR = vec4f(CURSOR_COLOR.x, CURSOR_COLOR.y, CURSOR_COLOR.z, 0.3); + + sr->verticies_count = 0; + + // If editor has a mark, make the cursor transparent + if (editor->has_mark) { + /* CURSOR_COLOR.w = 0.0f; // Set alpha to 0 (fully transparent) */ + } + + // Rendering based on mode + switch (current_mode) { + + case NORMAL: { + float cursor_width; + // Check if the cursor is on an actual character or an empty line + if (editor->cursor < editor->data.count && editor->data.items[editor->cursor] != '\n') { + Vec2f next_char_pos = cursor_pos; + free_glyph_atlas_measure_line_sized( + atlas, editor->data.items + editor->cursor, + 1, // Measure the actual character at the cursor + &next_char_pos); + cursor_width = next_char_pos.x - cursor_pos.x; + } else { + cursor_width = whitespace_width; + } + + simple_renderer_solid_rect( + sr, cursor_pos, vec2f(cursor_width, FREE_GLYPH_FONT_SIZE), + CURSOR_COLOR); + } break; + + case EMACS: { + float cursor_width; + // Check if the cursor is on an actual character or an empty line + if (editor->cursor < editor->data.count && editor->data.items[editor->cursor] != '\n') { + Vec2f next_char_pos = cursor_pos; + free_glyph_atlas_measure_line_sized( + atlas, editor->data.items + editor->cursor, + 1, // Measure the actual character at the cursor + &next_char_pos); + cursor_width = next_char_pos.x - cursor_pos.x; + } else { + cursor_width = whitespace_width; + } + + simple_renderer_solid_rect( + sr, cursor_pos, vec2f(cursor_width, FREE_GLYPH_FONT_SIZE), + CURSOR_COLOR); + } break; + + case HELIX: { + float cursor_width; + // Check if the cursor is on an actual character or an empty line + if (editor->cursor < editor->data.count && editor->data.items[editor->cursor] != '\n') { + Vec2f next_char_pos = cursor_pos; + free_glyph_atlas_measure_line_sized( + atlas, editor->data.items + editor->cursor, + 1, // Measure the actual character at the cursor + &next_char_pos); + cursor_width = next_char_pos.x - cursor_pos.x; + } else { + cursor_width = whitespace_width; + } + + simple_renderer_solid_rect( + sr, cursor_pos, vec2f(cursor_width, FREE_GLYPH_FONT_SIZE), + CURSOR_COLOR); + } break; + + case VISUAL_LINE: + case VISUAL: { + float cursor_width; + // Check if the cursor is on an actual character or an empty line + if (editor->cursor < editor->data.count && editor->data.items[editor->cursor] != '\n') { + Vec2f next_char_pos = cursor_pos; + free_glyph_atlas_measure_line_sized(atlas, editor->data.items + editor->cursor, 1, &next_char_pos); + cursor_width = next_char_pos.x - cursor_pos.x; + } else { + cursor_width = whitespace_width; + } + + simple_renderer_solid_rect(sr, cursor_pos, vec2f(cursor_width, FREE_GLYPH_FONT_SIZE), CURSOR_COLOR); + } break; + + case MINIBUFFER: { + // TODO + } break; + + case INSERT: + CURSOR_COLOR = themes[currentThemeIndex].insert_cursor; + /* CURSOR_COLOR = CURRENT_THEME.cursor; */ + if (BlockInsertCursor) { + // Check if the cursor is on an actual character or an empty line + if (editor->cursor < editor->data.count && editor->data.items[editor->cursor] != '\n') { + Vec2f next_char_pos = cursor_pos; + free_glyph_atlas_measure_line_sized( + atlas, editor->data.items + editor->cursor, + 1, // Measure the actual character at the cursor + &next_char_pos); + CURSOR_WIDTH = next_char_pos.x - cursor_pos.x; + } else { + CURSOR_WIDTH = whitespace_width; + } + } else { + CURSOR_WIDTH = 5.0f; // Thin line + } + // blinking for INSERT mode + if (t < CURSOR_BLINK_THRESHOLD || + (t / CURSOR_BLINK_PERIOD) % 2 != 0) { + simple_renderer_solid_rect( + sr, cursor_pos, + vec2f(CURSOR_WIDTH, FREE_GLYPH_FONT_SIZE), + CURSOR_COLOR); + } + break; + + /* case VISUAL: { */ + /* float cursor_width; */ + + /* // Check if the cursor is on an actual character or an empty line */ + /* if (editor->cursor < editor->data.count && */ + /* editor->data.items[editor->cursor] != '\n') { */ + /* Vec2f next_char_pos = cursor_pos; */ + /* free_glyph_atlas_measure_line_sized( */ + /* atlas, editor->data.items + editor->cursor, 1, */ + /* &next_char_pos); */ + /* cursor_width = next_char_pos.x - cursor_pos.x; */ + /* } else { */ + /* Vec2f next_char_pos = cursor_pos; */ + /* free_glyph_atlas_measure_line_sized(atlas, "a", 1, */ + /* &next_char_pos); */ + /* cursor_width = next_char_pos.x - cursor_pos.x; */ + /* } */ + + /* // Draw inner rectangle */ + /* simple_renderer_solid_rect( */ + /* sr, */ + /* vec2f(cursor_pos.x + BORDER_THICKNESS, */ + /* cursor_pos.y + BORDER_THICKNESS), */ + /* vec2f(cursor_width - 2 * BORDER_THICKNESS, */ + /* FREE_GLYPH_FONT_SIZE - 2 * BORDER_THICKNESS), */ + /* INNER_COLOR); */ + + /* // Draw the outline (borders) using the theme's cursor color */ + /* simple_renderer_solid_rect(sr, cursor_pos, */ + /* vec2f(cursor_width, BORDER_THICKNESS), */ + /* CURSOR_COLOR); // Top border */ + /* simple_renderer_solid_rect( */ + /* sr, */ + /* vec2f(cursor_pos.x, */ + /* cursor_pos.y + FREE_GLYPH_FONT_SIZE - BORDER_THICKNESS), */ + /* vec2f(cursor_width, BORDER_THICKNESS), */ + /* CURSOR_COLOR); // Bottom border */ + /* simple_renderer_solid_rect( */ + /* sr, cursor_pos, vec2f(BORDER_THICKNESS, FREE_GLYPH_FONT_SIZE), */ + /* CURSOR_COLOR); // Left border */ + /* simple_renderer_solid_rect( */ + /* sr, */ + /* vec2f(cursor_pos.x + cursor_width - BORDER_THICKNESS, */ + /* cursor_pos.y), */ + /* vec2f(BORDER_THICKNESS, FREE_GLYPH_FONT_SIZE), */ + /* CURSOR_COLOR); // Right border */ + + /* break; */ + /* } */ + + } + simple_renderer_flush(sr); + } + + + // Render marked search result + { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_COLOR); + if (editor->has_mark) { + for (size_t row = 0; row < editor->lines.count; ++row) { + size_t mark_begin_chr = editor->mark_start; + size_t mark_end_chr = editor->mark_end; + + Line line_chr = editor->lines.items[row]; + + if (mark_begin_chr < line_chr.begin) { + mark_begin_chr = line_chr.begin; + } + + if (mark_end_chr > line_chr.end) { + mark_end_chr = line_chr.end; + } + + if (mark_begin_chr <= mark_end_chr) { + Vec2f mark_begin_scr = vec2f(0, -((float)row + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE); + free_glyph_atlas_measure_line_sized( + atlas, editor->data.items + line_chr.begin, mark_begin_chr - line_chr.begin, + &mark_begin_scr); + + Vec2f mark_end_scr = mark_begin_scr; + free_glyph_atlas_measure_line_sized( + atlas, editor->data.items + mark_begin_chr, mark_end_chr - mark_begin_chr, + &mark_end_scr); + + // Adjust for line numbers width if they are displayed + if (showLineNumbers) { + mark_begin_scr.x += lineNumberWidth; + mark_end_scr.x += lineNumberWidth; + } + + Vec4f mark_color = CURRENT_THEME.marks; + simple_renderer_solid_rect(sr, mark_begin_scr, vec2f(mark_end_scr.x - mark_begin_scr.x, FREE_GLYPH_FONT_SIZE), mark_color); + } + } + } + simple_renderer_flush(sr); + } + + // Render text + // TODO IMPORTANT alot of this stuff should not run every frame + { + + if (isWave) { + simple_renderer_set_shader(sr, VERTEX_SHADER_WAVE, SHADER_FOR_TEXT); + } else { + simple_renderer_set_shader(sr, VERTEX_SHADER_SIMPLE, SHADER_FOR_TEXT); + } + + for (size_t i = 0; i < editor->tokens.count; ++i) { + + Token token = editor->tokens.items[i]; + Vec2f pos = token.position; + //Vec4f color = vec4fs(1); + // TODO match color for open and close + Vec4f color = CURRENT_THEME.text; + + // Adjust for line numbers width if they are displayed + if (showLineNumbers) { + pos.x += lineNumberWidth; + } + + switch (token.kind) { + case TOKEN_PREPROC: + if (token.text_len >= 7 && token.text[0] == '#') { // Check if it's likely a hex color + bool valid_hex = true; + for (size_t j = 1; j < 7 && valid_hex; ++j) { + if (!is_hex_digit(token.text[j])) { + valid_hex = false; + } + } + + if (valid_hex) { + unsigned int hex_value; + if(sscanf(token.text, "#%06x", &hex_value) == 1) { + color = hex_to_vec4f(hex_value); + } else { + color = CURRENT_THEME.hashtag; // Default to the hashtag color if not a valid hex + } + } else { + color = CURRENT_THEME.hashtag; // Not a valid hex color + } + } else { + color = CURRENT_THEME.hashtag; // Default color for preprocessor directives + } + break; + + case TOKEN_KEYWORD: + color = CURRENT_THEME.logic; + break; + + case TOKEN_TYPE: + color = CURRENT_THEME.type; + break; + + case TOKEN_NULL: + color = CURRENT_THEME.null; + break; + + case TOKEN_FUNCTION_DEFINITION: + color = CURRENT_THEME.function_definition; + break; + + case TOKEN_LINK: + color = CURRENT_THEME.link; + break; + + case TOKEN_OR: + color = CURRENT_THEME.logic_or; + break; + + case TOKEN_PIPE: + color = CURRENT_THEME.pipe; + break; + + case TOKEN_AND: + color = CURRENT_THEME.logic_and; + break; + + case TOKEN_AMPERSAND: + color = CURRENT_THEME.ampersand; + break; + + case TOKEN_POINTER: + color = CURRENT_THEME.pointer; + break; + + case TOKEN_MULTIPLICATION: + color = CURRENT_THEME.multiplication; + break; + + // TODO this should happen at lexing time not at render time + case TOKEN_COMMENT: + { + color = CURRENT_THEME.comment; + + // Checking for TODOOOO... + char* todoLoc = strstr(token.text, "TODO"); + if (todoLoc && (size_t)(todoLoc - token.text + 3) < token.text_len) { + + size_t numOs = 0; + char* ptr = todoLoc + 4; // Start right after "TODO" + + // Count 'O's without crossing token boundary + while ((size_t)(ptr - token.text) < token.text_len && (*ptr == 'O' || *ptr == 'o')) { + + numOs++; + ptr++; + } + + Vec4f baseColor = CURRENT_THEME.todo; + float deltaRed = (1.0f - baseColor.x) / 5; // Adjusting for maximum of TODOOOOO + + color.x = baseColor.x + deltaRed * numOs; + color.y = baseColor.y * (1 - 0.2 * numOs); + color.z = baseColor.z * (1 - 0.2 * numOs); + color.w = baseColor.w; + } + + // Checking for FIXMEEEE... + char* fixmeLoc = strstr(token.text, "FIXME"); + if (fixmeLoc && (size_t)(fixmeLoc - token.text + 4) < token.text_len) { + + size_t numEs = 0; + char* ptr = fixmeLoc + 5; // Start right after "FIXME" + + // Count 'E's without crossing token boundary + while ((size_t)(ptr - token.text) < token.text_len && (*ptr == 'E' || *ptr == 'e')) { + + numEs++; + ptr++; + } + + Vec4f baseColor = CURRENT_THEME.fixme; + float deltaRed = (1.0f - baseColor.x) / 5; // Adjusting for maximum of FIXMEEEE + + color.x = baseColor.x + deltaRed * numEs; + color.y = baseColor.y * (1 - 0.2 * numEs); + color.z = baseColor.z * (1 - 0.2 * numEs); + color.w = baseColor.w; + } + + // Checking for BUG... + char* bugLoc = strstr(token.text, "BUG"); + if (bugLoc && (size_t)(bugLoc - token.text + 2) < token.text_len) { + + color = CURRENT_THEME.bug; + } + + + // Checking for NOTE... + char* noteLoc = strstr(token.text, "NOTE"); + if (noteLoc && (size_t)(noteLoc - token.text + 3) < token.text_len) { + + color = CURRENT_THEME.note; + } + + // Continue rendering with + } + break; + + case TOKEN_EQUALS: + color = CURRENT_THEME.equals; + break; + case TOKEN_EXCLAMATION: + color = CURRENT_THEME.exclamation; + break; + case TOKEN_NOT_EQUALS: + color = CURRENT_THEME.not_equals; + break; + case TOKEN_EQUALS_EQUALS: + color = CURRENT_THEME.equals_equals; + break; + case TOKEN_LESS_THAN: + color = CURRENT_THEME.less_than; + break; + case TOKEN_GREATER_THAN: + color = CURRENT_THEME.greater_than; + break; + case TOKEN_ARROW: + color = CURRENT_THEME.arrow; + break; + case TOKEN_MINUS: + color = CURRENT_THEME.minus; + break; + case TOKEN_PLUS: + color = CURRENT_THEME.plus; + break; + case TOKEN_TRUE: + color = CURRENT_THEME.truee; + break; + case TOKEN_FALSE: + color = CURRENT_THEME.falsee; + break; + case TOKEN_OPEN_SQUARE: + color = CURRENT_THEME.open_square; + break; + case TOKEN_CLOSE_SQUARE: + color = CURRENT_THEME.close_square; + break; + case TOKEN_ARRAY_CONTENT: + color = CURRENT_THEME.array_content; + break; + case TOKEN_BAD_SPELLCHECK: + color = CURRENT_THEME.bug; + break; + case TOKEN_STRING: + color = CURRENT_THEME.string; + break; + case TOKEN_OPEN_CURLY: + color = CURRENT_THEME.open_curly; + break; + case TOKEN_CLOSE_CURLY: + color = CURRENT_THEME.close_curly; + break; + case TOKEN_COLOR: { + unsigned long long hex_value; + if(sscanf(token.text, "0x%llx", &hex_value) == 1) { + color = hex_to_vec4f((uint32_t)hex_value); + } + } + break; + default: + {} + } + + free_glyph_atlas_render_line_sized(atlas, sr, token.text, token.text_len, &pos, color); + // TODO: the max_line_len should be calculated based on what's visible on the screen right now + if (max_line_len < pos.x) max_line_len = pos.x; + } + simple_renderer_flush(sr); + } + + + + + // Render minibuffer + { + if (showMinibuffer) { + simple_renderer_set_shader(sr, VERTEX_SHADER_FIXED, SHADER_FOR_COLOR); + simple_renderer_solid_rect(sr, (Vec2f){0.0f, 0.0f}, (Vec2f){w, minibufferHeight}, CURRENT_THEME.minibuffer); + simple_renderer_flush(sr); + } + } + + + // Render modeline + { + if (showModeline) { + simple_renderer_set_shader(sr, VERTEX_SHADER_FIXED, SHADER_FOR_COLOR); + simple_renderer_solid_rect(sr, (Vec2f){0.0f, minibufferHeight}, (Vec2f){w, modelineHeight}, CURRENT_THEME.modeline); + // render accent + simple_renderer_solid_rect(sr, (Vec2f){0.0f, minibufferHeight}, (Vec2f){modelineAccentWidth, modelineHeight}, CURRENT_THEME.modeline_accent); + } + } + + + // Render fringe + { + simple_renderer_set_shader(sr, VERTEX_SHADER_FIXED, SHADER_FOR_COLOR); + simple_renderer_solid_rect(sr, (Vec2f){0.0f, modelineHeight + minibufferHeight}, (Vec2f){fringeWidth, h}, CURRENT_THEME.fringe); + simple_renderer_flush(sr); + } + + + + + // render clock + time_t t = time(NULL); + struct tm *tm = localtime(&t); + int hours = tm->tm_hour; + int minutes = tm->tm_min; + render_clock(sr, hours, minutes); + + + // Update camera + { + if (followCursor && !instantCamera) { + if (automatic_zoom) { + float len = calculate_max_line_length(editor); + if (len > 0) { // Check if there is at least one line + if (len <= 62) { + zoom_factor = 4.0f; + } else if (len <= 78) { + zoom_factor = 5.0f; + } else if (len <= 94) { + zoom_factor = 6.0f; + } else { + zoom_factor = 5.0f; + } + + if (showLineNumbers) { + zoom_factor += 1.5f; + } + if (zoomInInsertMode) { + if (current_mode == INSERT) { + zoom_factor -= 1; + } + } + } + } + + if (max_line_len > 1000.0f) { + max_line_len = 1000.0f; + } + + float target_scale = w / zoom_factor / (max_line_len * 0.75); // TODO: division by 0 + + Vec2f target = cursor_pos; + float offset = 0.0f; + + if (target_scale > 3.0f) { + target_scale = 3.0f; + } else { + + float division; + if (centeredText) { + division = 3; + } else /* if (!showLineNumbers) */ { + division = 2.016; + /* } else { */ + /* division = 2.0; */ + } + + offset = cursor_pos.x - w/3/sr->camera_scale; + if (offset < 0.0f) offset = 0.0f; + target = vec2f(w/division/sr->camera_scale + offset, cursor_pos.y); + } + + sr->camera_vel = vec2f_mul( + vec2f_sub(target, sr->camera_pos), + vec2fs(2.0f)); + sr->camera_scale_vel = (target_scale - sr->camera_scale) * 2.0f; + + sr->camera_pos = vec2f_add(sr->camera_pos, vec2f_mul(sr->camera_vel, vec2fs(DELTA_TIME))); + sr->camera_scale = sr->camera_scale + sr->camera_scale_vel * DELTA_TIME; + + } else if (followCursor && instantCamera) { + // TODO looks bas maybe implement double buffering + if (max_line_len > 1000.0f) { + max_line_len = 1000.0f; + } + + float target_scale = w / zoom_factor / (max_line_len * 0.75); // Handle potential division by 0 + + Vec2f target = cursor_pos; + float offset = 0.0f; + + float division; + if (centeredText) { + division = 3; + } else { + division = 2.02; + } + + if (target_scale > 3.0f) { + target_scale = 3.0f; + } else { + offset = cursor_pos.x - w/3/sr->camera_scale; + if (offset < 0.0f) offset = 0.0f; + target = vec2f(w/division/sr->camera_scale + offset, cursor_pos.y); + } + + // Instantly set the camera position and scale + sr->camera_pos = target; + sr->camera_scale = target_scale; + + } else { + sr->camera_scale = 0.33f; // 0.24 + + // Static flag to ensure initial camera position is set only once + static bool hasSetInitialPosition = false; + + // If the initial position hasn't been set, set it now + if (!hasSetInitialPosition) { + /* sr->camera_pos.x = 2870.0f; // Set the x-position */ + + if (showLineNumbers) { + sr->camera_pos.x = 2855.0f; + } else { + sr->camera_pos.x = 2890.0f; + } + sr->camera_pos.y = -4000.0f; // Set the initial y-position + hasSetInitialPosition = true; + } else { + // Calculate the vertical position of the cursor in world coordinates. + int currentLine = editor_cursor_row(editor); + float cursorPosY = -((float)currentLine + CURSOR_OFFSET) * FREE_GLYPH_FONT_SIZE; + + // Define the top and bottom edges of the current camera view. + float cameraTopEdge = sr->camera_pos.y - (h * 1/2.3f) / sr->camera_scale; + float cameraBottomEdge = sr->camera_pos.y + (h * 1/2.3f) / sr->camera_scale; + + + // Adjust the camera's Y position if the cursor is outside the viewport. + if (cursorPosY > cameraBottomEdge) { + sr->camera_pos.y += cursorPosY - cameraBottomEdge; // Move camera down just enough + } else if (cursorPosY < cameraTopEdge) { + sr->camera_pos.y -= cameraTopEdge - cursorPosY; // Move camera up just enough + } + + // Keeping the x-position fixed as per the previous logic + if (showLineNumbers) { + sr->camera_pos.x = 2855.0f; + } else { + sr->camera_pos.x = 2890.0f; + } + } + } + } +} + + + diff --git a/src/render.h b/src/render.h new file mode 100644 index 00000000..92c31686 --- /dev/null +++ b/src/render.h @@ -0,0 +1,44 @@ +#ifndef RENDER_H +#define RENDER_H + +#include +#include "free_glyph.h" +#include "simple_renderer.h" +#include "editor.h" + + +extern float tokenInterpolationProgress; +extern float tokenLerpSpeed; +extern bool tokenLerp; +extern float lineNumberWidth; +extern bool mixSelectionColor; + + +void update_tokens_interpolation(); +void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor); +void render_search_text(Free_Glyph_Atlas *minibuffer_atlas, Simple_Renderer *sr, Editor *editor); +/* void render_M_x(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor); */ +/* void render_M_x(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor, const char *prefixText); */ +void render_minibuffer_content(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor, const char *prefixText); +void render_line_numbers(Simple_Renderer *sr, Free_Glyph_Atlas *atlas, Editor *editor); + +#include "file_browser.h" +void render_markdown(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor, File_Browser *fb); + +void render_column(Simple_Renderer *sr, Free_Glyph_Atlas *atlas, Editor *editor); + + +// TODO lerp tokens +typedef struct { + Vec4f originalColor; + Vec4f targetColor; + float interpolationProgress; +} TokenColorData; + + + + + + + +#endif // RENDER_H diff --git a/src/simple_renderer.c b/src/simple_renderer.c index 546678b7..4ae3935c 100644 --- a/src/simple_renderer.c +++ b/src/simple_renderer.c @@ -6,16 +6,42 @@ #include #include "./simple_renderer.h" #include "./common.h" +#include "./editor.h" +#include + +char vert_shader_file_path[COUNT_VERTEX_SHADERS][MAX_SHADER_PATH_LENGTH]; +char frag_shader_file_paths[COUNT_FRAGMENT_SHADERS][MAX_SHADER_PATH_LENGTH]; + +/* void set_shader_path(char* buffer, const char* shaderName) { */ +/* const char* home = getenv("HOME"); */ +/* snprintf(buffer, MAX_SHADER_PATH_LENGTH, "%s/.config/ded/shaders/%s", home, shaderName); */ +/* } */ + +void set_shader_path(char* buffer, const char* shaderName) { + const char* home = getenv("HOME"); + if (home == NULL) { + fprintf(stderr, "ERROR: HOME environment variable not set.\n"); + return; // or handle error appropriately + } + snprintf(buffer, MAX_SHADER_PATH_LENGTH, "%s/.config/ded/shaders/%s", home, shaderName); + fprintf(stderr, "Shader path set to: %s\n", buffer); // Debug statement +} -#define vert_shader_file_path "./shaders/simple.vert" -static_assert(COUNT_SIMPLE_SHADERS == 4, "The amount of fragment shaders has changed"); -const char *frag_shader_file_paths[COUNT_SIMPLE_SHADERS] = { - [SHADER_FOR_COLOR] = "./shaders/simple_color.frag", - [SHADER_FOR_IMAGE] = "./shaders/simple_image.frag", - [SHADER_FOR_TEXT] = "./shaders/simple_text.frag", - [SHADER_FOR_EPICNESS] = "./shaders/simple_epic.frag", -}; +void initialize_shader_paths() { + set_shader_path(vert_shader_file_path[VERTEX_SHADER_SIMPLE], "simple.vert"); + set_shader_path(vert_shader_file_path[VERTEX_SHADER_FIXED], "fixed.vert"); + set_shader_path(vert_shader_file_path[VERTEX_SHADER_MINIBUFFER], "minibuffer.vert"); + set_shader_path(vert_shader_file_path[VERTEX_SHADER_WAVE], "wave.vert"); + + set_shader_path(frag_shader_file_paths[SHADER_FOR_COLOR], "simple_color.frag"); + set_shader_path(frag_shader_file_paths[SHADER_FOR_IMAGE], "simple_image.frag"); + set_shader_path(frag_shader_file_paths[SHADER_FOR_TEXT], "simple_text.frag"); + set_shader_path(frag_shader_file_paths[SHADER_FOR_EPICNESS], "simple_epic.frag"); + set_shader_path(frag_shader_file_paths[SHADER_FOR_RAINBOW], "simple_rainbow.frag"); + set_shader_path(frag_shader_file_paths[SHADER_FOR_GLOW], "simple_glow.frag"); + set_shader_path(frag_shader_file_paths[SHADER_FOR_CURSOR], "cursor.frag"); +} static const char *shader_type_as_cstr(GLuint shader) { @@ -50,6 +76,7 @@ static bool compile_shader_source(const GLchar *source, GLenum shader_type, GLui return true; } + static bool compile_shader_file(const char *file_path, GLenum shader_type, GLuint *shader) { bool result = true; @@ -128,106 +155,244 @@ static void get_uniform_location(GLuint program, GLint locations[COUNT_UNIFORM_S } } -void simple_renderer_init(Simple_Renderer *sr) -{ - sr->camera_scale = 3.0f; - - { - glGenVertexArrays(1, &sr->vao); - glBindVertexArray(sr->vao); - - glGenBuffers(1, &sr->vbo); - glBindBuffer(GL_ARRAY_BUFFER, sr->vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(sr->verticies), sr->verticies, GL_DYNAMIC_DRAW); - - // position - glEnableVertexAttribArray(SIMPLE_VERTEX_ATTR_POSITION); - glVertexAttribPointer( - SIMPLE_VERTEX_ATTR_POSITION, - 2, - GL_FLOAT, - GL_FALSE, - sizeof(Simple_Vertex), - (GLvoid *) offsetof(Simple_Vertex, position)); - - // color - glEnableVertexAttribArray(SIMPLE_VERTEX_ATTR_COLOR); - glVertexAttribPointer( - SIMPLE_VERTEX_ATTR_COLOR, - 4, - GL_FLOAT, - GL_FALSE, - sizeof(Simple_Vertex), - (GLvoid *) offsetof(Simple_Vertex, color)); - - // uv - glEnableVertexAttribArray(SIMPLE_VERTEX_ATTR_UV); - glVertexAttribPointer( - SIMPLE_VERTEX_ATTR_UV, - 2, - GL_FLOAT, - GL_FALSE, - sizeof(Simple_Vertex), - (GLvoid *) offsetof(Simple_Vertex, uv)); +/* void simple_renderer_init(Simple_Renderer *sr) */ +/* { */ + +/* if (followCursor) { */ +/* sr->camera_scale = 3.0f; */ +/* } */ + +/* { */ +/* glGenVertexArrays(1, &sr->vao); */ +/* glBindVertexArray(sr->vao); */ + +/* glGenBuffers(1, &sr->vbo); */ +/* glBindBuffer(GL_ARRAY_BUFFER, sr->vbo); */ +/* glBufferData(GL_ARRAY_BUFFER, sizeof(sr->verticies), sr->verticies, GL_DYNAMIC_DRAW); */ + +/* // position */ +/* glEnableVertexAttribArray(SIMPLE_VERTEX_ATTR_POSITION); */ +/* glVertexAttribPointer( */ +/* SIMPLE_VERTEX_ATTR_POSITION, */ +/* 2, */ +/* GL_FLOAT, */ +/* GL_FALSE, */ +/* sizeof(Simple_Vertex), */ +/* (GLvoid *) offsetof(Simple_Vertex, position)); */ + +/* // color */ +/* glEnableVertexAttribArray(SIMPLE_VERTEX_ATTR_COLOR); */ +/* glVertexAttribPointer( */ +/* SIMPLE_VERTEX_ATTR_COLOR, */ +/* 4, */ +/* GL_FLOAT, */ +/* GL_FALSE, */ +/* sizeof(Simple_Vertex), */ +/* (GLvoid *) offsetof(Simple_Vertex, color)); */ + +/* // uv */ +/* glEnableVertexAttribArray(SIMPLE_VERTEX_ATTR_UV); */ +/* glVertexAttribPointer( */ +/* SIMPLE_VERTEX_ATTR_UV, */ +/* 2, */ +/* GL_FLOAT, */ +/* GL_FALSE, */ +/* sizeof(Simple_Vertex), */ +/* (GLvoid *) offsetof(Simple_Vertex, uv)); */ +/* } */ + +/* GLuint shaders[2] = {0}; */ + +/* if (!compile_shader_file(vert_shader_file_path[VERTEX_SHADER_SIMPLE], GL_VERTEX_SHADER, &shaders[0])) { */ +/* exit(1); */ +/* } */ + +/* for (int v = 0; v < COUNT_VERTEX_SHADERS; ++v) { */ +/* for (int f = 0; f < COUNT_FRAGMENT_SHADERS; ++f) { */ +/* GLuint vertexShader, fragmentShader; */ +/* compile_shader_file(vert_shader_file_path[v], GL_VERTEX_SHADER, &vertexShader); */ +/* compile_shader_file(frag_shader_file_paths[f], GL_FRAGMENT_SHADER, &fragmentShader); */ + +/* GLuint program = glCreateProgram(); */ +/* glAttachShader(program, vertexShader); */ +/* glAttachShader(program, fragmentShader); */ +/* link_program(program, __FILE__, __LINE__); */ + +/* sr->programs[v][f] = program; */ + +/* glDeleteShader(fragmentShader); */ +/* glDeleteShader(vertexShader); */ +/* } */ +/* } */ +/* glDeleteShader(shaders[0]); */ +/* } */ + +/* void simple_renderer_reload_shaders(Simple_Renderer *sr) */ +/* { */ +/* GLuint programs[COUNT_FRAGMENT_SHADERS]; */ +/* GLuint shaders[2] = {0}; */ + +/* bool ok = true; */ + +/* if (!compile_shader_file(vert_shader_file_path, GL_VERTEX_SHADER, &shaders[0])) { */ +/* ok = false; */ +/* } */ + +/* for (int i = 0; i < COUNT_FRAGMENT_SHADERS; ++i) { */ +/* if (!compile_shader_file(frag_shader_file_paths[i], GL_FRAGMENT_SHADER, &shaders[1])) { */ +/* ok = false; */ +/* } */ +/* programs[i] = glCreateProgram(); */ +/* attach_shaders_to_program(shaders, sizeof(shaders) / sizeof(shaders[0]), programs[i]); */ +/* if (!link_program(programs[i], __FILE__, __LINE__)) { */ +/* ok = false; */ +/* } */ +/* glDeleteShader(shaders[1]); */ +/* } */ +/* glDeleteShader(shaders[0]); */ + +/* if (ok) { */ +/* /\* for (int i = 0; i < COUNT_FRAGMENT_SHADERS; ++i) { *\/ */ +/* /\* glDeleteProgram(sr->programs[i]); *\/ */ +/* /\* sr->programs[i] = programs[i]; *\/ */ +/* /\* } *\/ */ +/* for (int v = 0; v < COUNT_VERTEX_SHADERS; ++v) { */ +/* for (int f = 0; f < COUNT_FRAGMENT_SHADERS; ++f) { */ +/* glDeleteProgram(sr->programs[v][f]); */ +/* sr->programs[v][f] = programs[v][f]; */ +/* } */ +/* } */ + +/* printf("Reloaded shaders successfully!\n"); */ +/* } else { */ +/* for (int i = 0; i < COUNT_FRAGMENT_SHADERS; ++i) { */ +/* glDeleteProgram(programs[i]); */ +/* } */ +/* } */ +/* } */ + + +void simple_renderer_init(Simple_Renderer *sr) { + if (followCursor) { + sr->camera_scale = 3.0f; } - GLuint shaders[2] = {0}; - - if (!compile_shader_file(vert_shader_file_path, GL_VERTEX_SHADER, &shaders[0])) { - exit(1); - } - - for (int i = 0; i < COUNT_SIMPLE_SHADERS; ++i) { - if (!compile_shader_file(frag_shader_file_paths[i], GL_FRAGMENT_SHADER, &shaders[1])) { - exit(1); - } - sr->programs[i] = glCreateProgram(); - attach_shaders_to_program(shaders, sizeof(shaders) / sizeof(shaders[0]), sr->programs[i]); - if (!link_program(sr->programs[i], __FILE__, __LINE__)) { - exit(1); + // Initialize VAO and VBO + glGenVertexArrays(1, &sr->vao); + glBindVertexArray(sr->vao); + glGenBuffers(1, &sr->vbo); + glBindBuffer(GL_ARRAY_BUFFER, sr->vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(sr->verticies), sr->verticies, GL_DYNAMIC_DRAW); + + // Setup vertex attributes + // Position + glEnableVertexAttribArray(SIMPLE_VERTEX_ATTR_POSITION); + glVertexAttribPointer(SIMPLE_VERTEX_ATTR_POSITION, 2, GL_FLOAT, GL_FALSE, sizeof(Simple_Vertex), (GLvoid *)offsetof(Simple_Vertex, position)); + + // Color + glEnableVertexAttribArray(SIMPLE_VERTEX_ATTR_COLOR); + glVertexAttribPointer(SIMPLE_VERTEX_ATTR_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof(Simple_Vertex), (GLvoid *)offsetof(Simple_Vertex, color)); + + // UV + glEnableVertexAttribArray(SIMPLE_VERTEX_ATTR_UV); + glVertexAttribPointer(SIMPLE_VERTEX_ATTR_UV, 2, GL_FLOAT, GL_FALSE, sizeof(Simple_Vertex), (GLvoid *)offsetof(Simple_Vertex, uv)); + + // Compile and link shaders for each combination + for (int v = 0; v < COUNT_VERTEX_SHADERS; ++v) { + for (int f = 0; f < COUNT_FRAGMENT_SHADERS; ++f) { + GLuint vertexShader, fragmentShader; + + if (!compile_shader_file(vert_shader_file_path[v], GL_VERTEX_SHADER, &vertexShader)) { + fprintf(stderr, "Failed to compile vertex in init: %s\n", vert_shader_file_path[v]); + continue; + } + + if (!compile_shader_file(frag_shader_file_paths[f], GL_FRAGMENT_SHADER, &fragmentShader)) { + fprintf(stderr, "Failed to compile fragment in init: %s\n", frag_shader_file_paths[f]); + glDeleteShader(vertexShader); + continue; + } + + GLuint program = glCreateProgram(); + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + + if (!link_program(program, __FILE__, __LINE__)) { + fprintf(stderr, "Failed to link shader program in init\n"); + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + continue; + } + + sr->programs[v][f] = program; + + // Delete shaders after linking + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); } - glDeleteShader(shaders[1]); } - glDeleteShader(shaders[0]); } -void simple_renderer_reload_shaders(Simple_Renderer *sr) -{ - GLuint programs[COUNT_SIMPLE_SHADERS]; - GLuint shaders[2] = {0}; - +void simple_renderer_reload_shaders(Simple_Renderer *sr) { bool ok = true; - if (!compile_shader_file(vert_shader_file_path, GL_VERTEX_SHADER, &shaders[0])) { - ok = false; - } + GLuint vertexShaders[COUNT_VERTEX_SHADERS]; + GLuint fragmentShaders[COUNT_FRAGMENT_SHADERS]; - for (int i = 0; i < COUNT_SIMPLE_SHADERS; ++i) { - if (!compile_shader_file(frag_shader_file_paths[i], GL_FRAGMENT_SHADER, &shaders[1])) { + // Compile all vertex shaders + for (int v = 0; v < COUNT_VERTEX_SHADERS; ++v) { + if (!compile_shader_file(vert_shader_file_path[v], GL_VERTEX_SHADER, &vertexShaders[v])) { ok = false; } - programs[i] = glCreateProgram(); - attach_shaders_to_program(shaders, sizeof(shaders) / sizeof(shaders[0]), programs[i]); - if (!link_program(programs[i], __FILE__, __LINE__)) { + } + + // Compile all fragment shaders + for (int f = 0; f < COUNT_FRAGMENT_SHADERS; ++f) { + if (!compile_shader_file(frag_shader_file_paths[f], GL_FRAGMENT_SHADER, &fragmentShaders[f])) { ok = false; } - glDeleteShader(shaders[1]); } - glDeleteShader(shaders[0]); + // Link programs for each combination + GLuint newPrograms[COUNT_VERTEX_SHADERS][COUNT_FRAGMENT_SHADERS]; + for (int v = 0; v < COUNT_VERTEX_SHADERS; ++v) { + for (int f = 0; f < COUNT_FRAGMENT_SHADERS; ++f) { + newPrograms[v][f] = glCreateProgram(); + glAttachShader(newPrograms[v][f], vertexShaders[v]); + glAttachShader(newPrograms[v][f], fragmentShaders[f]); + if (!link_program(newPrograms[v][f], __FILE__, __LINE__)) { + ok = false; + } + } + } + + // Clean up old programs and assign new ones if (ok) { - for (int i = 0; i < COUNT_SIMPLE_SHADERS; ++i) { - glDeleteProgram(sr->programs[i]); - sr->programs[i] = programs[i]; + for (int v = 0; v < COUNT_VERTEX_SHADERS; ++v) { + for (int f = 0; f < COUNT_FRAGMENT_SHADERS; ++f) { + glDeleteProgram(sr->programs[v][f]); + sr->programs[v][f] = newPrograms[v][f]; + } } printf("Reloaded shaders successfully!\n"); } else { - for (int i = 0; i < COUNT_SIMPLE_SHADERS; ++i) { - glDeleteProgram(programs[i]); + for (int v = 0; v < COUNT_VERTEX_SHADERS; ++v) { + for (int f = 0; f < COUNT_FRAGMENT_SHADERS; ++f) { + glDeleteProgram(newPrograms[v][f]); + } } } + + // Clean up shaders + for (int v = 0; v < COUNT_VERTEX_SHADERS; ++v) { + glDeleteShader(vertexShaders[v]); + } + for (int f = 0; f < COUNT_FRAGMENT_SHADERS; ++f) { + glDeleteShader(fragmentShaders[f]); + } } + // TODO: Don't render triples of verticies that form a triangle that is completely outside of the screen // // Ideas on how to check if a triangle is outside of the screen: @@ -301,6 +466,23 @@ void simple_renderer_solid_rect(Simple_Renderer *sr, Vec2f p, Vec2f s, Vec4f c) uv, uv, uv, uv); } +void simple_renderer_circle(Simple_Renderer *sr, Vec2f center, float radius, Vec4f color, int segments) { + float angleStep = 2.0f * M_PI / segments; + + // Generate vertices for the circle + Vec2f lastVertex = {center.x + radius, center.y}; + for (int i = 1; i <= segments; ++i) { + float angle = i * angleStep; + Vec2f newVertex = {center.x + cosf(angle) * radius, center.y + sinf(angle) * radius}; + + // Add the triangle for this segment + simple_renderer_triangle(sr, center, lastVertex, newVertex, color, color, color, vec2fs(0), vec2fs(0), vec2fs(0)); + lastVertex = newVertex; + } +} + + + void simple_renderer_sync(Simple_Renderer *sr) { glBufferSubData(GL_ARRAY_BUFFER, @@ -314,17 +496,34 @@ void simple_renderer_draw(Simple_Renderer *sr) glDrawArrays(GL_TRIANGLES, 0, sr->verticies_count); } -void simple_renderer_set_shader(Simple_Renderer *sr, Simple_Shader shader) -{ - sr->current_shader = shader; - glUseProgram(sr->programs[sr->current_shader]); - get_uniform_location(sr->programs[sr->current_shader], sr->uniforms); +/* void simple_renderer_set_shader(Simple_Renderer *sr, Simple_Shader shader) */ +/* { */ +/* sr->current_shader = shader; */ +/* glUseProgram(sr->programs[sr->current_shader]); */ +/* get_uniform_location(sr->programs[sr->current_shader], sr->uniforms); */ +/* glUniform2f(sr->uniforms[UNIFORM_SLOT_RESOLUTION], sr->resolution.x, sr->resolution.y); */ +/* glUniform1f(sr->uniforms[UNIFORM_SLOT_TIME], sr->time); */ +/* glUniform2f(sr->uniforms[UNIFORM_SLOT_CAMERA_POS], sr->camera_pos.x, sr->camera_pos.y); */ +/* glUniform1f(sr->uniforms[UNIFORM_SLOT_CAMERA_SCALE], sr->camera_scale); */ +/* } */ + +void simple_renderer_set_shader(Simple_Renderer *sr, int vertexShaderIndex, int fragmentShaderIndex) { + GLuint program = sr->programs[vertexShaderIndex][fragmentShaderIndex]; + glUseProgram(program); + + get_uniform_location(program, sr->uniforms); + glUniform2f(sr->uniforms[UNIFORM_SLOT_RESOLUTION], sr->resolution.x, sr->resolution.y); glUniform1f(sr->uniforms[UNIFORM_SLOT_TIME], sr->time); glUniform2f(sr->uniforms[UNIFORM_SLOT_CAMERA_POS], sr->camera_pos.x, sr->camera_pos.y); glUniform1f(sr->uniforms[UNIFORM_SLOT_CAMERA_SCALE], sr->camera_scale); + + // Optionally store the current shader indices if needed + sr->current_vertex_shader_index = vertexShaderIndex; + sr->current_fragment_shader_index = fragmentShaderIndex; } + void simple_renderer_flush(Simple_Renderer *sr) { simple_renderer_sync(sr); diff --git a/src/simple_renderer.h b/src/simple_renderer.h index b67bdb03..7b6f02e8 100644 --- a/src/simple_renderer.h +++ b/src/simple_renderer.h @@ -39,16 +39,28 @@ typedef enum { SHADER_FOR_COLOR = 0, SHADER_FOR_IMAGE, SHADER_FOR_TEXT, - SHADER_FOR_EPICNESS, // This is the one that does that cool rainbowish animation - COUNT_SIMPLE_SHADERS, + SHADER_FOR_GLOW, + SHADER_FOR_EPICNESS, + SHADER_FOR_RAINBOW, + SHADER_FOR_CURSOR, + VERTEX_SHADER_SIMPLE, + VERTEX_SHADER_FIXED, + VERTEX_SHADER_MINIBUFFER, + VERTEX_SHADER_WAVE, + COUNT_FRAGMENT_SHADERS, + COUNT_VERTEX_SHADERS, } Simple_Shader; typedef struct { GLuint vao; GLuint vbo; - GLuint programs[COUNT_SIMPLE_SHADERS]; + /* GLuint programs[COUNT_SIMPLE_SHADERS]; */ + GLuint programs[COUNT_VERTEX_SHADERS][COUNT_FRAGMENT_SHADERS]; Simple_Shader current_shader; + int current_vertex_shader_index; + int current_fragment_shader_index; + GLint uniforms[COUNT_UNIFORM_SLOTS]; Simple_Vertex verticies[SIMPLE_VERTICIES_CAP]; size_t verticies_count; @@ -62,25 +74,42 @@ typedef struct { Vec2f camera_vel; } Simple_Renderer; -void simple_renderer_init(Simple_Renderer *sr); -void simple_renderer_reload_shaders(Simple_Renderer *sr); +// old +/* extern const char *vert_shader_file_path; */ + + +#define MAX_SHADER_PATH_LENGTH 256 +/* extern char vert_shader_file_path[MAX_SHADER_PATH_LENGTH]; */ +extern char vert_shader_file_path[COUNT_VERTEX_SHADERS][MAX_SHADER_PATH_LENGTH]; + + -void simple_renderer_vertex(Simple_Renderer *sr, - Vec2f p, Vec4f c, Vec2f uv); -void simple_renderer_set_shader(Simple_Renderer *sr, Simple_Shader shader); -void simple_renderer_triangle(Simple_Renderer *sr, - Vec2f p0, Vec2f p1, Vec2f p2, - Vec4f c0, Vec4f c1, Vec4f c2, - Vec2f uv0, Vec2f uv1, Vec2f uv2); -void simple_renderer_quad(Simple_Renderer *sr, - Vec2f p0, Vec2f p1, Vec2f p2, Vec2f p3, - Vec4f c0, Vec4f c1, Vec4f c2, Vec4f c3, + + + +void simple_renderer_init(Simple_Renderer *sr); +void simple_renderer_reload_shaders(Simple_Renderer *sr); +void simple_renderer_vertex(Simple_Renderer *sr, Vec2f p, Vec4f c, Vec2f uv); +void simple_renderer_set_shader(Simple_Renderer *sr, int vertexShaderIndex, + int fragmentShaderIndex); +void simple_renderer_triangle(Simple_Renderer *sr, Vec2f p0, Vec2f p1, Vec2f p2, + Vec4f c0, Vec4f c1, Vec4f c2, Vec2f uv0, + Vec2f uv1, Vec2f uv2); +void simple_renderer_quad(Simple_Renderer *sr, Vec2f p0, Vec2f p1, Vec2f p2, + Vec2f p3, Vec4f c0, Vec4f c1, Vec4f c2, Vec4f c3, Vec2f uv0, Vec2f uv1, Vec2f uv2, Vec2f uv3); void simple_renderer_solid_rect(Simple_Renderer *sr, Vec2f p, Vec2f s, Vec4f c); -void simple_renderer_image_rect(Simple_Renderer *sr, Vec2f p, Vec2f s, Vec2f uvp, Vec2f uvs, Vec4f c); -void simple_renderer_flush(Simple_Renderer *sr); -void simple_renderer_sync(Simple_Renderer *sr); -void simple_renderer_draw(Simple_Renderer *sr); +void simple_renderer_image_rect(Simple_Renderer *sr, Vec2f p, Vec2f s, + Vec2f uvp, Vec2f uvs, Vec4f c); +void simple_renderer_flush(Simple_Renderer * sr); +void simple_renderer_sync(Simple_Renderer * sr); +void simple_renderer_draw(Simple_Renderer * sr); + +// ADDED +void initialize_shader_paths(); +void simple_renderer_circle(Simple_Renderer *sr, Vec2f center, float radius, Vec4f color, int segments); + + /* const char *resolve_shader_path(const char *shader_file_name); */ #endif // SIMPLE_RENDERER_H_ diff --git a/src/theme.c b/src/theme.c new file mode 100644 index 00000000..b389009a --- /dev/null +++ b/src/theme.c @@ -0,0 +1,1003 @@ +#include "theme.h" +#include "common.h" +#include "editor.h" +#include + +int currentThemeIndex = 9; +int previousThemeIndex = 9; +float interpolationProgress; +Theme themes[11]; +Theme currentTheme; +Theme previousTheme; + +bool theme_lerp = true; +float theme_lerp_speed = 0.015f; +float theme_lerp_treshold = 1.0f; // 0.5 mix themes + +Vec4f color_lerp(Vec4f start, Vec4f end, float t) { + Vec4f result; + result.x = start.x + (end.x - start.x) * t; + result.y = start.y + (end.y - start.y) * t; + result.z = start.z + (end.z - start.z) * t; + result.w = start.w + (end.w - start.w) * t; + return result; +} + +// Function to smoothly transition a color field in the current theme +void transition_color(Vec4f *color_field, Vec4f target_color, + float transition_speed) { + if (theme_lerp) { + *color_field = color_lerp(*color_field, target_color, transition_speed); + } else { + *color_field = target_color; + } +} + +void switch_to_theme(int *currentThemeIndex, int newIndex) { + const int themeCount = sizeof(themes) / sizeof(themes[0]); + + // Check if newIndex is valid + if (newIndex < 0 || newIndex >= themeCount) { + return; // Invalid index, do nothing + } + + // Update previous theme information + previousTheme = currentTheme; + previousThemeIndex = *currentThemeIndex; + + // Set the new theme index + *currentThemeIndex = newIndex; + + // Reset interpolation progress + interpolationProgress = 0.0f; + + if (!theme_lerp) { + // If theme lerp is disabled, set the current theme immediately + currentTheme = themes[*currentThemeIndex]; + } +} + +void theme_next(int *currentThemeIndex) { + previousTheme = currentTheme; // Capture the current interpolated state + previousThemeIndex = *currentThemeIndex; + + const int themeCount = sizeof(themes) / sizeof(themes[0]); + *currentThemeIndex = (*currentThemeIndex + 1) % themeCount; + + // Check if the new index is 7; if so, skip it + if (*currentThemeIndex == 7) { + *currentThemeIndex = (*currentThemeIndex + 1) % themeCount; + } + + if (!theme_lerp) { + currentTheme = themes[*currentThemeIndex]; + } + interpolationProgress = 0.0f; // Restart interpolation +} + +void theme_previous(int *currentThemeIndex) { + previousTheme = currentTheme; // Capture the current interpolated state + previousThemeIndex = *currentThemeIndex; + + *currentThemeIndex -= 1; + if (*currentThemeIndex < 0) { + const int themeCount = sizeof(themes) / sizeof(themes[0]); + *currentThemeIndex = themeCount - 1; + } + + // Check if the new index is 7; if so, skip it + if (*currentThemeIndex == 7) { + *currentThemeIndex -= 1; + if (*currentThemeIndex < 0) { + const int themeCount = sizeof(themes) / sizeof(themes[0]); + *currentThemeIndex = themeCount - 1; + } + } + + if (!theme_lerp) { + currentTheme = themes[*currentThemeIndex]; + } + + interpolationProgress = 0.0f; // Restart interpolation +} + +void update_theme_interpolation() { + if (theme_lerp && interpolationProgress < theme_lerp_treshold) { + interpolationProgress += theme_lerp_speed; + Theme startTheme = previousTheme; + Theme endTheme = themes[currentThemeIndex]; + + // Interpolate each color component + currentTheme.cursor = color_lerp(startTheme.cursor, endTheme.cursor, interpolationProgress); + currentTheme.insert_cursor = color_lerp(startTheme.insert_cursor, endTheme.insert_cursor, interpolationProgress); + currentTheme.emacs_cursor = color_lerp(startTheme.emacs_cursor, endTheme.emacs_cursor, interpolationProgress); + currentTheme.text = color_lerp(startTheme.text, endTheme.text, interpolationProgress); + currentTheme.background = color_lerp(startTheme.background, endTheme.background, interpolationProgress); + currentTheme.logic = color_lerp(startTheme.logic, endTheme.logic, interpolationProgress); + currentTheme.comment = color_lerp(startTheme.comment, endTheme.comment, interpolationProgress); + currentTheme.hashtag = color_lerp(startTheme.hashtag, endTheme.hashtag, interpolationProgress); + currentTheme.string = color_lerp(startTheme.string, endTheme.string, interpolationProgress); + currentTheme.selection = color_lerp(startTheme.selection, endTheme.selection, interpolationProgress); + currentTheme.search = color_lerp(startTheme.search, endTheme.search, interpolationProgress); + currentTheme.line_numbers = color_lerp(startTheme.line_numbers, endTheme.line_numbers, interpolationProgress); + currentTheme.todo = color_lerp(startTheme.todo, endTheme.todo, interpolationProgress); + currentTheme.fixme = color_lerp(startTheme.fixme, endTheme.fixme, interpolationProgress); + currentTheme.note = color_lerp(startTheme.note, endTheme.note, interpolationProgress); + currentTheme.bug = color_lerp(startTheme.bug, endTheme.bug, interpolationProgress); + currentTheme.equals = color_lerp(startTheme.equals, endTheme.equals, interpolationProgress); + currentTheme.not_equals = color_lerp(startTheme.not_equals, endTheme.not_equals, interpolationProgress); + currentTheme.exclamation = color_lerp(startTheme.exclamation, endTheme.exclamation, interpolationProgress); + currentTheme.equals_equals = color_lerp(startTheme.equals_equals, endTheme.equals_equals, interpolationProgress); + currentTheme.less_than = color_lerp(startTheme.less_than, endTheme.less_than, interpolationProgress); + currentTheme.greater_than = color_lerp(startTheme.greater_than, endTheme.greater_than, interpolationProgress); + currentTheme.arrow = color_lerp(startTheme.arrow, endTheme.arrow, interpolationProgress); + currentTheme.plus = color_lerp(startTheme.plus, endTheme.plus, interpolationProgress); + currentTheme.minus = color_lerp(startTheme.minus, endTheme.minus, interpolationProgress); + currentTheme.truee = color_lerp(startTheme.truee, endTheme.truee, interpolationProgress); + currentTheme.falsee = color_lerp(startTheme.falsee, endTheme.falsee, interpolationProgress); + currentTheme.open_square = color_lerp(startTheme.open_square, endTheme.open_square, interpolationProgress); + currentTheme.close_square = color_lerp(startTheme.close_square, endTheme.close_square, interpolationProgress); + currentTheme.array_content = color_lerp(startTheme.array_content, endTheme.array_content, interpolationProgress); + currentTheme.current_line_number = color_lerp(startTheme.current_line_number, endTheme.current_line_number, interpolationProgress); + currentTheme.marks = color_lerp(startTheme.marks, endTheme.marks, interpolationProgress); + currentTheme.fb_selection = color_lerp(startTheme.fb_selection, endTheme.fb_selection, interpolationProgress); + currentTheme.link = color_lerp(startTheme.link, endTheme.link, interpolationProgress); + currentTheme.logic_or = color_lerp(startTheme.logic_or, endTheme.logic_or, interpolationProgress); + currentTheme.pipe = color_lerp(startTheme.pipe, endTheme.pipe, interpolationProgress); + currentTheme.logic_and = color_lerp(startTheme.logic_and, endTheme.logic_and, interpolationProgress); + currentTheme.ampersand = color_lerp(startTheme.ampersand, endTheme.ampersand, interpolationProgress); + currentTheme.multiplication = color_lerp(startTheme.multiplication, endTheme.multiplication, interpolationProgress); + currentTheme.pointer = color_lerp(startTheme.pointer, endTheme.pointer, interpolationProgress); + currentTheme.modeline = color_lerp(startTheme.modeline, endTheme.modeline, interpolationProgress); + currentTheme.modeline_accent = color_lerp(startTheme.modeline_accent, endTheme.modeline_accent, interpolationProgress); + currentTheme.minibuffer = color_lerp(startTheme.minibuffer, endTheme.minibuffer, interpolationProgress); + currentTheme.matching_parenthesis = color_lerp(startTheme.matching_parenthesis, endTheme.matching_parenthesis, interpolationProgress); + currentTheme.hl_line = color_lerp(startTheme.hl_line, endTheme.hl_line, interpolationProgress); + currentTheme.type = color_lerp(startTheme.type, endTheme.type, interpolationProgress); + currentTheme.function_definition = color_lerp(startTheme.function_definition, endTheme.function_definition, interpolationProgress); + currentTheme.anchor = color_lerp(startTheme.anchor, endTheme.anchor, interpolationProgress); + currentTheme.whitespace = color_lerp(startTheme.whitespace, endTheme.whitespace, interpolationProgress); + currentTheme.indentation_line = color_lerp(startTheme.indentation_line, endTheme.indentation_line, interpolationProgress); + currentTheme.null = color_lerp(startTheme.null, endTheme.null, interpolationProgress); + currentTheme.code_block = color_lerp(startTheme.code_block, endTheme.code_block, interpolationProgress); + currentTheme.fringe = color_lerp(startTheme.fringe, endTheme.fringe, interpolationProgress); + currentTheme.fill_column = color_lerp(startTheme.fill_column, endTheme.fill_column, interpolationProgress); + currentTheme.open_curly = color_lerp(startTheme.open_curly, endTheme.open_curly, interpolationProgress); + currentTheme.close_curly = color_lerp(startTheme.close_curly, endTheme.close_curly, interpolationProgress); + currentTheme.fb_dir_name = color_lerp(startTheme.fb_dir_name, endTheme.fb_dir_name, interpolationProgress); + currentTheme.fb_size = color_lerp(startTheme.fb_size, endTheme.fb_size, interpolationProgress); + currentTheme.fb_date_time = color_lerp(startTheme.fb_date_time, endTheme.fb_date_time, interpolationProgress); + currentTheme.fb_no_priv = color_lerp(startTheme.fb_no_priv, endTheme.fb_no_priv, interpolationProgress); + currentTheme.fb_read_priv = color_lerp(startTheme.fb_read_priv, endTheme.fb_read_priv, interpolationProgress); + currentTheme.fb_write_priv = color_lerp(startTheme.fb_write_priv, endTheme.fb_write_priv, interpolationProgress); + currentTheme.fb_exec_priv = color_lerp(startTheme.fb_exec_priv, endTheme.fb_exec_priv, interpolationProgress); + currentTheme.fb_dir_priv = color_lerp(startTheme.fb_dir_priv, endTheme.fb_dir_priv, interpolationProgress); + currentTheme.line_numbers_background = color_lerp(startTheme.line_numbers_background, endTheme.line_numbers_background, interpolationProgress); + + + + if (interpolationProgress >= 1.0f) { + interpolationProgress = 1.0f; + } + } else if (!theme_lerp) { + currentTheme = themes[currentThemeIndex]; + interpolationProgress = 1.0f; + } +} + + +// TODO each theme should have a name not only an index +void initialize_themes() { + // Nature + themes[0] = (Theme){ + .cursor = hex_to_vec4f(0x658B5FFF), + .notext_cursor = hex_to_vec4f(0x658B5FFF), + .EOF_cursor = hex_to_vec4f(0x658B5FFF), + .insert_cursor = hex_to_vec4f(0x514B8EFF), + .emacs_cursor = hex_to_vec4f(0x565663FF), + .text = hex_to_vec4f(0xC0ACD1FF), + /* .background = hex_to_vec4f(0x090909FF), */ + .background = hex_to_vec4f(0x0B0B0BFF), + /* .fringe = hex_to_vec4f(0x090909FF), */ + .fringe = hex_to_vec4f(0x0B0B0BFF), + .comment = hex_to_vec4f(0x867892FF), + .hashtag = hex_to_vec4f(0x658B5FFF), + .logic = hex_to_vec4f(0x658B5FFF), + .string = hex_to_vec4f(0x4C6750FF), + .selection = hex_to_vec4f(0x262626FF), + .search = hex_to_vec4f(0x262626FF), + .todo = hex_to_vec4f(0x565663FF), + .line_numbers = hex_to_vec4f(0x171717FF), + .current_line_number = hex_to_vec4f(0xC0ACD1FF), + .fixme = hex_to_vec4f(0x444E46FF), + .note = hex_to_vec4f(0x4C6750FF), + .bug = hex_to_vec4f(0x867892FF), + .not_equals = hex_to_vec4f(0x867892FF), + .exclamation = hex_to_vec4f(0x4C6750FF), + .equals = hex_to_vec4f(0xC0ACD1FF), + .equals_equals = hex_to_vec4f(0x658B5FFF), + .greater_than = hex_to_vec4f(0x834EB6FF), + .less_than = hex_to_vec4f(0x834EB6FF), + .marks = hex_to_vec4f(0x658B5FFF), + .fb_selection = hex_to_vec4f(0x262626FF), + .plus = hex_to_vec4f(0x658B5FFF), + .minus = hex_to_vec4f(0x658B5FFF), + .truee = hex_to_vec4f(0x4C6750FF), + .falsee = hex_to_vec4f(0x867892FF), + .arrow = hex_to_vec4f(0x834EB6FF), + .open_curly = hex_to_vec4f(0x4C6750FF), + .close_curly = hex_to_vec4f(0x4C6750FF), + .open_square = hex_to_vec4f(0x514B8EFF), + .close_square = hex_to_vec4f(0x514B8EFF), + .array_content = hex_to_vec4f(0xC0ACD1FF), + .link = hex_to_vec4f(0x565663FF), + .logic_or = hex_to_vec4f(0x658B5FFF), + .pipe = hex_to_vec4f(0x565663FF), + .ampersand = hex_to_vec4f(0x658B5FFF), + .logic_and = hex_to_vec4f(0x658B5FFF), + .pointer = hex_to_vec4f(0x514B8EFF), + .multiplication = hex_to_vec4f(0x867892FF), + .matching_parenthesis = hex_to_vec4f(0x262626FF), + .hl_line = hex_to_vec4f(0x070707FF), + .type = hex_to_vec4f(0x565663FF), + .function_definition = hex_to_vec4f(0x564F96FF), + .anchor = hex_to_vec4f(0x564F96FF), + .minibuffer = hex_to_vec4f(0x090909FF), + .modeline = hex_to_vec4f(0x060606FF), + .modeline_accent = hex_to_vec4f(0x658B5FFF), + .whitespace = hex_to_vec4f(0x171717FF), + .selected_whitespaces = hex_to_vec4f(0x9989A7FF), + .indentation_line = hex_to_vec4f(0x171717FF), + .null = hex_to_vec4f(0x564F96FF), + .code_block = hex_to_vec4f(0x080808FF), + .nest1 = hex_to_vec4f(0x658B5FFF), + .nest2 = hex_to_vec4f(0x514B8EFF), + .nest3 = hex_to_vec4f(0x658B5FFF), + .nest4 = hex_to_vec4f(0x514B8EFF), + .nest5 = hex_to_vec4f(0x658B5FFF), + .nest6 = hex_to_vec4f(0x514B8EFF), + .fill_column = hex_to_vec4f(0x262626FF), + .fb_dir_name = hex_to_vec4f(0x658B5FFF), + .fb_size = hex_to_vec4f(0x48534AFF), + .fb_date_time = hex_to_vec4f(0x645B97FF), + .fb_no_priv = hex_to_vec4f(0x867892FF), + .fb_read_priv = hex_to_vec4f(0x565663FF), + .fb_write_priv = hex_to_vec4f(0x444E46FF), + .fb_exec_priv = hex_to_vec4f(0x4C6750FF), + .fb_dir_priv = hex_to_vec4f(0x658B5FFF), + .line_numbers_background = hex_to_vec4f(0x090909FF), + + }; + + // DOOM one + themes[1] = (Theme){ + .cursor = hex_to_vec4f(0x51AFEFFF), // #51AFEF + .notext_cursor = hex_to_vec4f(0x51AFEFFF), // #51AFEF + .EOF_cursor = hex_to_vec4f(0x51AFEFFF), // #51AFEF + .insert_cursor = hex_to_vec4f(0x51AFEFFF), + .emacs_cursor = hex_to_vec4f(0xECBE7BFF), // #ECBE7B + .text = hex_to_vec4f(0xBBC2CFFF), + .background = hex_to_vec4f(0x282C34FF), + .fringe = hex_to_vec4f(0x282C34FF), + .comment = hex_to_vec4f(0x5B6268FF), + .hashtag = hex_to_vec4f(0x51AFEFFF), + .logic = hex_to_vec4f(0x51AFEFFF), + .string = hex_to_vec4f(0x98BE65FF), // #98BE65 + .selection = hex_to_vec4f(0x42444AFF), + .search = hex_to_vec4f(0x387AA7FF), // #387AA7 + .todo = hex_to_vec4f(0xECBE7BFF), + .line_numbers = hex_to_vec4f(0x3F444AFF), + .current_line_number = hex_to_vec4f(0xBBC2CFFF), + .fixme = hex_to_vec4f(0xFF6C6BFF), // #FF6C6B + .note = hex_to_vec4f(0x98BE65FF), + .bug = hex_to_vec4f(0xFF6C6BFF), + .not_equals = hex_to_vec4f(0xFF6C6BFF), + .exclamation = hex_to_vec4f(0x51AFEFFF), + .equals = hex_to_vec4f(0x98BE65FF), + .equals_equals = hex_to_vec4f(0x98BE65FF), + .greater_than = hex_to_vec4f(0x98BE65FF), + .less_than = hex_to_vec4f(0xFF6C6BFF), + .marks = hex_to_vec4f(0x387AA7FF), + .fb_selection = hex_to_vec4f(0x42444AFF), + .plus = hex_to_vec4f(0x98BE65FF), + .minus = hex_to_vec4f(0xFF6C6BFF), + .truee = hex_to_vec4f(0x98BE65FF), + .falsee = hex_to_vec4f(0xFF6C6BFF), + .arrow = hex_to_vec4f(0xBBC2CFFF), + .open_curly = hex_to_vec4f(0x51AFEFFF), + .close_curly = hex_to_vec4f(0x51AFEFFF), + .open_square = hex_to_vec4f(0xBBC2CFFF), + .close_square = hex_to_vec4f(0xBBC2CFFF), + .array_content = hex_to_vec4f(0xA9A1E1FF), + .link = hex_to_vec4f(0xA9A1E1FF), // #A9A1E1 + .matching_parenthesis = hex_to_vec4f(0x42444AFF), + .type = hex_to_vec4f(0xECBE7BFF), + .function_definition = hex_to_vec4f(0xC678DDFF), // #C678DD + .anchor = hex_to_vec4f(0xA9A1E1FF), + .hl_line = hex_to_vec4f(0x21242BFF), // #21242B + .multiplication = hex_to_vec4f(0x98BE65FF), + .pointer = hex_to_vec4f(0xA9A1E1FF), + .logic_and = hex_to_vec4f(0x98BE65FF), + .logic_or = hex_to_vec4f(0xFF6C6BFF), + .ampersand = hex_to_vec4f(0x51AFEFFF), + .pipe = hex_to_vec4f(0x98BE65FF), + .minibuffer = hex_to_vec4f(0x21242BFF), + .line_numbers_background = hex_to_vec4f(0x21242BFF), + .modeline = hex_to_vec4f(0x1D2026FF), + .modeline_accent = hex_to_vec4f(0x51AFEFFF), + .whitespace = hex_to_vec4f(0x3F444AFF), + .selected_whitespaces = hex_to_vec4f(0x959BA5FF), + .indentation_line = hex_to_vec4f(0x3F444AFF), + .null = hex_to_vec4f(0xA9A1E1FF), + .code_block = hex_to_vec4f(0x23272EFF), + .fill_column = hex_to_vec4f(0x42444AFF), + .fb_dir_name = hex_to_vec4f(0x51AFEFFF), + .fb_size = hex_to_vec4f(0xDA8548FF), + .fb_date_time = hex_to_vec4f(0x46D9FFFF), + .fb_no_priv = hex_to_vec4f(0x5B6268FF), + .fb_read_priv = hex_to_vec4f(0xECBE7BFF), + .fb_write_priv = hex_to_vec4f(0xFF6C6BFF), + .fb_exec_priv = hex_to_vec4f(0x98BE65FF), + .fb_dir_priv = hex_to_vec4f(0x51AFEFFF), + + + }; + + // Dracula + themes[2] = (Theme){ + .cursor = hex_to_vec4f(0xBD93F9FF), // #BD93F9 + .notext_cursor = hex_to_vec4f(0xBD93F9FF), // #BD93F9 + .EOF_cursor = hex_to_vec4f(0xBD93F9FF), // #BD93F9 + .insert_cursor = hex_to_vec4f(0xBD93F9FF), + .emacs_cursor = hex_to_vec4f(0xF1FA8CFF), // #F1FA8C + .text = hex_to_vec4f(0xF8F8F2FF), + .background = hex_to_vec4f(0x282A36FF), + .fringe = hex_to_vec4f(0x282A36FF), + .comment = hex_to_vec4f(0x6272A4FF), + .hashtag = hex_to_vec4f(0xBD93F9FF), + .logic = hex_to_vec4f(0xFF79C6FF), // #FF79C6 + .string = hex_to_vec4f(0xF1FA8CFF), + .selection = hex_to_vec4f(0x44475AFF), + .search = hex_to_vec4f(0x8466AEFF), // #8466AE + .todo = hex_to_vec4f(0xF1FA8CFF), + .line_numbers = hex_to_vec4f(0x6272A4FF), + .current_line_number = hex_to_vec4f(0xF8F8F2FF), + .fixme = hex_to_vec4f(0xFF5555FF), // #FF5555 + .note = hex_to_vec4f(0x50FA7BFF), // #50FA7B + .bug = hex_to_vec4f(0xFF5555FF), + .not_equals = hex_to_vec4f(0xFF5555FF), + .exclamation = hex_to_vec4f(0xBD93F9FF), + .equals = hex_to_vec4f(0x50FA7BFF), + .equals_equals = hex_to_vec4f(0x50FA7BFF), + .greater_than = hex_to_vec4f(0x50FA7BFF), + .less_than = hex_to_vec4f(0xFF5555FF), + .marks = hex_to_vec4f(0x8466AEFF), + .fb_selection = hex_to_vec4f(0x44475AFF), + .plus = hex_to_vec4f(0x50FA7BFF), + .minus = hex_to_vec4f(0xFF5555FF), + .truee = hex_to_vec4f(0x50FA7BFF), + .falsee = hex_to_vec4f(0xFF5555FF), + .arrow = hex_to_vec4f(0x8BE9FDFF), // #8BE9FD + .open_curly = hex_to_vec4f(0xFF79C6FF), + .close_curly = hex_to_vec4f(0xFF79C6FF), + .open_square = hex_to_vec4f(0xF8F8F2FF), + .close_square = hex_to_vec4f(0xF8F8F2FF), + .array_content = hex_to_vec4f(0xBD93F9FF), + .link = hex_to_vec4f(0x8BE9FDFF), + .matching_parenthesis = hex_to_vec4f(0x44475AFF), + .type = hex_to_vec4f(0xBD93F9FF), + .function_definition = hex_to_vec4f(0x50FA7BFF), + .anchor = hex_to_vec4f(0xFF79C6FF), + .hl_line = hex_to_vec4f(0x1E2029FF), // #1E2029 + .multiplication = hex_to_vec4f(0x50FA7BFF), + .pointer = hex_to_vec4f(0xFFC9E8FF), // #FFC9E8 + .logic_and = hex_to_vec4f(0x50FA7BFF), + .logic_or = hex_to_vec4f(0xFF5555FF), + .ampersand = hex_to_vec4f(0x8BE9FDFF), + .pipe = hex_to_vec4f(0x50FA7BFF), + .minibuffer = hex_to_vec4f(0x1E2029FF), // #1E2029 + .line_numbers_background = hex_to_vec4f(0x1E2029FF), // #1E2029 + .modeline = hex_to_vec4f(0x22232DFF), + .modeline_accent = hex_to_vec4f(0xBD93F9FF), + .whitespace = hex_to_vec4f(0x565761FF), + .selected_whitespaces = hex_to_vec4f(0xC6C6C1FF), + .indentation_line = hex_to_vec4f(0x565761FF), + .null = hex_to_vec4f(0x8BE9FDFF), + .code_block = hex_to_vec4f(0x23242FFF), + .fill_column = hex_to_vec4f(0x44475AFF), + .fb_dir_name = hex_to_vec4f(0x61BFFFFF), + .fb_size = hex_to_vec4f(0xFFB86CFF), + .fb_date_time = hex_to_vec4f(0x8BE9FDFF), + .fb_no_priv = hex_to_vec4f(0x6272A4FF), + .fb_read_priv = hex_to_vec4f(0xF1FA8CFF), + .fb_write_priv = hex_to_vec4f(0xFF5555FF), + .fb_exec_priv = hex_to_vec4f(0x50FA7BFF), + .fb_dir_priv = hex_to_vec4f(0x61BFFFFF), + + }; + + // Palenigh + themes[3] = (Theme){ + .cursor = hex_to_vec4f(0xC792EAFF), // #C792EA + .notext_cursor = hex_to_vec4f(0xC792EAFF), // #C792EA + .EOF_cursor = hex_to_vec4f(0xC792EAFF), // #C792EA + .insert_cursor = hex_to_vec4f(0xC792EAFF), + .emacs_cursor = hex_to_vec4f(0xFFCB6BFF), // #FFCB6B + .text = hex_to_vec4f(0xEEFFFFFF), + .background = hex_to_vec4f(0x292D3EFF), + .fringe = hex_to_vec4f(0x292D3EFF), + .comment = hex_to_vec4f(0x676E95FF), + .hashtag = hex_to_vec4f(0x89DDFFFF), // #89DDFF + .logic = hex_to_vec4f(0x89DDFFFF), + .string = hex_to_vec4f(0xC3E88DFF), // #C3E88D + .selection = hex_to_vec4f(0x3C435EFF), + .search = hex_to_vec4f(0x4E5579FF), + .todo = hex_to_vec4f(0xFFCB6BFF), + .line_numbers = hex_to_vec4f(0x676E95FF), + .current_line_number = hex_to_vec4f(0xEEFFFFFF), + .fixme = hex_to_vec4f(0xFF5370FF), // #FF5370 + .note = hex_to_vec4f(0xC3E88DFF), + .bug = hex_to_vec4f(0xFF5370FF), + .not_equals = hex_to_vec4f(0xFF5370FF), + .exclamation = hex_to_vec4f(0x89DDFFFF), + .equals = hex_to_vec4f(0xC3E88DFF), + .equals_equals = hex_to_vec4f(0xC3E88DFF), + .greater_than = hex_to_vec4f(0xC3E88DFF), + .less_than = hex_to_vec4f(0xFF5370FF), + .marks = hex_to_vec4f(0x4E5579FF), + .fb_selection = hex_to_vec4f(0x3C435EFF), + .plus = hex_to_vec4f(0xC3E88DFF), + .minus = hex_to_vec4f(0xFF5370FF), + .truee = hex_to_vec4f(0xC3E88DFF), + .falsee = hex_to_vec4f(0xFF5370FF), + .arrow = hex_to_vec4f(0xFFCB6BFF), + .open_curly = hex_to_vec4f(0xC792EAFF), + .close_curly = hex_to_vec4f(0xC792EAFF), + .open_square = hex_to_vec4f(0xEEFFFFFF), + .close_square = hex_to_vec4f(0xEEFFFFFF), + .array_content = hex_to_vec4f(0x82AAFFFF), // #82AAFF + .link = hex_to_vec4f(0x89DDFFFF), + .logic_or = hex_to_vec4f(0xFF5370FF), + .pipe = hex_to_vec4f(0xC3E88DFF), + .ampersand = hex_to_vec4f(0x89DDFFFF), + .logic_and = hex_to_vec4f(0xC3E88DFF), + .pointer = hex_to_vec4f(0xF78C6CFF), // #F78C6C + .multiplication = hex_to_vec4f(0xC3E88DFF), + .matching_parenthesis = hex_to_vec4f(0x3C435EFF), + .hl_line = hex_to_vec4f(0x242837FF), + .type = hex_to_vec4f(0xC792EAFF), + .function_definition = hex_to_vec4f(0x82AAFFFF), + .anchor = hex_to_vec4f(0xFF5370FF), + .minibuffer = hex_to_vec4f(0x292D3EFF), + .line_numbers_background = hex_to_vec4f(0x292D3EFF), + .modeline = hex_to_vec4f(0x232635FF), + .modeline_accent = hex_to_vec4f(0xC792EAFF), + .whitespace = hex_to_vec4f(0x4E5579FF), + .selected_whitespaces = hex_to_vec4f(0xBECCCCFF), + .indentation_line = hex_to_vec4f(0x4E5579FF), + .null = hex_to_vec4f(0xF78C6CFF), + .code_block = hex_to_vec4f(0x232635FF), + .fill_column = hex_to_vec4f(0x3C435EFF), + .fb_dir_name = hex_to_vec4f(0x82AAFFFF), + .fb_size = hex_to_vec4f(0xF78C6CFF), + .fb_date_time = hex_to_vec4f(0x89DDFFFF), + .fb_no_priv = hex_to_vec4f(0x676E95FF), + .fb_read_priv = hex_to_vec4f(0xFFCB6BFF), + .fb_write_priv = hex_to_vec4f(0xFF5370FF), + .fb_exec_priv = hex_to_vec4f(0xC3E88DFF), + .fb_dir_priv = hex_to_vec4f(0x82AAFFFF), + + }; + + + // DOOM city lights + themes[4] = (Theme){ + .cursor = hex_to_vec4f(0x5EC4FFFF), // #5EC4FF + .notext_cursor = hex_to_vec4f(0x5EC4FFFF), // #5EC4FF + .EOF_cursor = hex_to_vec4f(0x5EC4FFFF), // #5EC4FF + .insert_cursor = hex_to_vec4f(0xE27E8DFF), // #E27E8D + .emacs_cursor = hex_to_vec4f(0xEBBF83FF), // #EBBF83 + .text = hex_to_vec4f(0xA0B3C5FF), + .background = hex_to_vec4f(0x1D252CFF), + .fringe = hex_to_vec4f(0x1D252CFF), + .comment = hex_to_vec4f(0x41505EFF), + .hashtag = hex_to_vec4f(0x5EC4FFFF), + .logic = hex_to_vec4f(0x5EC4FFFF), + .string = hex_to_vec4f(0x539AFCFF), // #539AFC + .selection = hex_to_vec4f(0x28323BFF), + .search = hex_to_vec4f(0x4189B2FF), + .todo = hex_to_vec4f(0xEBBF83FF), + .line_numbers = hex_to_vec4f(0x384551FF), + .current_line_number = hex_to_vec4f(0xA0B3C5FF), + .fixme = hex_to_vec4f(0xD95468FF), // #D95468 + .note = hex_to_vec4f(0x8BD49CFF), // #8BD49C + .bug = hex_to_vec4f(0xD95468FF), + .not_equals = hex_to_vec4f(0xD95468FF), + .exclamation = hex_to_vec4f(0x5EC4FFFF), + .equals = hex_to_vec4f(0x8BD49CFF), + .equals_equals = hex_to_vec4f(0x8BD49CFF), + .greater_than = hex_to_vec4f(0x8BD49CFF), + .less_than = hex_to_vec4f(0xD95468FF), + .marks = hex_to_vec4f(0x4189B2FF), + .fb_selection = hex_to_vec4f(0x28323BFF), + .plus = hex_to_vec4f(0x8BD49CFF), + .minus = hex_to_vec4f(0xD95468FF), + .truee = hex_to_vec4f(0x8BD49CFF), + .falsee = hex_to_vec4f(0xD95468FF), + .arrow = hex_to_vec4f(0xA0B3C5FF), + .open_curly = hex_to_vec4f(0x5EC4FFFF), + .close_curly = hex_to_vec4f(0x5EC4FFFF), + .open_square = hex_to_vec4f(0xA0B3C5FF), + .close_square = hex_to_vec4f(0xA0B3C5FF), + .array_content = hex_to_vec4f(0x539AFCFF), + .link = hex_to_vec4f(0x539AFCFF), + .matching_parenthesis = hex_to_vec4f(0x28323BFF), + .type = hex_to_vec4f(0xEBBF83FF), + .function_definition = hex_to_vec4f(0x33CED8FF), // #33CED8 + .anchor = hex_to_vec4f(0xE27E8DFF), + .hl_line = hex_to_vec4f(0x181E24FF), + .multiplication = hex_to_vec4f(0x8BD49CFF), + .pointer = hex_to_vec4f(0x539AFCFF), + .logic_and = hex_to_vec4f(0x8BD49CFF), + .logic_or = hex_to_vec4f(0xD95468FF), + .ampersand = hex_to_vec4f(0x5EC4FFFF), + .pipe = hex_to_vec4f(0x8BD49CFF), + .minibuffer = hex_to_vec4f(0x181E24FF), + .line_numbers_background = hex_to_vec4f(0x181E24FF), + .modeline = hex_to_vec4f(0x181F25FF), + .modeline_accent = hex_to_vec4f(0x5EC4FFFF), + .whitespace = hex_to_vec4f(0x384551FF), + .selected_whitespaces = hex_to_vec4f(0x808F9DFF), + .indentation_line = hex_to_vec4f(0x384551FF), + .null = hex_to_vec4f(0xE27E8DFF), + .code_block = hex_to_vec4f(0x20282FFF), + .fill_column = hex_to_vec4f(0x28323BFF), + .fb_dir_name = hex_to_vec4f(0x5EC4FFFF), + .fb_size = hex_to_vec4f(0xD98E48FF), + .fb_date_time = hex_to_vec4f(0x70E1E8FF), + .fb_no_priv = hex_to_vec4f(0x56697AFF), + .fb_read_priv = hex_to_vec4f(0xEBBF83FF), + .fb_write_priv = hex_to_vec4f(0xD95468FF), + .fb_exec_priv = hex_to_vec4f(0x8BD49CFF), + .fb_dir_priv = hex_to_vec4f(0x5EC4FFFF), + + }; + + // DOOM molokai + themes[5] = (Theme){ + .cursor = hex_to_vec4f(0xFB2874FF), // #FB2874 + .notext_cursor = hex_to_vec4f(0xFB2874FF), // #FB2874 + .EOF_cursor = hex_to_vec4f(0xFB2874FF), // #FB2874 + .insert_cursor = hex_to_vec4f(0xFB2874FF), + .emacs_cursor = hex_to_vec4f(0xE2C770FF), // #E2C770 + .text = hex_to_vec4f(0xD6D6D4FF), + .background = hex_to_vec4f(0x1C1E1FFF), + .fringe = hex_to_vec4f(0x1C1E1FFF), + .comment = hex_to_vec4f(0x555556FF), + .hashtag = hex_to_vec4f(0x9C91E4FF), // #9C91E4 + .logic = hex_to_vec4f(0xFB2874FF), + .string = hex_to_vec4f(0xE2C770FF), + .selection = hex_to_vec4f(0x4E4E4EFF), + .search = hex_to_vec4f(0x9C91E4FF), + .todo = hex_to_vec4f(0xE2C770FF), + .line_numbers = hex_to_vec4f(0x555556FF), + .current_line_number = hex_to_vec4f(0xCFC0C5FF), + .fixme = hex_to_vec4f(0xE74C3CFF), // #E74C3C + .note = hex_to_vec4f(0xB6E63EFF), // #B6E63E + .bug = hex_to_vec4f(0xE74C3CFF), + .not_equals = hex_to_vec4f(0xE74C3CFF), + .exclamation = hex_to_vec4f(0x9C91E4FF), + .equals = hex_to_vec4f(0xB6E63EFF), + .equals_equals = hex_to_vec4f(0xB6E63EFF), + .greater_than = hex_to_vec4f(0xB6E63EFF), + .less_than = hex_to_vec4f(0xE74C3CFF), + .marks = hex_to_vec4f(0xB6E63EFF), + .fb_selection = hex_to_vec4f(0x4E4E4EFF), + .plus = hex_to_vec4f(0xB6E63EFF), + .minus = hex_to_vec4f(0xE74C3CFF), + .truee = hex_to_vec4f(0xB6E63EFF), + .falsee = hex_to_vec4f(0xE74C3CFF), + .arrow = hex_to_vec4f(0xD6D6D4FF), + .open_curly = hex_to_vec4f(0xFB2874FF), + .close_curly = hex_to_vec4f(0xFB2874FF), + .open_square = hex_to_vec4f(0xD6D6D4FF), + .close_square = hex_to_vec4f(0xD6D6D4FF), + .array_content = hex_to_vec4f(0x9C91E4FF), + .link = hex_to_vec4f(0x9C91E4FF), + .matching_parenthesis = hex_to_vec4f(0x4E4E4EFF), + .type = hex_to_vec4f(0x66D9EFFF), + .function_definition = hex_to_vec4f(0xB6E63EFF), + .anchor = hex_to_vec4f(0x9C91E4FF), + .hl_line = hex_to_vec4f(0x222323FF), + .multiplication = hex_to_vec4f(0xB6E63EFF), + .pointer = hex_to_vec4f(0x9C91E4FF), + .logic_and = hex_to_vec4f(0xB6E63EFF), + .logic_or = hex_to_vec4f(0xE74C3CFF), + .ampersand = hex_to_vec4f(0x9C91E4FF), + .pipe = hex_to_vec4f(0xB6E63EFF), + .minibuffer = hex_to_vec4f(0x222323FF), + .line_numbers_background = hex_to_vec4f(0x222323FF), + .modeline = hex_to_vec4f(0x2D2E2EFF), + .modeline_accent = hex_to_vec4f(0xB6E63EFF), + .whitespace = hex_to_vec4f(0x4E4E4EFF), + .selected_whitespaces = hex_to_vec4f(0x808F9DFF), + .indentation_line = hex_to_vec4f(0x4E4E4EFF), + .null = hex_to_vec4f(0xFD971FFF), + .code_block = hex_to_vec4f(0x2D2E2EFF), + .fill_column = hex_to_vec4f(0x4E4E4EFF), + .fb_dir_name = hex_to_vec4f(0x268BD2FF), + .fb_size = hex_to_vec4f(0xFD971FFF), + .fb_date_time = hex_to_vec4f(0x66D9EFFF), + .fb_no_priv = hex_to_vec4f(0x555556FF), + .fb_read_priv = hex_to_vec4f(0xE2C770FF), + .fb_write_priv = hex_to_vec4f(0xE74C3CFF), + .fb_exec_priv = hex_to_vec4f(0xB6E63EFF), + .fb_dir_priv = hex_to_vec4f(0x268BD2FF), + + }; + + // SUNSET + themes[6] = (Theme){ + .cursor = hex_to_vec4f(0xD9A173FF), // #D9A173 + .notext_cursor = hex_to_vec4f(0xD9A173FF), // #D9A173 + .EOF_cursor = hex_to_vec4f(0xD9A173FF), // #D9A173 + .insert_cursor = hex_to_vec4f(0xD46A7DFF), // #D46A7D + .emacs_cursor = hex_to_vec4f(0x9A8B6AFF), // #9A8B6A + .text = hex_to_vec4f(0xCCCCC5FF), + .background = hex_to_vec4f(0x0C0D12FF), + .fringe = hex_to_vec4f(0x0C0D12FF), + .comment = hex_to_vec4f(0x8E8E89FF), + .hashtag = hex_to_vec4f(0xD9A173FF), + .logic = hex_to_vec4f(0xD9A173FF), + .string = hex_to_vec4f(0x6A7E74FF), // #6A7E74 + .selection = hex_to_vec4f(0x28292DFF), + .search = hex_to_vec4f(0x805F44FF), // #805F44 + .todo = hex_to_vec4f(0x9A8B6AFF), + .line_numbers = hex_to_vec4f(0x1B1B21FF), + .current_line_number = hex_to_vec4f(0xCCCCC5FF), + .fixme = hex_to_vec4f(0xC06873FF), // #C06873 + .note = hex_to_vec4f(0x6A7E74FF), + .bug = hex_to_vec4f(0xC06873FF), + .not_equals = hex_to_vec4f(0xD46A7DFF), + .exclamation = hex_to_vec4f(0xD46A7DFF), + .equals = hex_to_vec4f(0x6A7E74FF), + .equals_equals = hex_to_vec4f(0x6A7E74FF), + .greater_than = hex_to_vec4f(0x6A7E74FF), + .less_than = hex_to_vec4f(0xC06873FF), + .marks = hex_to_vec4f(0x805F44FF), + .fb_selection = hex_to_vec4f(0x28292DFF), + .plus = hex_to_vec4f(0x6A7E74FF), + .minus = hex_to_vec4f(0xD46A7DFF), + .truee = hex_to_vec4f(0x6A7E74FF), + .falsee = hex_to_vec4f(0xD46A7DFF), + .arrow = hex_to_vec4f(0xCCCCC5FF), + .open_curly = hex_to_vec4f(0x6A7E74FF), + .close_curly = hex_to_vec4f(0x6A7E74FF), + .open_square = hex_to_vec4f(0xCCCCC5FF), + .close_square = hex_to_vec4f(0xCCCCC5FF), + .array_content = hex_to_vec4f(0xCCCCC5FF), + .link = hex_to_vec4f(0xD9A173FF), + .logic_or = hex_to_vec4f(0xD46A7DFF), + .pipe = hex_to_vec4f(0x6A7E74FF), + .ampersand = hex_to_vec4f(0x6A7E74FF), + .logic_and = hex_to_vec4f(0x6A7E74FF), + .pointer = hex_to_vec4f(0xD9A173FF), + .multiplication = hex_to_vec4f(0x6A7E74FF), + .matching_parenthesis = hex_to_vec4f(0x28292DFF), + .hl_line = hex_to_vec4f(0x0A0B0FFF), + .type = hex_to_vec4f(0x9A8B6AFF), + .function_definition = hex_to_vec4f(0xE07084FF), // #E07084 + .anchor = hex_to_vec4f(0xE07084FF), + .minibuffer = hex_to_vec4f(0x0C0D12FF), + .line_numbers_background = hex_to_vec4f(0x0C0D12FF), + .modeline = hex_to_vec4f(0x08090CFF), + .modeline_accent = hex_to_vec4f(0xD9A173FF), + .whitespace = hex_to_vec4f(0x1B1B21FF), + .selected_whitespaces = hex_to_vec4f(0xA3A39DFF), + .indentation_line = hex_to_vec4f(0x28292DFF), + .null = hex_to_vec4f(0xD46A7DFF), + .code_block = hex_to_vec4f(0x0B0C11FF), + .fill_column = hex_to_vec4f(0x28292DFF), + .fb_dir_name = hex_to_vec4f(0xD9A173FF), + .fb_size = hex_to_vec4f(0xCB6E7AFF), + .fb_date_time = hex_to_vec4f(0x718D87FF), + .fb_no_priv = hex_to_vec4f(0x8E8E89FF), + .fb_read_priv = hex_to_vec4f(0x9A8B6AFF), + .fb_write_priv = hex_to_vec4f(0xC06873FF), + .fb_exec_priv = hex_to_vec4f(0x6A7E74FF), + .fb_dir_priv = hex_to_vec4f(0xD9A173FF), + + }; + + // Helix + themes[7] = (Theme){ + .cursor = hex_to_vec4f(0x5A5977FF), // #5A5977 + .notext_cursor = hex_to_vec4f(0x5A5977FF), // #5A5977 + .EOF_cursor = hex_to_vec4f(0x5A5977FF), // #5A5977 + .insert_cursor = hex_to_vec4f(0x5A5977FF), + .emacs_cursor = hex_to_vec4f(0x5A5977FF), + .text = hex_to_vec4f(0xFFFFFFFF), + .fringe = hex_to_vec4f(0x3B224CFF), // #3B224C + .comment = hex_to_vec4f(0x697C81FF), + .hashtag = hex_to_vec4f(0xDBBFEFFF), // #DBBFEF + .logic = hex_to_vec4f(0xECCDBAFF), // #ECCDBA + .string = hex_to_vec4f(0xCCCCCCFF), + .selection = hex_to_vec4f(0x540099FF), // #540099 + .search = hex_to_vec4f(0x540099FF), + .todo = hex_to_vec4f(0x6F44F0FF), + .line_numbers = hex_to_vec4f(0x5A5977FF), + .current_line_number = hex_to_vec4f(0xDBBFEFFF), + .fixme = hex_to_vec4f(0xF47868FF), // #F47868 + .note = hex_to_vec4f(0x6F44F0FF), + .bug = hex_to_vec4f(0xF47868FF), + .not_equals = hex_to_vec4f(0xDBBFEFFF), // #DBBFEF + .exclamation = hex_to_vec4f(0xDBBFEFFF), + .equals = hex_to_vec4f(0xDBBFEFFF), + .equals_equals = hex_to_vec4f(0xDBBFEFFF), + .greater_than = hex_to_vec4f(0xDBBFEFFF), + .less_than = hex_to_vec4f(0xDBBFEFFF), + .marks = hex_to_vec4f(0x540099FF), + .fb_selection = hex_to_vec4f(0x540099FF), + .plus = hex_to_vec4f(0xDBBFEFFF), + .minus = hex_to_vec4f(0xDBBFEFFF), + .truee = hex_to_vec4f(0xFFFFFFFF), + .falsee = hex_to_vec4f(0xFFFFFFFF), + .arrow = hex_to_vec4f(0xA4A0E8FF), // #A4A0E8 + .open_curly = hex_to_vec4f(0xDBBFEFFF), + .close_curly = hex_to_vec4f(0xDBBFEFFF), + .open_square = hex_to_vec4f(0xA4A0E8FF), + .close_square = hex_to_vec4f(0xA4A0E8FF), + .array_content = hex_to_vec4f(0xA4A0E8FF), + .link = hex_to_vec4f(0xA4A0E8FF), + .logic_or = hex_to_vec4f(0xDBBFEFFF), + .pipe = hex_to_vec4f(0xDBBFEFFF), + .ampersand = hex_to_vec4f(0xDBBFEFFF), + .logic_and = hex_to_vec4f(0xDBBFEFFF), + .pointer = hex_to_vec4f(0xFFFFFFFF), + .multiplication = hex_to_vec4f(0xFFFFFFFF), + .matching_parenthesis = hex_to_vec4f(0x6C6999FF), + .hl_line = hex_to_vec4f(0x281733FF), + .type = hex_to_vec4f(0xFFFFFFFF), + .function_definition = hex_to_vec4f(0xFFFFFFFF), + .anchor = hex_to_vec4f(0xFFFFFFFF), + .minibuffer = hex_to_vec4f(0x3B224CFF), + .line_numbers_background = hex_to_vec4f(0x3B224CFF), + .modeline = hex_to_vec4f(0x281733FF), + .modeline_accent = hex_to_vec4f(0x281733FF), + .whitespace = hex_to_vec4f(0x281733FF), + .selected_whitespaces = hex_to_vec4f(0xFFFFFFFF), + .indentation_line = hex_to_vec4f(0x281733FF), + .null = hex_to_vec4f(0xFFFFFFFF), + .code_block = hex_to_vec4f(0x281733FF), + .fill_column = hex_to_vec4f(0x540099FF), // #540099 + }; + + // GUM + themes[8] = (Theme){ + .cursor = hex_to_vec4f(0xD6A0D1FF), //#D6A0D1 + .notext_cursor = hex_to_vec4f(0xD6A0D1FF), + .EOF_cursor = hex_to_vec4f(0xD6A0D1FF), + .insert_cursor = hex_to_vec4f(0xC79AF4FF), + .emacs_cursor = hex_to_vec4f(0xDBAC66FF), + .text = hex_to_vec4f(0xD4D4D6FF), //#D4D4D6 + .background = hex_to_vec4f(0x14171EFF), //#14171E + .fringe = hex_to_vec4f(0x14171EFF), + .comment = hex_to_vec4f(0x454459FF), + .hashtag = hex_to_vec4f(0xC79AF4FF), //#C79AF4 + .logic = hex_to_vec4f(0x9587DDFF), //#9587DD + .string = hex_to_vec4f(0x62D2DBFF), //#62D2DB + .selection = hex_to_vec4f(0x272C3AFF), //#272C3A + .search = hex_to_vec4f(0x272C3AFF), + .todo = hex_to_vec4f(0xDBAC66FF), //#DBAC66 + .line_numbers = hex_to_vec4f(0x272C3AFF), //#272C3A + .current_line_number = hex_to_vec4f(0xC79AF4FF), + .fixme = hex_to_vec4f(0xE55C7AFF), //#E55C7A + .note = hex_to_vec4f(0x35BF88FF), //#35BF88 + .bug = hex_to_vec4f(0xE55C7AFF), + .not_equals = hex_to_vec4f(0xE55C7AFF), + .exclamation = hex_to_vec4f(0xE55C7AFF), + .equals = hex_to_vec4f(0xD4D4D6FF), + .equals_equals = hex_to_vec4f(0xD4D4D6FF), + .greater_than = hex_to_vec4f(0xD4D4D6FF), + .less_than = hex_to_vec4f(0xD4D4D6FF), + .marks = hex_to_vec4f(0x272C3AFF), + .fb_selection = hex_to_vec4f(0x272C3AFF), + .plus = hex_to_vec4f(0xD4D4D6FF), + .minus = hex_to_vec4f(0xD4D4D6FF), + .truee = hex_to_vec4f(0x35BF88FF), + .falsee = hex_to_vec4f(0xE55C7AFF), + .arrow = hex_to_vec4f(0xD4D4D6FF), + .open_curly = hex_to_vec4f(0x11CCB2FF), + .close_curly = hex_to_vec4f(0x11CCB2FF), + .open_square = hex_to_vec4f(0xD4D4D6FF), + .close_square = hex_to_vec4f(0xD4D4D6FF), + .array_content = hex_to_vec4f(0xD4D4D6FF), + .link = hex_to_vec4f(0x41B0F3FF), //#41B0F3 + .matching_parenthesis = hex_to_vec4f(0x272C3AFF), + .type = hex_to_vec4f(0x11CCB2FF), //#11CCB2 + .function_definition = hex_to_vec4f(0xD6A0D1FF), //#D6A0D1 + .anchor = hex_to_vec4f(0x9587DDFF), + .hl_line = hex_to_vec4f(0x202430FF), + .multiplication = hex_to_vec4f(0xD4D4D6FF), + .pointer = hex_to_vec4f(0xD4D4D6FF), + .logic_and = hex_to_vec4f(0x35BF88FF), + .logic_or = hex_to_vec4f(0xE55C7AFF), + .ampersand = hex_to_vec4f(0xC79AF4FF), + .pipe = hex_to_vec4f(0x35BF88FF), + .minibuffer = hex_to_vec4f(0x14171EFF), + .line_numbers_background = hex_to_vec4f(0x14171EFF), + .modeline = hex_to_vec4f(0x191D26FF), + .modeline_accent = hex_to_vec4f(0x9587DDFF), + .whitespace = hex_to_vec4f(0x454459FF), + .selected_whitespaces = hex_to_vec4f(0xBEBEC4FF), + .indentation_line = hex_to_vec4f(0x272C3AFF), + .null = hex_to_vec4f(0x41B0F3FF), + .code_block = hex_to_vec4f(0x191D26FF), + .fill_column = hex_to_vec4f(0x272C3AFF), //#272C3A + .fb_dir_name = hex_to_vec4f(0x9587DDFF), + .fb_size = hex_to_vec4f(0xE361C3FF), + .fb_date_time = hex_to_vec4f(0xC79AF4FF), + .fb_no_priv = hex_to_vec4f(0x454459FF), + .fb_read_priv = hex_to_vec4f(0x49BDB0FF), + .fb_write_priv = hex_to_vec4f(0xF5C791FF), + .fb_exec_priv = hex_to_vec4f(0xE55C7AFF), + .fb_dir_priv = hex_to_vec4f(0x9587DDFF), + + }; + + // kaolin dark + themes[9] = (Theme){ + .cursor = hex_to_vec4f(0xEFEFF1FF), + .notext_cursor = hex_to_vec4f(0xEFEFF1FF), + .EOF_cursor = hex_to_vec4f(0xEFEFF1FF), + .insert_cursor = hex_to_vec4f(0x9D81BAFF), + .emacs_cursor = hex_to_vec4f(0xEFEFF1FF), + .text = hex_to_vec4f(0xE4E4E8FF), + .background = hex_to_vec4f(0x18181BFF), + .fringe = hex_to_vec4f(0x18181BFF), + .comment = hex_to_vec4f(0x545C5EFF), + .hashtag = hex_to_vec4f(0x9D81BAFF), + .logic = hex_to_vec4f(0x4D9391FF), + .string = hex_to_vec4f(0x6FB593FF), + .selection = hex_to_vec4f(0x2E403BFF), + .search = hex_to_vec4f(0x303035FF), + .todo = hex_to_vec4f(0xDBAC66FF), + .line_numbers = hex_to_vec4f(0x303035FF), + .current_line_number = hex_to_vec4f(0x545C5EFF), + .fixme = hex_to_vec4f(0xCD5C60FF), + .note = hex_to_vec4f(0x6FB593FF), + .bug = hex_to_vec4f(0x6FB593FF), + .not_equals = hex_to_vec4f(0x6FB593FF), + .exclamation = hex_to_vec4f(0x6FB593FF), + .equals = hex_to_vec4f(0xE4E4E8FF), + .equals_equals = hex_to_vec4f(0xE4E4E8FF), + .greater_than = hex_to_vec4f(0xE4E4E8FF), + .less_than = hex_to_vec4f(0xE4E4E8FF), + .marks = hex_to_vec4f(0x2E403BFF), + .fb_selection = hex_to_vec4f(0xE4E4E8FF), + .plus = hex_to_vec4f(0xE4E4E8FF), + .minus = hex_to_vec4f(0xE4E4E8FF), + .truee = hex_to_vec4f(0x6FB593FF), + .falsee = hex_to_vec4f(0x6FB593FF), + .arrow = hex_to_vec4f(0xE4E4E8FF), + .open_curly = hex_to_vec4f(0xE4E4E8FF), + .close_curly = hex_to_vec4f(0xE4E4E8FF), + .open_square = hex_to_vec4f(0xE4E4E8FF), + .close_square = hex_to_vec4f(0xE4E4E8FF), + .array_content = hex_to_vec4f(0xE4E4E8FF), + .link = hex_to_vec4f(0x968CC7FF), + .matching_parenthesis = hex_to_vec4f(0x303035FF), + .type = hex_to_vec4f(0xCD9575FF), + .function_definition = hex_to_vec4f(0x80BCB6FF), + .anchor = hex_to_vec4f(0x968CC7FF), + .hl_line = hex_to_vec4f(0x222225FF), + .multiplication = hex_to_vec4f(0xE4E4E8FF), + .pointer = hex_to_vec4f(0xE4E4E8FF), + .logic_and = hex_to_vec4f(0xE4E4E8FF), + .logic_or = hex_to_vec4f(0xE4E4E8FF), + .ampersand = hex_to_vec4f(0xE4E4E8FF), + .pipe = hex_to_vec4f(0xE4E4E8FF), + .minibuffer = hex_to_vec4f(0x18181BFF), + .line_numbers_background = hex_to_vec4f(0x222225FF), + .modeline = hex_to_vec4f(0x222225FF), + .modeline_accent = hex_to_vec4f(0x968CC7FF), + .whitespace = hex_to_vec4f(0x222225FF), + .selected_whitespaces = hex_to_vec4f(0xE4E4E8FF), + .indentation_line = hex_to_vec4f(0x222225FF), + .null = hex_to_vec4f(0xAB98B5FF), + .code_block = hex_to_vec4f(0x222225FF), + .fill_column = hex_to_vec4f(0x222225FF), + .fb_dir_name = hex_to_vec4f(0x4D9391FF), + .fb_size = hex_to_vec4f(0xCD5C60FF), + .fb_date_time = hex_to_vec4f(0x9D81BAFF), + .fb_no_priv = hex_to_vec4f(0x545C5EFF), + .fb_read_priv = hex_to_vec4f(0x35BF88FF), + .fb_write_priv = hex_to_vec4f(0xBC90D4FF), + .fb_exec_priv = hex_to_vec4f(0xCD5C60FF), + .fb_dir_priv = hex_to_vec4f(0x4D9391FF), + }; + + // doom-material-dark + themes[10] = (Theme){ + .cursor = hex_to_vec4f(0xFFCB6BFF), + .notext_cursor = hex_to_vec4f(0xFFCB6BFF), + .EOF_cursor = hex_to_vec4f(0xFFCB6BFF), + .insert_cursor = hex_to_vec4f(0xFFCB6BFF), + .emacs_cursor = hex_to_vec4f(0xFFCB6BFF), + .text = hex_to_vec4f(0xEEFFFFFF), + .background = hex_to_vec4f(0x212121FF), + .fringe = hex_to_vec4f(0x212121FF), + .comment = hex_to_vec4f(0x626262FF), + .hashtag = hex_to_vec4f(0x89DDFFFF), + .logic = hex_to_vec4f(0x89DDFFFF), + .string = hex_to_vec4f(0xC3E88DFF), //#C3E88D + .selection = hex_to_vec4f(0x406562FF), + .search = hex_to_vec4f(0x617446FF), + .todo = hex_to_vec4f(0xFFCB6BFF), + .line_numbers = hex_to_vec4f(0x585858FF), + .current_line_number = hex_to_vec4f(0x89DDFFFF), + .fixme = hex_to_vec4f(0xF57373FF), //FIXME + .note = hex_to_vec4f(0xC3E88DFF), // NOTE + .bug = hex_to_vec4f(0xF57373FF), + .not_equals = hex_to_vec4f(0xF57373FF), + .exclamation = hex_to_vec4f(0xF57373FF), + .equals = hex_to_vec4f(0xC3E88DFF), + .equals_equals = hex_to_vec4f(0xC3E88DFF), + .greater_than = hex_to_vec4f(0xC3E88DFF), + .less_than = hex_to_vec4f(0xF57373FF), + .marks = hex_to_vec4f(0xF57373FF), + .fb_selection = hex_to_vec4f(0xF57373FF), + .plus = hex_to_vec4f(0xC3E88DFF), + .minus = hex_to_vec4f(0xF57373FF), + .truee = hex_to_vec4f(0xC3E88DFF), + .falsee = hex_to_vec4f(0xF57373FF), + .arrow = hex_to_vec4f(0xEEFFFFFF), + .open_curly = hex_to_vec4f(0xC792EAFF), + .close_curly = hex_to_vec4f(0xC792EAFF), + .open_square = hex_to_vec4f(0xC792EAFF), + .close_square = hex_to_vec4f(0xC792EAFF), + .array_content = hex_to_vec4f(0xEEFFFFFF), + .link = hex_to_vec4f(0x89DDFFFF), + .matching_parenthesis = hex_to_vec4f(0x171F24FF), + .type = hex_to_vec4f(0xC792EAFF), + .function_definition = hex_to_vec4f(0x82AAFFFF), //#82AAFF + .anchor = hex_to_vec4f(0xF57373FF), + .hl_line = hex_to_vec4f(0x303030FF), + .multiplication = hex_to_vec4f(0xEEFFFFFF), + .pointer = hex_to_vec4f(0xEEFFFFFF), + .logic_and = hex_to_vec4f(0xC3E88DFF), + .logic_or = hex_to_vec4f(0xF57373FF), + .ampersand = hex_to_vec4f(0x82AAFFFF), + .pipe = hex_to_vec4f(0xC3E88DFF), + .minibuffer = hex_to_vec4f(0x212121FF), + .line_numbers_background = hex_to_vec4f(0x212121FF), + .modeline = hex_to_vec4f(0x303030FF), + .modeline_accent = hex_to_vec4f(0xC792EAFF), + .whitespace = hex_to_vec4f(0x4A4A4AFF), + .selected_whitespaces = hex_to_vec4f(0x80CBC4FF), + .indentation_line = hex_to_vec4f(0x4A4A4AFF), + .null = hex_to_vec4f(0xF78C6CFF), + .code_block = hex_to_vec4f(0x303030FF), + .fill_column = hex_to_vec4f(0x4A4A4AFF), + .fb_dir_name = hex_to_vec4f(0x82AAFFFF), + .fb_size = hex_to_vec4f(0xF78C6CFF), + .fb_date_time = hex_to_vec4f(0x89DDFFFF), + .fb_no_priv = hex_to_vec4f(0x585858FF), + .fb_read_priv = hex_to_vec4f(0xFFCB6BFF), + .fb_write_priv = hex_to_vec4f(0xF57373FF), + .fb_exec_priv = hex_to_vec4f(0xC3E88DFF), + .fb_dir_priv = hex_to_vec4f(0x82AAFFFF), + }; + + + // Initialize currentTheme to the first theme + if (current_mode == HELIX) { + currentTheme = themes[7]; + } + /* } else { */ + /* currentTheme = themes[0]; */ + /* } */ + /* previousThemeIndex = 0; */ + /* currentThemeIndex = 0; */ + /* interpolationProgress = 1.0f; // No interpolation needed at start */ +} diff --git a/src/theme.h b/src/theme.h new file mode 100644 index 00000000..2b64dabe --- /dev/null +++ b/src/theme.h @@ -0,0 +1,111 @@ +#ifndef THEME_H +#define THEME_H + +#include "la.h" +#include "stdbool.h" + +typedef struct { + Vec4f cursor; + Vec4f notext_cursor; + Vec4f EOF_cursor; + Vec4f insert_cursor; + Vec4f emacs_cursor; + Vec4f text; + Vec4f background; + Vec4f logic; + Vec4f comment; + Vec4f hashtag; + Vec4f string; + Vec4f selection; + Vec4f search; + Vec4f line_numbers; + Vec4f current_line_number; + Vec4f line_numbers_background; + Vec4f todo; + Vec4f fixme; + Vec4f note; + Vec4f bug; + Vec4f equals; + Vec4f not_equals; + Vec4f exclamation; + Vec4f equals_equals; + Vec4f less_than; + Vec4f greater_than; + Vec4f arrow; + Vec4f plus; + Vec4f minus; + Vec4f truee; + Vec4f falsee; + Vec4f open_square; + Vec4f close_square; + Vec4f array_content; + Vec4f marks; + Vec4f fb_selection; + Vec4f link; + Vec4f logic_or; + Vec4f pipe; + Vec4f logic_and; + Vec4f ampersand; + Vec4f multiplication; + Vec4f pointer; + Vec4f modeline; + Vec4f modeline_accent; + Vec4f minibuffer; + Vec4f matching_parenthesis; + Vec4f hl_line; + Vec4f type; + Vec4f function_definition; + Vec4f anchor; + Vec4f whitespace; + Vec4f selected_whitespaces; + Vec4f indentation_line; + Vec4f null; + Vec4f code_block; + Vec4f fringe; + Vec4f nest1; + Vec4f nest2; + Vec4f nest3; + Vec4f nest4; + Vec4f nest5; + Vec4f nest6; + Vec4f fill_column; + Vec4f open_curly; + Vec4f close_curly; + Vec4f fb_size; + Vec4f fb_dir_name; + Vec4f fb_date_time; + Vec4f fb_no_priv; + Vec4f fb_read_priv; + Vec4f fb_write_priv; + Vec4f fb_exec_priv; + Vec4f fb_dir_priv; +} Theme; + +#define CURRENT_THEME (currentTheme) // interpolated theme + +/* #define CURRENT_THEME (themes[currentThemeIndex]) */ + + +extern Theme themes[]; +extern Theme currentTheme; // Interpolated theme +extern Theme previousTheme; +extern int currentThemeIndex; +extern int previousThemeIndex; // Index of the previous theme + +extern float interpolationProgress; +extern bool theme_lerp; +extern float theme_lerp_speed; +extern float theme_lerp_treshold; + + + +void initialize_themes(); +void theme_next(int *currentThemeIndex); +void theme_previous(int *currentThemeIndex); +void update_theme_interpolation(); // Function to handle interpolation +Vec4f color_lerp(Vec4f start, Vec4f end, float t); // Function to interpolate colors +void switch_to_theme(int *currentThemeIndex, int newIndex); +void transition_color(Vec4f* color_field, Vec4f target_color, float transition_speed); + + +#endif // THEME_H diff --git a/src/utilities.c b/src/utilities.c new file mode 100644 index 00000000..04080251 --- /dev/null +++ b/src/utilities.c @@ -0,0 +1,155 @@ +#include "utilities.h" + +// Utility functions make it easier to add new functionality + +bool extractLine(Editor *editor, size_t cursor, char *line, size_t max_length) { + size_t start = cursor; + while (start > 0 && editor->data.items[start - 1] != '\n') { + start--; + } + + size_t end = start; + while (end < editor->data.count && editor->data.items[end] != '\n') { + end++; + } + + size_t length = end - start; + if (length < max_length) { + strncpy(line, &editor->data.items[start], length); + line[length] = '\0'; + return true; + } + + return false; +} + +size_t editor_row_from_pos(const Editor *e, size_t pos) { + assert(e->lines.count > 0); + for (size_t row = 0; row < e->lines.count; ++row) { + Line line = e->lines.items[row]; + if (line.begin <= pos && pos <= line.end) { + return row; + } + } + return e->lines.count - 1; +} + +bool extract_word_under_cursor(Editor *editor, char *word) { + size_t cursor = editor->cursor; + + // Move left to find the start of the word. + while (cursor > 0 && isalnum(editor->data.items[cursor - 1])) { + cursor--; + } + + // Check if the cursor is on a word or on whitespace/special character. + if (!isalnum(editor->data.items[cursor])) return false; + + int start = cursor; + + // Move right to find the end of the word. + while (cursor < editor->data.count && isalnum(editor->data.items[cursor])) { + cursor++; + } + + int end = cursor; + + // Copy the word to the provided buffer. + // Make sure not to overflow the buffer and null-terminate the string. + int length = end - start; + strncpy(word, &editor->data.items[start], length); + word[length] = '\0'; + + return true; +} + +bool extract_word_left_of_cursor(Editor *e, char *word, size_t max_word_length) { + if (e->cursor == 0 || !isalnum(e->data.items[e->cursor - 1])) { + return false; + } + + size_t end = e->cursor; + size_t start = end; + + while (start > 0 && isalnum(e->data.items[start - 1])) { + start--; + } + + size_t word_length = end - start; + if (word_length >= max_word_length) { + return false; + } + + memcpy(word, &e->data.items[start], word_length); + word[word_length] = '\0'; + e->cursor = start; + return true; +} + +bool editor_is_line_empty(Editor *e, size_t row) { + if (row >= e->lines.count) return true; // Non-existent lines are considered empty + + return e->lines.items[row].begin == e->lines.items[row].end; +} + +bool editor_is_line_whitespaced(Editor *e, size_t row) { + if (row >= e->lines.count) return false; + + size_t line_begin = e->lines.items[row].begin; + size_t line_end = e->lines.items[row].end; + + for (size_t i = line_begin; i < line_end; ++i) { + if (!isspace(e->data.items[i])) { + return false; + } + } + return true; +} + +float measure_whitespace_width(Free_Glyph_Atlas *atlas) { + Vec2f whitespaceSize = {0.0f, 0.0f}; + free_glyph_atlas_measure_line_sized(atlas, " ", 1, &whitespaceSize); + return whitespaceSize.x; +} + +float measure_whitespace_height(Free_Glyph_Atlas *atlas) { + Vec2f whitespaceSize = {0.0f, 0.0f}; + free_glyph_atlas_measure_line_sized(atlas, " ", 1, &whitespaceSize); + return whitespaceSize.y; +} + +size_t find_first_non_whitespace(const char* items, size_t begin, size_t end) { + size_t pos = begin; + while (pos < end && isspace((unsigned char)items[pos])) { + pos++; + } + return pos; +} + +size_t find_last_non_whitespace(const char* items, size_t begin, size_t end) { + if (end > begin) { + size_t pos = end; + do { + pos--; + if (!isspace((unsigned char)items[pos])) { + return pos + 1; // return the position right after the non-whitespace char + } + } while (pos > begin); + } + return begin; // If no non-whitespace found, return the beginning (handles the empty line case too) +} + + + +bool is_number(const char *str) { + if (!str || *str == '\0') + return false; // Empty string is not a number + + // Check if each character is a digit + for (const char *p = str; *p != '\0'; p++) { + if (!isdigit((unsigned char)*p)) + return false; + } + return true; +} + diff --git a/src/utilities.h b/src/utilities.h new file mode 100644 index 00000000..f96b56de --- /dev/null +++ b/src/utilities.h @@ -0,0 +1,21 @@ +#ifndef UTILITIES_H +#define UTILITIES_H + +#include "editor.h" + +// UTILITY +bool extractLine(Editor *editor, size_t cursor, char *line, size_t max_length); +size_t editor_row_from_pos(const Editor *e, size_t pos); +bool extract_word_under_cursor(Editor *editor, char *word); +bool extract_word_left_of_cursor(Editor *e, char *word, size_t max_word_length); +bool editor_is_line_empty(Editor *e, size_t row); +bool editor_is_line_whitespaced(Editor *e, size_t row); +float measure_whitespace_width(Free_Glyph_Atlas *atlas); +float measure_whitespace_height(Free_Glyph_Atlas *atlas); +size_t find_first_non_whitespace(const char* items, size_t begin, size_t end); +bool is_number(const char *str); +size_t find_last_non_whitespace(const char* items, size_t begin, size_t end); + + + +#endif // UTILITIES_H diff --git a/src/yasnippet.c b/src/yasnippet.c new file mode 100644 index 00000000..70d7d324 --- /dev/null +++ b/src/yasnippet.c @@ -0,0 +1,265 @@ +#include +#include +#include "editor.h" +#include "yasnippet.h" +#include "utilities.h" + +SnippetArray snippets; + +void init_snippet_array(SnippetArray *a, size_t initial_size) { + a->array = (Snippet *)malloc(initial_size * sizeof(Snippet)); + a->used = 0; + a->size = initial_size; +} + +void insert_snippet(SnippetArray *a, Snippet snippet) { + if (a->used == a->size) { + a->size *= 2; + a->array = (Snippet *)realloc(a->array, a->size * sizeof(Snippet)); + } + a->array[a->used++] = snippet; +} + +void free_snippet_array(SnippetArray *a) { + free(a->array); + a->array = NULL; + a->used = a->size = 0; +} + + +void load_snippets_from_directory() { + const char * home = getenv("HOME"); + if (!home) { + fprintf(stderr, "ERROR: HOME environment variable not set.\n"); + return; + } + + char directory[256]; + snprintf(directory, sizeof(directory), "%s/.config/ded/snippets", home); + + DIR *dir; + struct dirent *entry; + + if ((dir = opendir(directory)) == NULL) { + fprintf(stderr, "opendir failed: %s\n", strerror(errno)); + return; + } + + init_snippet_array(&snippets, 10); // Start with an initial size of 10 + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_REG) { + char filepath[256]; + snprintf(filepath, sizeof(filepath), "%s/%s", directory, entry->d_name); + + FILE *file = fopen(filepath, "r"); + if (file) { + Snippet new_snippet; + strncpy(new_snippet.key, entry->d_name, MAX_SNIPPET_KEY_LENGTH - 1); + new_snippet.key[MAX_SNIPPET_KEY_LENGTH - 1] = '\0'; + new_snippet.content[0] = '\0'; // Initialize content as empty string + + char line[256]; + while (fgets(line, sizeof(line), file)) { + strncat(new_snippet.content, line, MAX_SNIPPET_CONTENT_LENGTH - strlen(new_snippet.content) - 1); + } + new_snippet.content[MAX_SNIPPET_CONTENT_LENGTH - 1] = '\0'; + + insert_snippet(&snippets, new_snippet); + fclose(file); + } + } + } + + closedir(dir); +} + +// TODO Indentation problem +void activate_snippet(Editor *e) { + char word[MAX_SNIPPET_KEY_LENGTH]; + size_t original_cursor_position = e->cursor; + + if (!extract_word_left_of_cursor(e, word, sizeof(word))) { + indent(e); + return; + } + + bool snippet_found = false; // Flag to check if a snippet is found + + for (size_t i = 0; i < snippets.used; i++) { + if (strcmp(snippets.array[i].key, word) == 0) { + snippet_found = true; // A matching snippet is found. + size_t word_length = strlen(word); + + // Delete the word from the buffer + memmove(&e->data.items[e->cursor], + &e->data.items[e->cursor + word_length], + e->data.count - (e->cursor + word_length)); + e->data.count -= word_length; + + // Duplicate snippet content to manipulate + char *snippet_content = strdup(snippets.array[i].content); + char *placeholder_pos = strstr(snippet_content, "$0"); + + // Capture the current indentation level + size_t cursor_row = editor_row_from_pos(e, e->cursor); + size_t line_start = e->lines.items[cursor_row].begin; + size_t current_indent = e->cursor - line_start; + + // Calculate the position of $0 + size_t placeholder_line = 0; + size_t placeholder_col = 0; + if (placeholder_pos) { + for (char *p = snippet_content; p < placeholder_pos; ++p) { + if (*p == '\n') { + placeholder_line++; + placeholder_col = 0; + } else { + placeholder_col++; + } + } + memmove(placeholder_pos, placeholder_pos + 2, strlen(placeholder_pos + 2) + 1); // Remove $0 + } + + // Process each line of the snippet + char *line_start_ptr = snippet_content; + char *line_end_ptr; + while ((line_end_ptr = strchr(line_start_ptr, '\n')) != NULL || *line_start_ptr) { + if (line_end_ptr != NULL) { + size_t line_length = line_end_ptr - line_start_ptr; + if (line_length > 0) { + editor_insert_buf(e, line_start_ptr, line_length); + } + editor_insert_char(e, '\n'); // Insert newline and move to the next line + line_start_ptr = line_end_ptr + 1; + } else { + // Last line of the snippet + editor_insert_buf(e, line_start_ptr, strlen(line_start_ptr)); + break; + } + + // Apply indentation for new lines + if (*line_start_ptr && cursor_row != editor_row_from_pos(e, e->cursor)) { + for (size_t i = 0; i < current_indent; ++i) { + editor_insert_char(e, ' '); + } + } + } + + // Adjust cursor position to where $0 was + if (placeholder_pos) { + e->cursor = e->lines.items[cursor_row + placeholder_line].begin + placeholder_col + (placeholder_line > 0 ? current_indent : 0); + } + + free(snippet_content); + break; // Exit the loop as the snippet is found and processed. + } + } + + if (!snippet_found) { + e->cursor = original_cursor_position; // Restore cursor to its original position. + indent(e); + } +} + + + + +// THIS fixes it but also bring new problems + +/* void activate_snippet(Editor *e) { */ +/* char word[MAX_SNIPPET_KEY_LENGTH]; */ +/* size_t original_cursor_position = e->cursor; */ + +/* // Extract the word left of the cursor to identify the snippet to activate */ +/* if (!extract_word_left_of_cursor(e, word, sizeof(word))) { */ +/* indent(e); // If no word is found, simply indent the line */ +/* return; */ +/* } */ + +/* bool snippet_found = false; */ + +/* for (size_t i = 0; i < snippets.used; i++) { */ +/* if (strcmp(snippets.array[i].key, word) == 0) { */ +/* snippet_found = true; */ + +/* // Delete the word triggering the snippet */ +/* size_t word_length = strlen(word); */ +/* memmove(&e->data.items[e->cursor], */ +/* &e->data.items[e->cursor + word_length], */ +/* e->data.count - (e->cursor + word_length)); */ +/* e->data.count -= word_length; */ + +/* char *snippet_content = strdup(snippets.array[i].content); */ +/* char *placeholder_pos = strstr(snippet_content, "$0"); */ + +/* // Capture the initial indentation level */ +/* size_t cursor_row = editor_row_from_pos(e, e->cursor); */ +/* size_t line_start = e->lines.items[cursor_row].begin; */ +/* size_t current_indent = e->cursor - line_start; */ + +/* // Process placeholder and snippet content */ +/* size_t placeholder_line = 0, placeholder_col = 0; */ +/* if (placeholder_pos) { */ +/* for (char *p = snippet_content; p < placeholder_pos; ++p) { */ +/* if (*p == '\n') { */ +/* placeholder_line++; */ +/* placeholder_col = 0; */ +/* } else { */ +/* placeholder_col++; */ +/* } */ +/* } */ +/* // Remove $0 from the content */ +/* memmove(placeholder_pos, placeholder_pos + 2, strlen(placeholder_pos + 2) + 1); */ +/* } */ + +/* // Insert the snippet content */ +/* char *line_start_ptr = snippet_content; */ +/* char *line_end_ptr; */ +/* while ((line_end_ptr = strchr(line_start_ptr, '\n')) != NULL || *line_start_ptr) { */ +/* if (line_end_ptr) { */ +/* editor_insert_buf(e, line_start_ptr, line_end_ptr - line_start_ptr); */ +/* editor_insert_char(e, '\n'); */ +/* line_start_ptr = line_end_ptr + 1; */ +/* } else { */ +/* editor_insert_buf(e, line_start_ptr, strlen(line_start_ptr)); */ +/* break; */ +/* } */ + +/* // Apply initial indentation to new lines */ +/* if (*line_start_ptr && cursor_row != editor_row_from_pos(e, e->cursor)) { */ +/* for (size_t i = 0; i < current_indent; ++i) { */ +/* editor_insert_char(e, ' '); */ +/* } */ +/* } */ +/* } */ + +/* // Adjust the cursor to the position of $0, considering initial indentation */ +/* if (placeholder_pos) { */ +/* // Calculate new cursor position */ +/* e->cursor = original_cursor_position - word_length; */ +/* for (size_t i = 0; i <= placeholder_line && i < e->lines.count; ++i) { */ +/* e->cursor += (i < placeholder_line ? e->lines.items[cursor_row + i].end - e->lines.items[cursor_row + i].begin + 1 : placeholder_col); */ +/* // Apply initial indentation if we're past the first line of the snippet */ +/* if (i > 0) { */ +/* e->cursor += current_indent; */ +/* } */ +/* } */ +/* } */ + +/* free(snippet_content); */ +/* break; // Found the snippet, no need to continue searching */ +/* } */ +/* } */ + +/* // If the snippet wasn't found, restore the cursor position and indent */ +/* if (!snippet_found) { */ +/* e->cursor = original_cursor_position; */ +/* indent(e); */ +/* } */ +/* } */ + + + + + diff --git a/src/yasnippet.h b/src/yasnippet.h new file mode 100644 index 00000000..7193f424 --- /dev/null +++ b/src/yasnippet.h @@ -0,0 +1,28 @@ +#ifndef YASNIPPET_H_ +#define YASNIPPET_H_ + +#include "editor.h" + +#define MAX_SNIPPET_KEY_LENGTH 50 +#define MAX_SNIPPET_CONTENT_LENGTH 1024 + +typedef struct { + char key[MAX_SNIPPET_KEY_LENGTH]; + char content[MAX_SNIPPET_CONTENT_LENGTH]; +} Snippet; + +typedef struct { + Snippet *array; + size_t used; + size_t size; +} SnippetArray; + +extern SnippetArray snippets; + +void init_snippet_array(SnippetArray *a, size_t initial_size); +void insert_snippet(SnippetArray *a, Snippet snippet); +void free_snippet_array(SnippetArray *a); +void load_snippets_from_directory(); +void activate_snippet(Editor *e); + +#endif // YASNIPPET_H_ diff --git a/todo.org b/todo.org new file mode 100644 index 00000000..a837d0d8 --- /dev/null +++ b/todo.org @@ -0,0 +1,131 @@ + +#+title: Todo +* FILE BROWSER +TODO IMPORTANT [ ] it crash the entire editor if there is a link + +* GIT +TODO [ ] Git gutter +TODO [ ] Magit + +* BUFFERS +TODO [ ] save open buffers list on quit and cursor position for each of them +while ded is running keep them in memory, save them on quit to ~/.config/ded/buffers [] + +* Theme +TODO [ ] each theme should have a name not only and index +TODO [ ] Refactor theme.c it's a mess +* Bugs +opening a file with ded file.c that contain a +function definition, trow a [1] 35755 segmentation fault (core dumped) + +* EDITING +TODO [ ] option to add one space when typing "{" on the right of a closing ")" +TODO [ ] option to add one space when typing "(" on the right of a keyword +TODO [ ] when typing "'" or '"' check if there is a "'" or '"' on the right if so, simply move the cursor to the right once +TODO [ ] when "/" is pressed in TEXTINPUT check if the line is empty or whitespaced, if its either of them add 2 "// " +TODO [ ] move-function-up/down +TODO [ ] drag-function-up/down +TODO [ ] aggressive-indent-mode +TODO [ ] select_function +TODO [ ] Typing "{" inside of empty "()" moves it outside with a space padding after ")", +correctly position the matching "}", and move the cursor inside the block. Togglable. +TODO [ ] pressing o on includes should spawn a new empty one and move cursor +** Drag Stuff +rename to drag_stuff_up/down/left/right +it should behave differently if editor.selection + +* SEARCH +TODO [ ] if the search found nothing until the end of the file wrap arround +TODO [ ] you can only type text already present in the file +inside the search buffer, it should be possible to type anything +(also highlight not found chars in red like emacs) +TODO [ ] search should not be case sensitive + +* RENDER +TODO IMPORTANT [ ] render the character under cursor using the same color as the bg +TODO [ ] option to lerp syntax highlighting color in +TODO LATER [ ] render whitespaced with error color when there is an error on a line +TODO LATER [ ] dim unfocused splits the smaller they are (when we have splits) +TODO [ ] sub-pixel font rendering +TODO [ ] better markdown support +TODO [ ] Togglable visibility of new lines +TODO [ ] render wavy or normal line under text for errors or whatever [] +TODO [ ] Batch rendering +TODO [ ] optional vscode style hl_line +TODO [ ] render_trailing_whitespaced +TODO [ ] line under links +TODO [/] render circles (we need to use a shader for better performance) +TODO [ ] render line numbers only on buffers with more than long-file + +* LSP +TODO [ ] goto_definition + +* Dumb stuff to fix +TODO [ ] measure_whitespace_width only once on font switch rather than 4 times every frame +TODO [/]find_first_non_whitespace(and refactor, some functions could use it) +TODO [ ] use window height and width dynamically instead of fixed position when animations are off + +* NEW FEATURES +TODO [ ] map all variables to a docstring introspectable and modifiable at runtime +TODO [ ] org mode support +TODO [ ] format on save +TODO [ ] Togglable emacs style camera centering [] +TODO LATER [ ] a reimplementation of imenu +TODO [ ] multicursor +TODO [ ] reimplementation of Iedit +TODO [ ] Pipe selection to Unix command + +* EVIL MODE +TODO [ ] Replace mode +TODO [ ] Replace character mode +TODO [ ] evil-find-char-backward +TODO [ ] evil-visual-line-mode (the current implementation is so bad) +TODO [ ] evil-search-backward +TODO [ ] Universal argument (5k should work) +TODO BUG [ ] Ctrl+n in insert mode sometimes take 2 times to work + +* Font +TODO [ ] changing font more times than the total number of font you have inside =~/.config/ded/fonts= make the text disappear +TODO [ ] Better api to load fonts and refactor to use it +* Lexer +TODO [ ] support multi line tokens +TODO [ ] check for strings in arrays +TODO [ ] multi line comments [] +TODO IMPORTANT [ ] different vertex and fragment shader per token (need batch rendering) +TODO different font per token [] +TODO [ ] Correctly highlight "#!/bin/bash" and "#include " + +* DONE +Togglable indentation lines[x] +replace on typing when selection is true [x] +most symple vim style completion (ctrl + n in insert mode)[/] TODO it should cycle like vim +option to render whitespaces on selection [x] +switch true to false or false to true in editor_enter [x] +in M-x if the input is a number +go to that line in the editor if it exist [x] +a theme should be able to define how much lighter of the bg +whitespaces indicators are in % (currently hardcoded to 70%)[x] +keep a list of opened files to quickly +go to the previous or next "buffer" or kill them [x] +mismatched cut and selection [x] +Drag lines up/down [x] // TODO drag selection or multiple cursors BUG behaves bad when there are only 2 lines in the editor and you swap them +Editor_new_line_down and up should mantain indentation [x] +yasnippet [x] TODO +when copying with y do it like vim[x] +evil join on shift + j [x] +Emacs mode [x] +Togglable visibility of whitespaces[x] +different cursor color based on the mode[x] +if editor->has_mark make the cursor trasparent [x] +ctrl + backspace should delete an entire word [x] +adjust size of the cursor based on hovered glyph [x] +use shaders from =~/.config/ded/shaders= instead [x] +'' [x] +NULL [x] +syntax highlighting for : | || & && [x] +highlight links [x] +shift + i [x] +evil-change-line(smarter version, don't delete ";") [x] +evil-delete-backward-char [x] +evil-search-word-forward (shift + 8) [x] +shift + 5 [x]